diff --git a/.github/stylua.toml b/.github/stylua.toml new file mode 100644 index 0000000..c09bec6 --- /dev/null +++ b/.github/stylua.toml @@ -0,0 +1,10 @@ +column_width = 200 +line_endings = "Windows" +indent_type = "Tabs" +indent_width = 4 +quote_style = "AutoPreferDouble" +call_parentheses = "Always" +collapse_simple_statement = "Never" + +[sort_requires] +enabled = false \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..1f7b60b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,22 @@ +name: Format Eclipse Code +on: + push: + branches: [ main, dev ] + pull_request: + branches: [ main, dev ] +jobs: + format_lua: + runs-on: windows-latest + steps: + - uses: actions/checkout@main + - uses: JohnnyMorganz/stylua-action@v3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + version: latest + args: --config-path ./.github/stylua.toml -g "*.lua" -g "!weapontweakdata.lua" -- lua + - uses: EndBug/add-and-commit@v9 + with: + add: "lua/*" + committer_name: GitHub Actions + committer_email: actions@github.com + message: Code formatting diff --git a/assets/guis/dlcs/gunperk/textures/pd2/blackmarket/icons/mods/wpn_fps_upg_perk_deadsilence.texture b/assets/guis/dlcs/gunperk/textures/pd2/blackmarket/icons/mods/wpn_fps_upg_perk_deadsilence.texture new file mode 100644 index 0000000..8575339 Binary files /dev/null and b/assets/guis/dlcs/gunperk/textures/pd2/blackmarket/icons/mods/wpn_fps_upg_perk_deadsilence.texture differ diff --git a/assets/guis/dlcs/gunperk/textures/pd2/blackmarket/icons/mods/wpn_fps_upg_perk_haste.texture b/assets/guis/dlcs/gunperk/textures/pd2/blackmarket/icons/mods/wpn_fps_upg_perk_haste.texture new file mode 100644 index 0000000..2593e60 Binary files /dev/null and b/assets/guis/dlcs/gunperk/textures/pd2/blackmarket/icons/mods/wpn_fps_upg_perk_haste.texture differ diff --git a/assets/guis/dlcs/gunperk/textures/pd2/blackmarket/icons/mods/wpn_fps_upg_perk_jawbreaker.texture b/assets/guis/dlcs/gunperk/textures/pd2/blackmarket/icons/mods/wpn_fps_upg_perk_jawbreaker.texture new file mode 100644 index 0000000..7091bb1 Binary files /dev/null and b/assets/guis/dlcs/gunperk/textures/pd2/blackmarket/icons/mods/wpn_fps_upg_perk_jawbreaker.texture differ diff --git a/assets/guis/dlcs/gunperk/textures/pd2/blackmarket/icons/mods/wpn_fps_upg_perk_speedloader.texture b/assets/guis/dlcs/gunperk/textures/pd2/blackmarket/icons/mods/wpn_fps_upg_perk_speedloader.texture new file mode 100644 index 0000000..d2300f7 Binary files /dev/null and b/assets/guis/dlcs/gunperk/textures/pd2/blackmarket/icons/mods/wpn_fps_upg_perk_speedloader.texture differ diff --git a/assets/guis/dlcs/gunperk/textures/pd2/blackmarket/icons/mods/wpn_fps_upg_perk_stockpile.texture b/assets/guis/dlcs/gunperk/textures/pd2/blackmarket/icons/mods/wpn_fps_upg_perk_stockpile.texture new file mode 100644 index 0000000..ef4ff77 Binary files /dev/null and b/assets/guis/dlcs/gunperk/textures/pd2/blackmarket/icons/mods/wpn_fps_upg_perk_stockpile.texture differ diff --git a/assets/guis/dlcs/gunperk/textures/pd2/blackmarket/icons/mods/wpn_fps_upg_perk_whirlwind.texture b/assets/guis/dlcs/gunperk/textures/pd2/blackmarket/icons/mods/wpn_fps_upg_perk_whirlwind.texture new file mode 100644 index 0000000..18c85ab Binary files /dev/null and b/assets/guis/dlcs/gunperk/textures/pd2/blackmarket/icons/mods/wpn_fps_upg_perk_whirlwind.texture differ diff --git a/assets/guis/textures/pd2/mission_briefing/assets/assets_risklevel_4.texture b/assets/guis/textures/pd2/mission_briefing/assets/assets_risklevel_4.texture new file mode 100644 index 0000000..e41d78a Binary files /dev/null and b/assets/guis/textures/pd2/mission_briefing/assets/assets_risklevel_4.texture differ diff --git a/assets/guis/textures/pd2/risklevel_deathwish_easywish_blackscreen.texture b/assets/guis/textures/pd2/risklevel_deathwish_easywish_blackscreen.texture new file mode 100644 index 0000000..30d562b Binary files /dev/null and b/assets/guis/textures/pd2/risklevel_deathwish_easywish_blackscreen.texture differ diff --git a/assets/guis/textures/pd2/specialization/icons_atlas.texture b/assets/guis/textures/pd2/specialization/icons_atlas.texture new file mode 100644 index 0000000..ab8d2ce Binary files /dev/null and b/assets/guis/textures/pd2/specialization/icons_atlas.texture differ diff --git a/assets/levels/narratives/classics/flat/nav_manager_data.nav_data b/assets/levels/narratives/classics/flat/nav_manager_data.nav_data new file mode 100644 index 0000000..fac2214 Binary files /dev/null and b/assets/levels/narratives/classics/flat/nav_manager_data.nav_data differ diff --git a/assets/levels/narratives/dentist/hox/stage_2/nav_manager_data.nav_data b/assets/levels/narratives/dentist/hox/stage_2/nav_manager_data.nav_data new file mode 100644 index 0000000..164d208 Binary files /dev/null and b/assets/levels/narratives/dentist/hox/stage_2/nav_manager_data.nav_data differ diff --git a/assets/levels/narratives/e_framing_frame/stage_1/nav_manager_data.nav_data b/assets/levels/narratives/e_framing_frame/stage_1/nav_manager_data.nav_data new file mode 100644 index 0000000..f3deb52 Binary files /dev/null and b/assets/levels/narratives/e_framing_frame/stage_1/nav_manager_data.nav_data differ diff --git a/assets/levels/narratives/e_framing_frame/stage_3/nav_manager_data.nav_data b/assets/levels/narratives/e_framing_frame/stage_3/nav_manager_data.nav_data new file mode 100644 index 0000000..f54bd73 Binary files /dev/null and b/assets/levels/narratives/e_framing_frame/stage_3/nav_manager_data.nav_data differ diff --git a/assets/levels/narratives/h_alex_must_die/stage_1/nav_manager_data.nav_data b/assets/levels/narratives/h_alex_must_die/stage_1/nav_manager_data.nav_data new file mode 100644 index 0000000..bca3db8 Binary files /dev/null and b/assets/levels/narratives/h_alex_must_die/stage_1/nav_manager_data.nav_data differ diff --git a/assets/levels/narratives/skm/skm_big2/nav_manager_data.nav_data b/assets/levels/narratives/skm/skm_big2/nav_manager_data.nav_data new file mode 100644 index 0000000..6993565 Binary files /dev/null and b/assets/levels/narratives/skm/skm_big2/nav_manager_data.nav_data differ diff --git a/assets/units/payday2/characters/ene_fbi_3/ene_fbi_3.unit b/assets/units/payday2/characters/ene_fbi_3/ene_fbi_3.unit deleted file mode 100644 index 5c680d2..0000000 --- a/assets/units/payday2/characters/ene_fbi_3/ene_fbi_3.unit +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/assets/units/payday2/weapons/wpn_npc_ksg/ksg_df.texture b/assets/units/payday2/weapons/wpn_npc_ksg/ksg_df.texture new file mode 100644 index 0000000..ea02691 Binary files /dev/null and b/assets/units/payday2/weapons/wpn_npc_ksg/ksg_df.texture differ diff --git a/assets/units/payday2/weapons/wpn_npc_ksg/ksg_nm.texture b/assets/units/payday2/weapons/wpn_npc_ksg/ksg_nm.texture new file mode 100644 index 0000000..aba6506 Binary files /dev/null and b/assets/units/payday2/weapons/wpn_npc_ksg/ksg_nm.texture differ diff --git a/assets/units/payday2/weapons/wpn_npc_ksg/wpn_npc_ksg.cooked_physics b/assets/units/payday2/weapons/wpn_npc_ksg/wpn_npc_ksg.cooked_physics new file mode 100644 index 0000000..e69de29 diff --git a/assets/units/payday2/weapons/wpn_npc_ksg/wpn_npc_ksg.material_config b/assets/units/payday2/weapons/wpn_npc_ksg/wpn_npc_ksg.material_config new file mode 100644 index 0000000..763e492 --- /dev/null +++ b/assets/units/payday2/weapons/wpn_npc_ksg/wpn_npc_ksg.material_config @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/assets/units/payday2/weapons/wpn_npc_ksg/wpn_npc_ksg.model b/assets/units/payday2/weapons/wpn_npc_ksg/wpn_npc_ksg.model new file mode 100644 index 0000000..af4610e Binary files /dev/null and b/assets/units/payday2/weapons/wpn_npc_ksg/wpn_npc_ksg.model differ diff --git a/assets/units/payday2/weapons/wpn_npc_ksg/wpn_npc_ksg.object b/assets/units/payday2/weapons/wpn_npc_ksg/wpn_npc_ksg.object new file mode 100644 index 0000000..44d7d6b --- /dev/null +++ b/assets/units/payday2/weapons/wpn_npc_ksg/wpn_npc_ksg.object @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/payday2/weapons/wpn_npc_ksg/wpn_npc_ksg.unit b/assets/units/payday2/weapons/wpn_npc_ksg/wpn_npc_ksg.unit new file mode 100644 index 0000000..61b47d6 --- /dev/null +++ b/assets/units/payday2/weapons/wpn_npc_ksg/wpn_npc_ksg.unit @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/payday2/weapons/wpn_npc_sawnoff_shotgun/wpn_npc_sawnoff_shotgun.unit b/assets/units/payday2/weapons/wpn_npc_sawnoff_shotgun/wpn_npc_sawnoff_shotgun.unit new file mode 100644 index 0000000..0b2f552 --- /dev/null +++ b/assets/units/payday2/weapons/wpn_npc_sawnoff_shotgun/wpn_npc_sawnoff_shotgun.unit @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/payday2/weapons/wpn_npc_shepheard/shepheard_df.texture b/assets/units/payday2/weapons/wpn_npc_shepheard/shepheard_df.texture new file mode 100644 index 0000000..8bc81a8 Binary files /dev/null and b/assets/units/payday2/weapons/wpn_npc_shepheard/shepheard_df.texture differ diff --git a/assets/units/payday2/weapons/wpn_npc_shepheard/shepheard_nm.texture b/assets/units/payday2/weapons/wpn_npc_shepheard/shepheard_nm.texture new file mode 100644 index 0000000..2dc2729 Binary files /dev/null and b/assets/units/payday2/weapons/wpn_npc_shepheard/shepheard_nm.texture differ diff --git a/assets/units/payday2/weapons/wpn_npc_shepheard/wpn_npc_shepheard.cooked_physics b/assets/units/payday2/weapons/wpn_npc_shepheard/wpn_npc_shepheard.cooked_physics new file mode 100644 index 0000000..e69de29 diff --git a/assets/units/payday2/weapons/wpn_npc_shepheard/wpn_npc_shepheard.material_config b/assets/units/payday2/weapons/wpn_npc_shepheard/wpn_npc_shepheard.material_config new file mode 100644 index 0000000..04a5ec4 --- /dev/null +++ b/assets/units/payday2/weapons/wpn_npc_shepheard/wpn_npc_shepheard.material_config @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/assets/units/payday2/weapons/wpn_npc_shepheard/wpn_npc_shepheard.model b/assets/units/payday2/weapons/wpn_npc_shepheard/wpn_npc_shepheard.model new file mode 100644 index 0000000..ec6542f Binary files /dev/null and b/assets/units/payday2/weapons/wpn_npc_shepheard/wpn_npc_shepheard.model differ diff --git a/assets/units/payday2/weapons/wpn_npc_shepheard/wpn_npc_shepheard.object b/assets/units/payday2/weapons/wpn_npc_shepheard/wpn_npc_shepheard.object new file mode 100644 index 0000000..534e08b --- /dev/null +++ b/assets/units/payday2/weapons/wpn_npc_shepheard/wpn_npc_shepheard.object @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/payday2/weapons/wpn_npc_shepheard/wpn_npc_shepheard.unit b/assets/units/payday2/weapons/wpn_npc_shepheard/wpn_npc_shepheard.unit new file mode 100644 index 0000000..4a1c76d --- /dev/null +++ b/assets/units/payday2/weapons/wpn_npc_shepheard/wpn_npc_shepheard.unit @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/payday2/weapons/wpn_npc_ump/wpn_npc_ump.unit b/assets/units/payday2/weapons/wpn_npc_ump/wpn_npc_ump.unit new file mode 100644 index 0000000..a6c3cda --- /dev/null +++ b/assets/units/payday2/weapons/wpn_npc_ump/wpn_npc_ump.unit @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_acc_shield_new_swat/ene_acc_shield_new_swat.model b/assets/units/pd2_dlc_gitgud/characters/ene_acc_shield_new_swat/ene_acc_shield_new_swat.model new file mode 100644 index 0000000..cbbc0bc Binary files /dev/null and b/assets/units/pd2_dlc_gitgud/characters/ene_acc_shield_new_swat/ene_acc_shield_new_swat.model differ diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_acc_shield_new_swat/ene_acc_shield_new_swat.object b/assets/units/pd2_dlc_gitgud/characters/ene_acc_shield_new_swat/ene_acc_shield_new_swat.object new file mode 100644 index 0000000..1f588a6 --- /dev/null +++ b/assets/units/pd2_dlc_gitgud/characters/ene_acc_shield_new_swat/ene_acc_shield_new_swat.object @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_acc_zeal_swat_heavy_helmet/ene_acc_zeal_swat_heavy_helmet.material_config b/assets/units/pd2_dlc_gitgud/characters/ene_acc_zeal_swat_heavy_helmet/ene_acc_zeal_swat_heavy_helmet.material_config new file mode 100644 index 0000000..3e72372 --- /dev/null +++ b/assets/units/pd2_dlc_gitgud/characters/ene_acc_zeal_swat_heavy_helmet/ene_acc_zeal_swat_heavy_helmet.material_config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_acc_zeal_swat_heavy_helmet/ene_acc_zeal_swat_heavy_helmet.model b/assets/units/pd2_dlc_gitgud/characters/ene_acc_zeal_swat_heavy_helmet/ene_acc_zeal_swat_heavy_helmet.model new file mode 100644 index 0000000..ab7112d Binary files /dev/null and b/assets/units/pd2_dlc_gitgud/characters/ene_acc_zeal_swat_heavy_helmet/ene_acc_zeal_swat_heavy_helmet.model differ diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_acc_zeal_swat_helmet/ene_acc_zeal_swat_helmet.material_config b/assets/units/pd2_dlc_gitgud/characters/ene_acc_zeal_swat_helmet/ene_acc_zeal_swat_helmet.material_config new file mode 100644 index 0000000..ee637b6 --- /dev/null +++ b/assets/units/pd2_dlc_gitgud/characters/ene_acc_zeal_swat_helmet/ene_acc_zeal_swat_helmet.material_config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_acc_zeal_swat_helmet/ene_acc_zeal_swat_helmet.model b/assets/units/pd2_dlc_gitgud/characters/ene_acc_zeal_swat_helmet/ene_acc_zeal_swat_helmet.model new file mode 100644 index 0000000..adc6a3f Binary files /dev/null and b/assets/units/pd2_dlc_gitgud/characters/ene_acc_zeal_swat_helmet/ene_acc_zeal_swat_helmet.model differ diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_medic_m4/ene_zeal_medic_m4.cooked_physics b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_medic_m4/ene_zeal_medic_m4.cooked_physics new file mode 100644 index 0000000..e69de29 diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_medic_m4/ene_zeal_medic_m4.material_config b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_medic_m4/ene_zeal_medic_m4.material_config new file mode 100644 index 0000000..630314f --- /dev/null +++ b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_medic_m4/ene_zeal_medic_m4.material_config @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_medic_m4/ene_zeal_medic_m4.model b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_medic_m4/ene_zeal_medic_m4.model new file mode 100644 index 0000000..ae33e8a Binary files /dev/null and b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_medic_m4/ene_zeal_medic_m4.model differ diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_medic_m4/ene_zeal_medic_m4.object b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_medic_m4/ene_zeal_medic_m4.object new file mode 100644 index 0000000..280616c --- /dev/null +++ b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_medic_m4/ene_zeal_medic_m4.object @@ -0,0 +1,218 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_medic_m4/ene_zeal_medic_m4.unit b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_medic_m4/ene_zeal_medic_m4.unit new file mode 100644 index 0000000..5b32797 --- /dev/null +++ b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_medic_m4/ene_zeal_medic_m4.unit @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_medic_m4/ene_zeal_medic_m4_contour.material_config b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_medic_m4/ene_zeal_medic_m4_contour.material_config new file mode 100644 index 0000000..596b14e --- /dev/null +++ b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_medic_m4/ene_zeal_medic_m4_contour.material_config @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_medic_m4/ene_zeal_medic_m4_husk.unit b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_medic_m4/ene_zeal_medic_m4_husk.unit new file mode 100644 index 0000000..af3c94e --- /dev/null +++ b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_medic_m4/ene_zeal_medic_m4_husk.unit @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_medic_r870/ene_zeal_medic_r870.unit b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_medic_r870/ene_zeal_medic_r870.unit new file mode 100644 index 0000000..b25276f --- /dev/null +++ b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_medic_r870/ene_zeal_medic_r870.unit @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_medic_r870/ene_zeal_medic_r870_husk.unit b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_medic_r870/ene_zeal_medic_r870_husk.unit new file mode 100644 index 0000000..8f10c99 --- /dev/null +++ b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_medic_r870/ene_zeal_medic_r870_husk.unit @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat/ene_zeal_swat.material_config b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat/ene_zeal_swat.material_config new file mode 100644 index 0000000..eae9702 --- /dev/null +++ b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat/ene_zeal_swat.material_config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat/ene_zeal_swat.model b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat/ene_zeal_swat.model new file mode 100644 index 0000000..33c52fa Binary files /dev/null and b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat/ene_zeal_swat.model differ diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat/ene_zeal_swat.unit b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat/ene_zeal_swat.unit new file mode 100644 index 0000000..7461886 --- /dev/null +++ b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat/ene_zeal_swat.unit @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat/ene_zeal_swat_contour.material_config b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat/ene_zeal_swat_contour.material_config new file mode 100644 index 0000000..20c29d8 --- /dev/null +++ b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat/ene_zeal_swat_contour.material_config @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat/ene_zeal_swat_husk.unit b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat/ene_zeal_swat_husk.unit new file mode 100644 index 0000000..0010af5 --- /dev/null +++ b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat/ene_zeal_swat_husk.unit @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_2/ene_zeal_swat_2.cooked_physics b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_2/ene_zeal_swat_2.cooked_physics new file mode 100644 index 0000000..e69de29 diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_2/ene_zeal_swat_2.material_config b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_2/ene_zeal_swat_2.material_config new file mode 100644 index 0000000..eae9702 --- /dev/null +++ b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_2/ene_zeal_swat_2.material_config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_2/ene_zeal_swat_2.model b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_2/ene_zeal_swat_2.model new file mode 100644 index 0000000..453ab99 Binary files /dev/null and b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_2/ene_zeal_swat_2.model differ diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_2/ene_zeal_swat_2.object b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_2/ene_zeal_swat_2.object new file mode 100644 index 0000000..fa4b378 --- /dev/null +++ b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_2/ene_zeal_swat_2.object @@ -0,0 +1,218 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_2/ene_zeal_swat_2.unit b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_2/ene_zeal_swat_2.unit new file mode 100644 index 0000000..3bfdb54 --- /dev/null +++ b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_2/ene_zeal_swat_2.unit @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_2/ene_zeal_swat_2_contour.material_config b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_2/ene_zeal_swat_2_contour.material_config new file mode 100644 index 0000000..20c29d8 --- /dev/null +++ b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_2/ene_zeal_swat_2_contour.material_config @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_2/ene_zeal_swat_2_husk.unit b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_2/ene_zeal_swat_2_husk.unit new file mode 100644 index 0000000..cf3f6eb --- /dev/null +++ b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_2/ene_zeal_swat_2_husk.unit @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy/ene_zeal_swat_heavy.material_config b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy/ene_zeal_swat_heavy.material_config new file mode 100644 index 0000000..3df4236 --- /dev/null +++ b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy/ene_zeal_swat_heavy.material_config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy/ene_zeal_swat_heavy.model b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy/ene_zeal_swat_heavy.model new file mode 100644 index 0000000..032bcfa Binary files /dev/null and b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy/ene_zeal_swat_heavy.model differ diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy/ene_zeal_swat_heavy.unit b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy/ene_zeal_swat_heavy.unit new file mode 100644 index 0000000..c1733c3 --- /dev/null +++ b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy/ene_zeal_swat_heavy.unit @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy/ene_zeal_swat_heavy_contour.material_config b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy/ene_zeal_swat_heavy_contour.material_config new file mode 100644 index 0000000..188def8 --- /dev/null +++ b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy/ene_zeal_swat_heavy_contour.material_config @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy/ene_zeal_swat_heavy_husk.unit b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy/ene_zeal_swat_heavy_husk.unit new file mode 100644 index 0000000..918e228 --- /dev/null +++ b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy/ene_zeal_swat_heavy_husk.unit @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy_2/ene_zeal_swat_heavy_2.cooked_physics b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy_2/ene_zeal_swat_heavy_2.cooked_physics new file mode 100644 index 0000000..e69de29 diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy_2/ene_zeal_swat_heavy_2.material_config b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy_2/ene_zeal_swat_heavy_2.material_config new file mode 100644 index 0000000..3df4236 --- /dev/null +++ b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy_2/ene_zeal_swat_heavy_2.material_config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy_2/ene_zeal_swat_heavy_2.model b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy_2/ene_zeal_swat_heavy_2.model new file mode 100644 index 0000000..b2a8e61 Binary files /dev/null and b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy_2/ene_zeal_swat_heavy_2.model differ diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy_2/ene_zeal_swat_heavy_2.object b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy_2/ene_zeal_swat_heavy_2.object new file mode 100644 index 0000000..73bc771 --- /dev/null +++ b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy_2/ene_zeal_swat_heavy_2.object @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy_2/ene_zeal_swat_heavy_2.unit b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy_2/ene_zeal_swat_heavy_2.unit new file mode 100644 index 0000000..6cf4daf --- /dev/null +++ b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy_2/ene_zeal_swat_heavy_2.unit @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy_2/ene_zeal_swat_heavy_2_contour.material_config b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy_2/ene_zeal_swat_heavy_2_contour.material_config new file mode 100644 index 0000000..188def8 --- /dev/null +++ b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy_2/ene_zeal_swat_heavy_2_contour.material_config @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy_2/ene_zeal_swat_heavy_2_husk.unit b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy_2/ene_zeal_swat_heavy_2_husk.unit new file mode 100644 index 0000000..06fb7c4 --- /dev/null +++ b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy_2/ene_zeal_swat_heavy_2_husk.unit @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_shield/ene_zeal_swat_shield.material_config b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_shield/ene_zeal_swat_shield.material_config new file mode 100644 index 0000000..6a6a45b --- /dev/null +++ b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_shield/ene_zeal_swat_shield.material_config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_shield/ene_zeal_swat_shield.model b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_shield/ene_zeal_swat_shield.model new file mode 100644 index 0000000..37639a6 Binary files /dev/null and b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_shield/ene_zeal_swat_shield.model differ diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_shield/ene_zeal_swat_shield.unit b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_shield/ene_zeal_swat_shield.unit new file mode 100644 index 0000000..a331433 --- /dev/null +++ b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_shield/ene_zeal_swat_shield.unit @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_shield/ene_zeal_swat_shield_contour.material_config b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_shield/ene_zeal_swat_shield_contour.material_config new file mode 100644 index 0000000..6cbc789 --- /dev/null +++ b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_shield/ene_zeal_swat_shield_contour.material_config @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/units/payday2/characters/ene_fbi_3/ene_fbi_3_husk.unit b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_shield/ene_zeal_swat_shield_husk.unit similarity index 52% rename from assets/units/payday2/characters/ene_fbi_3/ene_fbi_3_husk.unit rename to assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_shield/ene_zeal_swat_shield_husk.unit index 5b51b58..9fef9b0 100644 --- a/assets/units/payday2/characters/ene_fbi_3/ene_fbi_3_husk.unit +++ b/assets/units/pd2_dlc_gitgud/characters/ene_zeal_swat_shield/ene_zeal_swat_shield_husk.unit @@ -1,28 +1,37 @@ - + + + + - - - + + + + + + - + + + + - + diff --git a/assets/units/pd2_dlc_gitgud/characters/shared_textures/swat_df.texture b/assets/units/pd2_dlc_gitgud/characters/shared_textures/swat_df.texture new file mode 100644 index 0000000..cc961b2 Binary files /dev/null and b/assets/units/pd2_dlc_gitgud/characters/shared_textures/swat_df.texture differ diff --git a/assets/units/pd2_dlc_gitgud/characters/shared_textures/swat_head_df.texture b/assets/units/pd2_dlc_gitgud/characters/shared_textures/swat_head_df.texture new file mode 100644 index 0000000..3b5bcd8 Binary files /dev/null and b/assets/units/pd2_dlc_gitgud/characters/shared_textures/swat_head_df.texture differ diff --git a/assets/units/pd2_dlc_gitgud/characters/shared_textures/swat_head_nm.texture b/assets/units/pd2_dlc_gitgud/characters/shared_textures/swat_head_nm.texture new file mode 100644 index 0000000..4326050 Binary files /dev/null and b/assets/units/pd2_dlc_gitgud/characters/shared_textures/swat_head_nm.texture differ diff --git a/assets/units/pd2_dlc_gitgud/characters/shared_textures/swat_heavy_df.texture b/assets/units/pd2_dlc_gitgud/characters/shared_textures/swat_heavy_df.texture new file mode 100644 index 0000000..7b54e0d Binary files /dev/null and b/assets/units/pd2_dlc_gitgud/characters/shared_textures/swat_heavy_df.texture differ diff --git a/assets/units/pd2_dlc_gitgud/characters/shared_textures/swat_heavy_head_df.texture b/assets/units/pd2_dlc_gitgud/characters/shared_textures/swat_heavy_head_df.texture new file mode 100644 index 0000000..277ddb5 Binary files /dev/null and b/assets/units/pd2_dlc_gitgud/characters/shared_textures/swat_heavy_head_df.texture differ diff --git a/assets/units/pd2_dlc_gitgud/characters/shared_textures/swat_heavy_head_nm.texture b/assets/units/pd2_dlc_gitgud/characters/shared_textures/swat_heavy_head_nm.texture new file mode 100644 index 0000000..629a7ea Binary files /dev/null and b/assets/units/pd2_dlc_gitgud/characters/shared_textures/swat_heavy_head_nm.texture differ diff --git a/assets/units/pd2_dlc_gitgud/characters/shared_textures/swat_heavy_nm.texture b/assets/units/pd2_dlc_gitgud/characters/shared_textures/swat_heavy_nm.texture new file mode 100644 index 0000000..8968f13 Binary files /dev/null and b/assets/units/pd2_dlc_gitgud/characters/shared_textures/swat_heavy_nm.texture differ diff --git a/assets/units/pd2_dlc_gitgud/characters/shared_textures/swat_nm.texture b/assets/units/pd2_dlc_gitgud/characters/shared_textures/swat_nm.texture new file mode 100644 index 0000000..4ab6cf3 Binary files /dev/null and b/assets/units/pd2_dlc_gitgud/characters/shared_textures/swat_nm.texture differ diff --git a/assets/units/pd2_dlc_gitgud/characters/shared_textures/zeal_medic_df.texture b/assets/units/pd2_dlc_gitgud/characters/shared_textures/zeal_medic_df.texture new file mode 100644 index 0000000..0f0e71d Binary files /dev/null and b/assets/units/pd2_dlc_gitgud/characters/shared_textures/zeal_medic_df.texture differ diff --git a/assets/units/pd2_dlc_gitgud/characters/shared_textures/zeal_medic_head_df.texture b/assets/units/pd2_dlc_gitgud/characters/shared_textures/zeal_medic_head_df.texture new file mode 100644 index 0000000..a87e1da Binary files /dev/null and b/assets/units/pd2_dlc_gitgud/characters/shared_textures/zeal_medic_head_df.texture differ diff --git a/assets/units/pd2_dlc_vip/characters/ene_acc_shield_phalanx/ene_acc_shield_phalanx.sequence_manager b/assets/units/pd2_dlc_vip/characters/ene_acc_shield_phalanx/ene_acc_shield_phalanx.sequence_manager new file mode 100644 index 0000000..3e165ce Binary files /dev/null and b/assets/units/pd2_dlc_vip/characters/ene_acc_shield_phalanx/ene_acc_shield_phalanx.sequence_manager differ diff --git a/assets/units/pd2_dlc_vip/characters/ene_acc_shield_phalanx/ene_acc_shield_phalanx.unit b/assets/units/pd2_dlc_vip/characters/ene_acc_shield_phalanx/ene_acc_shield_phalanx.unit new file mode 100644 index 0000000..ffe125f --- /dev/null +++ b/assets/units/pd2_dlc_vip/characters/ene_acc_shield_phalanx/ene_acc_shield_phalanx.unit @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/units/pd2_dlc_vip/characters/ene_acc_shield_phalanx/ene_acc_shield_phalanx_dummy.unit b/assets/units/pd2_dlc_vip/characters/ene_acc_shield_phalanx/ene_acc_shield_phalanx_dummy.unit new file mode 100644 index 0000000..131602e --- /dev/null +++ b/assets/units/pd2_dlc_vip/characters/ene_acc_shield_phalanx/ene_acc_shield_phalanx_dummy.unit @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/assets/units/payday2/characters/ene_spook_1/ene_spook_1.unit b/assets/units/pd2_dlc_vip/characters/ene_phalanx_1/ene_phalanx_1.unit similarity index 52% rename from assets/units/payday2/characters/ene_spook_1/ene_spook_1.unit rename to assets/units/pd2_dlc_vip/characters/ene_phalanx_1/ene_phalanx_1.unit index f23b10a..883861c 100644 --- a/assets/units/payday2/characters/ene_spook_1/ene_spook_1.unit +++ b/assets/units/pd2_dlc_vip/characters/ene_phalanx_1/ene_phalanx_1.unit @@ -1,36 +1,37 @@ - + - - - - - - - + + + - - + + + + + - - + + + + - + @@ -43,7 +44,7 @@ - + diff --git a/assets/units/payday2/characters/ene_spook_1/ene_spook_1_husk.unit b/assets/units/pd2_dlc_vip/characters/ene_phalanx_1/ene_phalanx_1_husk.unit similarity index 56% rename from assets/units/payday2/characters/ene_spook_1/ene_spook_1_husk.unit rename to assets/units/pd2_dlc_vip/characters/ene_phalanx_1/ene_phalanx_1_husk.unit index 78ec3a7..259c7de 100644 --- a/assets/units/payday2/characters/ene_spook_1/ene_spook_1_husk.unit +++ b/assets/units/pd2_dlc_vip/characters/ene_phalanx_1/ene_phalanx_1_husk.unit @@ -1,32 +1,37 @@ - + - - - + + + - - + + + + + - - + + + + - + diff --git a/loc/en.txt b/loc/en.txt index ab75ecf..c12d4df 100644 --- a/loc/en.txt +++ b/loc/en.txt @@ -1,40 +1,77 @@ { - "bm_wp_upg_a_custom_desc": "Bigger pellets with more impact. One shell only contains 8 pellets. This ammo type is harder to find.", + "bm_wp_upg_a_custom_desc": "Bigger pellets with more impact.\nOne shell contains 6 pellets.\nDamage ramps up to 40% in a 3m range.\nDamage falloff starts earlier.", + "bm_wp_upg_a_explosive_desc": "One deadly high explosive slug.\nThis ammo type is harder to find.\nIncreased precision.\nNo damage falloff.", + "bm_wp_upg_a_piercing_desc": "Pointed steel projectiles.\nOne shell contains 12 flechettes.\nArmor piercing.\nDamage falloff starts later.", + "bm_wp_upg_a_slug_desc": "One heavy lead slug.\nHeavily increased precision.\nArmor, shield, enemy, wall piercing.\nNo damage falloff.", + "bm_wp_upg_a_dragons_breath_desc": "Special type of Incendiary-effect rounds.\nOne shell contains 12 magnesium pellets.\nIgnites all enemies within 15m range.\nThis ammo type is harder to find.", "bm_wp_upg_mk2_rare_desc": "This fuel is easier to find.", "bm_wp_upg_mk2_welldone_desc": "This fuel is harder to find.", - "menu_nine_lives_beta": "Tough Guy", - "menu_inspire_beta_desc": "BASIC: ##$basic##\nShouting at your teammates will increase their movement and reload speed by ##$multibasic## for ##$multibasic2## seconds.\n\nACE: ##$pro##\nYou revive crew members ##$multipro## faster.", - "menu_nine_lives_beta_desc": "BASIC: ##$basic##\nYou gain a ##$multibasic2## increase to your bleedout health.\n\nACE: ##$pro##\nYou can use your primary weapon in bleedout.", - "menu_black_marketeer_beta_desc": "BASIC: ##$basic##\nHaving at least ##$multibasic## hostages or dominated enemies makes you regenerate ##$multibasic2## health every ##$multibasic3## seconds.\n\nACE: ##$pro##\nThe amount of hostages required is decreased to ##$multipro##.\nThe amount of healing you receive is increased to ##$multipro2##.\n\nYour converted enemies will now count towards hostage related boosts.\n\nNote: Doesn't apply to Confident and Hostage Situation.", + "bm_w_hajk": "CR 805B Rifle", + + "bm_menu_bonus": "Gun Perk", + "bm_menu_perk_speedloader": "Speedloader", + "bm_menu_perk_speedloader_desc": "Increases reload speed at the cost of total ammo.\n", + "bm_menu_perk_haste": "Haste", + "bm_menu_perk_haste_desc": "Increases movement speed by 10% at the cost of total ammo.\n", + "bm_menu_perk_deadsilence": "Dead Silence", + "bm_menu_perk_deadsilence_desc": "Increases concealment at the cost of total ammo and weapon handling.\n", + "bm_menu_perk_jawbreaker": "Jawbreaker", + "bm_menu_perk_jawbreaker_desc": "Increases damage at the cost of rate of fire and 25% pickup rate.\n", + "bm_menu_perk_whirlwind": "Whirlwind", + "bm_menu_perk_whirlwind_desc": "Increases rate of fire at the cost of weapon handling.\n", + "bm_menu_perk_stockpile": "Stockpile", + "bm_menu_perk_stockpile_desc": "Increases total ammo at the cost of reload speed.\n", + + + "menu_infamy_desc_root_new": "As a new to the criminal elite, the first order of business is for you to get gear and fanfare befitting someone of your status.\n\nBONUSES:\nYour infamous base drop rate is increased from ##0.3%## to ##0.6%##.\nExperience gained is increased by ##5%##.", + + + "st_menu_mastermind_inspire": "Support", + "menu_bandage": "Bandages", + "menu_bandage_desc": "BASIC: ##$basic##\nCrew members you revive take ##$multibasic## less damage for ##$multibasic2## seconds.\n\nACE: ##$pro##\nReviving a crew member gives them ##$multipro## more health.", + "menu_field_surgery": "Field Surgery", + "menu_field_surgery_desc": "BASIC: ##$basic##\nCrew members you revive take additional ##$multibasic## less damage for ##$multibasic2## seconds.\n\nACE: ##$pro##\nYou take ##$multipro## less damage for ##$multibasic2## seconds after being revived.", + "menu_company_soul": "Company Soul", + "menu_company_soul_desc": "BASIC: ##$basic##\nYou and your crew's weapon stability is increased by ##$multibasic## points.\n\nACE: ##$pro##\nYou and your crew's stamina is increased by ##$multipro##.", + "menu_combat_doctor_desc": "BASIC: ##$basic##\nYou gain a ##$multibasic## damage reduction for ##$multibasic2## seconds both after and during reviving a teammate.\n\nACE: ##$pro##\nUpon reviving a crew member you gain a ##$multipro2## damage bonus for ##$multipro3## seconds.\n\nYou revive crew members ##$multipro## faster.", + "menu_keepers": "Keepers", + "menu_inspire_beta_desc": "BASIC: ##$basic##\nShouting at your teammates within a ##$multibasic## range will increase their movement and reload speed by ##$multibasic2## for ##$multibasic3## seconds.\n\nACE: ##$pro##\nYou can now revive your teammates within a ##$multibasic## range by shouting at them.\n\nThis can not occur more once every ##$multipro## seconds.", + "menu_black_marketeer_beta_desc": "BASIC: ##$basic##\nHaving at least ##$multibasic## hostage or dominated enemy makes you regenerate ##$multibasic2## health every ##$multibasic3## seconds.\n\nACE: ##$pro##\nThe amount of healing you receive is increased to ##$multipro##.\nYour converted enemies will now count towards hostage related boosts.\n\nNote: Doesn't apply to Stockholm Syndrome and Hostage Situation skills.", + "menu_cable_guy_beta": "Stockholm Syndrome", + "menu_cable_guy_beta_desc": "BASIC: ##$basic##\nThe power and range of your intimidation is increased by ##$multibasic2##.\n\nACE: ##$pro##\nCivilians are intimidated by the noise you make and remain intimidated ##$multipro## longer.\n\nNearby untied civilians have a chance of reviving you if you interact with them.", + "menu_triathlete_beta_desc": "BASIC: ##$basic##\nIncrease your supply of cable ties by ##$multibasic##.\n\nACE: ##$pro##\nYou can cable tie hostages ##$multipro## faster.", "menu_stockholm_syndrome_beta": "Hostage Situation", - "menu_stockholm_syndrome_beta_desc": "BASIC: ##$basic##\nCivilians are intimidated by the noise you make and remain intimidated ##50%## longer.\n\nACE: ##$pro##\nThe hostage hesitation delay between assaults is now extended by ##$multipro## seconds, for a total of ##$multipro2## seconds.", - "menu_wolverine_beta_desc": "BASIC: ##$basic##\nThe lower your health, the more damage you do. When your health is below ##$multibasic##, you will do up to ##$multibasic2## more melee and saw damage.\n\nACE: ##$pro##\nThe lower your health, the more movement speed you gain. When your health is below ##$multipro##, you will gain up to ##$multipro2## movement speed.", - "menu_perseverance_beta_desc": "BASIC: ##$basic##\nInstead of getting downed instantly, you gain the ability to keep on fighting for ##$multibasic2## seconds with a ##$multibasic3## movement penalty before going down.\n\nNote: Does not trigger on fall or fire damage. \n\nACE: ##$pro##\nIncreases the duration of Swan Song by ##$multipro## seconds.", - "menu_overkill_beta_desc": "BASIC: ##$basic##\nWhen you kill an enemy with a shotgun or the OVE9000 portable saw, you receive a ##$multibasic## damage increase for ##$multibasic2## seconds.\n\nACE: ##$pro##\nThe damage bonus is increased to ##$multipro##, applies to all weapons and lasts ##$multipro2## seconds. Skill must still be activated using a Shotgun or the OVE9000 portable saw. Your weapon swap speed is increased by ##$multipro3##.\n\nNote: Does not apply to melee damage, throwables, grenade launchers, or rocket launchers.", - "menu_juggernaut_beta_desc": "BASIC: ##$basic##\nUnlocks the ability to wear the Improved Combined Tactical Vest.\n\nACE: ##$pro##\nYour total armor value is increased by ##$multibasic##.", - "menu_fire_control_beta_desc": "BASIC: ##$basic##\nYou gain ##$multibasic## weapon accuracy while firing from the hip.\n\nACE: ##$pro##\nAll of your ranged weapons can now pierce through enemy body armor. Does not apply to throwable weapons.", - "menu_fast_fire_beta": "Mag Plus", - "menu_sprinter": "Athlete", - "menu_sprinter_desc": "BASIC: ##$basic##\nYour speed while walking is increased by ##$multibasic##. Your total movement speed is increased by ##$multibasic2##.\n\nACE: ##$pro##\nThe time before stamina regeneration starts is reduced by ##$multipro## and the regeneration rate of stamina is increased by ##$multipro##.", - "menu_awareness": "Duck and Cover", - "menu_awareness_desc": "BASIC: ##$basic##\nYour speed while crouching is increased by ##$multibasic##.\n\nACE: ##$pro##\nYou gain a ##$multipro## chance to dodge enemy fire while crouching.", - "menu_optic_illusions": "Sprinter", - "menu_optic_illusions_desc": "BASIC: ##$basic##\nYou can now sprint while strafing. You also sprint ##$multibasic## faster.\n\nACE: ##$pro##\nYou gain a ##$multipro## chance to dodge enemy fire while sprinting and zip-lining.", - "menu_silenced_damage": "Silencer Expert", - "menu_silenced_damage_desc": "BASIC: ##$basic##\nYou gain ##$multibasic## concealment for each silenced weapon equipped. Reduce the concealment penalty of silencers by ##$multibasic2##.\n\nACE: ##$pro##\nYou deal ##$multipro## more damage and have a ##$multipro2## chance to pierce enemy armor with all silenced weapons.", - "menu_fast_fire_beta_desc": "BASIC: ##$basic##\nYour SMGs, LMGs and Assault Rifles gain ##$multibasic## more bullets in their magazine.\n\nACE: ##$pro##\nYour SMGs, LMGs and Assault Rifles gain ##$multipro## additional bullets in their magazine.\n\nNote: This does not affect the Aced \"Fast Hands\" skill.", - "menu_body_expertise_beta": "Heavy Gun Expert", - "menu_body_expertise_beta_desc": "BASIC: ##$basic##\nYou no longer have movement penalty when wielding Heavy Weaponry.\n\nACE: ##$pro##\n##$multipro## from the bonus headshot damage is permanently applied to hitting enemies on the body. This skill is only activated by SMGs, LMGs, Assault Rifles or Special Weapons fired in automatic fire mode.", - "menu_bandoliers_beta_desc": "BASIC: ##$basic##\nYou gain a chance to pick up a throwable from an ammo box, starting at ##$multibasic##. This chance is increased by ##$multibasic2## for each ammo box you pick up that does not contain a throwable. When a throwable has been found, the chance is reset to its base value.\n\nACE: ##$pro##\nYour total ammo capacity is increased by ##$multipro2##. The amount of ammo you gain from ammo boxes is increased by ##$multipro3##.\n\nNote: Does not stack with the \"Walk-in Closet\" perk.", - "menu_show_of_force_beta": "Thick Skin", - "menu_show_of_force_beta_desc": "BASIC: ##$basic##\nYour steadiness is increased by ##$multibasic##.\n\nACE: ##$pro##\nIncreases the armor of all ballistic vests by ##$multipro2##.", - "menu_scavenging_beta_desc": "BASIC: ##$basic##\nYour ammo pick up range is increased by ##$multibasic##.\n\nNote Doesn't stack with Gambler perk deck.\n\nACE: ##$pro##\nEvery ##6th## enemy you kill will drop an extra ammo box.", - "menu_defense_up_beta": "Rifleman", - "menu_defense_up_beta_desc": "BASIC: ##$basic##\nYou can now use steelsight while in bleedout.\n\nACE: ##$pro##\nYour movement speed is unhindered while using steelsight.\n\nYour weapon accuracy while moving with SMGs, Assault Rifles and Sniper Rifles is increased by ##$multipro##.", + "menu_stockholm_syndrome_beta_desc": "BASIC: ##$basic##\nYou and your crew gain ##$multibasic## damage absorption for each hostage you have.\n\nACE: ##$pro##\nEach hostage you have will extend the downtime between police assaults by ##$multipro additional## seconds.\n\nNote: These effects stack up to a maximum of ##4## hostages.", + "menu_control_freak_beta_desc": "BASIC: ##$basic##\nYour converted enemy takes ##$multibasic3## less damage.\n\nACE: ##$pro##\nYour converted enemy takes an additional ##$multipro4## less damage.", "menu_rifleman": "Lock N' Load", - "menu_rifleman_desc": "BASIC: ##$basic##\nYour snap to zoom ##$multibasic## faster and you swap your weapons ##$multibasic2## faster.\n\nACE: ##$pro##\nYou can now hip-fire with your weapons while sprinting.", + "menu_rifleman_desc": "BASIC: ##$basic##\nYour snap to zoom is ##$multibasic## faster and you swap your weapons ##$multibasic2## faster.\n\nACE: ##$pro##\nYou can now hip-fire with your weapons while sprinting.", + "menu_speedy_reload_beta_desc": "BASIC: ##$basic##\nYou reload your Sniper and Assault Rifles ##$multibasic## faster.\n\nACE: ##$pro##\nAny killing headshot will increase your reload speed by ##$multipro## for ##$multipro2## seconds. Can only be triggered by SMGs, Assault Rifles and Sniper Rifles fired in single shot fire mode.", + "menu_sniper_graze_damage": "Mind Blown", + "menu_sniper_graze_damage_desc": "BASIC: ##$basic##\nScoring a headshot with a Sniper Rifle deals ##$multibasic## of the damage to the closest enemy in a ##$multibasic2## radius.\n\nNote: Does not affect ##300 damage## Sniper Rifles.\n\nACE: ##$pro##\nAny killing headshot with a Sniper Rifle now deals ##$multipro## of the damage to the chained enemy.", + + + "menu_from_the_hip_desc": "BASIC: ##$basic##\nYou deal ##$multibasic## more damage with Shotguns.\n\nACE: ##$pro##\nThe time it takes to draw and holster Shotguns is decreased by ##$multipro##.", + "menu_shotgun_cqb_beta": "Fast Hands", + "menu_shotgun_cqb_beta_desc": "BASIC: ##$basic##\nYou reload ##Tube-fed## Shotguns ##$multibasic## faster.\n\nACE: ##$pro##\nYou reload ##Tube-fed## Shotguns an additional ##$multipro## faster.\nYou gain a ##$multipro2## increased steel sight zoom speed when using Shotguns.", + "menu_shotgun_impact_beta": "Point Blank", + "menu_shotgun_impact_beta_desc": "BASIC: ##$basic##\nShells for your Shotguns contain ##$multibasic## extra pellets.\n\nNote: Does not apply to AP or HE Slugs.\n\nACE: ##$pro##\nYour spread with Shotguns is increased by ##$multipro## when firing from the hip.", + "menu_far_away_beta": "Shotgun CQB", + "menu_far_away_beta_desc": "BASIC: ##$basic##\nYour rate of fire is increased by ##$multibasic## while firing from the hip with single shot Shotguns.\n\nACE: ##$pro##\nFor every hit with a Shotgun you gain a ##$multipro## movement speed boost that lasts for ##$multipro2## seconds.\nStacks up to ##$multipro3## times.", + "menu_close_by_beta": "Mag-fed Specialist", + "menu_close_by_beta_desc": "BASIC: ##$basic##\nYou reload ##Mag-fed## Shotguns ##$multibasic## faster.\n\nACE: ##$pro##\nYour ##Mag-fed## Shotguns have their magazine size increased by ##$multipro## shells.", + "menu_overkill_beta": "Shotgun Hell", + "menu_overkill_beta_desc": "BASIC: ##$basic##\nFiring a Shotgun has a ##$multibasic## chance to not consume any ammo.\n\nACE: ##$pro##\nWhen killing an enemy with a Shotgun, you have a ##$multipro## chance to cause nearby enemies to panic.\nPanic will make enemies go into short bursts of uncontrollable fear.\n\nThis cannot occur more than once every ##$multipro2## seconds.", + "menu_juggernaut_beta_desc": "BASIC: ##$basic##\nUnlocks the Improved Combined Tactical Vest for you to wear.\n\nACE: ##$pro##\nYour total armor value is increased by ##$multibasic##.", + "menu_show_of_force_beta": "Thick Skin", + "menu_show_of_force_beta_desc": "BASIC: ##$basic##\nYour steadiness is increased by ##$multibasic##.\nHigher steadiness reduces aimpunch.\n\nACE: ##$pro##\nIncreases the armor of all ballistic vests by ##$multipro2##.", + "menu_scavenging_beta_desc": "BASIC: ##$basic##\nYour ammo pick up range is increased by ##$multibasic##.\n\nNote: Doesn't stack with Gambler perk deck.\n\nACE: ##$pro##\nEvery ##6th## enemy you kill will drop an extra ammo box.", + "menu_bandoliers_beta_desc": "BASIC: ##$basic##\nThe heavier your armor, the more total ammo you get.\n\nThe total capacity increase scales up to ##$multibasic## with Improved Combined Tactical Vest.\n\nACE: ##$pro##\nYour total ammo capacity is increased by ##$multipro##.", + + + "menu_defense_up_beta": "Transporter", + "menu_defense_up_beta_desc": "BASIC: ##$basic##\nYou move ##$multibasic## faster when carrying bags.\n\nACE: ##$pro##\nYou can throw bags ##$multipro## further.", "menu_sentry_targeting_package_beta": "Daredevil", - "menu_sentry_targeting_package_beta_desc": "BASIC: ##$basic##\nYou take ##$multibasic## less damage while interacting with objects.\n\nACE: ##$pro##\nYou can reload your weapons while sprinting.", + "menu_sentry_targeting_package_beta_desc": "BASIC: ##$basic##\nYou take ##$multibasic## less damage while interacting with objects.\n\nACE: ##$pro##\nYou can now reload your weapons while sprinting.", "menu_engineering_beta": "Defense Package", "menu_engineering_beta_desc": "BASIC: ##$basic##\nYour sentry guns gain ##$multibasic## increased health.\n\nACE: ##$pro##\nYour sentry guns gain a protective shield.", "menu_tower_defense_beta": "Sentry Nest", @@ -42,60 +79,186 @@ "menu_eco_sentry_beta": "PhD in Engineering", "menu_eco_sentry_beta_desc": "BASIC: ##$basic##\nYour sentry guns now have an upgraded motion sensor, allowing them to mark all enemies in a ##10m## radius and giving them a ##$multibasic## increase in accuracy.\n\nACE: ##$pro##\nYou can now toggle AP rounds on your sentry guns, lowering the rate of fire by ##$multipro##, but increasing damage by ##$multipro2## and allowing it to pierce through enemies and shields.", "menu_combat_engineering_beta": "Combat Engineer", - "menu_fast_hands": "Fast Hands", + "st_menu_technician_auto": "Heavy Weapons Guy", + "menu_fast_fire_beta": "Mag Plus", + "menu_fast_fire_beta_desc": "BASIC: ##$basic##\nYour SMGs, LMGs and Assault Rifles gain ##$multibasic## more bullets in their magazine.\n\nACE: ##$pro##\nYour SMGs, LMGs and Assault Rifles gain ##$multipro## additional bullets in their magazine.\n\nNote: Does not affect the Aced \"Sleight of Hand\" skill.", + "menu_body_expertise_beta_desc": "BASIC: ##$basic##\nYou no longer have movement penalty when wielding Heavy Weaponry.\n\nACE: ##$pro##\n##$multipro## from the bonus headshot damage is permanently applied to hitting enemies on the body.\nThis skill is only activated by SMGs fired in automatic mode, LMGs or Miniguns.", + "menu_fast_hands": "Sleight of Hand", + "menu_fast_hands_desc": "BASIC: ##$basic##\nYou reload your SMGs and LMGs ##$multibasic## faster.\n\nACE: ##$pro##\nKilling ##$multipro## enemies with SMGs, LMGs or Special Weapons set on automatic fire mode will increase your next reload speed by up to ##$multipro2##. This bonus is reduced by ##$multipro3## for each bullet above ##$multipro4## in the total magazine size, down to a maximum of ##$multipro5## reload speed increase.", "menu_oppressor_desc": "BASIC: ##$basic##\nYour weapons are ##$multibasic## more effective at threatening enemies.\n\nACE: ##$pro##\nYour weapons are additionally ##$multipro## more effective at threatening enemies.", - "menu_fast_hands_desc": "BASIC: ##$basic##\nYou reload your SMGs and LMGs ##$multibasic## faster.\n\nACE: ##$pro##\nKilling ##$multipro## enemies with SMGs, LMGs, Assault Rifles or Special Weapons set on automatic fire mode will increase your next reload speed by up to ##$multipro2##. This bonus is reduced by ##$multipro3## for each bullet above ##$multipro4## in the total magazine size, down to a maximum of ##$multipro5## reload speed increase.", - "menu_speedy_reload_beta_desc": "BASIC: ##$basic##\nYou reload your Sniper and Assault Rifles ##$multibasic## faster.\n\nACE: ##$pro##\nAny killing headshot will increase your reload speed by ##$multipro## for ##$multipro2## seconds. Can only be triggered by SMGs, Assault Rifles and Sniper Rifles fired in single shot fire mode.", - "menu_silence_expert_beta_desc": "BASIC: ##$basic##\nYou gain ##$multibasic## weapon stability with silenced weapons.\n\nACE: ##$pro##\nYou gain ##$multipro## weapon accuracy with silenced weapons and ##$multipro2## snap to zoom speed increase with silenced weapons.", - "menu_unseen_strike_beta_desc": "BASIC: ##$basic##\nIf you do not lose any armor or health for ##$multibasic## seconds, you gain a ##$multibasic2## critical hit chance for ##$multibasic3## seconds.\n\nACE: ##$pro##\nThe critical hit chance is increased to ##$multipro## and the duration is increased to ##$multipro2## seconds.", - "menu_cable_guy_beta_desc": "BASIC: ##$basic##\nThe power and range of your intimidation is increased by ##$multibasic2##.\n\nACE: ##$pro##\nYou and your crew gain ##$multipro## damage absorption for each hostage you have. This effect stacks with up to a maximum of ##$multipro2## hostages.", - "menu_triathlete_beta_desc": "BASIC: ##$basic##\nIncrease your supply of cable ties by ##$multibasic##.\n\nACE: ##$pro##\nYou can cable tie hostages ##$multipro## faster.", - "menu_spotter_teamwork_beta_desc": "BASIC: ##$basic##\nEnemies you mark take ##$multibasic## more damage.\n\nACE: ##$pro##\nEnemies you mark take an additional ##$multipro## damage when further away than ##$multipro2## meters.\n\nYou can now mark specials by aiming down at them with any weapon.", + "menu_fire_control_beta": "AP Rounds", + "menu_fire_control_beta_desc": "BASIC: ##$basic##\nYou gain ##$multibasic## weapon accuracy while firing from the hip.\n\nACE: ##$pro##\nAll of your ranged weapons can now pierce through enemy body armor. Does not apply to throwable weapons.", + + + "menu_sprinter": "Athlete", + "menu_sprinter_desc": "BASIC: ##$basic##\nThe time before stamina regeneration starts is reduced by ##$multibasic## and the regeneration rate of stamina is increased by ##$multibasic##.\n\nACE: ##$pro##\nYour speed while walking is increased by ##$multipro##. Your total movement speed is increased by ##$multipro2##.", + "menu_awareness": "Duck and Cover", + "menu_awareness_desc": "BASIC: ##$basic##\nYour speed while crouching is increased by ##$multibasic##.\n\nACE: ##$pro##\nYou gain a ##$multipro## chance to dodge enemy fire while crouching.", + "menu_optic_illusions": "Sprinter", + "menu_optic_illusions_desc": "BASIC: ##$basic##\nYou can now sprint while strafing. You also sprint ##$multibasic## faster.\n\nACE: ##$pro##\nYou gain a ##$multipro## chance to dodge enemy fire while sprinting and zip-lining.", + "menu_dire_need_beta": "Second Wind", + "menu_dire_need_beta_desc": "BASIC: ##$basic##\nWhen your armor breaks your movement speed is increased by ##$multibasic## for ##$multibasic2## seconds.\n\nACED: ##$pro##\nWhen your armor breaks you have a ##$multipro## chance to cause nearby enemies to panic. Panic will make enemies go into short bursts of uncontrollable fear.\n\nThis cannot occur more than once every ##$multipro2## seconds.", + "menu_insulation_beta_desc": "BASIC: ##$basic##\nWhen tased, the shock effect will be ##$multibasic## weaker.\n\nYou no longer randomly pull the trigger while being tased.\n\nACED: ##$pro##\nWhen tased, you are able to free yourself from the taser by interacting with it within ##$multipro2## seconds of getting tased.", + "st_menu_ghost_silencer": "Specialized Killing", + "menu_scavenger_beta": "Resilient Assault", + "menu_scavenger_beta_desc": "BASIC: ##$basic##\nYou gain ##$multibasic## critical hit chance.\n\nACE: ##$pro##\nWhen your armor breaks, the first shot on every enemy will cause that enemy to stagger.\n\nThe effect persists for ##$multipro## seconds after your armor has recovered.", + "menu_silenced_damage": "Silent Killer", + "menu_silenced_damage_desc": "BASIC: ##$basic##\nYou gain ##$multibasic## concealment for each silenced weapon equipped. Reduce the concealment penalty of silencers by ##$multibasic2##.\n\nACE: ##$pro##\nYou deal ##$multipro## more damage and have a ##$multipro2## chance to pierce enemy armor with all silenced weapons.", + "menu_silence_expert_beta_desc": "BASIC: ##$basic##\nYou gain ##$multibasic## weapon stability with silenced weapons.\n\nACE: ##$pro##\nYou gain ##$multipro## weapon accuracy and ##$multipro2## snap to zoom speed increase with silenced weapons.", + "menu_spotter_teamwork_beta_desc": "BASIC: ##$basic##\nEnemies you mark take ##$multibasic## more damage when farther away than ##$multibasic2## meters.\n\nACE: ##$pro##\nEnemies you mark take an additional ##$multipro## damage from all sources at all ranges.\n\nThe marked state duration is increased by ##$multipro2##.", "menu_jail_workout_beta_desc": "BASIC: ##$basic##\nIncreases the time before you start getting detected by ##$multibasic## while in casing mode.\n\nYou are ##$multibasic## less likely to be targeted when you are close to your crew members in loud.\n\nACE: ##$pro##\nYour concealment is increased by ##$multipro##.", "menu_cleaner_beta": "Eagle Eye", - "menu_cleaner_beta_desc": "BASIC: ##$basic##\nYou deal ##$multibasic## more damage against special enemies.\n\nACE: ##$pro##\nThe range at which you can manually mark enemies is increased by ##$multipro## times. The marked state duration is increased by ##$multipro2##.", - "menu_second_chances_beta": "Quick Grab", - "menu_second_chances_beta_desc": "BASIC: ##$basic##\nYou can pack and interact with loot bags ##$multibasic## faster.\n\nACE: ##$pro##\nYou lockpick ##$multipro## faster. You also gain the ability to lockpick safes.", + "menu_cleaner_beta_desc": "BASIC: ##$basic##\nYou deal ##$multibasic## more damage against special enemies.\n\nACE: ##$pro##\nYou can now mark specials by aiming down at them with any weapon.\n\nThe range at which you can manually mark enemies is ##$multipro## times longer.", + "menu_second_chances_beta": "Logistician", + "menu_second_chances_beta_desc": "BASIC: ##$basic##\nYou can pack and interact with loot bags ##$multibasic## faster.\n\nACE: ##$pro##\nYou lockpick ##$multipro## faster.\n\nYou can now silently crack safes by hand.", "menu_chameleon_beta": "Blackout", "menu_chameleon_beta_desc": "BASIC: ##$basic##\nYou gain the ability to disable ##1## camera from detecting you and your crew. Effect lasts for ##25## seconds.\n\nACE: ##$pro##\nYour ECM jammer can now also be used to open certain electronic doors. Pagers are delayed by the ECM jammers.", "menu_ecm_booster_beta": "ECM Feedback", "menu_ecm_booster_beta_desc": "BASIC: ##$basic##\nYou can now interact with ECM jammers to cause a feedback loop that stuns enemies.\n\nFeedback loop has a ##$multibasic## radius.\nObstructed line of sight ##halves## the radius.\n\nACE: ##$pro##\nECM feedback now recharges every ##$multipro## minutes.", - "menu_ecm_2x_beta_desc": "BASIC: ##$basic##\nYou can now place ##2## ECM jammers instead of just one.\n\nACE: ##$pro##\nYour ECM jammer and feedback now last ##$multipro## seconds. You gain access to all insider assets.", - "menu_drop_soap_beta_desc": "BASIC: ##$basic##\nYou can now pull out melee while sprinting.\n\nACE: ##$pro##\nYou can now knock down bulldozers with melee.\nThis cannot occur more than every ##$multipro## seconds.", + "menu_ecm_2x_beta_desc": "BASIC: ##$basic##\nYou can now place ##2## ECM jammers instead of just one.\n\nACE: ##$pro##\nYour ECM jammer and feedback now last ##$multipro## seconds.", + "menu_backstab_beta_desc": "BASIC: ##$basic##\nYou gain a ##$multibasic## critical hit chance for every ##$multibasic2## points of detection rate under ##$multibasic3## up to ##$multibasic4##.\n\nCritical hits deal ##$multibasic5## damage.\n\nACE: ##$pro##\nYou now gain ##$multipro## critical hit chance for every ##$multipro2## point of detection rate instead.\n\nCritical hits now deal an additional ##$multipro3## damage.\n\nNote: Critical hits don't apply to grenade / rocket launchers.", + + + "menu_nine_lives_beta": "Tough Guy", "menu_pistol_beta_messiah_desc": "BASIC: ##$basic##\nWhile in bleedout, you can revive yourself if you kill an enemy. You only have ##$multibasic## charge.\n\nYour bleedout timer is increased by ##$multibasic2## seconds.\n\nNote: Getting out of custody doesn't replenish the charges.\n\nACE: ##$pro##\nYou gain an additional ##$multipro## charges.\n\nWhenever you get into custody, your hostages will trade themselves for your safe return. This effect can occur during assaults, but only ##1## time during a heist.", - "menu_backstab_beta_desc": "BASIC: ##$basic##\nYou gain a ##$multibasic## critical hit for every ##$multibasic2## points of detection rate under ##$multibasic3## up to ##$multibasic4##.\n\nACE: ##$pro##\nYou gain ##$multipro## critical hit chance for every ##$multipro2## point of detection rate under ##$multipro3## up to ##$multipro4##.\n\nNote: Critical hits deal ##double## damage and don't apply to grenade / rocket launchers.", - "menu_sniper_graze_damage": "Mind Blown", - "menu_sniper_graze_damage_desc": "BASIC: ##$basic##\nScoring a headshot with a Sniper Rifle deals ##$multibasic## of the damage to the closest enemy in a ##$multibasic2## radius.\n\nACE: ##$pro##\nAny killing headshot with a Sniper Rifle now deals ##$multipro## of the damage to the chained enemy.\n\nNote: Does not affect Semi-Auto Sniper Rifles.", - "menu_deck21_9_desc": "Crew members killing enemies while the feedback effect is active will regenerate ##$multiperk## health.", + "menu_drop_soap_beta_desc": "BASIC: ##$basic##\nYou can now sprint with melee weapons.\n\nACE: ##$pro##\nYou can now knock down bulldozers with melee.\n\nThis cannot occur more than every ##$multipro## seconds.", + "menu_wolverine_beta_desc": "BASIC: ##$basic##\nThe lower your health, the more movement speed you gain. When your health is below ##$multibasic##, you will gain up to ##$multibasic2## movement speed.\n\nACE: ##$pro##\nThe lower your health, the more damage you do. When your health is below ##$multipro##, you will do up to ##$multipro2## more melee and saw damage.", + "menu_trigger_happy_beta_desc": "BASIC: ##$basic##\nFor every hit with a pistol you gain a ##$multibasic## damage boost that lasts for ##$multibasic2## seconds.\nStacks up to ##$multibasic3## times.\n\nACE: ##$pro##\nYou now gain ##$multipro## damage boost for every hit and the damage boost lasts ##$multipro2## seconds.\nStacks up to ##$multipro3## times.", + "menu_perseverance_beta_desc": "BASIC: ##$basic##\nInstead of getting downed instantly, you gain the ability to keep on fighting for ##$multibasic2## seconds with a ##$multibasic3## movement penalty before going down.\n\nNote: Does not trigger on fall or fire damage. \n\nACE: ##$pro##\nIncreases the duration of Swan Song by ##$multipro## seconds.", + "menu_nine_lives_beta_desc": "BASIC: ##$basic##\nYou can now use your steelsight while in bleedout.\n\nACE: ##$pro##\nYou can use your primary weapon in bleedout.", + "menu_tea_cookies_beta_desc": "BASIC ##$basic##\nYou can now carry ##$multibasic## more First Aid Kits.\n\nACE: ##$pro##\nYou can now additionaly carry ##$multipro## more First Aid Kits.\n\nYour deployed First Aid Kits will be automatically used if a player is downed within a ##$multipro2## meter radius of the First Aid Kit. This cannot occur more than once every ##$multipro3## seconds.", + + + + "menu_st_category_health": "Health", + "menu_st_category_healing": "Healing", + "menu_st_category_armor": "Armor", + "menu_st_category_armor_gating": "Armor Gating", + "menu_st_category_resistance": "Resistance", + "menu_st_category_support": "Support", + "menu_st_category_dodge": "Dodge", + + + "menu_deckall_2": "Refund", + "menu_deckall_2_desc": "You gain a ##2%## base chance to get a throwable from an ammo box.\n\nThe base chance is increased by ##0.1%## for each ammo box you pick up that does not contain a throwable.\n\nWhen a throwable has been found, the chance is reset to its base value.", + "menu_deckall_4_desc": "You gain access to all insider assets.\n\nYou gain ##+1## increased concealment.", + "menu_deckall_6_desc": "Unlocks an armor bag equipment for you to use. The armor bag can be used to change your armor during a heist.\n\nWhen wearing armor, your movement speed is ##15%## less affected.", + "menu_deck1_3_desc": "You and your crew's stamina is increased by ##$multiperk##.\n\nIncreases your shout distance by ##$multiperk2##.", - "menu_deck1_9_desc": "You and your crew will gain ##$multiperk## max health and ##$multiperk2## stamina for each hostage up to ##$multiperk3## times.\n\nNote: Crew perks do not stack.", - "menu_deck21_7_desc": "Killing ##$multiperk## enemies while the jamming effect is active will grant you ##$multiperk2## dodge chance for ##$multiperk3## seconds.", - "menu_deck10_3_desc": "Your ammo pickup is increased by ##$multiperk##.\n\nYou gain ##$multiperk3## more health.", - "menu_deck10_9_desc": "Increase health gained from ammo packs by additional ##$multiperk##.\n\nYour ammo pick up range is increased by ##$multiperk4##.", - "menu_deck11_1_desc": "Damaging an enemy heals ##$multiperk## life points every ##$multiperk2## seconds for ##$multiperk3## seconds.\n\nThis effect stacks but cannot occur more than once every ##$multiperk4## seconds and only while wearing the Two-piece Suit.", + "menu_deck1_9_desc": "You and your crew will gain ##$multiperk## max health and ##$multiperk2## stamina for each hostage up to ##$multiperk3## times.\n\nNote: Crew perks do not stack.\n\nDeck Completion Bonus: Your chance of getting a higher quality item during a Payday is increased by ##10%##.", + "menu_deck2_7": "Frenzied", + "menu_deck2_7_desc": "When your health gets below ##$multiperk## you will be immune to health damage for ##$multiperk2## seconds.\n\nThis cannot occur more than once every ##$multiperk3## seconds.", + "menu_deck3_9_desc": "Reduces the armor recovery time for you and your crew by ##$multiperk2##.\n\nNote: Crew perks do not stack.\n\nDeck Completion Bonus: Your chance of getting a higher quality item during a Payday is increased by ##10%##.", + "menu_deck5_1": "Gunslinger", + "menu_deck5_1_desc": "You now automatically reload your secondary after scoring ##$multiperk## kills with your primary, and ##vice versa##.", + "menu_deck5_3": "Fastest Hand in the West", + "menu_deck5_3_desc": "You now swap your weapons ##$multiperk## faster.", + "menu_deck5_5": "Unseen Strike", + "menu_deck5_5_desc": "You gain ##$multiperk## critical hit chance for ##$multiperk2## seconds after not getting hit for ##$multiperk3## seconds.\n\nCritical hits deal ##double## damage and don't apply to grenade / rocket launchers. Taking any damage cancels the no-hit timer.", + "menu_deck5_7": "Sharpshooter", + "menu_deck5_7_desc": "Scoring a headshot will refund ##$multiperk## bullet to your used weapon.\n\nThis cannot occur more than once every ##$multiperk2## seconds.", + "menu_deck5_9_desc": "Killing an enemy regenerates ##$multiperk## armor.\n\nThis cannot occur more than once every ##$multiperk2## second.\n\nDeck Completion Bonus: Your chance of getting a higher quality item during a Payday is increased by ##10%##.", + "menu_deck6_3_desc": "Your chance to dodge and armor are increased for ballistic vests.\n\nHeavier vests receive more dodge and armor.", + "menu_deck6_5_desc": "Your chance to dodge and armor are further increased for ballistic vests.\n\nHeavier vests receive more dodge and armor.", + "menu_deck6_7_desc": "Your chance to dodge and armor are further increased for ballistic vests.\n\nHeavier vests receive more dodge and armor.", + "menu_deck7_7_desc": "Your chance to be targeted while standing still and crouching is decreased by an additional ##$multiperk2##.\n\nYou answer pagers ##$multiperk3## faster.", + "menu_deck7_9": "Ricochet", + "menu_deck7_9_desc": "Dodged bullets will now ##ricochet## back to the enemy that fired them.\n\nDeck Completion Bonus: Your chance of getting a higher quality item during a Payday is increased by ##10%##.", + "menu_deck9_7_desc": "Killing an enemy at medium range recovers ##$multiperk## armor.\n\nThis cannot occur more than once every ##$multiperk2## second.", + "menu_deck10_3_desc": "Your ammo pickup rate is increased by ##$multiperk##.\n\nYou gain ##$multiperk3## more health.", + "menu_deck10_9_desc": "Increase health gained from ammo packs by additional ##$multiperk##.\n\nYour ammo pick up range is increased by ##$multiperk4##.\n\nDeck Completion Bonus: Your chance of getting a higher quality item during a Payday is increased by ##10%##.", + "menu_deck11_1_desc": "Damaging an enemy heals ##$multiperk## life point every ##$multiperk2## seconds for ##$multiperk3## seconds.\n\nThis effect stacks but cannot occur more than once every ##$multiperk4## seconds and only while wearing the Two-piece Suit.", "menu_deck11_3_desc": "Damaging an enemy now heals ##$multiperk## life points every ##$multiperk2## seconds for ##$multiperk3## seconds.", "menu_deck11_7_desc": "Damaging an enemy now heals ##$multiperk## life points every ##$multiperk2## seconds for ##$multiperk3## seconds.", + "menu_deck12_1_desc": "The lower your health, the faster your armor recovers.\n\nYou will gain up to ##$multiperk## armor recovery rate.", + "menu_deck12_3_desc": "Increases weapon swapping speed by ##$multiperk##.", + "menu_deck12_5_desc": "You are ##$multiperk## less likely to be targeted when you are close to your crew members.", + "menu_deck12_7_desc": "The lower your health, the more likely you are to dodge.\n\nYou will gain up to ##$multiperk## dodge.", + "menu_deck12_9_desc": "The lower your health, the more damage you do.\n\nYou will do up to ##$multiperk## more damage with ranged weapons.\n\nDeck Completion Bonus: Your chance of getting a higher quality item during a Payday is increased by ##10%##.", "menu_deck13_3_desc": "Increases the amount of health stored from kills by ##$multiperk##.", "menu_deck13_5_desc": "Increases the maximum health that can be stored by ##$multiperk##.\n\nYour chance to dodge is increased by ##$multiperk3##.", - "menu_deck13_7_desc": "Increases the amount of health stored from kills by ##$multiperk##.", - "menu_deck12_3_desc": "Increases weapon swapping speed by ##$multiperk##.", + "menu_deck13_7_desc": "Increases the amount of health stored from kills by ##$multiperk##.", + "menu_deck14_3_desc": "Members of your crew also gains the effect of your Hysteria Stacks.\nHysteria Stacks from multiple crew members do not stack and only the stacks that gives the highest damage absorption will have an effect.\n\nAll firearms have a chance to spread panic among your enemies.\nPanic will make enemies go into short bursts of uncontrollable fear.", + "menu_deck15_1_desc": "Instead of fully regenerating armor when out of combat, The Anarchist will continuously regenerate armor throughout the entire combat. Heavier armor regenerates more armor, but during longer intervals.\n\nNote: Skills and perks that increases the armor recovery rate are disabled when using this perk deck.", "menu_deck15_7_desc": "##$multiperk## of your health is converted into ##$multiperk2## armor.\n\nYour chance to dodge is increased by ##$multiperk3##.", - "menu_deck12_9_desc": "The lower your health, the more damage you do. When your health is below ##$multiperk##, you will do up to ##$multiperk2## more damage with ranged weapons as well.\n\nAll berserker state effects in this perk deck will start at ##$multiperk3## health instead of ##$multiperk4##.", + "menu_deck15_9_desc": "Dealing damage will grant you ##$multiperk## armor - this can only occur once every ##$multiperk2## seconds.\n\nYour armor is increased by ##$multiperk3##.\n\nDeck Completion Bonus: Your chance of getting a higher quality item during a Payday is increased by ##10%##.", + "menu_deck17_9_desc": "For every ##5 points## of health gained during the injector effect while at maximum health, the recharge time of the injector is reduced by ##1 second##.\n\nDeck Completion Bonus: Your chance of getting a higher quality item during a Payday is increased by ##10%##.", + "menu_deck21_5_desc": "Killing the enemy while the feedback is active will regenerate ##$multiperk## health.\n\nYour chance to dodge is increased by ##$multiperk2##.", + "menu_deck21_7_desc": "You gain ##$multiperk## additional health.", + "menu_deck21_9_desc": "Crew members killing enemies while the feedback effect is active will regenerate ##$multiperk## health.\n\nDeck Completion Bonus: Your chance of getting a higher quality item during a Payday is increased by ##10%##.", + "menu_deck22_1_desc": "Unlocks and equips the Leech Ampule.\n\nChanging to another perk deck will make the Leech Ampule unavailable again. The Leech Ampule replaces your current throwable, is equipped in your throwable slot and can be switched out if desired. While in game you can use throwable key to activate the Leech Ampule.\n\nActivating the Leech Ampule will ##fully## restore your health and disable your armor for the duration of the Leech Ampule.\n\nWhile the Leech Ampule is active your health is divided into segments of ##$multiperk2## and damage taken from enemies removes one segment.\n\nKilling ##1## enemy will block damage for ##$multiperk3## seconds.\nAnytime you take damage your teammates are healed for ##$multiperk4## health.\n\nThe Leech Ampule lasts ##$multiperk5## seconds with a ##$multiperk6## seconds cooldown.", "menu_deck22_9_desc": "While the Leech Ampule is active your health is now divided into segments of ##$multiperk##.\n\nYour maximum health is increased by ##$multiperk2##.\n\nDeck Completion Bonus: Your chance of getting a higher quality item during a Payday is increased by ##10%##.", - "menu_deck22_1_desc": "Unlocks and equips the Leech Ampule.\n\nChanging to another perk deck will make the Leech Ampule unavailable again. The Leech Ampule replaces your current throwable, is equipped in your throwable slot and can be switched out if desired. While in game you can use throwable key to activate the Leech Ampule.\n\nActivating the Leech Ampule will ##fully## restore your health and disable your armor for the duration of the Leech Ampule.\n\nWhile the Leech Ampule is active your health is divided into segments of ##$multiperk2## and damage taken from enemies removes one segment.\n\nKilling ##2## enemies will block damage for ##$multiperk3## seconds.\nAnytime you take damage your teammates are healed for ##$multiperk4## of their health.\n\nThe Leech Ampule lasts ##$multiperk5## seconds with a ##$multiperk6## seconds cooldown.", - "menu_deck3_9_desc": "Reduces the armor recovery time for you and your crew by ##$multiperk2##.\n\nNote: Crew perks do not stack.", - "menu_deck17_9_desc": "For every ##5 points## of health gained during the injector effect while at maximum health, the recharge time of the injector is reduced by ##1 second##.", - "menu_deck15_1_desc": "Instead of fully regenerating armor when out of combat, The Anarchist will continuously regenerate armor throughout the entire combat. Heavier armor regenerates more armor, but during longer intervals.\n\nNote: Skills and perks that increases the armor recovery rate are disabled when using this perk deck.", - "menu_deck9_7_desc": "Killing an enemy at medium range recovers ##$multiperk## armor.\n\nThis cannot occur more than once every ##$multiperk2## second.", + + "menu_deck1_3_short": "You and your crew's stamina is increased by ##$multiperk##. Increases your shout distance by ##$multiperk2##.", + "menu_deck1_9_short": "You and your crew will gain ##$multiperk## max health and ##$multiperk2## stamina for each hostage up to ##$multiperk3## times. Note: Crew perks do not stack.", + "menu_deck2_7_short": "When your health gets below ##$multiperk## you will be immune to health damage for ##$multiperk2## seconds. This cannot occur more than once every ##$multiperk3## seconds.", + "menu_deck3_9_short": "Reduces the armor recovery time for you and your crew by ##$multiperk2##. Note: Crew perks do not stack.", + "menu_deck5_1_short": "You now automatically reload your secondary after scoring ##$multiperk## kills with your primary, and ##vice versa##.", + "menu_deck5_3_short": "You now swap your weapons ##$multiperk## faster.", + "menu_deck5_5_short": "You gain ##$multiperk## critical hit chance for ##$multiperk2## seconds after not getting hit for ##$multiperk3## seconds.\n\nCritical hits deal ##double## damage and don't apply to grenade / rocket launchers. Taking any damage cancels the no-hit timer.", + "menu_deck5_7_short": "Scoring a headshot will refund ##$multiperk## bullet to your used weapon.\n\nThis cannot occur more than once every ##$multiperk2## seconds.", + "menu_deck5_9_short": "Killing an enemy regenerates ##$multiperk## armor.\n\nThis cannot occur more than once every ##$multiperk2## second.", + "menu_deck6_3_short": "Your chance to dodge and armor are increased for ballistic vests. Heavier vests receive more dodge and armor.", + "menu_deck6_5_short": "Your chance to dodge and armor are further increased for ballistic vests. Heavier vests receive more dodge and armor.", + "menu_deck6_7_short": "Your chance to dodge and armor are further increased for ballistic vests. Heavier vests receive more dodge and armor.", + "menu_deck7_7_short": "Your chance to be targeted while standing still and crouching is decreased by an additional ##$multiperk2##. You answer pagers ##$multiperk3## faster.", + "menu_deck7_9_short": "Dodged bullets will now ##ricochet## back to the enemy that fired them.", + "menu_deck9_7_short": "Killing an enemy at medium range recovers ##$multiperk## armor. This cannot occur more than once every ##$multiperk2## second.", + "menu_deck10_3_short": "Your ammo pickup rate is increased by ##$multiperk##. You gain ##$multiperk3## more health.", + "menu_deck10_9_short": "Increase health gained from ammo packs by additional ##$multiperk##. Your ammo pick up range is increased by ##$multiperk4##.", + "menu_deck11_1_short": "Damaging an enemy heals ##$multiperk## life points every ##$multiperk2## seconds for ##$multiperk3## seconds. This effect stacks but cannot occur more than once every ##$multiperk4## seconds and only while wearing the Two-piece Suit.", + "menu_deck11_3_short": "Damaging an enemy now heals ##$multiperk## life points every ##$multiperk2## seconds for ##$multiperk3## seconds.", + "menu_deck11_7_short": "Damaging an enemy now heals ##$multiperk## life points every ##$multiperk2## seconds for ##$multiperk3## seconds.", + "menu_deck12_1_short": "The lower your health, the faster your armor recovers. You will gain up to ##$multiperk## armor recovery rate.", + "menu_deck12_3_short": "Increases weapon swapping speed by ##$multiperk##.", + "menu_deck12_5_short": "You are ##$multiperk## less likely to be targeted when you are close to your crew members.", + "menu_deck12_7_short": "The lower your health, the better you dodge. You will gain up to ##$multiperk## dodge.", + "menu_deck12_9_short": "The lower your health, the more damage you do. You will do up to ##$multiperk## more damage with ranged weapons.", + "menu_deck13_3_short": "Increases the amount of health stored from kills by ##$multiperk##.", + "menu_deck13_5_short": "Increases the maximum health that can be stored by ##$multiperk##. Your chance to dodge is increased by ##$multiperk3##.", + "menu_deck13_7_short": "Increases the amount of health stored from kills by ##$multiperk##.", + "menu_deck14_3_short": "Members of your crew also gains the effect of your Hysteria Stacks. All firearms have a chance to spread panic among your enemies.", + "menu_deck15_1_short": "Instead of fully regenerating armor when out of combat, The Anarchist will continuously regenerate armor throughout the entire combat. Heavier armor regenerates more armor, but during longer intervals. Note: Skills and perks that increases the armor recovery rate are disabled when using this perk deck.", + "menu_deck15_7_short": "##$multiperk## of your health is converted into ##$multiperk2## armor. Your chance to dodge is increased by ##$multiperk3##.", + "menu_deck15_9_short": "Dealing damage will grant you ##$multiperk## armor - this can only occur once every ##$multiperk2## seconds. Your armor is increased by ##$multiperk3##.", + "menu_deck17_9_short": "For every ##5 points## of health gained during the injector effect while at maximum health, the recharge time of the injector is reduced by ##1 second##.", + "menu_deck21_5_short": "Killing the enemy while the feedback is active will regenerate ##$multiperk## health. Your chance to dodge is increased by ##$multiperk2##.", + "menu_deck21_7_short": "You gain ##$multiperk## additional health.", + "menu_deck21_9_short": "Crew members killing enemies while the feedback effect is active will regenerate ##$multiperk## health.", + "menu_deck22_1_short": "Unlocks and equips the Leech Ampule.", + "menu_deck22_9_short": "While the Leech Ampule is active your health is now divided into segments of ##$multiperk##. Your maximum health is increased by ##$multiperk2##.", + + + "bm_global_value_mrwi_unlock": "LOCKED AND ITS CARDS ARE REUSED FOR OTHER PERK DECKS.", "hud_int_equipment_drill_upgrade": "PRESS $BTN_INTERACT TO UPGRADE THE DRILL", - "menu_difficulty_sm_wish": "Eclipse", - "menu_risk_sm_wish": "Eclipse. You only celebrate in the light... because I allow it.", - "menu_risk_elite": "Death Wish.\nFor hardcore players. ", - "menu_toggle_one_down": "Pro-Job", - "menu_one_down": "Pro-Job", - "menu_toggle_one_down_lobbies": "Allow Pro-Job Lobbies" + "hud_assault_ponr": "Full Force Onslaught", + "hud_assault_normal_ponr": "Maximum Force Responders Inbound", + "hud_assault_zeal_ponr": "Zeal Legion Inbound", + "eclipse_menu_main": "Eclipse", + "eclipse_menu_ponr_assault_text": "Assault banner edits", + "eclipse_menu_ponr_assault_text_desc": "Assault banner flavor edits. Incompatible with mods that edit assault banner.", + + + "menu_pro_warning": "This is a pro job!\n\nFriendly fire is enabled.\nVarious heists have points of no return enabled.\nEach consecutive down decreases bleedout timer and revive health.", + "menu_difficulty_easy_wish": "Eclipse", + "menu_risk_easy_wish": "Operation 'Eclipse'.\nFor you, action is the juice.", + "menu_risk_special": "Overkill.\nMaximum Force Responders inbound.", + "menu_difficulty_very_hard": "Hard", + "menu_risk_fbi": "Hard.\nFBI on high alert.", + "menu_difficulty_hard": "Normal", + "menu_risk_swat": "Normal.\nAverage risk.", + "menu_difficulty_normal": "Easy", + "menu_risk_pd": "Easy.\nHang on! I forgot my donuts!", + + "menu_toggle_one_down": "Pro Job", + "menu_one_down": "Pro Job", + "menu_toggle_one_down_lobbies": "Allow Pro Job Lobbies" + "menu_stat_job_completed": "", "menu_max_progress": "Max account progression", "menu_max_progress_help": "Max out your account progression and skip grind.", - "menu_progress_msg": "Are you sure you want to max out your progress? This will max out your level, infamy, cash, and unlock all available items." + "menu_progress_msg": "Are you sure you want to max out your progress?\nThis will max out your character and unlock all available items.", + "menu_max_masks": "Max mask customization", + "menu_max_masks_help": "Max out your masks and customization for them to skip grind.", + "menu_masks_msg": "Are you sure you want to max out your masks?\nThis will give you 10 of each mask, material, pattern and color." } diff --git a/loc/schinese.json b/loc/schinese.json new file mode 100644 index 0000000..26a8ffc --- /dev/null +++ b/loc/schinese.json @@ -0,0 +1,262 @@ +{ + "bm_wp_upg_a_custom_desc": "更大的弹丸可以造成更大的冲击力。\n一发霰弹共有6发弹丸。\n3米内,造成的伤害增加40%。\n伤害衰减在更近的距离开始生效。", + "bm_wp_upg_a_explosive_desc": "内含可爆炸且致命的弹药。\n这种弹药很难找到。\n大幅提高精准度。\n无伤害衰减。", + "bm_wp_upg_a_piercing_desc": "内含带有尖刺的金属飞行物。\n一发霰弹共有12发箭弹。\n可穿透护甲。\n伤害衰减在更远的距离开始生效。", + "bm_wp_upg_a_slug_desc": "内含一发大号铅弹。\n巨幅提高精准度。\n可穿透护甲、护盾、墙壁和敌人。\n无伤害衰减。", + "bm_wp_upg_a_dragons_breath_desc": "内含可点燃人体的化学物质。\n一发霰弹共有12发镁弹。\n点燃15米内的任何敌人。\n这种弹药很难找到。", + "bm_wp_upg_mk2_rare_desc": "这种燃料很容易就能找到。", + "bm_wp_upg_mk2_welldone_desc": "这种燃料很难找到。", + "bm_w_hajk": "CR 805B 步枪", + + "bm_menu_bonus": "枪械技能", + "bm_menu_perk_speedloader": "极速装填", + "bm_menu_perk_speedloader_desc": "以总弹量作为代价获得额外的换弹速度。\n", + "bm_menu_perk_haste": "极速奔跑", + "bm_menu_perk_haste_desc": "以总弹量作为代价获得额外的10%移动速度。\n", + "bm_menu_perk_deadsilence": "死寂隐蔽", + "bm_menu_perk_deadsilence_desc": "以总弹量和武器控制性作代价获得额外的隐匿度。\n", + "bm_menu_perk_jawbreaker": "头骨碎裂", + "bm_menu_perk_jawbreaker_desc": "以射速和25%的捡弹作为代价获得额外的伤害。\n", + "bm_menu_perk_whirlwind": "旋风扳机", + "bm_menu_perk_whirlwind_desc": "以武器稳定性作为代价获得额外的射速。\n", + "bm_menu_perk_stockpile": "过度储备", + "bm_menu_perk_stockpile_desc": "以装弹速度作为代价获得额外的总弹量。\n", + + "menu_infamy_desc_root_new": "作为初来乍到的犯罪精英,你要做的第一件事就是拿上装备,搞清楚现在你适合做些什么。\n\n加成:\n你的恶名物品基础掉落率从##0.3%##增加至##0.6%##。获得的经验值增加##5%##。", + + "st_menu_mastermind_inspire": "支援系", + "menu_bandage": "急救绷带", + "menu_bandage_desc": "基础: ##$basic##\n你救起的队友在##$multibasic2##秒内少受到##$multibasic##的伤害。\n\n精通: ##$pro##\n救起一个队友还会额外给他##$multipro##的血量。", + "menu_field_surgery": "战地医生", + "menu_field_surgery_desc": "基础: ##$basic##\n你救起的队友在##$multibasic2##秒内少受到##$multibasic##的伤害。\n\n精通: ##$pro##\n你在救起一个队友后##$multibasic2##秒之内会额外少受到##$multipro##的伤害。", + "menu_company_soul": "团队之魂", + "menu_company_soul_desc": "基础: ##$basic##\n你和你队友的所有武器的武器稳定性提高##$multibasic##点。\n\n精通: ##$pro##\n你和你的队友的耐力提高##$multipro##。", + "menu_combat_doctor_desc": "基础: ##$basic##\n你在救起队友时和救起队友之后的##$multibasic2##秒内受到的伤害减少##$multibasic##。\n\n精通: ##$pro##\n在救起一名队友后你还会在##$multipro3##秒内造成额外的##$multipro2##伤害。\n\n你救起队友的速度加快##$multipro##。", + "menu_keepers": "多重保险", + "menu_inspire_beta_desc": "基础: ##$basic##\n向一个距离你##$multibasic##内的队友呼喊会给予他们##$multibasic3##秒的##$multibasic2##的移动速度和##$multibasic2##的换弹速度加成。\n\n精通: ##$pro##\n你还可以通过呼喊的方式救起在##$multibasic##距离内的倒地的队友。\n\n该技能每##$multipro##秒最多触发一次。", + "menu_black_marketeer_beta_desc": "基础: ##$basic##\n拥有至少##$multibasic##名人质或者投降的敌人会让你每##$multibasic3##秒恢复##$multibasic2##点血量。\n\n精通: ##$pro##\n你回复的血量提高到##$multipro##点。\n你的被转化的敌人现在也被算作人质。\n\n注意:不影响人质情结和人质事件。", + "menu_cable_guy_beta": "人质情结", + "menu_cable_guy_beta_desc": "基础: ##$basic##\n你吼人的距离增加##$multibasic2##。\n\n精通: ##$pro##\n平民现在会被你发出的声音所压制。压制时间提高##$multipro##。\n\n你有概率会被周围没有被捆绑的平民通过互动救起。", + "menu_triathlete_beta_desc": "基础: ##$basic##\n你的绑带增加##$multibasic##个。\n\n精通: ##$pro##\n你绑平民为人质的速度增加##$multipro##。", + "menu_stockholm_syndrome_beta": "人质事件", + "menu_stockholm_syndrome_beta_desc": "基础: ##$basic##\n每拥有一名人质会为你和你的队友提供##$multibasic##点伤害吸收。\n\n精通: ##$pro##\n每拥有一名人质会延长控制阶段##$multipro##秒。\n\n注意:技能效果最多叠加##4##次。", + "menu_control_freak_beta_desc": "基础: ##$basic##\n你的被转化的敌人获得##$multibasic3##的伤害减免。\n\n精通: ##$pro##\n你的被转化的敌人额外获得##$multipro4##伤害减免。", + "menu_rifleman": "灵活行动", + "menu_rifleman_desc": "基础: ##$basic##\n你开镜速度增加##$multibasic##。\n武器切换速度增加##$multibasic2##。\n\n精通: ##$pro##\n冲刺时可使用武器腰射。", + "menu_speedy_reload_beta_desc": "基础: ##$basic##\n你的狙击步枪和突击步枪获得##$multibasic##的装弹速度加成。\n\n精通: ##$pro##\n爆头击杀会在##$multipro2##秒内增加##$multipro##的换弹速度。只能被单发模式的冲锋枪,突击步枪和狙击枪触发。", + "menu_sniper_graze_damage": "脑洞大开", + "menu_sniper_graze_damage_desc": "基础: ##$basic##\n爆头会对##$multibasic2##内最近的敌人造成##$multibasic##的伤害。\n\n注意:不影响##300档##狙击枪。\n\n精通: ##$pro##\n爆头击杀会造成##$multipro##的连锁伤害。", + + + "menu_from_the_hip_desc": "基础: ##$basic##\n霰弹枪伤害额外增加##$multibasic##。\n\n精通: ##$pro##\n拿出和收起霰弹枪的时间缩短##$multipro##。", + "menu_shotgun_cqb_beta": "快手换弹", + "menu_shotgun_cqb_beta_desc": "基础: ##$basic##\n你装填##单发上弹##的霰弹枪的速度加快##$multibasic##。\n\n精通: ##$pro##\n你装填##单发上弹##的霰弹枪的速度额外加快##$multipro##。\n你的霰弹枪开镜速度加快##$multipro2##。", + "menu_shotgun_impact_beta": "广域掌控", + "menu_shotgun_impact_beta_desc": "基础: ##$basic##\n你的霰弹枪的子弹增加##$multibasic##颗弹丸。\n\n注意:不影响高爆弹和独头穿甲弹。\n\n精通: ##$pro##\n你的霰弹枪腰射的时候额外增加##$multipro##的扩散。", + "menu_far_away_beta": "近战霰弹", + "menu_far_away_beta_desc": "基础: ##$basic##\n单发霰弹枪腰射时射速增加##$multibasic##。\n\n精通: ##$pro##\n每当你用霰弹枪获得一个击杀时,你会获得持续##$multipro2##秒的##$multipro##移速加成。\n最多可以同时叠加##$multipro3##次。", + "menu_close_by_beta": "弹匣专家", + "menu_close_by_beta_desc": "基础: ##$basic##\n你装填##弹夹式##霰弹枪的速度加快##$multibasic##。\n\n精通: ##$pro##\n你的##弹夹式##霰弹枪的弹匣容量增加##$multipro##。", + "menu_overkill_beta": "霰弹炼狱", + "menu_overkill_beta_desc": "基础: ##$basic##\n你的霰弹枪开火时有##$multibasic##的概率不消耗子弹。\n\n精通: ##$pro##\n当你使用霰弹枪击杀一名敌人时,你有##$multipro##的概率散播恐慌。\n恐慌会让敌人进入不可控的恐惧状态。\n\n该技能每##$multipro2##秒最多触发一次。", + "menu_juggernaut_beta_desc": "基础: ##$basic##\n解锁改良型复合战术背心。\n\n精通: ##$pro##\n你的总护甲值增加##$multibasic##。", + "menu_show_of_force_beta": "皮糙肉厚", + "menu_show_of_force_beta_desc": "基础: ##$basic##\n你的坚韧值增加##$multibasic##。\n坚韧值越高、受击抖动越轻。\n\n精通: ##$pro##\n所有的防弹背心护甲值增加##$multipro2##。", + "menu_scavenging_beta_desc": "基础: ##$basic##\n子弹盒拾取范围增加##$multibasic##。\n\n注意:不与赌徒天赋叠加。\n\n精通: ##$pro##\n每击杀##6##个敌人就会额外掉落一个弹药盒。", + "menu_bandoliers_beta_desc": "基础: ##$basic##\n你的护甲越重,总弹量越多。\n最多在穿着改良型复合战术背心时增加##$multibasic##。\n\n精通: ##$pro##\n你的总弹量额外增加##$multipro##。", + + + "menu_defense_up_beta": "运包专家", + "menu_defense_up_beta_desc": "基础: ##$basic##\n你背着包的时候的移速加快##$multibasic##。\n\n精通: ##$pro##\n你丢包的距离增加##$multipro##。", + "menu_sentry_targeting_package_beta": "冒险行事", + "menu_sentry_targeting_package_beta_desc": "基础: ##$basic##\n你在互动的时候获得##$multibasic##伤害减免。\n\n精通: ##$pro##\n你现在可以在跑步的时候给武器换弹。", + "menu_engineering_beta": "防守套件", + "menu_engineering_beta_desc": "基础: ##$basic##\n你的哨戒机枪额外获得##$multibasic##的血量。\n\n精通: ##$pro##\n你的哨戒机枪获得一个护盾。", + "menu_tower_defense_beta": "步哨群落", + "menu_tower_defense_beta_desc": "基础: ##$basic##\n你可以额外带##1##个更多的哨戒机枪。\n\n精通: ##$pro##\n你部署哨戒机枪的消耗降低##$multipro##。\n你的哨戒机枪额外获得##$multipro2##的弹药。", + "menu_eco_sentry_beta": "工程博士", + "menu_eco_sentry_beta_desc": "基础: ##$basic##\n你的哨戒机枪获得了一个升级过的运动传感器,可以标记##10m##内的所有敌人并提高##$multibasic##的精准度。\n\nACE: ##$pro##\n你的哨戒机枪获得了穿甲弹, 降低##$multipro##的开火速度, 但是增加了##$multipro2##的伤害并且可以穿透敌人和盾牌。", + "menu_combat_engineering_beta": "战斗技师", + "st_menu_technician_auto": "重武系", + "menu_fast_fire_beta": "魔法弹匣", + "menu_fast_fire_beta_desc": "基础: ##$basic##\n你的冲锋枪,突击步枪和轻机枪弹匣容量增加##$multibasic##。\n\n精通: ##$pro##\n你的冲锋枪,突击步枪和轻机枪弹匣容量额外增加##$multipro##。\n\n注意:不影响技能\"敏捷手法\"。", + "menu_body_expertise_beta_desc": "基础: ##$basic##\n你在手持重武器时不再受到移速惩罚。\n\n精通: ##$pro##\n你对敌人身体造成的伤害永久额外获得敌人爆头伤害的##$multipro##。该技能只能被minigun,轻机枪和全自动模式下的冲锋枪触发。", + "menu_fast_hands": "敏捷手法", + "menu_fast_hands_desc": "基础: ##$basic##\n你的冲锋枪和轻机枪换弹速度加快##$multibasic##。\n\n精通: ##$pro##\n使用全自动模式的冲锋枪,轻机枪和特殊武器击杀##$multipro##名敌人最多会使你下次换弹速度加快##$multipro2##。你的武器弹夹若超过##$multipro4##发子弹,每发多的子弹均会减少##$multipro3##的加成,最多降低至增加##$multipro5##的换弹速度。", + "menu_oppressor_desc": "基础: ##$basic##\n你的武器压制敌人的效率增加##$multibasic##。\n\n精通: ##$pro##\n你的武器压制敌人的效率额外增加##$multipro##。", + "menu_fire_control_beta": "穿甲子弹", + "menu_fire_control_beta_desc": "基础: ##$basic##\n你腰射的时候武器精准度增加##$multibasic##。\n\n精通: ##$pro##\n你的所有远程武器均可以穿透敌人护甲。\n不影响投掷物。", + + + "menu_sprinter": "运动健儿", + "menu_sprinter_desc": "基础: ##$basic##\n你的耐力开始恢复时间提前##$multibasic##,恢复速度增加##$multibasic##。\n\n精通: ##$pro##\n你走路的速度增加##$multipro##。你总体的速度增加##$multipro2##。", + "menu_awareness": "蹲伏行动", + "menu_awareness_desc": "基础: ##$basic##\n你蹲下行走的速度增加##$multibasic##。\n\n精通: ##$pro##\n你蹲下的时候增加##$multipro##的闪避几率。", + "menu_optic_illusions": "极限跑者", + "menu_optic_illusions_desc": "基础: ##$basic##\n你可以向任意方向冲刺。你冲刺的速度增加##$multibasic##。\n\n精通: ##$pro##\n你在跑步或者在滑索上时额外获得##$multipro##闪避几率。", + "menu_dire_need_beta": "卷土重来", + "menu_dire_need_beta_desc": "基础: ##$basic##\n你的护甲耗尽后,移速在##$multibasic2##秒内增加##$multibasic##。\n\n精通: ##$pro##\n你的护甲耗尽时有##$multipro##的概率散播恐慌。恐慌会让敌人进入不可控的恐惧状态。\n\n每##$multipro2##秒最多触发一次。", + "menu_insulation_beta_desc": "基础: ##$basic##\n你在被电击的时候电击效果减弱##$multibasic##。\n\n你在被电击的时候不再会随机的扣动扳机了。\n\n精通: ##$pro##\n你在被电击的##$multipro2##秒内可以通过互动从电击中解救自己。", + "st_menu_ghost_silencer": "暗杀系", + "menu_scavenger_beta": "突击适应", + "menu_scavenger_beta_desc": "基础: ##$basic##\n你获得##$multibasic##的暴击概率。\n\nACE: ##$pro##\n当你破甲后,你对所有敌人造成的第一击会直接击退敌人。\n\n这个效果持续到你护甲回复后的##$multipro##秒。", + "menu_silenced_damage": "沉默杀手", + "menu_silenced_damage_desc": "基础: ##$basic##\n你的消音武器获得##$multibasic##点隐匿度。降低##$multibasic2##点消音器的隐匿度惩罚。\n\n精通: ##$pro##\n你的消音武器对敌人造成额外的##$multipro##伤害并且有##$multipro2##的概率穿甲。", + "menu_silence_expert_beta_desc": "基础: ##$basic##\n你的消音武器获得##$multibasic##点稳定度。\n\n精通: ##$pro##\n你的消音武器获得##$multipro##点精准性并且开镜速度增加##$multipro2##。", + + "menu_spotter_teamwork_beta_desc": "基础: ##$basic##\n你标记的在##$multibasic2##米外的敌人受到的伤害增加##$multibasic##。\n\n精通: ##$pro##\n你标记的敌人受到的伤害额外增加##$multipro##。\n\n标记的时间增加##$multipro2##。", + "menu_jail_workout_beta_desc": "基础: ##$basic##\n你在踩点模式被保安发现所需的时间增加##$multibasic##。\n\n强袭时在队友的身边会使被敌人选中为目标的概率降低##$multibasic##。\n\n精通: ##$pro##\n你的隐匿度增加##$multipro##。", + "menu_cleaner_beta": "鹰之视野", + "menu_cleaner_beta_desc": "基础: ##$basic##\n你对特殊敌人额外造成##$multibasic##的伤害。\n\n精通: ##$pro##\n你可以在开镜瞄准时自动标记特殊敌人。\n\n你手动标记敌人的距离增加##$multipro##倍。", + "menu_second_chances_beta": "后勤苦力", + "menu_second_chances_beta_desc": "基础: ##$basic##\n你与战利品互动和打包的速度加快##$multibasic##。\n\n精通: ##$pro##\n你的开锁速度加快##$multipro##。\n\n你可以徒手安静地打开保险箱。", + "menu_chameleon_beta": "熄灯时刻", + "menu_chameleon_beta_desc": "基础: ##$basic##\n你现在可以使##1##个摄像头失效,使其无法探测到你和你的队友,持续##25##秒。\n\n精通: ##$pro##\n你的电子干扰器可以被用来开启一部分电子门。你的电子干扰器可以使对讲机的响起被延迟至电子干扰器效果结束。", + "menu_ecm_booster_beta": "干扰器反馈", + "menu_ecm_booster_beta_desc": "基础: ##$basic##\n你现在可以和电子干扰器互动使其反馈并震晕周围的敌人。\n\n反馈的生效距离为##$multibasic##。\n若电子干扰器的##视野受阻##,则效果生效范围的半径减半。\n\n精通: ##$pro##\n现在电子干扰器在完成反馈后经过##$multipro##分钟便可再次使用反馈。", + "menu_ecm_2x_beta_desc": "基础: ##$basic##\n你可以带##2##个电子干扰器而不是1个。\n\n精通: ##$pro##\n你的电子干扰器的干扰和反馈现在持续##$multipro##秒。", + "menu_backstab_beta_desc": "基础: ##$basic##\n当你的暴露风险低于##$multibasic3##时,每降低##$multibasic2##点会提高##$multibasic##的暴击概率,最高为##$multibasic4##。\n\n暴击造成##$multibasic5##伤害。\n\n精通: ##$pro##\n效果增强为每降低##$multipro2##点会提高##$multipro##的暴击概率。原暴露风险需求是一致的。\n\n暴击额外多造成##$multipro3##伤害。\n\n注意:暴击不影响手雷和投掷物。", + + + + "menu_nine_lives_beta": "永垂不朽", + "menu_pistol_beta_messiah_desc": "基础: ##$basic##\n当你倒地的时候,击杀一名敌人将可以直接起身。 你只有##$multibasic##次机会。\n\n你倒地后被逮捕的所需时间增加##$multibasic2##秒。\n\n注意:从被拘留的状态被解救时不会重置该技能的机会次数。\n\n精通: ##$pro##\n你额外获得##$multipro##次机会。\n\n当你被拘留的时候,人质会自己进行交易将你赎回。可以在突击中触发,但是一场劫案只能触发##1##次。", + "menu_drop_soap_beta_desc": "基础: ##$basic##\n你冲刺的时候可以使用近战武器进行蓄力和攻击。\n\n精通: ##$pro##\n你可以使用近战武器击退防爆特警。\n\n每##$multipro##秒最多触发一次。", + "menu_wolverine_beta_desc": "基础: ##$basic##\n你的血量越低,你的移速越高。你的血量低于##$multibasic##时, 你最多获得##$multibasic2##的额外移速。\n\n精通: ##$pro##\n你的血量越低,你造成的近战伤害越高。当你的血量低于##$multipro##时, 你最多额外造成##$multipro2##的近战和电锯伤害。", + "menu_trigger_happy_beta_desc": "基础: ##$basic##\n你的手枪每次命中敌人时会获得##$multibasic##的持续##$multibasic2##秒的伤害加成。\n最多叠加##$multibasic3##次。\n\n精通: ##$pro##\n你的手枪每次命中敌人时会获得##$multipro##的持续##$multipro2##秒的伤害加成。\n最多叠加##$multipro3##次。", + "menu_perseverance_beta_desc": "基础: ##$basic##\n你血量耗尽时不会立刻倒地,而是有##$multibasic2##秒的额外战斗时间。在此期间,移动速度获得##$multibasic3##的惩罚。\n\n注意:坠落伤害和火焰伤害不会触发该技能。 \n\n精通: ##$pro##\n天鹅绝唱的持续时间额外增加##$multipro##秒。", + "menu_nine_lives_beta_desc": "基础: ##$basic##\n你倒地的时候可以开镜瞄准了。\n\n精通: ##$pro##\n你倒地时可以使用主武器了。", + "menu_tea_cookies_beta_desc": "基础 ##$basic##\n开局时可额外携带##$multibasic##个急救包。\n\n精通: ##$pro##\n开局时可额外携带##$multipro##个急救包。\n\n你部署的急救包会自动救援##$multipro2##米内倒地的玩家。每##$multipro3##秒最多触发一次。", + + + + "menu_st_category_health": "血量", + "menu_st_category_healing": "回血", + "menu_st_category_armor": "护甲", + "menu_st_category_armor_gating": "回甲", + "menu_st_category_resistance": "减伤", + "menu_st_category_support": "辅助", + "menu_st_category_dodge": "闪避", + + "menu_deckall_2": "战场拾荒", + "menu_deckall_2_desc": "你有##2%##的基础概率从子弹盒里获得一个投掷物。\n\n每当你没有从弹药盒中获得投掷物时,该概率便会增加##0.1%##。\n\n当你捡到一个投掷物时,该概率将被重置回基础值。", + "menu_deckall_4_desc": "你现在可以购买全部有利条件。\n\n你获得##1##点隐匿度。", + "menu_deckall_6_desc": "解锁护甲箱。可以在劫案中改变你的护甲。\n\n当你穿着护甲时,移速惩罚减少##15%##。", + + "menu_deck1_3_desc": "你和你的队友的耐力增加##$multiperk##。\n\n你的呼喊距离增加##$multiperk2##。", + "menu_deck1_9_desc": "每有一名人质,你和你的队友增加##$multiperk##的血量上限和##$multiperk2##耐力,最多叠加##$multiperk3##次。\n\n注意:多个天赋效果不叠加。\n\n牌组完成奖励:收获日阶段抽到稀有物品几率增加##10%##。", + "menu_deck2_7": "陷入狂乱", + "menu_deck2_7_desc": "当你的血量低于##$multiperk##时你在##$multiperk2##秒内会免疫血量伤害。\n\n每##$multiperk3##秒最多触发一次。", + "menu_deck3_9_desc": "提高##$multiperk2##你和你队友的护甲恢复速度。\n\n注意:多个天赋效果不叠加。\n\n牌组完成奖励:收获日阶段抽到稀有物品几率增加##10%##。", + "menu_deck5_1": "职业枪手", + "menu_deck5_1_desc": "在你用主武器击杀##$multiperk##个敌人后自动装填你的副武器,##反之亦然##。", + "menu_deck5_3": "整个西部最快的手!", + "menu_deck5_3_desc": "你切枪的速度加快##$multiperk##。", + "menu_deck5_5": "暗中打击", + "menu_deck5_5_desc": "你在##$multiperk3##内没有受到伤害后,获得持续##$multiperk2##秒的##$multiperk##暴击概率。\n\n暴击造成##双倍##伤害并且不影响手雷和榴弹。受到任何伤害会重置无伤计时。", + "menu_deck5_7": "精准射手", + "menu_deck5_7_desc": "爆头会往你的武器返还##$multiperk##颗子弹。\n\n每##$multiperk2##秒最多触发一次。", + "menu_deck5_9_desc": "击杀一名敌人会恢复##$multiperk##点护甲。\n\n每##$multiperk2##秒最多触发一次。\n\n牌组完成奖励:收获日阶段抽到稀有物品几率增加##10%##。", + "menu_deck6_3_desc": "当你穿着防弹背心时,你的闪避概率和护甲量会提高。\n\n越重的背心会获得更多的数值。", + "menu_deck6_5_desc": "当你穿着防弹背心时,你的闪避概率和护甲量会进一步提高。\n\n越重的背心会获得更多的数值。", + "menu_deck6_7_desc": "当你穿着防弹背心时,你的闪避概率和护甲量会进一步提高。\n\n越重的背心会获得更多的数值。", + "menu_deck7_7_desc": "你在站立或者蹲下时被敌人选中为目标的概率进一步降低##$multiperk2##。\n\n你回答对讲机加快##$multiperk3##。", + "menu_deck7_9": "扔回弹丸", + "menu_deck7_9_desc": "你成功闪避的子弹将##反弹##给开火的敌人。\n\n牌组完成奖励:收获日阶段抽到稀有物品几率增加##10%##。", + "menu_deck9_7_desc": "在中距离击杀敌人会恢复##$multiperk##点护甲。\n\n每##$multiperk2##秒最多触发一次。", + "menu_deck10_3_desc": "你的捡弹率提高##$multiperk##。\n\n你获得##$multiperk3##的血量。", + "menu_deck10_9_desc": "你从弹药盒里获得的血量额外提高##$multiperk##点。\n\n你捡弹药盒的距离增加##$multiperk4##。\n\n牌组完成奖励:收获日阶段抽到稀有物品几率增加##10%##。", + "menu_deck11_1_desc": "对敌人造成伤害会使你每##$multiperk2##秒恢复##$multiperk##点血量,持续##$multiperk3##秒。\n\n效果可以叠加,但是每##$multiperk4##秒只能触发一次,并且只能在装备两件套西装时生效。", + "menu_deck11_3_desc": "对敌人造成伤害会使你每##$multiperk2##秒恢复##$multiperk##点血量,持续##$multiperk3##秒。", + "menu_deck11_7_desc": "对敌人造成伤害会使你每##$multiperk2##秒恢复##$multiperk##点血量,持续##$multiperk3##秒。", + "menu_deck12_1_desc": "你的血量越低,你的护甲恢复速度越快。\n\n你最高可以获得额外的##$multiperk##护甲恢复速度。", + "menu_deck12_3_desc": "你的切枪速度加快##$multiperk##。", + "menu_deck12_5_desc": "当你靠近队友时,敌人将你视作攻击目标的概率降低##$multiperk##。", + "menu_deck12_7_desc": "你的血量越低,你的闪避越高。\n\n你最多可以获得##$multiperk##的闪避概率。", + "menu_deck12_9_desc": "你的血量越低,你造成的远程伤害越高。当你的血量低于##$multiperk##时,你的远程武器最多额外造成##$multiperk2##的伤害。\n\n牌组完成奖励:收获日阶段抽到稀有物品几率增加##10%##。", + "menu_deck13_3_desc": "你击杀敌人之后储存的血量增加##$multiperk##点。", + "menu_deck13_5_desc": "你可以储存的血量增加##$multiperk##。\n\n你的闪避概率增加##$multiperk3##。", + "menu_deck13_7_desc": "你击杀敌人之后储存的血量增加##$multiperk##点。", + "menu_deck14_3_desc": "你的队友也会享受到你的狂乱度的加成。\n多个队友的狂乱度不叠加,只有最高的生效。\n\n未消音的武器开枪时有一定几率在你的敌人中散播恐慌。\n恐慌的敌人会进入不可控的恐惧状态。", + "menu_deck15_1_desc": "你不再会在脱离战斗之后恢复所有护甲,而是会以固定间隔持续恢复护甲。重甲每次恢复的护甲更多,但间隔更久。\n\n注意:使用该天赋时,来自其他天赋和技能的护甲恢复速度全部失效。", + "menu_deck15_7_desc": "你##$multiperk##的血量被转换成##$multiperk2##额外护甲。\n\n你的闪避概率增加##$multiperk3##。", + "menu_deck15_9_desc": "造成伤害会恢复##$multiperk##点护甲。\n每##$multiperk2##秒只能触发一次。\n\n你的护甲提高##$multiperk3##。\n\n牌组完成奖励:收获日阶段抽到稀有物品几率增加##10%##。", + "menu_deck17_9_desc": "从首脑注射器每获得##5点过量治疗##就会减少##1秒##首脑注射器的冷却时间。\n\n牌组完成奖励:收获日阶段抽到稀有物品几率增加##10%##。", + "menu_deck21_5_desc": "你的闪避概率增加##$multiperk2##,你在反馈期间击杀敌人会恢复##$multiperk##点血量。", + "menu_deck21_7_desc": "你额外获得##$multiperk##的血量。", + "menu_deck21_9_desc": "你的队友击杀敌人会恢复##$multiperk##点血量。\n\n牌组完成奖励:收获日阶段抽到稀有物品几率增加##10%##。", + "menu_deck22_1_desc": "解锁水蛭安瓿瓶。\n\n切换到其他天赋会导致其不可用。水蛭安瓿瓶会替代你的投掷物,装备在投掷物栏里,并在需要的时候可以被切换。在游戏中你可以按投掷物键激活安瓿瓶。\n\n激活安瓿瓶会##完全##恢复你的血量并使你的护甲不可用。\n\n当安瓿瓶被激活时,你的血量以被划分为总血量的##$multiperk2##多个格子,敌人的任何伤害会移除一格。\n\n击杀##1##名敌人将会得到##$multiperk3##秒的无敌。\n你受伤时,队友将会恢复##$multiperk4##血量。\n\n水蛭安瓿瓶持续##$multiperk5##秒,冷却##$multiperk6##秒。", + "menu_deck22_9_desc": "当你激活安瓿瓶时,你的血量以被划分为总血量的##$multiperk##的多个格子。\n\n你的血量提高##$multiperk2##。\n\n牌组完成奖励:收获日阶段抽到稀有物品几率增加##10%##。", + + "menu_deck1_3_short": "你和你的队友的耐力增加##$multiperk##。你的呼喊距离增加##$multiperk2##。", + "menu_deck1_9_short": "每有一名人质,你和你的队友增加##$multiperk##的血量和##$multiperk2##耐力,最多叠加##$multiperk3##次。 注意:多个天赋效果不叠加。", + "menu_deck2_7_short": "当你的血量低于##$multiperk##时你在##$multiperk2##秒内会免疫血量伤害。每##$multiperk3##秒最多触发一次。", + "menu_deck3_9_short": "提高##$multiperk2##你和你队友的护甲恢复速度。注意:多个天赋效果不叠加。", + "menu_deck5_1_short": "在你用主武器击杀##$multiperk##个敌人后自动装填你的副武器,##反之亦然##。", + "menu_deck5_3_short": "你切枪的速度加快##$multiperk##。", + "menu_deck5_5_short": "你在##$multiperk3##内没有受到伤害后,获得持续##$multiperk2##秒的##$multiperk##暴击概率。暴击造成##双倍##伤害并且不影响手雷和榴弹。受到任何伤害会重置无伤计时。", + "menu_deck5_7_short": "爆头会往你的武器返还##$multiperk##颗子弹。每##$multiperk2##秒最多触发一次。。", + "menu_deck5_9_short": "击杀一名敌人会恢复##$multiperk##点护甲。每##$multiperk2##秒最多触发一次。", + "menu_deck6_3_short": "当你穿着防弹背心时,你的闪避概率和护甲量会提高。\n\n越重的背心会获得更多的数值。", + "menu_deck6_5_short": "当你穿着防弹背心时,你的闪避概率和护甲量会进一步提高。\n\n越重的背心会获得更多的数值。", + "menu_deck6_7_short": "当你穿着防弹背心时,你的闪避概率和护甲量会进一步提高。\n\n越重的背心会获得更多的数值。", + "menu_deck7_7_short": "你在站立或者蹲下时被敌人选中为目标的概率进一步降低##$multiperk2##。你回答对讲机加快##$multiperk3##。", + "menu_deck7_9_short": "你成功闪避的子弹将##反弹##给开火的敌人。", + "menu_deck9_7_short": "在中距离击杀敌人会恢复##$multiperk##点护甲。每##$multiperk2##秒最多触发一次。", + "menu_deck10_3_short": "你的捡弹率提高##$multiperk##。你获得##$multiperk3##的血量。", + "menu_deck10_9_short": "你从弹药盒里获得的血量额外提高##$multiperk##点。你捡弹药盒的距离增加##$multiperk4##。", + "menu_deck11_1_short": "伤害敌人会使你每##$multiperk2##秒恢复##$multiperk##点血量,持续##$multiperk3##秒。效果可以叠加但是每##$multiperk4##秒只能触发一次并且只能在穿着两件套西装时被触发。", + "menu_deck11_3_short": "血量伤害敌人会使你每##$multiperk2##秒恢复##$multiperk##点血量,持续##$multiperk3##秒。", + "menu_deck11_7_short": "血量伤害敌人会使你每##$multiperk2##秒恢复##$multiperk##点血量,持续##$multiperk3##秒。", + "menu_deck12_1_short": "你的血量越低,你的护甲恢复速度越快。你最高可以获得额外的##$multiperk##护甲恢复速度。", + "menu_deck12_3_short": "你的切枪速度加快##$multiperk##", + "menu_deck12_5_short": "当你靠近队友时,敌人将你视作攻击目标的概率降低##$multiperk##。", + "menu_deck12_7_short": "你的血量越低,你的闪避越高。\n\n你最多可以获得##$multiperk##的闪避概率。", + "menu_deck12_9_short": "你的血量越低,你造成的伤害越高。当你的血量低于##$multiperk##时,你的远程武器最多额外造成##$multiperk2##的伤害。", + "menu_deck13_3_short": "你击杀敌人之后储存的血量增加##$multiperk##。", + "menu_deck13_5_short": "你击杀敌人之后储存的血量增加##$multiperk##点。你的闪避概率增加##$multiperk3##。", + "menu_deck13_7_short": "你击杀敌人之后储存的血量增加##$multiperk##。", + "menu_deck14_3_short": "你的队友也享受你的狂乱度的加成。未消音的武器开枪时有一定几率在你的敌人中散播恐慌。", + "menu_deck15_1_short": "你不再会在脱离战斗之后恢复所有护甲,而是会以固定间隔持续恢复护甲。重甲每次恢复的护甲更多,但间隔更久。注意:使用该天赋时,来自其他天赋和技能的护甲恢复速度全部无效。", + "menu_deck15_7_short": "你##$multiperk##的血量被转换成##$multiperk2##额外护甲。你的闪避概率增加##$multiperk3##。", + "menu_deck17_9_short": "从首脑注射器每获得##5点过量治疗##就会减少##1秒##首脑注射器的冷却时间。", + "menu_deck21_5_short": "你的闪避概率增加##$multiperk2##,你在反馈期间击杀敌人会恢复##$multiperk##点血量。", + "menu_deck21_7_short": "你额外获得##$multiperk##的血量。", + "menu_deck21_9_short": "你的队友击杀敌人会恢复##$multiperk##点血量。", + "menu_deck22_1_short": "解锁水蛭安瓿瓶。", + "menu_deck22_9_short": "当你激活安瓿瓶时,你的血量以被划分为总血量的##$multiperk##的多个格子。你的血量提高##$multiperk2##。", + + + + "bm_global_value_mrwi_unlock": "锁定,其效果被其他天赋所复用!", + "hud_int_equipment_drill_upgrade": "按$BTN_INTERACT以升级钻机", + "hud_assault_ponr": "所有单位出击", + "hud_assault_normal_ponr": "所有部队倾巢出击", + "hud_assault_zeal_ponr": "Zeal单位入场", + "eclipse_menu_main": "日蚀", + "eclipse_menu_ponr_assault_text": "编辑突击横幅", + "eclipse_menu_ponr_assault_text_desc": "编辑突击横幅样式。和其他修改了突击横幅的mod冲突。", + + "menu_pro_warning": "这是一个专家任务!\n\n友军伤害被启用。\n多个劫案启用了有去无回倒计时。\n连续倒地会减少倒计时和被救起之后的血量。", + "menu_difficulty_easy_wish": "日蚀", + "menu_risk_easy_wish": "'日蚀'行动。\n对你来说, 行动才是关键。", + "menu_risk_special": "枪林弹雨。\n所有警力正在进场。", + "menu_difficulty_very_hard": "困难", + "menu_risk_fbi": "困难。\nFBI高度警觉。", + "menu_difficulty_hard": "普通", + "menu_risk_swat": "普通。\n中等水平的风险。", + "menu_difficulty_normal": "简单", + "menu_risk_pd": "简单。\n等一下!我忘记了我的甜甜圈!", + + "menu_toggle_one_down": "专家任务", + "menu_one_down": "专家任务", + "menu_toggle_one_down_lobbies": "允许专家任务大厅", + + "menu_stat_job_completed": "", + "menu_max_progress": "一键满级", + "menu_max_progress_help": "跳过刷级,直接满级。", + "menu_progress_msg": "你确定要一键满级吗?\n这将会解锁所有可以解锁的物品。", + "menu_max_masks": "所有的面具自定义", + "menu_max_masks_help": "解锁所有的面具自定义选项。", + "menu_masks_msg": "你确定要解锁所有的面具自定义吗?\n这将会给你10个所有的面具,材质,花纹和颜色。" +} diff --git a/lua/achievmentmanager.lua b/lua/achievmentmanager.lua index e9ef87a..91ce9ee 100644 --- a/lua/achievmentmanager.lua +++ b/lua/achievmentmanager.lua @@ -1,6 +1,6 @@ function AchievmentManager:award(id, ...) - return + return end function AchievmentManager:award_progress(stat, value, ...) - return -end \ No newline at end of file + return +end diff --git a/lua/actionspooc.lua b/lua/actionspooc.lua index 300312f..69dc8cd 100644 --- a/lua/actionspooc.lua +++ b/lua/actionspooc.lua @@ -1,3 +1,11 @@ +-- Force Cloakers to stand up before starting a charge attack +Hooks:PreHook(ActionSpooc, "init", "sh_init", function(self, action_desc, common_data) + if not common_data.ext_anim.pose or not action_desc.flying_strike and common_data.ext_anim.pose ~= "stand" then + StreamHeist:log("ActionSpooc started in wrong pose, playing stand state") + common_data.ext_movement:play_state("std/stand/still/idle/look") + end +end) + -- Fix endless Cloaker beatdown function ActionSpooc:complete() return self._beating_end_t and self._beating_end_t < TimerManager:game():time() and (not self:is_flying_strike() or self._last_vel_z == 0) diff --git a/lua/aiattentionobject.lua b/lua/aiattentionobject.lua new file mode 100644 index 0000000..57b5967 --- /dev/null +++ b/lua/aiattentionobject.lua @@ -0,0 +1,47 @@ +-- Remove attention objects that are considered stealth only when the game goes loud +-- Simply exclude any attention setting that doesnt have a team relation or violent reaction +local add_attention_original = AIAttentionObject.add_attention +function AIAttentionObject:add_attention(settings, ...) + settings.stealth_only = not settings.relation or settings.reaction and settings.reaction < AIAttentionObject.REACT_AIM + + if settings.stealth_only then + if managers.groupai:state():enemy_weapons_hot() then + return + end + + if not self._enemy_weapons_hot_listen_id then + self._enemy_weapons_hot_listen_id = "AIAttentionObject_enemy_weapons_hot" .. tostring(self._unit:key()) + managers.groupai:state():add_listener(self._enemy_weapons_hot_listen_id, { "enemy_weapons_hot" }, callback(self, self, "clbk_enemy_weapons_hot")) + end + end + + return add_attention_original(self, settings, ...) +end + +function AIAttentionObject:clbk_enemy_weapons_hot() + self._enemy_weapons_hot_listen_id = nil + + if not self._attention_data then + return + end + + for id, settings in pairs(self._attention_data) do + if settings.stealth_only then + self._attention_data[id] = nil + end + end + + if not next(self._attention_data) then + managers.groupai:state():unregister_AI_attention_object((self._parent_unit or self._unit):key()) + self._attention_data = nil + end + + self:_call_listeners() +end + +Hooks:PreHook(AIAttentionObject, "destroy", "sh_destroy", function(self) + if self._enemy_weapons_hot_listen_id then + managers.groupai:state():remove_listener(self._enemy_weapons_hot_listen_id) + self._enemy_weapons_hot_listen_id = nil + end +end) diff --git a/lua/ammobagbase.lua b/lua/ammobagbase.lua index d7de087..edf0fa4 100644 --- a/lua/ammobagbase.lua +++ b/lua/ammobagbase.lua @@ -1,31 +1,31 @@ -- bulletstorm overall lasts longer AmmoBagBase._BULLET_STORM = { 6, - 15 + 15, } -- Bulletstorm 60s duration fix function AmmoBagBase:_take_ammo(unit) - local taken = 0 - local inventory = unit:inventory() + local taken = 0 + local inventory = unit:inventory() - if inventory then - for _, weapon in pairs(inventory:available_selections()) do - local took = self:round_value(weapon.unit:base():add_ammo_from_bag(self._ammo_amount)) - taken = taken + took - self._ammo_amount = self:round_value(self._ammo_amount - took) + if inventory then + for _, weapon in pairs(inventory:available_selections()) do + local took = self:round_value(weapon.unit:base():add_ammo_from_bag(self._ammo_amount)) + taken = taken + took + self._ammo_amount = self:round_value(self._ammo_amount - took) - if self._ammo_amount <= 0 then - return taken - end - end - end + if self._ammo_amount <= 0 then + return taken + end + end + end - return taken + return taken end -Hooks:PreHook(AmmoBagBase, "_set_empty", "eclipse__set_empty", function (self) - managers.network:session():send_to_peers_synched("sync_ammo_bag_ammo_taken", self._unit, self._max_ammo_amount + 1) +Hooks:PreHook(AmmoBagBase, "_set_empty", "eclipse__set_empty", function(self) + managers.network:session():send_to_peers_synched("sync_ammo_bag_ammo_taken", self._unit, self._max_ammo_amount + 1) end) --- Thanks Hoppip for this one too \ No newline at end of file +-- Thanks Hoppip for this one too diff --git a/lua/animatedvehiclebase.lua b/lua/animatedvehiclebase.lua index 99e523d..82de0b3 100644 --- a/lua/animatedvehiclebase.lua +++ b/lua/animatedvehiclebase.lua @@ -1,12 +1,11 @@ -- Get rid of stupid turrets (exceptions made for henry's rock and black cat to not break things) -- courtesy of gorg bus set_animated_vehicle_base_spawn_original = AnimatedVehicleBase.spawn_module -local level = Global.level_data and Global.level_data.level_id or '' -if level ~= "des" and level ~= "chca" then - function AnimatedVehicleBase:spawn_module(module_unit_name, ...) - if type_name(module_unit_name) == "spawn_turret" then - return set_animated_vehicle_base_spawn_original(self, module_unit_name, ...) - end - - end +local level = Global.level_data and Global.level_data.level_id or "" +if level ~= "des" and level ~= "chca" and level ~= "friend" then + function AnimatedVehicleBase:spawn_module(module_unit_name, ...) + if type_name(module_unit_name) == "spawn_turret" then + return set_animated_vehicle_base_spawn_original(self, module_unit_name, ...) + end + end end diff --git a/lua/blackmarketgui.lua b/lua/blackmarketgui.lua new file mode 100644 index 0000000..ca0499d --- /dev/null +++ b/lua/blackmarketgui.lua @@ -0,0 +1,17 @@ +-- Fix melee weapon knockdown stat display +Hooks:PreHook(BlackMarketGui, "reload", "shc_reload", function(self) + self._shc_patch = nil +end) + +Hooks:PreHook(BlackMarketGui, "on_slot_selected", "shc_on_slot_selected", function(self) + if self._shc_patch then + return + end + for _, v in pairs(self._mweapon_stats_shown) do + if v.name == "damage_effect" then + v.multiple_of = nil + self._shc_patch = true + return + end + end +end) diff --git a/lua/blackmarketmanager.lua b/lua/blackmarketmanager.lua new file mode 100644 index 0000000..b4be001 --- /dev/null +++ b/lua/blackmarketmanager.lua @@ -0,0 +1,79 @@ +function BlackMarketManager:modify_damage_falloff(damage_falloff, custom_stats) + if damage_falloff and custom_stats then + for part_id, stats in pairs(custom_stats) do + if stats.falloff_override then + damage_falloff.optimal_distance = stats.falloff_override.optimal_distance or damage_falloff.optimal_distance + damage_falloff.optimal_range = stats.falloff_override.optimal_range or damage_falloff.optimal_range + damage_falloff.near_falloff = stats.falloff_override.near_falloff or damage_falloff.near_falloff + damage_falloff.far_falloff = stats.falloff_override.far_falloff or damage_falloff.far_falloff + damage_falloff.near_mul = stats.falloff_override.near_mul or damage_falloff.near_mul + damage_falloff.far_mul = stats.falloff_override.far_mul or damage_falloff.far_mul + end + + if stats.far_falloff_mul ~= nil then + damage_falloff.far_falloff = damage_falloff.far_falloff * stats.far_falloff_mul + end + + if stats.falloff_mul ~= nil then + damage_falloff.optimal_distance = damage_falloff.optimal_distance * stats.falloff_mul + damage_falloff.optimal_range = damage_falloff.optimal_range * stats.falloff_mul + damage_falloff.near_falloff = damage_falloff.near_falloff * stats.falloff_mul + damage_falloff.far_falloff = damage_falloff.far_falloff * stats.falloff_mul + end + + if stats.near_damage_mul ~= nil then + damage_falloff.near_mul = damage_falloff.near_mul * stats.near_damage_mul + end + + if stats.far_damage_mul ~= nil then + damage_falloff.far_mul = damage_falloff.far_mul * stats.far_damage_mul + end + + if stats.falloff_damage_mul ~= nil then + damage_falloff.near_mul = damage_falloff.near_mul * stats.falloff_damage_mul + damage_falloff.far_mul = damage_falloff.far_mul * stats.falloff_damage_mul + end + + if stats.damage_near_mul ~= nil then + damage_falloff.optimal_range = damage_falloff.optimal_range * stats.damage_near_mul + end + + if stats.damage_far_mul ~= nil then + damage_falloff.far_falloff = damage_falloff.far_falloff * stats.damage_far_mul + end + + if stats.optimal_range_mul ~= nil then + damage_falloff.optimal_range = damage_falloff.optimal_range * stats.optimal_range_mul + end + + -- Optimal Distance stuff + if stats.optimal_distance_addend ~= nil then + damage_falloff.optimal_distance = damage_falloff.optimal_distance + stats.optimal_distance_addend + end + if stats.near_falloff_addend ~= nil then + damage_falloff.near_falloff = damage_falloff.near_falloff + stats.near_falloff_addend + end + end + end +end + +-- Uncouple melee knockdown from damage +Hooks:OverrideFunction(BlackMarketManager, "equipped_melee_weapon_damage_info", function(self, lerp_value) + lerp_value = lerp_value or 0 + local melee_entry = self:equipped_melee_weapon() + local stats = tweak_data.blackmarket.melee_weapons[melee_entry].stats + local primary = self:equipped_primary() + local bayonet_id = self:equipped_bayonet(primary.weapon_id) + local player = managers.player:player_unit() + + local bayonet + if bayonet_id and player:movement():current_state()._equipped_unit:base():selection_index() == 2 and melee_entry == "weapon" then + stats = tweak_data.weapon.factory.parts[bayonet_id].stats + bayonet = true + end + + local dmg = math.lerp(stats.min_damage, stats.max_damage, lerp_value) + local dmg_effect = (bayonet and dmg or 0.1) * math.lerp(stats.min_damage_effect, stats.max_damage_effect, lerp_value) + + return dmg, dmg_effect +end) diff --git a/lua/blackmarkettweakdata.lua b/lua/blackmarkettweakdata.lua index ec67c0c..8473f06 100644 --- a/lua/blackmarkettweakdata.lua +++ b/lua/blackmarkettweakdata.lua @@ -1,10 +1,4 @@ Hooks:PostHook(BlackMarketTweakData, "init", "eclipse_init", function(self) - - -- obey weapon movement speed penalty - for _, weap_data in pairs(self.melee_weapons) do - weap_data.stats.remove_weapon_movement_penalty = nil - end - -- nuke silent sentry gun self.deployables.sentry_gun_silent = nil -end) \ No newline at end of file +end) diff --git a/lua/bosslogicattack.lua b/lua/bosslogicattack.lua index 0a826d3..12a39e0 100644 --- a/lua/bosslogicattack.lua +++ b/lua/bosslogicattack.lua @@ -1,13 +1,19 @@ -local mvec3_add = mvector3.add -local mvec3_cpy = mvector3.copy -local mvec3_dir = mvector3.direction -local mvec3_lerp = mvector3.lerp -local mvec3_mul = mvector3.multiply -local mvec3_set = mvector3.set -local mvec3_set_z = mvector3.set_z -local math_abs = math.abs -local tmp_vec = Vector3() - +local mrot_set_axis_angle = mrotation.set_axis_angle +local mvec_add = mvector3.add +local mvec_copy = mvector3.copy +local mvec_dir = mvector3.direction +local mvec_dis_sq = mvector3.distance_sq +local mvec_lerp = mvector3.lerp +local mvec_mul = mvector3.multiply +local mvec_rot_with = mvector3.rotate_with +local mvec_set = mvector3.set +local mvec_set_l = mvector3.set_length +local mvec_set_z = mvector3.set_z +local mvec_step = mvector3.step +local mvec_sub = mvector3.subtract +local tmp_rot = Rotation() +local tmp_vec1 = Vector3() +local tmp_vec2 = Vector3() -- Boss should basically always be in shooting action function BossLogicAttack._upd_aim(data, my_data, ...) @@ -40,16 +46,16 @@ function BossLogicAttack._upd_aim(data, my_data, ...) end if not my_data.shooting and not my_data.spooc_attack and not data.unit:anim_data().reload and not data.unit:movement():chk_action_forbidden("action") then - my_data.shooting = data.unit:brain():action_request({ + my_data.shooting = data.brain:action_request({ body_part = 3, - type = "shoot" + type = "shoot", }) end else if my_data.shooting then - local success = data.unit:brain():action_request({ + local success = data.brain:action_request({ body_part = 3, - type = "idle" + type = "idle", }) if success then my_data.shooting = nil @@ -65,7 +71,6 @@ function BossLogicAttack._upd_aim(data, my_data, ...) CopLogicAttack.aim_allow_fire(shoot, aim, data, my_data) end - -- Adjust throwable code to allow for non throwable projectiles -- Also make the throwing use an actual action so it properly interrupts shooting -- We're adjusting the throwing vector to always throw at player's feet and add z compensation depending on projectile speed @@ -80,7 +85,7 @@ function BossLogicAttack._chk_use_throwable(data, my_data, focus) return end - if not focus.criminal_record or focus.is_deployable or (not focus.verified) == data.char_tweak.throwable_target_verified then + if (not focus.verified) == data.char_tweak.throwable_target_verified then return end @@ -106,29 +111,29 @@ function BossLogicAttack._chk_use_throwable(data, my_data, focus) local throw_from if is_throwable then throw_from = mov_ext:m_rot():y() - mvec3_mul(throw_from, 40) - mvec3_add(throw_from, mov_ext:m_head_pos()) + mvec_mul(throw_from, 40) + mvec_add(throw_from, mov_ext:m_head_pos()) local offset = mov_ext:m_rot():x() - mvec3_mul(offset, -20) - mvec3_add(throw_from, offset) + mvec_mul(offset, -20) + mvec_add(throw_from, offset) else throw_from = data.unit:inventory():equipped_unit():position() end local throw_to = focus.verified and focus.m_pos or focus.last_verified_pos - local slotmask = managers.slot:get_mask("world_geometry") - mvec3_set(tmp_vec, throw_to) - mvec3_set_z(tmp_vec, tmp_vec.z - 200) - local ray = data.unit:raycast("ray", throw_to, tmp_vec, "slot_mask", slotmask) + local slotmask = managers.slot:get_mask("bullet_blank_impact_targets") + mvec_set(tmp_vec1, throw_to) + mvec_set_z(tmp_vec1, tmp_vec1.z - 200) + local ray = data.unit:raycast("ray", throw_to, tmp_vec1, "slot_mask", slotmask) if not ray then return end throw_to = ray.hit_position local compensation = throwable_tweak.adjust_z ~= 0 and (((throw_dis - 400) / 10) ^ 2) / ((throwable_tweak.launch_speed or 250) / 10) or 0 - mvec3_set_z(throw_to, throw_to.z + compensation) - mvec3_lerp(tmp_vec, throw_from, throw_to, 0.5) - if data.unit:raycast("ray", throw_from, tmp_vec, "sphere_cast_radius", 15, "slot_mask", slotmask, "report") then + mvec_set_z(throw_to, throw_to.z + compensation) + mvec_step(tmp_vec1, throw_from, throw_to, 400) + if data.unit:raycast("ray", throw_from, tmp_vec1, "sphere_cast_radius", 15, "slot_mask", slotmask, "report") then return end @@ -137,19 +142,74 @@ function BossLogicAttack._chk_use_throwable(data, my_data, focus) local action_data = { body_part = 3, type = "act", - variant = is_throwable and "throw_grenade" or "recoil_single" + variant = is_throwable and "throw_grenade" or "recoil_single", } - if not data.unit:brain():action_request(action_data) then + if not data.brain:action_request(action_data) then return end - local throw_dir = tmp_vec - mvec3_dir(throw_dir, throw_from, throw_to) + local throw_dir = tmp_vec1 + mvec_dir(throw_dir, throw_from, throw_to) ProjectileBase.throw_projectile_npc(throwable, throw_from, throw_dir, data.unit) return true end +-- New chase position function, try to walk around the target instead of random positions +function BossLogicAttack._find_chase_position(data, pos, min_dis, max_dis) + local dir, test_pos = tmp_vec1, tmp_vec2 + local min_dis_sq = min_dis ^ 2 + local dis_diff_sq = 300 ^ 2 + local current_rot = math.rand(-90, 90) + local fallback_dis_sq = 0 + local fallback_pos + local steps = 6 + local rotate_step = 180 / steps + + mvec_dir(dir, pos, data.m_pos) + mvec_mul(dir, max_dis) + + local ray_params = { + allow_entry = true, + trace = true, + pos_from = pos, + pos_to = test_pos, + } + + repeat + mrot_set_axis_angle(tmp_rot, math.UP, current_rot) + + mvec_set(test_pos, dir) + mvec_rot_with(test_pos, tmp_rot) + mvec_add(test_pos, pos) + + if not managers.navigation:raycast(ray_params) or mvec_dis_sq(ray_params.trace[1], pos) > min_dis_sq then + mvec_sub(test_pos, pos) + mvec_set_l(test_pos, min_dis) + mvec_add(test_pos, pos) + + local chase_pos = ray_params.trace[1] + mvec_lerp(chase_pos, test_pos, chase_pos, math.random()) + + local dis_sq = mvec_dis_sq(data.m_pos, chase_pos) + if dis_sq > dis_diff_sq then + return chase_pos + elseif dis_sq > fallback_dis_sq then + fallback_dis_sq = dis_sq + fallback_pos = chase_pos + end + end + + current_rot = current_rot + rotate_step + if current_rot > 90 then + current_rot = current_rot - 180 + end + + steps = steps - 1 + until steps <= 0 + + return fallback_pos +end -- Check for weapon range to determine wether to move closer function BossLogicAttack._upd_combat_movement(data, my_data) @@ -158,7 +218,6 @@ function BossLogicAttack._upd_combat_movement(data, my_data) local enemy_visible = focus_enemy.verified local action_taken = data.logic.action_taken(data, my_data) local weapon_range = my_data.weapon_range - local chase if not action_taken then if my_data.chase_path_failed_t and t - my_data.chase_path_failed_t <= 1 then @@ -166,138 +225,90 @@ function BossLogicAttack._upd_combat_movement(data, my_data) end if my_data.chase_path then - local enemy_dis = enemy_visible and focus_enemy.dis or focus_enemy.verified_dis - local run_dist = enemy_visible and weapon_range.optimal or weapon_range.close - local speed = enemy_dis < run_dist and "walk" or "run" - - BossLogicAttack._chk_request_action_walk_to_chase_pos(data, my_data, speed) + BossLogicAttack._chk_request_action_walk_to_chase_pos(data, my_data, enemy_visible and focus_enemy.dis < weapon_range.optimal and "walk" or "run") elseif not my_data.chase_path_search_id and focus_enemy.nav_tracker then - local height_diff = math_abs(data.m_pos.z - focus_enemy.m_pos.z) - if height_diff < 300 then - chase = true - else - local engage = my_data.attitude == "engage" - if enemy_visible then - chase = focus_enemy.dis > weapon_range.optimal or engage and focus_enemy.dis > weapon_range.close - else - chase = focus_enemy.verified_dis > weapon_range.optimal or engage and focus_enemy.verified_dis > weapon_range.close or not focus_enemy.verified_t or t - focus_enemy.verified_t > 2 - end + local height_diff = math.abs(data.m_pos.z - focus_enemy.m_pos.z) + local chase = height_diff < 300 or focus_enemy.dis > weapon_range.optimal or my_data.attitude == "engage" and focus_enemy.dis > weapon_range.close + if not chase and not enemy_visible then + chase = not focus_enemy.verified_t or t - focus_enemy.verified_t > 2 end if not chase then return end - my_data.chase_pos = nil - local chase_pos = focus_enemy.nav_tracker:field_position() - local new_chase_pos = CopLogicTravel._get_pos_on_wall(chase_pos, weapon_range.close, nil, nil) - if chase_pos.x ~= new_chase_pos.x or chase_pos.y ~= new_chase_pos.y then - my_data.chase_pos = new_chase_pos + local enemy_pos = focus_enemy.nav_tracker:field_position() + local chase_pos = BossLogicAttack._find_chase_position(data, enemy_pos, weapon_range.close * 0.5, (weapon_range.close + weapon_range.optimal) * 0.5) + if not chase_pos then + chase_pos = CopLogicTravel._get_pos_on_wall(enemy_pos, weapon_range.close, nil, nil) + end - local my_pos = data.unit:movement():nav_tracker():field_position() - local unobstructed_line = nil + if chase_pos and mvec_dis_sq(focus_enemy.m_pos, chase_pos) > 100 ^ 2 then + my_data.chase_pos = chase_pos - if math_abs(my_pos.z - my_data.chase_pos.z) < 40 then + local my_pos = data.unit:movement():nav_tracker():field_position() + if math.abs(my_pos.z - my_data.chase_pos.z) < 50 then local ray_params = { allow_entry = false, pos_from = my_pos, - pos_to = my_data.chase_pos + pos_to = my_data.chase_pos, } if not managers.navigation:raycast(ray_params) then - unobstructed_line = true + my_data.chase_path = { + my_pos, + my_data.chase_pos, + } + return end end - if unobstructed_line then - my_data.chase_path = { - mvec3_cpy(my_pos), - my_data.chase_pos - } - local enemy_dis = enemy_visible and focus_enemy.dis or focus_enemy.verified_dis - local run_dist = enemy_visible and weapon_range.optimal or weapon_range.close - local speed = enemy_dis < run_dist and "walk" or "run" - - BossLogicAttack._chk_request_action_walk_to_chase_pos(data, my_data, speed) - else - my_data.chase_path_search_id = tostring(data.unit:key()) .. "chase" - my_data.pathing_to_chase_pos = true - - data.brain:add_pos_rsrv("path", { - radius = 60, - position = mvec3_cpy(my_data.chase_pos) - }) - data.brain:search_for_path(my_data.chase_path_search_id, my_data.chase_pos) - end + my_data.chase_path_search_id = tostring(data.unit:key()) .. "chase" + my_data.pathing_to_chase_pos = true + + data.brain:add_pos_rsrv("path", { + radius = 50, + position = my_data.chase_pos, + }) + data.brain:search_for_path(my_data.chase_path_search_id, my_data.chase_pos) else + my_data.chase_pos = nil my_data.chase_path_failed_t = t end end - elseif my_data.walking_to_chase_pos and not my_data.use_flank_pos_when_chasing then + elseif my_data.walking_to_chase_pos then local current_haste = my_data.advancing and my_data.advancing:haste() if not current_haste then return end local change_speed - local enemy_dis = enemy_visible and focus_enemy.dis or focus_enemy.verified_dis - local run_dist = enemy_visible and weapon_range.optimal or weapon_range.close if current_haste == "run" then - change_speed = enemy_dis < run_dist * 0.5 and "walk" + change_speed = enemy_visible and focus_enemy.dis < weapon_range.close else - change_speed = enemy_dis > run_dist and "run" - end - - if not change_speed then - return - end - - local my_pos = data.unit:movement():nav_tracker():field_position() - local moving_to_pos = my_data.walking_to_chase_pos:get_walk_to_pos() - local unobstructed_line = nil - - if math_abs(my_pos.z - moving_to_pos.z) < 40 then - local ray_params = { - allow_entry = false, - pos_from = my_pos, - pos_to = moving_to_pos - } - - if not managers.navigation:raycast(ray_params) then - unobstructed_line = true - end + change_speed = not enemy_visible or focus_enemy.dis > weapon_range.optimal end - if unobstructed_line then - moving_to_pos = mvec3_cpy(moving_to_pos) - + if change_speed then BossLogicAttack._cancel_chase_attempt(data, my_data) - - my_data.chase_path = { - mvec3_cpy(my_pos), - moving_to_pos - } - - BossLogicAttack._chk_request_action_walk_to_chase_pos(data, my_data, change_speed) end end end - -- Check new position for being different than the current one function BossLogicAttack._chk_start_action_move_out_of_the_way(data, my_data) local from_pos = data.m_pos local reservation = { radius = 30, position = from_pos, - filter = data.pos_rsrv_id + filter = data.pos_rsrv_id, } if managers.navigation:is_pos_free(reservation) then return end local to_pos = CopLogicTravel._get_pos_on_wall(from_pos, 500) - if from_pos.x == to_pos.x and from_pos.y == to_pos.y then + if mvec_dis_sq(from_pos, to_pos) < 100 ^ 2 then return end @@ -306,9 +317,9 @@ function BossLogicAttack._chk_start_action_move_out_of_the_way(data, my_data) body_part = 2, type = "walk", nav_path = { - mvec3_cpy(from_pos), - to_pos - } + mvec_copy(from_pos), + to_pos, + }, }) if my_data.advancing then @@ -318,7 +329,6 @@ function BossLogicAttack._chk_start_action_move_out_of_the_way(data, my_data) end end - -- Update logic every frame function BossLogicAttack.update(data) local my_data = data.internal_data @@ -352,4 +362,4 @@ function BossLogicAttack.update(data) end function BossLogicAttack.queued_update() end -function BossLogicAttack.queue_update() end \ No newline at end of file +function BossLogicAttack.queue_update() end diff --git a/lua/carrydata.lua b/lua/carrydata.lua new file mode 100644 index 0000000..2027f4c --- /dev/null +++ b/lua/carrydata.lua @@ -0,0 +1,61 @@ +if Network:is_client() then + return +end + +-- Tweak bag stealing conditions +function CarryData:clbk_pickup_SO_verification(unit) + if not self._steal_SO_data or not self._steal_SO_data.SO_id then + return false + end + + if unit:movement():cool() then + return false + end + + if not unit:base():char_tweak().steal_loot then + return false + end + + local objective = unit:brain():objective() + if not objective or objective.type == "free" or not objective.area then + return true + end + + if objective.grp_objective and objective.grp_objective.type == "reenforce_area" then + return false + end + + local nav_seg = unit:movement():nav_tracker():nav_segment() + if objective.area == self._steal_SO_data.pickup_area then + return true + end + + if self._steal_SO_data.pickup_area.nav_segs[nav_seg] then + return managers.groupai:state()._rescue_allowed + end +end + +-- Fix zipline trying to copy non existing bag attention data +Hooks:OverrideFunction(CarryData, "set_zipline_unit", function(self, zipline_unit) + self._zipline_unit = zipline_unit + self:_set_expire_enabled(not self._zipline_unit) + + if managers.groupai:state():enemy_weapons_hot() then + return + end + + if self._zipline_unit and self._zipline_unit:zipline():ai_ignores_bag() then + local attention_data = self._unit:attention() and self._unit:attention():attention_data() + if attention_data then + self._saved_attention_data = deep_clone(attention_data) + for attention_id, _ in pairs(self._saved_attention_data) do + self._unit:attention():remove_attention(attention_id) + end + end + elseif not self._zipline_unit and self._saved_attention_data then + for _, attention_data in pairs(self._saved_attention_data) do + self._unit:attention():add_attention(attention_data) + end + self._saved_attention_data = nil + end +end) diff --git a/lua/charactertweakdata.lua b/lua/charactertweakdata.lua index b9273a4..99ad2e3 100644 --- a/lua/charactertweakdata.lua +++ b/lua/charactertweakdata.lua @@ -1,167 +1,271 @@ -Hooks:PostHook(CharacterTweakData, "init", "eclipse_init", function(self) - -- SWAT units - self.fbi_swat.move_speed = self.presets.move_speed.fast - self.fbi_swat.suppression = {panic_chance_mul = 0.3, duration = {3, 4}, react_point = {0, 2}, brown_point = {5, 6}} - self.fbi_heavy_swat.suppression = {panic_chance_mul = 0.3, duration = {3, 4}, react_point = {0, 2}, brown_point = {5, 6}} - self.fbi_heavy_swat.damage.hurt_severity = self.presets.hurt_severities.no_heavy_hurt - self.city_swat.suppression = {panic_chance_mul = 0.15, duration = {1.5, 2}, react_point = {2, 5},brown_point = {5, 6}} - self.city_swat.weapon = self.presets.weapon.expert +-- Clones a weapon preset and optionally sets values for all weapons contained in that preset +-- if the value is a function, it calls the function with the data of the value name instead +local nil_value = {} +local function based_on(preset, values) + local p = deep_clone(preset) + if not values then + return p + end + for _, entry in pairs(p) do + for val_name, val in pairs(values) do + if type(val) == "function" then + val(entry[val_name]) + else + entry[val_name] = val ~= nil_value and val + end + end + end + return p +end - -- Specials - self.sniper.suppression = nil - self.sniper.misses_first_player_shot = true +local _presets_orig = CharacterTweakData._presets +function CharacterTweakData:_presets(tweak_data, ...) + local presets = _presets_orig(self, tweak_data, ...) - self.spooc.spooc_sound_events = {detect_stop = "cloaker_presence_stop", detect = "cloaker_presence_loop"} -- remove cloaker charge noise - self.spooc.use_animation_on_fire_damage = true - self.spooc.damage.hurt_severity = self.presets.hurt_severities.only_light_hurt_and_fire - self.spooc.spooc_attack_use_smoke_chance = 0 + presets.weapon.base = based_on(presets.weapon.expert, { + focus_delay = 0.35, + aim_delay = { 0, 0.2 }, + melee_dmg = 10, + melee_speed = 1, + melee_retry_delay = { 1, 2 }, + range = { close = 750, optimal = 1500, far = 3000 }, + RELOAD_SPEED = 1, + }) - self.tank.damage.hurt_severity = self.presets.hurt_severities.dozer -- cool damage react thing - self.tank.no_run_start = false -- honestly idk why they got rid of this since it looks much cooler with it - self.tank.ecm_vulnerability = 0 - self.tank.damage.explosion_damage_mul = 0.1 + presets.weapon.base.is_pistol.FALLOFF = { + { dmg_mul = 3.5, r = 0, acc = { 0.6, 0.9 }, recoil = { 0.15, 0.3 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 3.5, r = 3000, acc = { 0.1, 0.4 }, recoil = { 0.3, 0.6 }, mode = { 1, 0, 0, 0 } }, + } - self.taser.damage.hurt_severity = self.presets.hurt_severities.base - self.medic.damage.hurt_severity = self.presets.hurt_severities.base - self.medic.use_animation_on_fire_damage = true - self.medic.suppression = nil - self.medic.move_speed = self.presets.move_speed.fast + presets.weapon.base.akimbo_pistol.melee_dmg = nil + presets.weapon.base.akimbo_pistol.melee_speed = nil + presets.weapon.base.akimbo_pistol.melee_retry_delay = nil + presets.weapon.base.akimbo_pistol.FALLOFF = { + { dmg_mul = 3.5, r = 0, acc = { 0.6, 0.9 }, recoil = { 0.1, 0.2 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 3.5, r = 3000, acc = { 0.1, 0.4 }, recoil = { 0.2, 0.4 }, mode = { 1, 0, 0, 0 } }, + } - self.shield.damage.explosion_damage_mul = 0.9 + presets.weapon.base.is_revolver.RELOAD_SPEED = 0.9 + presets.weapon.base.is_revolver.range = { close = 1000, optimal = 2000, far = 4000 } + presets.weapon.base.is_revolver.FALLOFF = { + { dmg_mul = 12.5, r = 0, acc = { 0.8, 1 }, recoil = { 0.75, 1 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 12.5, r = 4000, acc = { 0.3, 0.6 }, recoil = { 1, 1.5 }, mode = { 1, 0, 0, 0 } }, + } - -- Set custom objective interrupt distance - self.taser.min_obj_interrupt_dis = 1000 - self.spooc.min_obj_interrupt_dis = 800 - self.shadow_spooc.min_obj_interrupt_dis = 800 - self.tank.min_obj_interrupt_dis = 600 - self.tank_hw.min_obj_interrupt_dis = 600 - self.shield.min_obj_interrupt_dis = 300 + presets.weapon.base.is_sniper = deep_clone(presets.weapon.base.is_revolver) + presets.weapon.base.is_sniper.range = { close = 5000, optimal = 10000, far = 15000 } + presets.weapon.base.is_sniper.FALLOFF = { + { dmg_mul = 18, r = 0, acc = { 0, 0.5 }, recoil = { 1, 1.5 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 18, r = 2000, acc = { 0.5, 1 }, recoil = { 1, 1.5 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 18, r = 4000, acc = { 0.5, 1 }, recoil = { 1, 1.5 }, mode = { 1, 0, 0, 0 } }, + } - -- Bosses - self.biker_boss.HEALTH_INIT = 400 - self.biker_boss.player_health_scaling_mul = 1.5 - self.biker_boss.headshot_dmg_mul = 1.65 - self.biker_boss.no_headshot_add_mul = false - self.biker_boss.DAMAGE_CLAMP_BULLET = 200 - self.biker_boss.DAMAGE_CLAMP_EXPLOSION = 200 - self.biker_boss.damage.explosion_damage_mul = 0.5 - self.biker_boss.damage.hurt_severity = self.presets.hurt_severities.dozer - self.biker_boss.move_speed = self.presets.move_speed.slow - self.biker_boss.no_run_start = true - self.biker_boss.no_run_stop = true - self.biker_boss.throwable = "concussion" - self.biker_boss.throwable_cooldown = 10 + presets.weapon.base.is_shotgun_pump.RELOAD_SPEED = 1.5 + presets.weapon.base.is_shotgun_pump.range = { close = 500, optimal = 1000, far = 2000 } + presets.weapon.base.is_shotgun_pump.FALLOFF = { + { dmg_mul = 17.5, r = 300, acc = { 0.8, 1 }, recoil = { 0.8, 1 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 10.5, r = 1000, acc = { 0.7, 0.9 }, recoil = { 1, 1.4 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 2.5, r = 2000, acc = { 0.6, 0.8 }, recoil = { 1.2, 1.8 }, mode = { 1, 0, 0, 0 } }, + } - self.chavez_boss.HEALTH_INIT = 400 - self.chavez_boss.player_health_scaling_mul = 1.5 - self.chavez_boss.headshot_dmg_mul = 1.65 - self.chavez_boss.no_headshot_add_mul = false - self.chavez_boss.DAMAGE_CLAMP_BULLET = 200 - self.chavez_boss.DAMAGE_CLAMP_EXPLOSION = 200 - self.chavez_boss.damage.explosion_damage_mul = 0.5 - self.chavez_boss.damage.hurt_severity = self.presets.hurt_severities.dozer - self.chavez_boss.move_speed = self.presets.move_speed.very_fast - self.chavez_boss.no_run_start = true - self.chavez_boss.no_run_stop = true + presets.weapon.base.is_shotgun_mag = deep_clone(presets.weapon.base.is_shotgun_pump) + presets.weapon.base.is_shotgun_mag.RELOAD_SPEED = 1 + presets.weapon.base.is_shotgun_mag.autofire_rounds = { 1, 3 } + presets.weapon.base.is_shotgun_mag.FALLOFF = { + { dmg_mul = 7.5, r = 300, acc = { 0.6, 0.9 }, recoil = { 0.4, 0.7 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 4, r = 1000, acc = { 0.5, 0.8 }, recoil = { 0.45, 0.8 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 1.5, r = 2000, acc = { 0.3, 0.6 }, recoil = { 1, 1.2 }, mode = { 1, 0, 0, 0 } }, + } - self.drug_lord_boss.HEALTH_INIT = 400 - self.drug_lord_boss.player_health_scaling_mul = 1.5 - self.drug_lord_boss.headshot_dmg_mul = 1.65 - self.drug_lord_boss.no_headshot_add_mul = false - self.drug_lord_boss.DAMAGE_CLAMP_BULLET = 200 - self.drug_lord_boss.DAMAGE_CLAMP_EXPLOSION = 200 - self.drug_lord_boss.damage.explosion_damage_mul = 0.5 - self.drug_lord_boss.damage.hurt_severity = self.presets.hurt_severities.no_heavy_hurt - self.drug_lord_boss.move_speed = self.presets.move_speed.normal - self.drug_lord_boss.no_run_start = true - self.drug_lord_boss.no_run_stop = true - self.drug_lord_boss.throwable = "launcher_m203" - self.drug_lord_boss.throwable_target_verified = true - self.drug_lord_boss.throwable_cooldown = 10 + presets.weapon.base.is_double_barrel = deep_clone(presets.weapon.base.is_shotgun_pump) + presets.weapon.base.is_double_barrel.FALLOFF = { + { dmg_mul = 17.5, r = 300, acc = { 0.8, 1 }, recoil = { 0.8, 1 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 2, r = 2000, acc = { 0.6, 0.8 }, recoil = { 1, 1.4 }, mode = { 1, 0, 0, 0 } }, + } - self.hector_boss.HEALTH_INIT = 400 - self.hector_boss.player_health_scaling_mul = 1.5 - self.hector_boss.headshot_dmg_mul = 1.65 - self.hector_boss.no_headshot_add_mul = false - self.hector_boss.DAMAGE_CLAMP_BULLET = 200 - self.hector_boss.DAMAGE_CLAMP_EXPLOSION = 200 - self.hector_boss.damage.explosion_damage_mul = 0.5 - self.hector_boss.damage.hurt_severity = self.presets.hurt_severities.no_heavy_hurt - self.hector_boss.move_speed = self.presets.move_speed.slow - self.hector_boss.no_run_start = true - self.hector_boss.no_run_stop = true - self.hector_boss.throwable = "frag" - self.hector_boss.throwable_cooldown = 15 + presets.weapon.base.is_rifle.autofire_rounds = { 1, 5 } + presets.weapon.base.is_rifle.FALLOFF = { + { dmg_mul = 5.5, r = 0, acc = { 0.45, 0.7 }, recoil = { 0.5, 1 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 5.5, r = 3000, acc = { 0.2, 0.45 }, recoil = { 1, 2 }, mode = { 1, 0, 0, 0 } }, + } - self.mobster_boss.HEALTH_INIT = 400 - self.mobster_boss.player_health_scaling_mul = 1.5 - self.mobster_boss.headshot_dmg_mul = 1.65 - self.mobster_boss.no_headshot_add_mul = false - self.mobster_boss.DAMAGE_CLAMP_BULLET = 200 - self.mobster_boss.DAMAGE_CLAMP_EXPLOSION = 200 - self.mobster_boss.damage.explosion_damage_mul = 0.5 - self.mobster_boss.damage.hurt_severity = self.presets.hurt_severities.dozer - self.mobster_boss.move_speed = self.presets.move_speed.fast - self.mobster_boss.no_run_start = true - self.mobster_boss.no_run_stop = true + presets.weapon.base.is_smg = deep_clone(presets.weapon.base.is_rifle) + presets.weapon.base.is_smg.autofire_rounds = { 3, 8 } + presets.weapon.base.is_smg.FALLOFF = { + { dmg_mul = 5, r = 0, acc = { 0.4, 0.7 }, recoil = { 0.5, 1 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 5, r = 3000, acc = { 0.1, 0.3 }, recoil = { 1, 2 }, mode = { 1, 0, 0, 0 } }, + } - self.triad_boss.HEALTH_INIT = 400 - self.triad_boss.player_health_scaling_mul = 1.5 - self.triad_boss.headshot_dmg_mul = 1.65 - self.triad_boss.no_headshot_add_mul = false - self.triad_boss.DAMAGE_CLAMP_BULLET = 200 - self.triad_boss.DAMAGE_CLAMP_EXPLOSION = 200 - self.triad_boss.damage.explosion_damage_mul = 0.5 - self.triad_boss.damage.hurt_severity = self.presets.hurt_severities.dozer - self.triad_boss.move_speed = self.presets.move_speed.slow - self.triad_boss.no_run_start = true - self.triad_boss.no_run_stop = true - self.triad_boss.bullet_damage_only_from_front = nil - self.triad_boss.throwable_target_verified = false - self.triad_boss.throwable_cooldown = 20 + presets.weapon.base.is_lmg = deep_clone(presets.weapon.base.is_smg) + presets.weapon.base.is_lmg.autofire_rounds = { 15, 30 } + presets.weapon.base.is_lmg.FALLOFF = { + { dmg_mul = 4.5, r = 0, acc = { 0.3, 0.7 }, recoil = { 0.7, 1.4 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 4.5, r = 1000, acc = { 0.2, 0.6 }, recoil = { 0.8, 1.6 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 4.5, r = 3000, acc = { 0.1, 0.3 }, recoil = { 1, 2 }, mode = { 1, 0, 0, 0 } }, + } - -- escort bs - self.escort_cfo.move_speed = self.presets.move_speed.escort_normal - self.escort_chinese_prisoner.move_speed = self.presets.move_speed.escort_slow - self.escort_sand.move_speed = self.presets.move_speed.escort_slow - self.spa_vip.move_speed = self.presets.move_speed.escort_normal - self.escort_undercover.move_speed = self.presets.move_speed.escort_slow -end) + presets.weapon.base.mini = deep_clone(presets.weapon.base.is_lmg) + presets.weapon.base.mini.autofire_rounds = { 50, 200 } + presets.weapon.base.mini.FALLOFF = { + { dmg_mul = 1, r = 0, acc = { 0.15, 0.35 }, recoil = { 0.7, 1.4 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 0.7, r = 1000, acc = { 0.1, 0.3 }, recoil = { 0.8, 1.6 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 0.1, r = 3000, acc = { 0, 0.15 }, recoil = { 1, 2 }, mode = { 1, 0, 0, 0 } }, + } --- Thanks RedFlame for helping with this -local _presets_orig = CharacterTweakData._presets -function CharacterTweakData:_presets(tweak_data, ...) - local presets = _presets_orig(self, tweak_data, ...) + presets.weapon.base.is_flamethrower.melee_dmg = nil + presets.weapon.base.is_flamethrower.melee_speed = nil + presets.weapon.base.is_flamethrower.melee_retry_delay = nil + presets.weapon.base.is_flamethrower.range = { close = 450, optimal = 900, far = 1800 } + presets.weapon.base.is_flamethrower.FALLOFF = { + { dmg_mul = 2.5, r = 0, acc = { 0.15, 0.35 }, recoil = { 0.4, 0.8 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 0.65, r = 1000, acc = { 0.1, 0.3 }, recoil = { 0.5, 1 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 0, r = 2000, acc = { 0, 0.15 }, recoil = { 1, 2 }, mode = { 1, 0, 0, 0 } }, + } + + presets.weapon.gc = based_on(presets.weapon.base) + presets.weapon.gc.is_rifle.autofire_rounds = { 1, 3 } + presets.weapon.gc.is_rifle.FALLOFF = { + { dmg_mul = 9, r = 0, acc = { 0.45, 0.7 }, recoil = { 0.5, 1 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 9, r = 3000, acc = { 0.2, 0.45 }, recoil = { 1, 2 }, mode = { 1, 0, 0, 0 } }, + } + presets.weapon.gc.is_smg.FALLOFF = { + { dmg_mul = 7.5, r = 0, acc = { 0.45, 0.7 }, recoil = { 0.5, 1 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 7.5, r = 3000, acc = { 0.2, 0.45 }, recoil = { 1, 2 }, mode = { 1, 0, 0, 0 } }, + } + + presets.weapon.elite = based_on(presets.weapon.base, { + focus = 0.15, + aim_delay = { 0, 0.1 }, + }) + + presets.weapon.elite.is_rifle.autofire_rounds = { 1, 3 } + presets.weapon.elite.is_rifle.FALLOFF = { + { dmg_mul = 8.5, r = 0, acc = { 0.6, 0.9 }, recoil = { 0.5, 1 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 8.5, r = 3000, acc = { 0.25, 0.6 }, recoil = { 1, 2 }, mode = { 1, 0, 0, 0 } }, + } + + presets.weapon.elite.is_smg.autofire_rounds = { 3, 8 } + presets.weapon.elite.is_smg.FALLOFF = { + { dmg_mul = 6.5, r = 0, acc = { 0.4, 0.75 }, recoil = { 0.5, 1 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 6.5, r = 3000, acc = { 0.15, 0.4 }, recoil = { 1, 2 }, mode = { 1, 0, 0, 0 } }, + } + + presets.weapon.elite.is_shotgun_pump.RELOAD_SPEED = 1.5 + presets.weapon.elite.is_shotgun_pump.range = { close = 500, optimal = 1000, far = 2000 } + presets.weapon.elite.is_shotgun_pump.FALLOFF = { + { dmg_mul = 17.5, r = 300, acc = { 0.8, 1 }, recoil = { 0.75, 0.75 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 10.5, r = 1000, acc = { 0.7, 0.9 }, recoil = { 0.9, 0.9 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 2.5, r = 2000, acc = { 0.6, 0.8 }, recoil = { 1, 1.2 }, mode = { 1, 0, 0, 0 } }, + } + + presets.weapon.shield = based_on(presets.weapon.base, { + melee_speed = nil_value, + melee_dmg = nil_value, + melee_retry_delay = nil_value, + range = { close = 500, optimal = 1000, far = 2000 }, + }) + presets.weapon.elite_shield = based_on(presets.weapon.shield, { + focus = 0.15, + aim_delay = { 0, 0.1 }, + }) + + presets.weapon.shield.is_smg.autofire_rounds = { 3, 8 } + presets.weapon.shield.is_smg.FALLOFF = { + { dmg_mul = 3, r = 0, acc = { 0.4, 0.7 }, recoil = { 0.5, 1 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 3, r = 3000, acc = { 0.1, 0.3 }, recoil = { 1, 2 }, mode = { 1, 0, 0, 0 } }, + } + + presets.weapon.elite_shield.is_pistol.FALLOFF = { + { dmg_mul = 7, r = 0, acc = { 0.6, 0.9 }, recoil = { 0.3, 0.45 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 7, r = 3000, acc = { 0.1, 0.4 }, recoil = { 0.5, 1 }, mode = { 1, 0, 0, 0 } }, + } + + presets.weapon.taser = based_on(presets.weapon.base, { + tase_sphere_cast_radius = 15, + tase_distance = 1500, + aim_delay_tase = { 0, 0.25 }, + }) + presets.weapon.taser.is_rifle.autofire_rounds = { 1, 1 } + presets.weapon.taser.is_rifle.FALLOFF = { + { dmg_mul = 9, r = 0, acc = { 0.45, 0.7 }, recoil = { 0.5, 1 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 9, r = 3000, acc = { 0.2, 0.45 }, recoil = { 1, 2 }, mode = { 1, 0, 0, 0 } }, + } + + presets.weapon.sniper = based_on(presets.weapon.base, { + focus_delay = 0.5, + aim_delay = { 0, 0.25 }, + range = { close = 5000, optimal = 10000, far = 15000 }, + }) + + presets.weapon.sniper.is_rifle.FALLOFF = { + { dmg_mul = 12, r = 0, acc = { 0, 0.5 }, recoil = { 3, 4 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 12, r = 1000, acc = { 0.5, 1 }, recoil = { 3, 4 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 12, r = 4000, acc = { 0.5, 1 }, recoil = { 3, 4 }, mode = { 1, 0, 0, 0 } }, + } + + presets.weapon.tank = based_on(presets.weapon.base, { + melee_dmg = 20, + }) + presets.weapon.elite_tank = based_on(presets.weapon.tank, { + melee_dmg = 20, + aim_delay = { 0, 0.1 }, + focus_delay = 0.15, + }) + + presets.weapon.tank.is_shotgun_pump.FALLOFF = { + { dmg_mul = 31.5, r = 300, acc = { 0.8, 1 }, recoil = { 1.25, 1.5 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 20, r = 1000, acc = { 0.7, 0.9 }, recoil = { 1.5, 1.75 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 4, r = 2000, acc = { 0.6, 0.8 }, recoil = { 1.75, 2 }, mode = { 1, 0, 0, 0 } }, + } + + presets.weapon.tank.is_shotgun_mag.autofire_rounds = { 1, 6 } + presets.weapon.tank.is_shotgun_mag.FALLOFF = { + { dmg_mul = 7.5, r = 300, acc = { 0.6, 0.9 }, recoil = { 0.4, 0.7 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 4, r = 1000, acc = { 0.5, 0.8 }, recoil = { 0.45, 0.8 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 1.5, r = 2000, acc = { 0.3, 0.6 }, recoil = { 1, 1.2 }, mode = { 1, 0, 0, 0 } }, + } + + presets.weapon.elite_tank.is_shotgun_pump.FALLOFF = { + { dmg_mul = 31.5, r = 300, acc = { 0.8, 1 }, recoil = { 1, 1 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 20, r = 1000, acc = { 0.7, 0.9 }, recoil = { 1.25, 1.25 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 4, r = 2000, acc = { 0.6, 0.8 }, recoil = { 1.5, 1.5 }, mode = { 1, 0, 0, 0 } }, + } + + presets.weapon.elite_tank.is_lmg.autofire_rounds = { 25, 50 } + presets.weapon.elite_tank.is_lmg.FALLOFF = { + { dmg_mul = 4.5, r = 0, acc = { 0.3, 0.7 }, recoil = { 0.7, 1.4 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 4.5, r = 1000, acc = { 0.2, 0.6 }, recoil = { 0.8, 1.6 }, mode = { 1, 0, 0, 0 } }, + { dmg_mul = 4.5, r = 3000, acc = { 0.1, 0.3 }, recoil = { 1, 2 }, mode = { 1, 0, 0, 0 } }, + } - -- pre-henchmen team ai hurt severity & low gp presets.gang_member_damage.MIN_DAMAGE_INTERVAL = 0.1 + presets.gang_member_damage.REGENERATE_TIME = 3 + presets.gang_member_damage.REGENERATE_TIME_AWAY = 4 presets.gang_member_damage.hurt_severity.bullet = { - health_reference = "current", + health_reference = "full", zones = { { health_limit = 0.4, none = 0.3, light = 0.6, - moderate = 0.1 + moderate = 0.1, }, { health_limit = 0.7, none = 0.1, light = 0.7, - moderate = 0.2 + moderate = 0.2, }, { none = 0.1, light = 0.5, moderate = 0.3, - heavy = 0.1 - } - } + heavy = 0.1, + }, + }, } - presets.weapon.sniper.is_rifle.use_laser = false - - -- escort bs + -- escort speed stuff presets.move_speed.escort_normal = deep_clone(presets.move_speed.normal) presets.move_speed.escort_slow = deep_clone(presets.move_speed.slow) @@ -182,25 +286,92 @@ function CharacterTweakData:_presets(tweak_data, ...) tase = false, bullet = { health_reference = "current", - zones = {{light = 1}} + zones = { { light = 1 } }, }, explosion = { health_reference = "current", - zones = {{light = 1}} + zones = { { light = 1 } }, }, melee = { health_reference = "current", - zones = {{light = 1}} + zones = { { light = 1 } }, }, fire = { health_reference = "current", - zones = {{none = 1} - } + zones = { { none = 1 } }, }, poison = { health_reference = "current", - zones = {{none = 1}} - } + zones = { { none = 1 } }, + }, + } + + -- taser / medic preset + presets.hurt_severities.base.bullet.zones = { + { + health_limit = 0.2, + none = 0.2, + light = 0.6, + moderate = 0.2, + }, + { + health_limit = 0.4, + light = 0.5, + moderate = 0.3, + heavy = 0.2, + }, + { + health_limit = 0.6, + light = 0.2, + moderate = 0.3, + heavy = 0.5, + }, + { + health_limit = 0.8, + moderate = 0.3, + heavy = 0.7, + }, + } + presets.hurt_severities.base.melee.zones = { + { + health_limit = 0.2, + light = 1, + }, + { + health_limit = 0.4, + light = 0.5, + moderate = 0.5, + }, + { + health_limit = 0.6, + moderate = 0.5, + heavy = 0.5, + }, + { + health_limit = 0.8, + heavy = 1, + }, + } + presets.hurt_severities.base.explosion.zones = { + { + health_limit = 0.2, + light = 0.5, + moderate = 0.5, + }, + { + health_limit = 0.4, + moderate = 0.5, + heavy = 0.5, + }, + { + health_limit = 0.6, + heavy = 0.5, + explode = 0.5, + }, + { + health_limit = 0.8, + explode = 1, + }, } -- heavies don't stumble as much @@ -211,2482 +382,528 @@ function CharacterTweakData:_presets(tweak_data, ...) zones = { { health_limit = 0.4, - none = 0.5, - light = 0.5 + light = 1, }, { health_limit = 0.7, - light = 0.7, - moderate = 0.3 + moderate = 1, }, { - light = 0.5, - moderate = 0.5 - } - } + light = 1, + moderate = 1, + }, + }, }, fire = { - health_reference = "current", + health_reference = "full", zones = { { - fire = 1 - } - } + fire = 1, + }, + }, }, poison = { - health_reference = "current", + health_reference = "full", zones = { { - poison = 1 - } - } - } - } - presets.hurt_severities.no_heavy_hurt.melee = deep_clone(presets.hurt_severities.no_heavy_hurt.bullet) - presets.hurt_severities.no_heavy_hurt.explosion = deep_clone(presets.hurt_severities.no_heavy_hurt.bullet) - - - -- rifle preset - presets.weapon.deathwish.is_rifle.aim_delay = {0.2, 0.2} - presets.weapon.deathwish.is_rifle.focus_delay = 1 - presets.weapon.deathwish.is_rifle.melee_dmg = 10 - presets.weapon.deathwish.is_rifle.FALLOFF = { - { - dmg_mul = 7.5, - r = 100, - acc = { - 0.9, - 0.9 - }, - recoil = { - 0.25, - 0.3 - }, - mode = { - 0, - 0, - 0, - 1 - }, - autofire_rounds = { - 8, - 16 - } - }, - { - dmg_mul = 7.5, - r = 500, - acc = { - 0.8, - 0.9 - }, - recoil = { - 0.25, - 0.3 - }, - mode = { - 0, - 0, - 0, - 1 - }, - autofire_rounds = { - 6, - 12 - } - }, - { - dmg_mul = 7.5, - r = 1000, - acc = { - 0.65, - 0.8 - }, - recoil = { - 0.35, - 0.55 - }, - mode = { - 0, - 0, - 0, - 1 - }, - autofire_rounds = { - 4, - 8 - } - }, - { - dmg_mul = 7.5, - r = 1500, - acc = { - 0.65, - 0.75 - }, - recoil = { - 0.4, - 0.6 - }, - mode = { - 0, - 0, - 2, - 3 - }, - autofire_rounds = { - 3, - 6 - } - }, - { - dmg_mul = 7.5, - r = 2000, - acc = { - 0.45, - 0.6 - }, - recoil = { - 0.6, - 0.8 + poison = 1, + }, }, - mode = { - 0, - 0, - 1, - 0 - } }, - { - dmg_mul = 7.5, - r = 3000, - acc = { - 0.25, - 0.4 - }, - recoil = { - 0.7, - 1.1 + explosion = { + health_reference = "full", + zones = { + { + health_limit = 0.2, + moderate = 1, + }, + { + health_limit = 0.5, + heavy = 1, + }, + { + health_limit = 0.8, + explode = 1, + }, + { + heavy = 0.5, + explode = 0.5, + }, }, - mode = { - 1, - 3, - 2, - 0 - } }, } - -- shotgun preset - presets.weapon.deathwish.is_shotgun_pump.aim_delay = {0.2, 0.2} - presets.weapon.deathwish.is_shotgun_pump.focus_delay = 1 - presets.weapon.deathwish.is_shotgun_pump.melee_dmg = 10 - presets.weapon.deathwish.is_shotgun_pump.FALLOFF = { - { - dmg_mul = 3, - r = 100, - acc = { - 0.95, - 0.95 - }, - recoil = { - 1.15, - 1.15 - }, - mode = { - 1, - 0, - 0, - 0 - } + presets.hurt_severities.no_heavy_hurt.melee = deep_clone(presets.hurt_severities.no_heavy_hurt.bullet) + + -- Setup surrender presets + presets.surrender.weak = { + base_chance = 0, + significant_chance = 0, + reasons = { + pants_down = 1, + weapon_down = 0.7, + flanked = 0.6, + unaware_of_aggressor = 0.4, + isolated = 0.3, }, - { - dmg_mul = 3, - r = 500, - acc = { - 0.7, - 0.95 + factors = { + health = { + [1.0] = 0, + [0.2] = 1, }, - recoil = { - 1.15, - 1.15 + aggressor_dis = { + [100] = 0.3, + [1000] = 0, }, - mode = { - 1, - 0, - 0, - 0 - } }, - { - dmg_mul = 2, - r = 1000, - acc = { - 0.5, - 0.8 - }, - recoil = { - 1.35, - 1.35 - }, - mode = { - 0, - 0, - 0, - 1 - } + } + presets.surrender.average = { + base_chance = 0, + significant_chance = 0, + reasons = { + pants_down = 0.9, + weapon_down = 0.5, + flanked = 0.5, + unaware_of_aggressor = 0.3, + isolated = 0.2, }, - { - dmg_mul = 1, - r = 2000, - acc = { - 0.45, - 0.65 + factors = { + health = { + [0.75] = 0, + [0.0] = 0.75, }, - recoil = { - 1.5, - 1.5 + aggressor_dis = { + [100] = 0.2, + [1000] = 0, }, - mode = { - 1, - 0, - 0, - 0 - } }, - { - dmg_mul = 0.5, - r = 3000, - acc = { - 0.3, - 0.5 - }, - recoil = { - 2, - 2 - }, - mode = { - 1, - 0, - 0, - 0 - } - } } - - -- revolver preset - -- 140 dmg - presets.weapon.deathwish.is_revolver.aim_delay = {0.2, 0.2} - presets.weapon.deathwish.is_revolver.focus_delay = 1 - presets.weapon.deathwish.is_revolver.melee_dmg = 10 - presets.weapon.deathwish.is_revolver.FALLOFF = { - { - dmg_mul = 16, - r = 100, - acc = { - 0.75, - 0.9 - }, - recoil = { - 0.6, - 0.6 - }, - mode = { - 1, - 0, - 0, - 0 - } + presets.surrender.hard = { + base_chance = 0, + significant_chance = 0, + reasons = { + pants_down = 0.8, + weapon_down = 0.3, + flanked = 0.4, + unaware_of_aggressor = 0.2, + isolated = 0.1, }, - { - dmg_mul = 16, - r = 500, - acc = { - 0.7, - 0.85 + factors = { + health = { + [0.5] = 0, + [0.0] = 0.5, }, - recoil = { - 0.6, - 0.6 + aggressor_dis = { + [100] = 0.1, + [1000] = 0, }, - mode = { - 1, - 0, - 0, - 0 - } }, - { - dmg_mul = 16, - r = 1000, - acc = { - 0.65, - 0.8 - }, - recoil = { - 0.8, - 1.1 - }, - mode = { - 1, - 0, - 0, - 0 - } + } + presets.surrender.veteran = { + base_chance = 0, + significant_chance = 0, + reasons = { + pants_down = 0.2, + weapon_down = 0.1, + flanked = 0.1, + unaware_of_aggressor = 0.1, + isolated = 0.1, }, - { - dmg_mul = 16, - r = 2000, - acc = { - 0.5, - 0.65 + factors = { + health = { + [0.5] = 0, + [0.0] = 0.2, }, - recoil = { - 1, - 1.3 + aggressor_dis = { + [100] = 0, + [1000] = 0, }, - mode = { - 1, - 0, - 0, - 0 - } }, - { - dmg_mul = 16, - r = 3000, - acc = { - 0.3, - 0.4 - }, - recoil = { - 1.3, - 1.5 - }, - mode = { - 1, - 0, - 0, - 0 - } - } } - -- pistol preset - -- 35 damage - presets.weapon.deathwish.is_pistol.aim_delay = {0.2, 0.2} - presets.weapon.deathwish.is_pistol.focus_delay = 1 - presets.weapon.deathwish.is_pistol.melee_dmg = 10 - presets.weapon.deathwish.is_pistol.FALLOFF = { - { - dmg_mul = 3.5, - r = 100, - acc = { - 0.9, - 0.95 - }, - recoil = { - 0.25, - 0.3 - }, - mode = { - 0, - 0, - 0, - 1 - } - }, - { - dmg_mul = 3.5, - r = 500, - acc = { - 0.7, - 0.85 - }, - recoil = { - 0.25, - 0.3 - }, - mode = { - 0, - 0, - 0, - 1 - } - }, - { - dmg_mul = 3.5, - r = 1000, - acc = { - 0.6, - 0.75 - }, - recoil = { - 0.3, - 0.4 - }, - mode = { - 0, - 0, - 2, - 1 - } - }, - { - dmg_mul = 3.5, - r = 2000, - acc = { - 0.3, - 0.5 - }, - recoil = { - 0.35, - 0.5 - }, - mode = { - 3, - 1, - 0, - 0 - } - }, - { - dmg_mul = 3.5, - r = 3000, - acc = { - 0.1, - 0.3 - }, - recoil = { - 0.7, - 0.9 - }, - mode = { - 1, - 0, - 0, - 0 - } - }, - { - dmg_mul = 3.5, - r = 4000, - acc = { - 0.0, - 0.2 - }, - recoil = { - 1, - 1.5 - }, - mode = { - 1, - 0, - 0, - 0 - } - } - } - - -- smg preset - -- 40 damage - presets.weapon.deathwish.is_smg.aim_delay = {0.2, 0.2} - presets.weapon.deathwish.is_smg.focus_delay = 1 - presets.weapon.deathwish.is_smg.melee_dmg = 10 - presets.weapon.deathwish.is_smg.FALLOFF = { - { - dmg_mul = 4, - r = 100, - acc = { - 0.8, - 0.8 - }, - recoil = { - 0.1, - 0.25 - }, - mode = { - 0, - 0, - 0, - 1 - }, - autofire_rounds = { - 4, - 8 - } - }, - { - dmg_mul = 4, - r = 500, - acc = { - 0.6, - 0.7 - }, - recoil = { - 0.1, - 0.3 - }, - mode = { - 0, - 0, - 0, - 1 - }, - autofire_rounds = { - 4, - 6 - } - }, - { - dmg_mul = 4, - r = 1000, - acc = { - 0.5, - 0.6 - }, - recoil = { - 0.35, - 0.5 - }, - mode = { - 0, - 0, - 0, - 1 - }, - autofire_rounds = { - 3, - 4 - } - }, - { - dmg_mul = 4, - r = 2000, - acc = { - 0.4, - 0.5 - }, - recoil = { - 0.35, - 0.5 - }, - mode = { - 0, - 0, - 1, - 0 - } - }, - { - dmg_mul = 4, - r = 3000, - acc = { - 0.25, - 0.35 - }, - recoil = { - 0.5, - 1.5 - }, - mode = { - 1, - 3, - 2, - 0 - } - }, - { - dmg_mul = 4, - r = 4500, - acc = { - 0.15, - 0.3 - }, - recoil = { - 1, - 1.5 - }, - mode = { - 1, - 2, - 0, - 0 - } - } - } - return presets - end -function CharacterTweakData:_set_overkill_290() - self:_multiply_all_hp(3, 1.25) +Hooks:PostHook(CharacterTweakData, "init", "eclipse_init", function(self) + -- Common SWAT + self.fbi_swat.move_speed = self.presets.move_speed.fast + self.fbi_swat.suppression = { panic_chance_mul = 0.3, duration = { 3, 4 }, react_point = { 0, 2 }, brown_point = { 5, 6 } } + self.fbi_heavy_swat.suppression = { panic_chance_mul = 0.3, duration = { 3, 4 }, react_point = { 0, 2 }, brown_point = { 5, 6 } } + self.fbi_heavy_swat.damage.hurt_severity = self.presets.hurt_severities.no_heavy_hurt + self.city_swat.suppression = { panic_chance_mul = 0.15, duration = { 1.5, 2 }, react_point = { 2, 5 }, brown_point = { 5, 6 } } - -- honestly it's easier to just do this than account for all the difficulty health and hs damage muls - self.swat.HEALTH_INIT = 30 - self.swat.headshot_dmg_mul = 1.8 - self.fbi_swat.HEALTH_INIT = 34 - self.fbi_swat.headshot_dmg_mul = 2 - self.city_swat.HEALTH_INIT = 40 - self.city_swat.headshot_dmg_mul = 1.8 - self.fbi_heavy_swat.HEALTH_INIT = 56 - self.fbi_heavy_swat.headshot_dmg_mul = 1.8 - - self.shield.HEALTH_INIT = 42 - self.shield.headshot_dmg_mul = 1.8 - self.sniper.HEALTH_INIT = 16 - self.taser.HEALTH_INIT = 84 - self.taser.headshot_dmg_mul = 1.4 - self.spooc.HEALTH_INIT = 102 - self.spooc.headshot_dmg_mul = 3.75 - self.medic.HEALTH_INIT = 60 - self.medic.headshot_dmg_mul = 2.2 - self.tank.HEALTH_INIT = 2160 - self.tank.headshot_dmg_mul = 35 - - -- Team AI nerf - self.presets.gang_member_damage.HEALTH_INIT = 400 - self.presets.gang_member_damage.REGENERATE_TIME = 3 - self.presets.gang_member_damage.REGENERATE_TIME_AWAY = 4 - - -- FBI Rifle preset - -- 45 damage M4 - self.fbi_swat.weapon.is_rifle = { - aim_delay = { - 0.25, - 0.25 - }, - focus_delay = 0.2, - focus_dis = 200, - spread = 15, - miss_dis = 40, - RELOAD_SPEED = 1.4, - melee_speed = 1, - melee_dmg = 10, - melee_retry_delay = { - 1, - 2 - }, - range = { - optimal = 1250, - far = 2500, - close = 750 - }, - autofire_rounds = { - 3, - 6 - }, - FALLOFF = { - { - dmg_mul = 4.5, - r = 100, - acc = { - 0.9, - 0.9 - }, - recoil = { - 0.25, - 0.3 - }, - mode = { - 0, - 0, - 0, - 1 - }, - autofire_rounds = { - 8, - 16 - } - }, - { - dmg_mul = 4.5, - r = 500, - acc = { - 0.8, - 0.9 - }, - recoil = { - 0.25, - 0.3 - }, - mode = { - 0, - 0, - 0, - 1 - }, - autofire_rounds = { - 6, - 12 - } - }, - { - dmg_mul = 4.5, - r = 1000, - acc = { - 0.65, - 0.8 - }, - recoil = { - 0.35, - 0.55 - }, - mode = { - 0, - 0, - 0, - 1 - }, - autofire_rounds = { - 4, - 8 - } - }, - { - dmg_mul = 4.5, - r = 1500, - acc = { - 0.65, - 0.75 - }, - recoil = { - 0.4, - 0.6 - }, - mode = { - 0, - 0, - 2, - 3 - }, - autofire_rounds = { - 3, - 6 - } - }, - { - dmg_mul = 4.5, - r = 2000, - acc = { - 0.4, - 0.5 - }, - recoil = { - 0.6, - 0.8 - }, - mode = { - 0, - 0, - 1, - 0 - } - }, - { - dmg_mul = 4.5, - r = 3000, - acc = { - 0.2, - 0.35 - }, - recoil = { - 0.7, - 1.1 - }, - mode = { - 1, - 3, - 2, - 0 - } - }, - } - } + -- Specials + -- sniper + self.sniper.suppression = nil + self.sniper.misses_first_player_shot = true - -- FBI / Medic Shotgun preset - -- 175 damage point blank, falls off down to 42 at max range - self.fbi_swat.weapon.is_shotgun_pump = { - aim_delay = { - 0.25, - 0.25 - }, - focus_delay = 0.2, - focus_dis = 200, - spread = 15, - miss_dis = 20, - RELOAD_SPEED = 1.4, - melee_speed = 1, - melee_dmg = 10, - melee_retry_delay = {1, 2}, - range = { - optimal = 500, - far = 1000, - close = 100 - }, - FALLOFF = { - { - dmg_mul = 2.5, - r = 100, - acc = { - 0.95, - 0.95 - }, - recoil = { - 1.15, - 1.15 - }, - mode = { - 1, - 0, - 0, - 0 - } - }, - { - dmg_mul = 2.5, - r = 500, - acc = { - 0.7, - 0.9 - }, - recoil = { - 1.15, - 1.15 - }, - mode = { - 1, - 0, - 0, - 0 - } - }, - { - dmg_mul = 1.5, - r = 1000, - acc = { - 0.5, - 0.6 - }, - recoil = { - 1.3, - 1.3 - }, - mode = { - 1, - 0, - 0, - 0 - } - }, - { - dmg_mul = 0.8, - r = 2000, - acc = { - 0.3, - 0.4 - }, - recoil = { - 1.5, - 1.5 - }, - mode = { - 1, - 0, - 0, - 0 - } - }, - { - dmg_mul = 0.4, - r = 3000, - acc = { - 0.1, - 0.1 - }, - recoil = { - 2, - 2 - }, - mode = { - 1, - 0, - 0, - 0 - } - } - } - } - self.medic.weapon.is_shotgun_pump = self.fbi_swat.weapon.is_shotgun_pump + -- cloaker + self.spooc.use_animation_on_fire_damage = true + self.spooc.damage.hurt_severity = self.presets.hurt_severities.only_light_hurt_and_fire + self.spooc.spooc_attack_use_smoke_chance = 0 + self.spooc.melee_weapon = "baton" - -- Elite Rifle preset - self.city_swat.weapon.is_rifle = { - aim_delay = { - 0.15, - 0.15 - }, - focus_delay = 0.2, - focus_dis = 200, - spread = 15, - miss_dis = 40, - RELOAD_SPEED = 1.4, - melee_speed = 1, - melee_dmg = 10, - melee_retry_delay = { - 1, - 2 - }, - range = { - optimal = 2250, - far = 3000, - close = 1500 - }, - autofire_rounds = { - 3, - 6 - }, - FALLOFF = { - { - dmg_mul = 6.75, - r = 100, - acc = { - 0.9, - 0.9 - }, - recoil = { - 0.25, - 0.3 - }, - mode = { - 0, - 0, - 0, - 1 - }, - autofire_rounds = { - 4, - 6 - } - }, - { - dmg_mul = 6.75, - r = 500, - acc = { - 0.8, - 0.9 - }, - recoil = { - 0.25, - 0.3 - }, - mode = { - 0, - 0, - 0, - 1 - }, - autofire_rounds = { - 4, - 6 - } - }, - { - dmg_mul = 6.75, - r = 1000, - acc = { - 0.7, - 0.8 - }, - recoil = { - 0.35, - 0.55 - }, - mode = { - 0, - 0, - 0, - 1 - }, - autofire_rounds = { - 3, - 4 - } - }, - { - dmg_mul = 6.75, - r = 1500, - acc = { - 0.65, - 0.75 - }, - recoil = { - 0.4, - 0.6 - }, - mode = { - 0, - 0, - 3, - 2 - }, - autofire_rounds = { - 3, - 4 - } - }, - { - dmg_mul = 6.75, - r = 2000, - acc = { - 0.5, - 0.65 - }, - recoil = { - 0.6, - 0.8 - }, - mode = { - 0, - 0, - 1, - 0 - } - }, - { - dmg_mul = 6.75, - r = 3000, - acc = { - 0.3, - 0.45 - }, - recoil = { - 0.7, - 1.1 - }, - mode = { - 1, - 3, - 2, - 0 - } - }, - } - } + -- tank + self.tank.damage.hurt_severity = self.presets.hurt_severities.dozer -- cool damage react thing + self.tank.ecm_vulnerability = 0 + self.tank.damage.explosion_damage_mul = 0.1 + self.tank.melee_weapon = "weapon" + self.tank.move_speed.stand.walk.cbt = { strafe = 176, fwd = 198, bwd = 154 } + self.tank.move_speed.stand.run.cbt = self.tank.move_speed.stand.walk.cbt - -- Elite SMG preset - -- 55 damage - self.city_swat.weapon.is_smg = { - aim_delay = { - 0.15, - 0.15 - }, - focus_delay = 0.2, - focus_dis = 200, - spread = 15, - miss_dis = 40, - RELOAD_SPEED = 1.4, - melee_speed = 1, - melee_dmg = 10, - melee_retry_delay = { - 1, - 2 - }, - range = { - optimal = 1250, - far = 2500, - close = 750 - }, - autofire_rounds = { - 3, - 6 - }, - FALLOFF = { - { - dmg_mul = 5.5, - r = 100, - acc = { - 0.95, - 0.95 - }, - recoil = { - 0.25, - 0.3 - }, - mode = { - 0, - 0, - 0, - 1 - }, - autofire_rounds = { - 6, - 12 - } - }, - { - dmg_mul = 5.5, - r = 500, - acc = { - 0.8, - 0.9 - }, - recoil = { - 0.25, - 0.3 - }, - mode = { - 0, - 0, - 0, - 1 - }, - autofire_rounds = { - 5, - 10 - } - }, - { - dmg_mul = 5.5, - r = 1000, - acc = { - 0.7, - 0.9 - }, - recoil = { - 0.35, - 0.55 - }, - mode = { - 0, - 0, - 0, - 1 - }, - autofire_rounds = { - 4, - 8 - } - }, - { - dmg_mul = 5.5, - r = 1500, - acc = { - 0.6, - 0.8 - }, - recoil = { - 0.4, - 0.6 - }, - mode = { - 0, - 0, - 1, - 3 - }, - autofire_rounds = { - 3, - 6 - } - }, - { - dmg_mul = 5.5, - r = 2000, - acc = { - 0.4, - 0.55 - }, - recoil = { - 0.2, - 0.3 - }, - mode = { - 0, - 0, - 1, - 0 - } - }, - { - dmg_mul = 5.5, - r = 3000, - acc = { - 0.25, - 0.4 - }, - recoil = { - 0.7, - 1.1 - }, - mode = { - 1, - 3, - 2, - 0 - } - } - } - } + -- elite tank + self.tank_elite = deep_clone(self.tank) + self.tank_elite.move_speed.stand.walk.cbt = { strafe = 196, fwd = 218, bwd = 174 } + self.tank_elite.move_speed.stand.run.cbt = self.tank_elite.move_speed.stand.walk.cbt + table.insert(self._enemy_list, "tank_elite") - -- Elite Shotgun preset - -- 175 damage point blank, falls off down to 50 at max range - self.city_swat.weapon.is_shotgun_pump = { - aim_delay = { - 0.15, - 0.15 - }, - focus_delay = 0.2, - focus_dis = 200, - spread = 15, - miss_dis = 20, - RELOAD_SPEED = 1.4, - melee_speed = 1, - melee_dmg = 10, - melee_retry_delay = {1, 2}, - range = { - optimal = 500, - far = 1000, - close = 100 - }, - FALLOFF = { - { - dmg_mul = 3.5, - r = 100, - acc = { - 0.95, - 0.95 - }, - recoil = { - 0.75, - 0.75 - }, - mode = { - 1, - 0, - 0, - 0 - } - }, - { - dmg_mul = 3.5, - r = 500, - acc = { - 0.7, - 0.95 - }, - recoil = { - 0.75, - 0.75 - }, - mode = { - 1, - 0, - 0, - 0 - } - }, - { - dmg_mul = 2.2, - r = 1000, - acc = { - 0.5, - 0.8 - }, - recoil = { - 0.9, - 0.9 - }, - mode = { - 1, - 0, - 0, - 0 - } - }, - { - dmg_mul = 1.6, - r = 2000, - acc = { - 0.4, - 0.45 - }, - recoil = { - 1.1, - 1.1 - }, - mode = { - 1, - 0, - 0, - 0 - } - }, - { - dmg_mul = 1, - r = 3000, - acc = { - 0.15, - 0.15 - }, - recoil = { - 1.5, - 1.5 - }, - mode = { - 1, - 0, - 0, - 0 - } - } - } - } + -- taser & medic + self.taser.damage.hurt_severity = self.presets.hurt_severities.base + self.medic.damage.hurt_severity = self.presets.hurt_severities.base + self.medic.use_animation_on_fire_damage = true + self.medic.suppression = nil + self.medic.move_speed = self.presets.move_speed.fast - -- Medic preset - self.medic.weapon.is_rifle = self.fbi_swat.weapon.is_rifle + -- shield + self.shield.damage.explosion_damage_mul = 0.7 + self.shield.damage.hurt_severity = self.presets.hurt_severities.no_hurts + + -- elite shield + self.phalanx_minion.DAMAGE_CLAMP_BULLET = nil + self.phalanx_minion.DAMAGE_CLAMP_EXPLOSION = nil + self.phalanx_minion.damage.explosion_damage_mul = 0.2 + self.phalanx_minion.access = "shield" + + self.phalanx_minion_break = deep_clone(self.phalanx_minion) + self.phalanx_minion_break.tags = { "law", "shield" } + self.phalanx_minion_break.move_speed = self.presets.move_speed.very_fast + self.phalanx_minion_break.allowed_stances = nil + self.phalanx_minion_break.allowed_poses = nil + self.phalanx_minion_break.no_equip_anim = nil + self.phalanx_minion_break.no_run_start = nil + self.phalanx_minion_break.no_run_stop = nil + self.phalanx_minion_break.always_face_enemy = nil + self.phalanx_minion_break.wall_fwd_offset = nil + self.phalanx_minion_break.priority_shout = nil + self.phalanx_minion_break.dodge = self.presets.dodge.athletic + self.phalanx_minion_break.access = "swat" + self.phalanx_minion_break.chatter = self.presets.enemy_chatter.swat + self.phalanx_minion_break.announce_incomming = nil + self.phalanx_minion_break.damage.hurt_severity = self.presets.hurt_severities.light_hurt_fire_poison + self.phalanx_minion_break.damage.explosion_damage_mul = 1 + self.phalanx_minion_break.use_animation_on_fire_damage = nil + self.phalanx_minion_break.damage.shield_knocked = nil + self.phalanx_minion_break.tmp_invulnerable_on_tweak_change = 0.1 + table.insert(self._enemy_list, "phalanx_minion_break") - -- Cloaker - self.spooc.weapon.is_pistol = self.presets.weapon.deathwish.is_pistol - self.spooc.weapon.is_pistol.aim_delay = {0.1, 0.1} + -- Set custom objective interrupt distance + self.taser.min_obj_interrupt_dis = 1000 + self.spooc.min_obj_interrupt_dis = 800 + self.shadow_spooc.min_obj_interrupt_dis = 800 + self.tank.min_obj_interrupt_dis = 600 + self.tank_hw.min_obj_interrupt_dis = 600 + self.shield.min_obj_interrupt_dis = 500 + self.phalanx_minion.min_obj_interrupt_dis = 500 + + -- ZEAL Team + self.zeal_swat = deep_clone(self.city_swat) + self.zeal_swat.dodge = self.presets.dodge.ninja + self.zeal_swat.suppression = nil + self.zeal_swat.speech_prefix_p2 = "d" + self.zeal_swat.damage.explosion_damage_mul = 0.8 + table.insert(self._enemy_list, "zeal_swat") + + self.zeal_heavy_swat = deep_clone(self.city_swat) + self.zeal_heavy_swat.suppression = nil + self.zeal_heavy_swat.speech_prefix_p2 = "d" + self.zeal_heavy_swat.move_speed = self.presets.move_speed.fast + self.zeal_heavy_swat.damage.explosion_damage_mul = 0.6 + table.insert(self._enemy_list, "zeal_heavy_swat") + + self.zeal_shield = deep_clone(self.shield) + self.zeal_shield.speech_prefix_p2 = "d" + table.insert(self._enemy_list, "zeal_shield") + + self.zeal_medic = deep_clone(self.medic) + self.zeal_medic.move_speed = self.presets.move_speed.very_fast + self.zeal_medic.speech_prefix_p2 = "d" + self.zeal_medic.damage.explosion_damage_mul = 0.6 + table.insert(self._enemy_list, "zeal_medic") + + self.zeal_taser = deep_clone(self.taser) + self.zeal_taser.speech_prefix_p2 = "d" + self.zeal_taser.damage.explosion_damage_mul = 0.6 + table.insert(self._enemy_list, "zeal_taser") + + -- surrender presets + self.security.surrender = self.presets.surrender.weak + self.cop_scared.surrender = self.presets.surrender.weak + self.cop.surrender = self.presets.surrender.weak + self.fbi.surrender = self.presets.surrender.weak + self.swat.surrender = self.presets.surrender.weak + self.fbi_swat.surrender = self.presets.surrender.average + self.fbi_heavy_swat.surrender = self.presets.surrender.average + self.city_swat.surrender = self.presets.surrender.hard + self.zeal_swat.surrender = self.presets.surrender.veteran + self.zeal_heavy_swat.surrender = self.presets.surrender.veteran - -- Sniper preset - -- Fast rate of fire but low damage, always missing first shot but pin-point accuracy after that - self.sniper.weapon.is_rifle.aim_delay = {0.3, 0.5} - self.sniper.weapon.is_rifle.FALLOFF = { - { - r = 700, - acc = {1, 1}, - dmg_mul = 6, - recoil = {0.6, 0.6}, - mode = { - 1, - 0, - 0, - 0 - } - }, - { - r = 4000, - acc = {0.95, 0.95}, - dmg_mul = 6, - recoil = {0.75, 0.75}, - mode = { - 1, - 0, - 0, - 0 - } - }, - { - r = 10000, - acc = {0.8, 0.8}, - dmg_mul = 6, - recoil = {0.825, 0.825}, - mode = { - 1, - 0, - 0, - 0 - } - } - } + -- Bosses + self.biker_boss.HEALTH_INIT = 500 + self.biker_boss.player_health_scaling_mul = 1.5 + self.biker_boss.headshot_dmg_mul = 2 + self.biker_boss.no_headshot_add_mul = false + self.biker_boss.damage.explosion_damage_mul = 0.5 + self.biker_boss.damage.hurt_severity = self.presets.hurt_severities.dozer + self.biker_boss.move_speed = self.presets.move_speed.slow + self.biker_boss.no_run_start = true + self.biker_boss.no_run_stop = true + self.biker_boss.throwable = "concussion" + self.biker_boss.throwable_cooldown = 10 - -- Saigadozer preset - -- 75 damage at point blank, drops to 30 at max range - self.tank.weapon.is_shotgun_mag.aim_delay = { - 0.2, - 0.2 - } - self.tank.weapon.is_shotgun_mag.range = { - optimal = 650, - far = 1300, - close = 250 - } - self.tank.weapon.is_shotgun_mag.focus_delay = 0.2 - self.tank.weapon.is_shotgun_mag.focus_dis = 200 - self.tank.weapon.is_shotgun_mag.FALLOFF = { - { - dmg_mul = 2.5, - r = 100, - acc = { - 0.9, - 0.975 - }, - recoil = { - 0.4, - 0.7 - }, - mode = { - 0, - 0, - 0, - 1 - }, - autofire_rounds = { - 10, - 15 - } - }, - { - dmg_mul = 2.5, - r = 500, - acc = { - 0.85, - 0.95 - }, - recoil = { - 0.4, - 0.7 - }, - mode = { - 0, - 0, - 0, - 1 - }, - autofire_rounds = { - 8, - 12 - } - }, - { - dmg_mul = 2, - r = 1000, - acc = { - 0.75, - 0.85 - }, - recoil = { - 0.45, - 0.8 - }, - mode = { - 0, - 0, - 0, - 1 - }, - autofire_rounds = { - 6, - 9 - } - }, - { - dmg_mul = 1.5, - r = 2000, - acc = { - 0.65, - 0.7 - }, - recoil = { - 0.45, - 0.8 - }, - mode = { - 0, - 0, - 2, - 3 - }, - autofire_rounds = { - 3, - 5 - } - }, - { - dmg_mul = 1, - r = 3000, - acc = { - 0.3, - 0.5 - }, - recoil = { - 1, - 1.2 - }, - mode = { - 2, - 3, - 1, - 0 - } - } - } + self.chavez_boss.HEALTH_INIT = 500 + self.chavez_boss.player_health_scaling_mul = 1.5 + self.chavez_boss.headshot_dmg_mul = 1.3 + self.chavez_boss.no_headshot_add_mul = false + self.chavez_boss.damage.explosion_damage_mul = 0.5 + self.chavez_boss.damage.hurt_severity = self.presets.hurt_severities.dozer + self.chavez_boss.move_speed = self.presets.move_speed.very_fast + self.chavez_boss.no_run_start = true + self.chavez_boss.no_run_stop = true - -- Greendozer preset - -- halved damage, higher rof - self.tank.weapon.is_shotgun_pump.aim_delay = { - 0.2, - 0.2 - } - self.tank.weapon.is_shotgun_pump.range = { - optimal = 500, - far = 1000, - close = 100 - } - self.tank.weapon.is_shotgun_pump.focus_delay = 0.2 - self.tank.weapon.is_shotgun_pump.focus_dis = 200 - self.tank.weapon.is_shotgun_pump.FALLOFF = { - { - dmg_mul = 4.5, - r = 100, - acc = { - 0.95, - 0.95 - }, - recoil = { - 0.8, - 0.8 - }, - mode = { - 1, - 0, - 0, - 0 - } - }, - { - dmg_mul = 4.5, - r = 500, - acc = { - 0.9, - 0.95 - }, - recoil = { - 0.9, - 0.9 - }, - mode = { - 1, - 0, - 0, - 0 - } - }, - { - dmg_mul = 4.5, - r = 1000, - acc = { - 0.6, - 0.8 - }, - recoil = { - 1, - 1 - }, - mode = { - 1, - 0, - 0, - 0 - } - }, - { - dmg_mul = 3, - r = 2000, - acc = { - 0.45, - 0.65 - }, - recoil = { - 1.25, - 1.25 - }, - mode = { - 1, - 0, - 0, - 0 - } - }, - { - dmg_mul = 1.5, - r = 3000, - acc = { - 0.3, - 0.5 - }, - recoil = { - 1.75, - 1.75 - }, - mode = { - 1, - 0, - 0, - 0 - } - } - } + self.drug_lord_boss.HEALTH_INIT = 500 + self.drug_lord_boss.player_health_scaling_mul = 1.5 + self.drug_lord_boss.headshot_dmg_mul = 2 + self.drug_lord_boss.no_headshot_add_mul = false + self.drug_lord_boss.damage.explosion_damage_mul = 0.5 + self.drug_lord_boss.damage.hurt_severity = self.presets.hurt_severities.dozer + self.drug_lord_boss.move_speed = self.presets.move_speed.normal + self.drug_lord_boss.no_run_start = true + self.drug_lord_boss.no_run_stop = true + self.drug_lord_boss.throwable = "launcher_m203" + self.drug_lord_boss.throwable_target_verified = true + self.drug_lord_boss.throwable_cooldown = 10 + self.hector_boss.HEALTH_INIT = 500 + self.hector_boss.player_health_scaling_mul = 1.5 + self.hector_boss.headshot_dmg_mul = 2 + self.hector_boss.no_headshot_add_mul = false + self.hector_boss.damage.explosion_damage_mul = 0.5 + self.hector_boss.damage.hurt_severity = self.presets.hurt_severities.dozer + self.hector_boss.move_speed = self.presets.move_speed.slow + self.hector_boss.no_run_start = true + self.hector_boss.no_run_stop = true + self.hector_boss.throwable = "frag" + self.hector_boss.throwable_cooldown = 15 - -- Skulldozer preset - -- 40 damage - self.tank.weapon.is_lmg.spread = 15 - self.tank.weapon.is_lmg.aim_delay = { - 0.1, - 0.1 - } - self.tank.weapon.is_lmg.autofire_rounds = { - 40, - 80 - } - self.tank.weapon.is_lmg.range = { - optimal = 1500, - far = 2500, - close = 1000 - } - self.tank.weapon.is_lmg.focus_delay = 0.2 - self.tank.weapon.is_lmg.FALLOFF = { - { - dmg_mul = 2, - r = 100, - acc = { - 0.9, - 1 - }, - recoil = { - 0.4, - 0.7 - }, - mode = { - 0, - 0, - 0, - 1 - } - }, - { - dmg_mul = 2, - r = 500, - acc = { - 0.8, - 0.95 - }, - recoil = { - 0.65, - 0.8 - }, - mode = { - 0, - 0, - 0, - 1 - } - }, - { - dmg_mul = 2, - r = 1000, - acc = { - 0.7, - 0.85 - }, - recoil = { - 1, - 1 - }, - mode = { - 0, - 0, - 0, - 1 - } - }, - { - dmg_mul = 2, - r = 2000, - acc = { - 0.45, - 0.6 - }, - recoil = { - 1, - 1 - }, - mode = { - 0, - 0, - 0, - 1 - } - }, - { - dmg_mul = 2, - r = 3000, - acc = { - 0.2, - 0.3 - }, - recoil = { - 1, - 2 - }, - mode = { - 0, - 0, - 0, - 1 - } - } - } + self.mobster_boss.HEALTH_INIT = 500 + self.mobster_boss.player_health_scaling_mul = 1.5 + self.mobster_boss.headshot_dmg_mul = 2 + self.mobster_boss.no_headshot_add_mul = false + self.mobster_boss.damage.explosion_damage_mul = 0.5 + self.mobster_boss.damage.hurt_severity = self.presets.hurt_severities.dozer + self.mobster_boss.move_speed = self.presets.move_speed.fast + self.mobster_boss.no_run_start = true + self.mobster_boss.no_run_stop = true - -- Shield preset - -- 30 damage - self.shield.weapon.is_smg.aim_delay = {0.2, 0.2} - self.shield.weapon.is_smg.focus_delay = 0.2 - self.shield.weapon.is_smg.focus_dis = 200 - self.shield.weapon.is_smg.range = { - optimal = 900, - far = 1750, - close = 500 - } - self.shield.weapon.is_smg.FALLOFF = { - { - r = 100, - acc = {0.9, 0.95}, - dmg_mul = 3, - recoil = {0.35, 0.35}, - mode = { - 0, - 0, - 0, - 1 - }, - autofire_rounds = { - 10, - 15 - } - }, - { - r = 700, - acc = {0.8, 0.8}, - dmg_mul = 3, - recoil = {0.35, 0.55}, - mode = { - 0, - 0, - 0, - 1 - }, - autofire_rounds = { - 8, - 12 - } - }, - { - r = 1000, - acc = {0.6, 0.65}, - dmg_mul = 3, - recoil = {0.35, 0.55}, - mode = { - 0, - 0, - 0, - 1 - }, - autofire_rounds = { - 4, - 7 - } - }, - { - r = 2000, - acc = {0.5, 0.7}, - dmg_mul = 3, - recoil = {0.35, 1}, - mode = { - 0, - 0, - 2, - 3 - }, - autofire_rounds = { - 3, - 5 - } - }, - { - r = 3000, - acc = {0.5, 0.5}, - dmg_mul = 3, - recoil = {0.5, 1.2}, - mode = { - 3, - 2, - 0, - 0 - } - } - } + self.triad_boss.HEALTH_INIT = 500 + self.triad_boss.player_health_scaling_mul = 1.5 + self.triad_boss.headshot_dmg_mul = 2 + self.triad_boss.no_headshot_add_mul = false + self.triad_boss.damage.explosion_damage_mul = 0.5 + self.triad_boss.damage.hurt_severity = self.presets.hurt_severities.dozer + self.triad_boss.move_speed = self.presets.move_speed.slow + self.triad_boss.no_run_start = true + self.triad_boss.no_run_stop = true + self.triad_boss.bullet_damage_only_from_front = nil + self.triad_boss.invulnerable_to_slotmask = nil + self.triad_boss.throwable_target_verified = false + self.triad_boss.throwable_cooldown = 20 - -- Pistol Shield preset - self.shield.weapon.is_pistol.aim_delay = {0.2, 0.2} - self.shield.weapon.is_pistol.focus_delay = 0.2 - self.shield.weapon.is_pistol.range = { - optimal = 900, - far = 1750, - close = 500 - } - self.shield.weapon.is_pistol.FALLOFF = { - { - r = 100, - acc = {0.9, 0.95}, - dmg_mul = 6.75, - recoil = {0.35, 0.35}, - mode = { - 0, - 0, - 0, - 1 - } - }, - { - r = 700, - acc = {0.8, 0.8}, - dmg_mul = 6.75, - recoil = {0.35, 0.55}, - mode = { - 0, - 0, - 0, - 1 - } - }, - { - r = 1000, - acc = {0.6, 0.65}, - dmg_mul = 5, - recoil = {0.35, 0.55}, - mode = { - 0, - 0, - 2, - 1 - } - }, - { - r = 2000, - acc = {0.5, 0.7}, - dmg_mul = 3.5, - recoil = {0.35, 1}, - mode = { - 1, - 2, - 0, - 0 - } - }, - { - r = 3000, - acc = {0.5, 0.5}, - dmg_mul = 1.5, - recoil = {0.5, 1.2}, - mode = { - 1, - 0, - 0, - 0 - } - } - } + self.deep_boss.HEALTH_INIT = 500 + self.deep_boss.player_health_scaling_mul = 1.5 + self.deep_boss.headshot_dmg_mul = 2 + self.deep_boss.no_headshot_add_mul = false + self.deep_boss.ignore_headshot = false + self.deep_boss.damage.explosion_damage_mul = 0.5 + self.deep_boss.damage.hurt_severity = self.presets.hurt_severities.dozer + self.deep_boss.move_speed = self.presets.move_speed.slow + self.deep_boss.no_run_start = true + self.deep_boss.no_run_stop = true + + -- escort speed stuff + self.escort_cfo.move_speed = self.presets.move_speed.escort_normal + self.escort_chinese_prisoner.move_speed = self.presets.move_speed.escort_slow + self.escort_sand.move_speed = self.presets.move_speed.escort_slow + self.spa_vip.move_speed = self.presets.move_speed.escort_normal + self.escort_undercover.move_speed = self.presets.move_speed.escort_slow - -- Taser preset - self.taser.weapon.is_rifle = deep_clone(self.presets.weapon.deathwish.is_rifle) - self.taser.weapon.is_rifle.tase_sphere_cast_radius = 30 - self.taser.weapon.is_rifle.tase_distance = 1500 - self.taser.weapon.is_rifle.aim_delay_tase = {0.2, 0.2} + -- apply weapon presets + self.security.weapon = self.presets.weapon.base + self.cop.weapon = self.presets.weapon.base + self.gangster.weapon = self.presets.weapon.base + self.biker.weapon = self.presets.weapon.base + self.biker_escape.weapon = self.presets.weapon.base + self.triad.weapon = self.presets.weapon.base + self.mobster.weapon = self.presets.weapon.base + + self.swat.weapon = self.presets.weapon.base + self.fbi.weapon = self.presets.weapon.gc + self.fbi_swat.weapon = self.presets.weapon.base + self.fbi_heavy_swat.weapon = self.presets.weapon.base + self.city_swat.weapon = self.presets.weapon.elite + + self.shield.weapon = self.presets.weapon.shield + self.taser.weapon = self.presets.weapon.taser + self.medic.weapon = self.presets.weapon.gc + self.spooc.weapon = self.presets.weapon.elite + self.sniper.weapon = self.presets.weapon.sniper + self.tank.weapon = self.presets.weapon.tank + self.tank_elite.weapon = self.presets.weapon.elite_tank + self.phalanx_minion.weapon = self.presets.weapon.elite_shield + self.phalanx_minion_break.weapon = self.presets.weapon.elite_shield + + self.biker_boss.weapon = self.presets.weapon.elite_tank + self.chavez_boss.weapon = self.presets.weapon.base + self.drug_lord_boss.weapon = self.presets.weapon.base + self.hector_boss.weapon = self.presets.weapon.tank + self.mobster_boss.weapon = self.presets.weapon.elite_tank + self.triad_boss.weapon = self.presets.weapon.base + self.deep_boss.weapon = self.presets.weapon.elite_tank + + self.zeal_swat.weapon = self.presets.weapon.elite + self.zeal_heavy_swat.weapon = self.presets.weapon.gc + self.zeal_shield.weapon = self.presets.weapon.elite_shield + self.zeal_medic.weapon = self.presets.weapon.gc + self.zeal_taser.weapon = self.presets.weapon.taser + + self.biker.melee_weapon = "knife_1" + + -- if bot weapons and equipment is installed and fixed weapon balance is on don't make any further changes + if BotWeapons and BotWeapons.settings and BotWeapons.settings.weapon_balance then + return + end - -- Bosses - self.chavez_boss.weapon.akimbo_pistol.FALLOFF = { - { - dmg_mul = 4, - r = 100, - acc = { - 0.9, - 0.95 - }, - recoil = { - 0.1, - 0.15 - }, - mode = { - 0, - 0, - 0, - 1 - } - }, - { - dmg_mul = 4, - r = 500, - acc = { - 0.7, - 0.85 - }, - recoil = { - 0.1, - 0.15 - }, - mode = { - 0, - 0, - 0, - 1 - } - }, - { - dmg_mul = 4, - r = 1000, - acc = { - 0.6, - 0.75 - }, - recoil = { - 0.15, - 0.2 - }, - mode = { - 0, - 0, - 2, - 1 - } - }, - { - dmg_mul = 4, - r = 2000, - acc = { - 0.3, - 0.5 - }, - recoil = { - 0.175, - 0.25 - }, - mode = { - 3, - 1, - 0, - 0 - } - }, - { - dmg_mul = 4, - r = 3000, - acc = { - 0.1, - 0.3 - }, - recoil = { - 0.35, - 0.45 - }, - mode = { - 1, - 0, - 0, - 0 - } - }, - { - dmg_mul = 4, - r = 4000, - acc = { - 0.0, - 0.2 - }, - recoil = { - 0.5, - 0.75 - }, - mode = { - 1, - 0, - 0, - 0 - } - } - } - self.hector_boss.weapon.is_shotgun_mag = self.tank.weapon.is_shotgun_mag - self.biker_boss.weapon.is_lmg = self.tank.weapon.is_lmg - self.mobster_boss.weapon.is_lmg = self.tank.weapon.is_lmg + self.presets.weapon.gang_member = self.presets.weapon.base +end) - --team ai - self.presets.weapon.gang_member.is_pistol.FALLOFF = { - { - dmg_mul = 10, - r = 300, - acc = { - 1, - 1 - }, - recoil = { - 0.25, - 0.45 - }, - mode = { - 0.1, - 0.3, - 4, - 7 - } - }, - { - dmg_mul = 5, - r = 10000, - acc = { - 1, - 1 - }, - recoil = { - 2, - 3 - }, - mode = { - 0.1, - 0.3, - 4, - 7 - } - } - } - self.presets.weapon.gang_member.is_rifle.FALLOFF = { - { - dmg_mul = 10, - r = 300, - acc = { - 1, - 1 - }, - recoil = { - 0.25, - 0.45 - }, - mode = { - 0.1, - 0.3, - 4, - 7 - } - }, - { - dmg_mul = 5, - r = 10000, - acc = { - 1, - 1 - }, - recoil = { - 2, - 3 - }, - mode = { - 0.1, - 0.3, - 4, - 7 - } - } - } - self.presets.weapon.gang_member.is_sniper.FALLOFF = { - { - dmg_mul = 20, - r = 500, - acc = { - 1, - 1 - }, - recoil = { - 1, - 1 - }, - mode = { - 1, - 0, - 0, - 0 - } - }, - { - dmg_mul = 20, - r = 1000, - acc = { - 1, - 1 - }, - recoil = { - 1, - 1 - }, - mode = { - 1, - 0, - 0, - 0 - } - }, - { - dmg_mul = 20, - r = 2500, - acc = { - 0.95, - 1 - }, - recoil = { - 1, - 1 - }, - mode = { - 1, - 0, - 0, - 0 - } - }, - { - dmg_mul = 10, - r = 4000, - acc = { - 0.9, - 0.95 - }, - recoil = { - 1, - 1 - }, - mode = { - 1, - 0, - 0, - 0 - } - }, - { - dmg_mul = 10, - r = 10000, - acc = { - 0.85, - 0.9 - }, - recoil = { - 1, - 1 - }, - mode = { - 1, - 0, - 0, - 0 - } - } - } - self.presets.weapon.gang_member.is_lmg.FALLOFF = { - { - dmg_mul = 10, - r = 100, - acc = { - 1, - 1 - }, - recoil = { - 0.25, - 0.45 - }, - mode = { - 0, - 0, - 0, - 1 - } - }, - { - dmg_mul = 7.5, - r = 1000, - acc = { - 0.85, - 0.9 - }, - recoil = { - 0.4, - 0.65 - }, - mode = { - 0, - 0, - 0, - 1 - } - }, - { - dmg_mul = 5, - r = 2000, - acc = { - 0.6, - 0.8 - }, - recoil = { - 0.8, - 1.25 - }, - mode = { - 0, - 0, - 0, - 1 - } - }, - { - dmg_mul = 3, - r = 3000, - acc = { - 0.5, - 0.7 - }, - recoil = { - 0.8, - 1.25 - }, - mode = { - 0, - 0, - 0, - 1 - } - }, - { - dmg_mul = 2, - r = 4000, - acc = { - 0.02, - 0.25 - }, - recoil = { - 1, - 2 - }, - mode = { - 0, - 0, - 0, - 1 - } - }, - { - dmg_mul = 0.5, - r = 10000, - acc = { - 0.01, - 0.1 - }, - recoil = { - 2, - 3 - }, - mode = { - 0, - 0, - 0, - 1 - } - } - } - self.presets.weapon.gang_member.is_shotgun_pump.FALLOFF = { - { - dmg_mul = 10, - r = 300, - acc = { - 1, - 1 - }, - recoil = { - 0.25, - 0.45 - }, - mode = { - 0.1, - 0.3, - 4, - 7 - } - }, - { - dmg_mul = 5, - r = 10000, - acc = { - 1, - 1 - }, - recoil = { - 2, - 3 - }, - mode = { - 0.1, - 0.3, - 4, - 7 - } - } - } - self.presets.weapon.gang_member.is_shotgun_mag.FALLOFF = { - { - dmg_mul = 10, - r = 100, - acc = { - 1, - 1 - }, - recoil = { - 0.1, - 0.1 - }, - mode = { - 1, - 1, - 4, - 6 - } - }, - { - dmg_mul = 8, - r = 500, - acc = { - 1, - 1 - }, - recoil = { - 0.1, - 0.1 - }, - mode = { - 1, - 1, - 4, - 5 - } - }, - { - dmg_mul = 7, - r = 1000, - acc = { - 0.85, - 0.95 - }, - recoil = { - 0.1, - 0.15 - }, - mode = { - 1, - 2, - 4, - 4 - } - }, - { - dmg_mul = 5, - r = 2000, - acc = { - 0.75, - 0.9 - }, - recoil = { - 0.25, - 0.45 - }, - mode = { - 1, - 4, - 4, - 1 - } - }, - { - dmg_mul = 2, - r = 3000, - acc = { - 0.4, - 0.7 - }, - recoil = { - 0.4, - 0.5 - }, - mode = { - 4, - 2, - 1, - 0 - } - }, - { - dmg_mul = 0.2, - r = 10000, - acc = { - 0.05, - 0.2 - }, - recoil = { - 0.5, - 1 - }, - mode = { - 2, - 1, - 0, - 0 - } - } - } - self.presets.weapon.gang_member.is_smg = self.presets.weapon.gang_member.is_rifle - self.presets.weapon.gang_member.is_pistol = self.presets.weapon.gang_member.is_pistol - self.presets.weapon.gang_member.is_revolver = self.presets.weapon.gang_member.is_pistol - self.presets.weapon.gang_member.is_rifle = self.presets.weapon.gang_member.is_rifle - self.presets.weapon.gang_member.is_shotgun_pump = self.presets.weapon.gang_member.is_shotgun_pump - self.presets.weapon.gang_member.mac11 = self.presets.weapon.gang_member.is_smg - self.presets.weapon.gang_member.rifle = deep_clone(self.presets.weapon.gang_member.is_rifle) - self.presets.weapon.gang_member.rifle.autofire_rounds = nil - self.presets.weapon.gang_member.akimbo_pistol = self.presets.weapon.gang_member.is_pistol - self.presets.weapon.gang_member.is_shotgun_mag = deep_clone(self.presets.weapon.gang_member.is_shotgun_pump) - - self:_set_characters_weapon_preset("deathwish") - - self.spooc.spooc_attack_timeout = {2, 3} - self.flashbang_multiplier = 1.75 - self.concussion_multiplier = 1 +-- Add new enemies to the character map +local character_map_original = CharacterTweakData.character_map +function CharacterTweakData:character_map(...) + local char_map = character_map_original(self, ...) + + table.insert(char_map.gitgud.list, "ene_zeal_swat_2") + table.insert(char_map.gitgud.list, "ene_zeal_swat_heavy_2") + table.insert(char_map.gitgud.list, "ene_zeal_medic_m4") + table.insert(char_map.gitgud.list, "ene_zeal_medic_r870") + + return char_map end -local _set_overkill_290_orig = CharacterTweakData._set_overkill_290 -function CharacterTweakData:_set_sm_wish() - _set_overkill_290_orig(self) - - -- Eclipse exclusive buffs - self.flashbang_multiplier = 2 - self.city_swat.dodge = self.presets.dodge.ninja - self:_multiply_all_speeds(1.15, 1.075) - -- set specific speeds for tanks and shields - self.tank.move_speed.stand.walk.cbt = {strafe = 186, fwd = 208, bwd = 164} - self.tank.move_speed.stand.run.cbt = {strafe = 355, fwd = 410, bwd = 225} - self.shield.move_speed.crouch.walk.cbt = {strafe = 270, fwd = 300, bwd = 250} - self.shield.move_speed.crouch.run.cbt = {strafe = 300, fwd = 340, bwd = 270} +-- Add new weapons +Hooks:PostHook(CharacterTweakData, "_create_table_structure", "sh__create_table_structure", function(self) + table.insert(self.weap_ids, "shepheard") + table.insert(self.weap_unit_names, Idstring("units/payday2/weapons/wpn_npc_shepheard/wpn_npc_shepheard")) + + table.insert(self.weap_ids, "ksg") + table.insert(self.weap_unit_names, Idstring("units/payday2/weapons/wpn_npc_ksg/wpn_npc_ksg")) +end) + +local function setup_presets(self) + local diff_i = self.tweak_data:difficulty_to_index(Global.game_settings and Global.game_settings.difficulty or "normal") + local f = ((diff_i ^ 2) / (diff_i * 3)) + self:_multiply_all_hp(3, 1.75) + + -- common swat + self.swat.HEALTH_INIT = 30 + self.swat.headshot_dmg_mul = 2.4 -- 125 head health + self.fbi_swat.HEALTH_INIT = 36 + self.fbi_swat.headshot_dmg_mul = 2.25 -- 160 head health + self.fbi_heavy_swat.HEALTH_INIT = 72 + self.fbi_heavy_swat.headshot_dmg_mul = 2.4 -- 300 head health + self.city_swat.HEALTH_INIT = 48 + self.city_swat.headshot_dmg_mul = 2.5 -- 192 head health + + -- specials + self.sniper.HEALTH_INIT = 16 + self.shield.HEALTH_INIT = 45 + self.shield.headshot_dmg_mul = 2.25 -- 200 head health + self.taser.HEALTH_INIT = 96 + self.taser.headshot_dmg_mul = 1.875 -- 512 head health + self.spooc.HEALTH_INIT = 102 + self.spooc.headshot_dmg_mul = 4 -- 255 head health + self.medic.HEALTH_INIT = 72 + self.medic.headshot_dmg_mul = 2 -- 360 head health + self.tank.HEALTH_INIT = 2160 + self.tank.headshot_dmg_mul = 45 -- 480 head health + self.tank_elite.HEALTH_INIT = 2160 + self.tank_elite.headshot_dmg_mul = 45 -- 480 head health + self.phalanx_minion.HEALTH_INIT = 72 + self.phalanx_minion.headshot_dmg_mul = 3 -- 240 head health + + -- zeal team + self.zeal_swat.HEALTH_INIT = 64 + self.zeal_swat.headshot_dmg_mul = 2.5 -- 256 head health + self.zeal_heavy_swat.HEALTH_INIT = 84 + self.zeal_heavy_swat.headshot_dmg_mul = 1.75 -- 480 head health + self.zeal_shield.HEALTH_INIT = 54 + self.zeal_shield.headshot_dmg_mul = 1.8 -- 300 head health + self.zeal_medic.HEALTH_INIT = 84 + self.zeal_medic.headshot_dmg_mul = 2 -- 420 head health + self.zeal_taser.HEALTH_INIT = 120 + self.zeal_taser.headshot_dmg_mul = 1.6 -- 750 head health + + -- misc + self.spooc.spooc_attack_timeout = { 4 / f, 5 / f } + self.taser.weapon.is_rifle.tase_distance = 750 * f + self.taser.weapon.is_rifle.aim_delay_tase = { 0, 0.5 / f } + self.zeal_taser.weapon.is_rifle.tase_distance = 750 * f + self.zeal_taser.weapon.is_rifle.aim_delay_tase = { 0, 0.5 / f } + self.tank_armor_balance_mul = { 2.5 * f, 3.25 * f, 4.25 * f, 5 * f } + self.flashbang_multiplier = 1 * f + self.concussion_multiplier = 1 + + -- team ai health scaling + self.presets.gang_member_damage.HEALTH_INIT = 100 * (f ^ 2) + + -- eclipse exclusive edits + if diff_i == 6 then + self.spooc.spooc_sound_events = { detect_stop = "cloaker_presence_stop", detect = "cloaker_presence_loop" } -- cloakers are silent on eclipse + + self:_multiply_all_speeds(1.15, 1.075) + self.tank.move_speed.stand.walk.cbt = { strafe = 196, fwd = 218, bwd = 174 } + self.tank.move_speed.stand.run.cbt = self.tank_elite.move_speed.stand.walk.cbt + self.tank_elite.move_speed.stand.walk.cbt = { strafe = 216, fwd = 238, bwd = 194 } + self.tank_elite.move_speed.stand.run.cbt = self.tank_elite.move_speed.stand.walk.cbt + self.shield.move_speed.crouch.walk.cbt = { strafe = 270, fwd = 300, bwd = 250 } + self.shield.move_speed.crouch.run.cbt = { strafe = 300, fwd = 340, bwd = 270 } + self.phalanx_minion.move_speed.crouch.walk.cbt = { strafe = 270, fwd = 300, bwd = 250 } + self.phalanx_minion.move_speed.crouch.run.cbt = { strafe = 300, fwd = 340, bwd = 270 } + self.zeal_shield.move_speed.crouch.walk.cbt = { strafe = 290, fwd = 320, bwd = 270 } + self.zeal_shield.move_speed.crouch.run.cbt = { strafe = 320, fwd = 360, bwd = 290 } + end end +CharacterTweakData._set_normal = setup_presets +CharacterTweakData._set_hard = setup_presets +CharacterTweakData._set_overkill = setup_presets +CharacterTweakData._set_overkill_145 = setup_presets +CharacterTweakData._set_easy_wish = setup_presets + -- fixed movement speed difficulty scaling --- thanks redflame for this code from like 6 months ago +-- thanks redflame function CharacterTweakData:_multiply_all_speeds(walk_mul, run_mul) - for preset_name, preset in pairs(self.presets.move_speed) do - if preset_name ~= "civ_fast" and preset_name ~= "escort_slow" and preset_name ~= "escort_normal" then - for _, pose in pairs(preset) do - for haste_name, haste in pairs(pose) do - for stance_name, stance in pairs(haste) do - if stance_name ~= "ntl" then - for move_dir in pairs(stance) do - stance[move_dir] = stance[move_dir] * (haste_name == "walk" and walk_mul or run_mul) - end - end - end - end - end - end - end -end \ No newline at end of file + for preset_name, preset in pairs(self.presets.move_speed) do + if preset_name ~= "civ_fast" and preset_name ~= "escort_slow" and preset_name ~= "escort_normal" then + for _, pose in pairs(preset) do + for haste_name, haste in pairs(pose) do + for stance_name, stance in pairs(haste) do + if stance_name ~= "ntl" then + for move_dir in pairs(stance) do + stance[move_dir] = stance[move_dir] * (haste_name == "walk" and walk_mul or run_mul) + end + end + end + end + end + end + end +end diff --git a/lua/civilianbase.lua b/lua/civilianbase.lua index 55f51cf..4569821 100644 --- a/lua/civilianbase.lua +++ b/lua/civilianbase.lua @@ -1,4 +1,4 @@ -- fixes civilians not derendering on hosts end when they should Hooks:PostHook(CivilianBase, "post_init", "eclipse_post_init", function(self) - self._allow_invisible = true -end) \ No newline at end of file + self._allow_invisible = true +end) diff --git a/lua/civilianbrain.lua b/lua/civilianbrain.lua new file mode 100644 index 0000000..3c8ed51 --- /dev/null +++ b/lua/civilianbrain.lua @@ -0,0 +1,8 @@ +-- Limit logic updates, there's no need to update it every frame +local update_original = CivilianBrain.update +function CivilianBrain:update(unit, t, ...) + if self._next_upd_t <= t then + self._next_upd_t = t + 1 / 30 + return update_original(self, unit, t, ...) + end +end diff --git a/lua/civilianlogicflee.lua b/lua/civilianlogicflee.lua index f38b65a..ee28984 100644 --- a/lua/civilianlogicflee.lua +++ b/lua/civilianlogicflee.lua @@ -1,8 +1,29 @@ --- Only allow hostage rescue if it's part of our tactics (or if we don't have any tactics to allow scripted cop/security spawns to rescue hostages) -local rescue_SO_verification_original = CivilianLogicFlee.rescue_SO_verification +-- Tweak hostage rescue conditions function CivilianLogicFlee.rescue_SO_verification(ignore_this, params, unit, ...) - local logic_data = unit:brain()._logic_data - if not logic_data or not logic_data.tactics or logic_data.tactics.rescue_hostages or logic_data.objective and logic_data.objective.grp_objective == "recon_area" then - return rescue_SO_verification_original(ignore_this, params, unit, ...) + if unit:movement():cool() then + return false end -end \ No newline at end of file + + if not unit:base():char_tweak().rescue_hostages then + return false + end + + local data = params.logic_data + if data.team.foes[unit:movement():team().id] then + return false + end + + local objective = unit:brain():objective() + if not objective or objective.type == "free" or not objective.area then + return true + end + + local nav_seg = data.unit:movement():nav_tracker():nav_segment() + if objective.area.nav_segs[nav_seg] then + return true + end + + if unit:movement():nav_tracker():nav_segment() == nav_seg then + return managers.groupai:state()._rescue_allowed + end +end diff --git a/lua/civillianlogicsurrender.lua b/lua/civillianlogicsurrender.lua new file mode 100644 index 0000000..8af5878 --- /dev/null +++ b/lua/civillianlogicsurrender.lua @@ -0,0 +1,83 @@ +-- Make civilians get down more consistently +-- If we have shouted at them and that shout would intimidate but not make them drop, run the function again after a short delay +Hooks:PostHook(CivilianLogicSurrender, "_delayed_intimidate_clbk", "sh__delayed_intimidate_clbk", function(ignore_this, params) + local data = params[1] + if data.unit:movement():chk_action_forbidden("walk") then + return + end + + local anim_data = data.unit:anim_data() + if anim_data.drop then + return + end + + -- Set amount to 0 for automatic consecutive calls + params[2] = 0 + + local my_data = data.internal_data + my_data.delayed_intimidate_id = "intimidate" .. tostring(data.unit:key()) + CopLogicBase.add_delayed_clbk( + my_data, + my_data.delayed_intimidate_id, + callback(CivilianLogicSurrender, CivilianLogicSurrender, "_delayed_intimidate_clbk", params), + TimerManager:game():time() + 0.1 + math.random() * 0.4 + ) +end) + +-- Fix civs randomly popping up to standing position and rework scared screams +function CivilianLogicSurrender.on_alert(data, alert_data) + local alert_type = alert_data[1] + if alert_type ~= "aggression" and alert_type ~= "bullet" and alert_type ~= "explosion" then + return + end + + local aggressor = alert_data[5] + if not data.is_tied and aggressor and aggressor:base() and CopLogicBase.is_alert_aggressive(alert_type) then + local is_intimidation + if aggressor:base().is_local_player then + is_intimidation = managers.player:has_category_upgrade("player", "civ_calming_alerts") + elseif aggressor:base().is_husk_player then + is_intimidation = aggressor:base():upgrade_value("player", "civ_calming_alerts") + end + + if is_intimidation then + data.brain:on_intimidated(1, aggressor) + return + end + end + + data.t = TimerManager:game():time() + + if not CopLogicBase.is_alert_dangerous(alert_type) then + return + end + + local my_data = data.internal_data + local scare_modifier = data.char_tweak.scare_shot + + local anim_data = data.unit:anim_data() + if anim_data.halt or anim_data.react or anim_data.stand then + scare_modifier = scare_modifier * 4 + end + + my_data.scare_meter = math.min(my_data.scare_max, my_data.scare_meter + scare_modifier) + + if data.is_tied and anim_data.stand then + data.unit:sound():say(math.random() < 0.5 and "a01x_any" or "a02x_any", true) + data.brain:on_hostage_move_interaction(aggressor, "stay") + elseif not my_data.scream_t or data.t < my_data.scream_t and not data.unit:sound():speaking(data.t) then + local alert_dis_sq = mvector3.distance_sq(data.m_pos, alert_data[2]) + local max_scare_dis_sq = 4000000 + + if alert_dis_sq < max_scare_dis_sq then + local dis_mul = math.map_range(alert_dis_sq, 0, max_scare_dis_sq, 1, 0) + local scare_mul = math.map_range(my_data.scare_meter, 0, my_data.scare_max, 0.5, 1) + local time_mul = math.map_range_clamped(my_data.scream_t and data.t - my_data.scream_t or 0, 0, 8, 0.5, 1) + + if math.random() < dis_mul * scare_mul * time_mul then + data.unit:sound():say(math.random() < 0.5 and "a01x_any" or "a02x_any", true) + my_data.scream_t = data.t + 4 + end + end + end +end diff --git a/lua/copactionhurt.lua b/lua/copactionhurt.lua index 2aeac32..3fc5346 100644 --- a/lua/copactionhurt.lua +++ b/lua/copactionhurt.lua @@ -1,13 +1,15 @@ -- Make concussion update function use hurt update (to update position and play the full animation) -Hooks:PostHook(CopActionHurt, "init", "sh_init", function (self) +-- Remove position reservations on death +Hooks:PostHook(CopActionHurt, "init", "sh_init", function(self) if self._hurt_type == "concussion" then self.update = self._upd_hurt + elseif self._hurt_type == "death" and Network:is_server() then + self._unit:brain():rem_all_pos_rsrv() end end) - -- Make sick update finish their hurt exit anims before expiring -Hooks:OverrideFunction(CopActionHurt, "_upd_sick", function (self, t) +Hooks:OverrideFunction(CopActionHurt, "_upd_sick", function(self, t) if self._sick_time then if t > self._sick_time then self._ext_movement:play_redirect("idle") @@ -18,7 +20,6 @@ Hooks:OverrideFunction(CopActionHurt, "_upd_sick", function (self, t) end end) - -- Prevent hurt and knockdown animations stacking, once one plays it needs to finish for another one to trigger local hurt_blocks = { heavy_hurt = true, @@ -27,9 +28,9 @@ local hurt_blocks = { knock_down = true, poison_hurt = true, shield_knock = true, - stagger = true + stagger = true, } -Hooks:OverrideFunction(CopActionHurt, "chk_block", function (self, action_type, t) +Hooks:OverrideFunction(CopActionHurt, "chk_block", function(self, action_type, t) if self._hurt_type == "death" then return true elseif hurt_blocks[action_type] and not self._ext_anim.hurt_exit then @@ -41,4 +42,4 @@ Hooks:OverrideFunction(CopActionHurt, "chk_block", function (self, action_type, end return CopActionAct.chk_block(self, action_type, t) -end) \ No newline at end of file +end) diff --git a/lua/copactionidle.lua b/lua/copactionidle.lua index 0806e70..8cbf42d 100644 --- a/lua/copactionidle.lua +++ b/lua/copactionidle.lua @@ -5,8 +5,23 @@ local mvec3_dir = mvector3.direction local mvec3_dot = mvector3.dot local tmp_rot = Rotation() +-- Helper function to check if turning is allowed +function CopActionIdle:_can_turn() + if self._ext_movement:chk_action_forbidden("turn") then + return + end + + local active_actions = self._common_data.active_actions + if active_actions[1] or active_actions[2] and active_actions[2]:type() ~= "idle" then + return + end + + local queued_actions = self._common_data.queued_actions + return not queued_actions or not queued_actions[1] or not queued_actions[2] +end + -- Enable client turn behavior on host -Hooks:PostHook(CopActionIdle, "init", "sh_init", function (self, action_desc, common_data) +Hooks:PostHook(CopActionIdle, "init", "sh_init", function(self, action_desc, common_data) self._start_fwd = common_data.rot:y() end) @@ -35,7 +50,7 @@ function CopActionIdle:update(t) if prog > 1 then self._look_trans = nil else - local prog_smooth = math.bezier({ 0, 0, 1, 1 }, prog) + local prog_smooth = math.bezier({ 0, 0, 1, 1 }, prog) mrot_set_lookat(tmp_rot, target_vec, math.UP) mrot_slerp(tmp_rot, look_trans.start_rot, tmp_rot, prog_smooth) mrot_y(tmp_rot, target_vec) @@ -56,14 +71,14 @@ function CopActionIdle:update(t) local active_actions = self._common_data.active_actions local queued_actions = self._common_data.queued_actions -- Use the same conditions as in CopActionShoot and reduce the required angle difference - if not active_actions[1] and not active_actions[2] and (not queued_actions or not queued_actions[1] and not queued_actions[2]) then + if self:_can_turn() then local spin = target_vec:to_polar_with_reference(self._common_data.fwd, math.UP).spin if math.abs(spin) > 50 then self._rot_offset = spin self._ext_movement:action_request({ body_part = 2, type = "turn", - angle = spin + angle = spin, }) end end @@ -77,7 +92,7 @@ function CopActionIdle:update(t) self._ext_movement:action_request({ body_part = 2, type = "turn", - angle = self._start_fwd:to_polar_with_reference(self._common_data.fwd, math.UP).spin + angle = self._start_fwd:to_polar_with_reference(self._common_data.fwd, math.UP).spin, }) self._rot_offset = nil end diff --git a/lua/copactionreload.lua b/lua/copactionreload.lua index 1ea2b53..fdcbf51 100644 --- a/lua/copactionreload.lua +++ b/lua/copactionreload.lua @@ -1,5 +1,5 @@ -- Make reload action use proper reload speed and sync it to clients -Hooks:PostHook(CopActionReload, "init", "sh_init", function (self) +Hooks:PostHook(CopActionReload, "init", "sh_init", function(self) -- Don't expire the action based on some arbitrary number, we'll expire based on the actual reload animation self._reload_t = math.huge end) diff --git a/lua/copactionshoot.lua b/lua/copactionshoot.lua index 70a8050..fa1437c 100644 --- a/lua/copactionshoot.lua +++ b/lua/copactionshoot.lua @@ -1,7 +1,4 @@ -local math_clamp = math.clamp local math_lerp = math.lerp -local math_max = math.max -local math_min = math.min local math_random = math.random local mrot_axis_angle = mrotation.set_axis_angle local mvec3_add = mvector3.add @@ -18,7 +15,6 @@ local temp_rot1 = Rotation() local temp_vec1 = Vector3() local temp_vec2 = Vector3() - -- Helper function to reset variables when shooting is stopped function CopActionShoot:_stop_firing() self._is_single_shot = nil @@ -26,54 +22,30 @@ function CopActionShoot:_stop_firing() self._weapon_base:stop_autofire() end - -- Set some values needed for fixed focus and aim delay -Hooks:PostHook(CopActionShoot, "on_attention", "sh_on_attention", function (self) +Hooks:PostHook(CopActionShoot, "on_attention", "sh_on_attention", function(self) -- Stop autofiring on target change if not self._w_usage_tweak.no_autofire_stop then self:_stop_firing() end - -- Reset focus delay on target change - if self._attention and self._attention.unit then - self._shoot_history.focus_start_t = math.max(TimerManager:game():time(), self._shoot_t) - self._shoot_history.focus_delay = self._w_usage_tweak.focus_delay - self._shooting_husk_player = self._attention.unit:base() and self._attention.unit:base().is_husk_player - end -end) - - --- Add a custom callback for the allow fire change, whenever we're allowed to shoot again, --- apply aim and focus delay if sufficient time has passed since we last shot -function CopActionShoot:allow_fire_clbk(state) - if not self._common_data.allow_fire == not state then + if not self._attention or not self._attention.unit then return end + self._shooting_husk_player = self._attention.unit:base() and self._attention.unit:base().is_husk_player + + -- Set aim and focus delay on target change local t = TimerManager:game():time() - if not state then - self._common_data._last_allow_fire_t = t - elseif self._attention and self._attention.unit then - local _, _, target_dis = self:_get_target_pos(self._shoot_from_pos, self._attention, t) - local no_fire_duration = t - (self._common_data._last_allow_fire_t or -100) - - -- Apply aim delay if we haven't shot for more than 2 seconds - if no_fire_duration > 2 then - local aim_delay_minmax = self._w_usage_tweak.aim_delay - local lerp_dis = math_min(1, target_dis / self._falloff[#self._falloff].r) - local aim_delay = math_lerp(aim_delay_minmax[1], aim_delay_minmax[2], lerp_dis) - if self._common_data.is_suppressed then - aim_delay = aim_delay * 1.5 - end - self._shoot_t = math.max(t + aim_delay, self._shoot_t) - end + local _, _, target_dis = self:_get_target_pos(self._shoot_from_pos, self._attention, t) + local aim_delay_minmax = self._w_usage_tweak.aim_delay + local aim_delay = math.map_range_clamped(target_dis, 0, self._falloff[#self._falloff].r, aim_delay_minmax[1], aim_delay_minmax[2]) - -- Reset focus delay when we're allowed to shoot again - self._shoot_history.focus_start_t = math.max(t, self._shoot_t) - self._shoot_history.focus_delay = self._w_usage_tweak.focus_delay - end -end + self._shoot_t = math.max(t + aim_delay * (self._common_data.is_suppressed and 1.5 or 1), self._shoot_t) + self._shoot_history.focus_start_t = self._shoot_t + self._shoot_history.focus_delay = self._w_usage_tweak.focus_delay +end) -- Thanks to the messy implementation of this function, we have to replace it completely, no hook can save us here function CopActionShoot:update(t) @@ -99,93 +71,102 @@ function CopActionShoot:update(t) mvec3_norm(tar_vec_flat) local fwd = self._common_data.fwd local fwd_dot = mvec3_dot(fwd, tar_vec_flat) - local active_actions = self._common_data.active_actions - local queued_actions = self._common_data.queued_actions -- This originally only executed on client side which causes great inconsistencies in enemy turning behaviour -- between host and client. Reworking the turning condition and enabling it for the host too should fix that. - if (not active_actions[2] or active_actions[2]:type() == "idle") and (not queued_actions or not queued_actions[1] and not queued_actions[2]) then + if CopActionIdle._can_turn(self) then local spin = tar_vec_flat:to_polar_with_reference(fwd, math.UP).spin if math.abs(spin) > 25 then self._ext_movement:action_request({ body_part = 2, type = "turn", - angle = spin + angle = spin, }) end end target_vec = self:_upd_ik(target_vec, fwd_dot, t) end - if not ext_anim.reload and not ext_anim.equip and not ext_anim.melee then - if self._weapon_base:clip_empty() then + if ext_anim.base_need_upd then + self._ext_movement:upd_m_head_pos() + end + + if ext_anim.reload or ext_anim.equip or ext_anim.melee then + return + end + + if target_vec and self._common_data.allow_fire and self._shield_use_cooldown and self._shield_use_cooldown < t and target_dis < self._shield_use_range then + self._shield_use_cooldown = self._shield_base:request_use(t) or self._shield_use_cooldown + end + + if self._weapon_base:clip_empty() then + -- Reload + self:_stop_firing() + CopActionReload._play_reload(self) + elseif not target_vec or not self._common_data.allow_fire then + -- Stop shooting + if self._autofiring then self:_stop_firing() - CopActionReload._play_reload(self) - elseif self._autofiring then - if not target_vec or not self._common_data.allow_fire then - self:_stop_firing() - else - local falloff, i_range = self:_get_shoot_falloff(target_dis, self._falloff) - local dmg_buff = self._unit:base():get_total_buff("base_damage") - local dmg_mul = (1 + dmg_buff) * falloff.dmg_mul - - target_pos = self:_get_unit_shoot_pos(t, target_pos, target_dis, self._w_usage_tweak, falloff, i_range, self._shooting_player) - mvec3_dir(target_vec, shoot_from_pos, target_pos) - - -- Pick and run the right shooting function - local fire_func = self._is_single_shot and self._weapon_base.singleshot or self._weapon_base.trigger_held - local fired = fire_func(self._weapon_base, shoot_from_pos, target_vec, dmg_mul, self._shooting_player, nil, nil, nil, self._attention.unit) - - if fired then - self._autofiring = self._autofiring and self._autofiring - 1 or 0 - if self._autofiring <= 0 then - self:_stop_firing() - self._shoot_t = t + (self._common_data.is_suppressed and 1.5 or 1) * math_lerp(falloff.recoil[1], falloff.recoil[2], self:_pseudorandom()) - end - - if vis_state == 1 and not ext_anim.base_no_recoil then - self._ext_movement:play_redirect("recoil_single") - end - - if fired.hit_enemy and fired.hit_enemy.type == "death" and self._unit:unit_data().mission_element then - self._unit:unit_data().mission_element:event("killshot", self._unit) - end - end - end - elseif target_vec and self._common_data.allow_fire and self._shoot_t < t and self._mod_enable_t < t then - local shoot = true - if self._common_data.char_tweak.no_move_and_shoot and ext_anim.move then - shoot = false - self._shoot_t = math_max(self._shoot_t, t + (self._common_data.char_tweak.move_and_shoot_cooldown or 1)) - end + end + elseif self._autofiring then + -- Update shooting + local falloff, i_range = self:_get_shoot_falloff(target_dis, self._falloff) + local dmg_buff = self._unit:base():get_total_buff("base_damage") + local dmg_mul = (1 + dmg_buff) * falloff.dmg_mul + + target_pos = self:_get_unit_shoot_pos(t, target_pos, target_dis, self._w_usage_tweak, falloff, i_range, self._shooting_player) or target_pos + mvec3_dir(target_vec, shoot_from_pos, target_pos) + + -- Pick and run the right shooting function + local fire_func = self._is_single_shot and self._weapon_base.singleshot or self._weapon_base.trigger_held + local fired = fire_func(self._weapon_base, shoot_from_pos, target_vec, dmg_mul, self._shooting_player, nil, nil, nil, self._attention.unit) + if not fired then + return + end + + self._autofiring = self._autofiring and self._autofiring - 1 or 0 + if self._autofiring <= 0 then + self:_stop_firing() + self._shoot_t = t + (self._common_data.is_suppressed and 1.5 or 1) * math_lerp(falloff.recoil[1], falloff.recoil[2], self:_pseudorandom()) + end + + if vis_state == 1 and not ext_anim.base_no_recoil then + self._ext_movement:play_redirect("recoil_single") + end + + if fired.hit_enemy and fired.hit_enemy.type == "death" and self._unit:unit_data().mission_element then + self._unit:unit_data().mission_element:event("killshot", self._unit) + end + elseif self._shoot_t < t and self._mod_enable_t < t then + -- Start shooting + if self._common_data.char_tweak.no_move_and_shoot and ext_anim.move then + self._shoot_t = math.max(self._shoot_t, t + (self._common_data.char_tweak.move_and_shoot_cooldown or 1)) + return + end + + if self:_chk_start_melee(t, target_dis) then + return + end - if shoot and not self:_chk_start_melee(t, target_dis) then - local number_of_rounds = 1 - local falloff = self:_get_shoot_falloff(target_dis, self._falloff) - local autofire_rounds = falloff.autofire_rounds or self._w_usage_tweak.autofire_rounds - if self._automatic_weap then - if falloff.autofire_rounds then - number_of_rounds = self:_pseudorandom(autofire_rounds[1], autofire_rounds[2]) - elseif self._w_usage_tweak.autofire_rounds then - local f = math_clamp((target_dis - self._falloff[1].r) / (self._falloff[#self._falloff].r - self._falloff[1].r) - 0.15 + self:_pseudorandom() * 0.3, 0, 1) - number_of_rounds = math.ceil(math_lerp(autofire_rounds[2], autofire_rounds[1], f)) - end - end - - self._is_single_shot = number_of_rounds == 1 - self._autofiring = number_of_rounds - if number_of_rounds > 1 then - self._weapon_base:start_autofire(number_of_rounds < 4 and number_of_rounds) - end + local num_rounds = 1 + if self._automatic_weap then + local falloff = self:_get_shoot_falloff(target_dis, self._falloff) + if falloff.autofire_rounds then + num_rounds = self:_pseudorandom(falloff.autofire_rounds[1], falloff.autofire_rounds[2]) + elseif self._w_usage_tweak.autofire_rounds then + local f = math.map_range(target_dis, self._falloff[1].r, self._falloff[#self._falloff].r, 0, 1) + local autofire_rounds = self._w_usage_tweak.autofire_rounds + num_rounds = math.ceil(math.map_range_clamped(f - 0.15 + self:_pseudorandom() * 0.3, 0, 1, autofire_rounds[2], autofire_rounds[1])) end end - end - if self._ext_anim.base_need_upd then - self._ext_movement:upd_m_head_pos() + self._is_single_shot = num_rounds == 1 + self._autofiring = num_rounds + if num_rounds > 1 then + self._weapon_base:start_autofire(num_rounds < 4 and num_rounds) + end end end - -- Remove pseudrandom hitchance and hit chance interpolation (interpolation is already done in _get_shoot_falloff) function CopActionShoot:_get_unit_shoot_pos(t, pos, dis, w_tweak, falloff, i_range, shooting_player) local shoot_hist = self._shoot_history @@ -236,12 +217,55 @@ function CopActionShoot:_get_unit_shoot_pos(t, pos, dis, w_tweak, falloff, i_ran return temp_vec2 end +-- Interpolate between entries in the FALLOFF table to prevent sudden changes in damage etc +function CopActionShoot:_get_shoot_falloff(target_dis, falloff) + local i = #falloff + local data = falloff[i] + for i_range, range_data in ipairs(falloff) do + if target_dis < range_data.r then + i = i_range + data = range_data + break + end + end + if i == 1 or target_dis > data.r then + return data, i + else + local prev_data = falloff[i - 1] + local t = (target_dis - prev_data.r) / (data.r - prev_data.r) + local n_data = { + r = target_dis, + dmg_mul = math_lerp(prev_data.dmg_mul, data.dmg_mul, t), + acc = { + math_lerp(prev_data.acc[1], data.acc[1], t), + math_lerp(prev_data.acc[2], data.acc[2], t), + }, + recoil = { + math_lerp(prev_data.recoil[1], data.recoil[1], t), + math_lerp(prev_data.recoil[2], data.recoil[2], t), + }, + autofire_rounds = prev_data.autofire_rounds and data.autofire_rounds and { + math_lerp(prev_data.autofire_rounds[1], data.autofire_rounds[1], t), + math_lerp(prev_data.autofire_rounds[2], data.autofire_rounds[2], t), + }, + mode = data.mode, + } + return n_data, i + end +end + -- Do all the melee related checks inside this function +-- Adjust melee code to work against npcs function CopActionShoot:_chk_start_melee(t, target_dis) if target_dis > 130 or not self._w_usage_tweak.melee_speed then return end + -- Only start melee if target is the local player (or an NPC on the server) + if Network:is_client() and not self._shooting_player or Network:is_server() and self._shooting_husk_player then + return + end + if self._melee_timeout_t > t or self._common_data.melee_countered_t and t - self._common_data.melee_countered_t < 15 then return end @@ -251,14 +275,25 @@ function CopActionShoot:_chk_start_melee(t, target_dis) return end + if not self:_play_melee_anim(t) then + return + end + + self._melee_unit = attention_unit + + self._common_data.ext_network:send("action_melee_attack", self._body_part) + + return true +end + +function CopActionShoot:_play_melee_anim(t) local melee_weapon = self._unit:base():melee_weapon() - local is_weapon = melee_weapon == "weapon" - local state = self._ext_movement:play_redirect(is_weapon and "melee" or "melee_item") + local state = self._ext_movement:play_redirect(melee_weapon == "weapon" and "melee" or melee_weapon == "bash" and "melee_bayonet" or "melee_item") if not state then return end - if not is_weapon then + if melee_weapon ~= "weapon" and melee_weapon ~= "bash" then local anim_attack_vars = self._common_data.char_tweak.melee_anims or { "var1", "var2" } local melee_var = self:_pseudorandom(#anim_attack_vars) self._common_data.machine:set_parameter(state, anim_attack_vars[melee_var], 1) @@ -272,15 +307,15 @@ function CopActionShoot:_chk_start_melee(t, target_dis) local retry_delay = self._w_usage_tweak.melee_retry_delay self._melee_timeout_t = t + (retry_delay and math.lerp(retry_delay[1], retry_delay[2], self:_pseudorandom()) or 1) - -- Set melee unit if we should process damage for it (server and not shooting a client, or client and shooting the local player) - local is_server = Network:is_server() - self._melee_unit = (is_server and not self._shooting_husk_player or not is_server and self._shooting_player) and attention_unit - return true end +function CopActionShoot:sync_start_melee() + if not self._ext_anim.melee then + self:_play_melee_anim(TimerManager:game():time()) + end +end --- Adjust this function to make NPC melee work against other NPCs function CopActionShoot:anim_clbk_melee_strike() if not alive(self._melee_unit) then return @@ -317,11 +352,34 @@ function CopActionShoot:anim_clbk_melee_strike() col_ray = { position = self._shoot_from_pos + fwd * 50, ray = mvector3.copy(target_vec), - body = self._melee_unit:body(0) - } + body = self._melee_unit:body(0), + }, }) - if defense_data == "countered" then + local melee_tweak = tweak_data.weapon.npc_melee[self._unit:base():melee_weapon()] + if melee_tweak and melee_tweak.tase_data then + if self._attention.unit:character_damage().on_non_lethal_electrocution then + if not self._attention.unit:character_damage().can_be_tased or self._attention.unit:character_damage():can_be_tased() then + self._attention.unit:character_damage():on_non_lethal_electrocution(melee_tweak.tase_data.electrocution_time_mul) + end + elseif self._attention.unit:character_damage().damage_tase then + self._attention.unit:character_damage():damage_tase({ + variant = melee_tweak.tase_data.tase_strength or "light", + damage = 0, + attacker_unit = self._unit, + col_ray = { + position = shoot_from_pos + fwd * 50, + ray = mvector3.copy(target_vec), + }, + }) + end + end + + if defense_data ~= "countered" then + if melee_tweak and melee_tweak.additional_impact_sound then + self._unit:sound():play(melee_tweak.additional_impact_sound) + end + else self._common_data.melee_countered_t = TimerManager:game():time() self._unit:character_damage():damage_melee({ damage_effect = 1, @@ -330,10 +388,12 @@ function CopActionShoot:anim_clbk_melee_strike() attacker_unit = self._melee_unit, col_ray = { body = self._unit:body(0), - position = self._common_data.pos + math.UP * 100 + position = self._common_data.pos + math.UP * 100, }, attack_dir = -target_vec:normalized(), - name_id = managers.blackmarket:equipped_melee_weapon() + name_id = managers.blackmarket:equipped_melee_weapon(), }) end -end \ No newline at end of file + + self._melee_unit = nil +end diff --git a/lua/copactiontase.lua b/lua/copactiontase.lua index 58ea8ad..5b5614f 100644 --- a/lua/copactiontase.lua +++ b/lua/copactiontase.lua @@ -1,30 +1,30 @@ -local mvec3_dot = mvector3.dot local mvec3_dir = mvector3.direction +local mvec3_dot = mvector3.dot local mvec3_norm = mvector3.normalize -local temp_vec1 = Vector3() -local temp_vec2 = Vector3() - +local mvec3_set = mvector3.set +local mvec3_set_z = mvector3.set_z +local tmp_vec1 = Vector3() +local tmp_vec2 = Vector3() +local tmp_vec3 = Vector3() -- Make tasers more consistent by allowing to tase through enemies and ignoring attention when already discharging function CopActionTase:on_attention(attention) if not attention then - if self._attention then - if self._tasing_local_unit then - if self._discharging then - self._tasing_local_unit:movement():on_tase_ended() - end - if self._tasing_player then - self._tasing_local_unit:movement():on_targetted_for_attack(false, self._unit) - end + if self._tasing_local_unit then + if self._discharging then + self._tasing_local_unit:movement():on_tase_ended() + end + if self._tasing_player then + self._tasing_local_unit:movement():on_targetted_for_attack(false, self._unit) end - - self._attention = nil - self._tasing_local_unit = nil - self._tasing_player = nil - self._discharging = nil end - self._expired = self._expired or Network:is_server() + self._attention = nil + self._tasing_local_unit = nil + self._tasing_player = nil + self._discharging = nil + + self._expired = Network:is_server() or self._expired self.update = self._upd_empty return elseif self._attention then @@ -37,12 +37,14 @@ function CopActionTase:on_attention(attention) self._weap_tweak = weap_tweak self._w_usage_tweak = weapon_usage_tweak self._falloff = weapon_usage_tweak.FALLOFF + self._tase_distance = weapon_usage_tweak.tase_distance + self._tase_radius = weapon_usage_tweak.tase_sphere_cast_radius self._attention = attention self._line_of_fire_slotmask = managers.slot:get_mask("bullet_blank_impact_targets") local target_pos = attention.handler and attention.handler:get_attention_m_pos() or attention.unit:movement():m_head_pos() local target_vec = target_pos - self._ext_movement:m_head_pos() - local lerp_dis = math.min(1, target_vec:length() / self._w_usage_tweak.tase_distance) + local lerp_dis = math.min(1, target_vec:length() / self._tase_distance) local aim_delay = weapon_usage_tweak.aim_delay_tase or weapon_usage_tweak.aim_delay local shoot_delay = math.lerp(aim_delay[1], aim_delay[2], lerp_dis) @@ -50,6 +52,7 @@ function CopActionTase:on_attention(attention) self._mod_enable_t = TimerManager:game():time() + shoot_delay self._tasing_local_unit = nil self._tasing_player = nil + self.update = nil if Network:is_server() then self._common_data.ext_network:send("action_tase_event", 1) @@ -69,13 +72,11 @@ function CopActionTase:on_attention(attention) end end - -- Helper function -function CopActionTase:_obstructed(from, to) - return self._unit:raycast("ray", from, to, "slot_mask", self._line_of_fire_slotmask, "sphere_cast_radius", self._w_usage_tweak.tase_sphere_cast_radius, "ignore_unit", self._tasing_local_unit, "report") +function CopActionTase.is_obstructed(from, to, slotmask, radius) + return World:raycast("ray", from, to, "slot_mask", slotmask, "sphere_cast_radius", radius, "report") end - -- Fix some general issues with turning function CopActionTase:update(t) if self._expired then @@ -83,13 +84,13 @@ function CopActionTase:update(t) end local shoot_from_pos = self._ext_movement:m_head_pos() - local target_vec = temp_vec1 - local target_pos = temp_vec2 + local target_vec, target_pos, target_vec_flat = tmp_vec1, tmp_vec2, tmp_vec3 self._attention.unit:character_damage():shoot_pos_mid(target_pos) - mvec3_dir(target_vec, shoot_from_pos, target_pos) - local target_vec_flat = target_vec:with_z(0) + local target_dis = mvec3_dir(target_vec, shoot_from_pos, target_pos) + mvec3_set(target_vec_flat, target_vec) + mvec3_set_z(target_vec_flat, 0) mvec3_norm(target_vec_flat) local fwd = self._common_data.fwd @@ -108,16 +109,14 @@ function CopActionTase:update(t) self._machine:allow_modifier(self._modifier_name) end - -- Similar to CopActionShoot, this originally only executed client side, with the addition of not checking for existing turn actions - local active_actions = self._common_data.active_actions - local queued_actions = self._common_data.queued_actions - if (not active_actions[2] or active_actions[2]:type() == "idle") and (not queued_actions or not queued_actions[1] and not queued_actions[2]) then + -- Similar to CopActionShoot, this originally only executed client side, in addition of not checking for existing turn actions + if CopActionIdle._can_turn(self) then local spin = target_vec_flat:to_polar_with_reference(fwd, math.UP).spin if math.abs(spin) > 25 then self._ext_movement:action_request({ type = "turn", body_part = 2, - angle = spin + angle = spin, }) end end @@ -125,54 +124,63 @@ function CopActionTase:update(t) target_vec = nil end - if not self._ext_anim.reload and not self._ext_anim.equip then - if self._discharging then - if not self._tasing_local_unit:movement():tased() or self:_obstructed(shoot_from_pos, target_pos) then - if Network:is_server() then - self._expired = true - else - self._tasing_local_unit:movement():on_tase_ended() - self._attention.unit:movement():on_targetted_for_attack(false, self._unit) - - self._discharging = nil - self._tasing_player = nil - self._tasing_local_unit = nil - self.update = self._upd_empty - end + if self._ext_anim.reload or self._ext_anim.equip then + return + end + + if self._discharging then + if not self._tasing_local_unit:movement():tased() or self.is_obstructed(shoot_from_pos, target_pos, self._line_of_fire_slotmask, self._tase_radius) then + if Network:is_server() then + self._expired = true + else + self._tasing_local_unit:movement():on_tase_ended() + self._attention.unit:movement():on_targetted_for_attack(false, self._unit) + + self._discharging = nil + self._tasing_player = nil + self._tasing_local_unit = nil + self.update = self._upd_empty + end + end + elseif target_vec and self._common_data.allow_fire and self._shoot_t and self._shoot_t < t and self._mod_enable_t < t then + if self._tase_effect then + World:effect_manager():fade_kill(self._tase_effect) + end + + self._tase_effect = World:effect_manager():spawn({ + force_synch = true, + effect = Idstring("effects/payday2/particles/character/taser_thread"), + parent = self._ext_inventory:equipped_unit():get_object(Idstring("fire")), + }) + + if self._tasing_local_unit then + if target_dis > self._tase_distance then + return end - elseif self._shoot_t and target_vec and self._common_data.allow_fire and self._shoot_t < t and self._mod_enable_t < t then - if self._tase_effect then - World:effect_manager():fade_kill(self._tase_effect) + + local record = managers.groupai:state():criminal_record(self._tasing_local_unit:key()) + if not record or record.status or self._tasing_local_unit:movement():chk_action_forbidden("hurt") or self._tasing_local_unit:movement():zipline_unit() then + self._expired = Network:is_server() or self._expired + return end - self._tase_effect = World:effect_manager():spawn({ - force_synch = true, - effect = Idstring("effects/payday2/particles/character/taser_thread"), - parent = self._ext_inventory:equipped_unit():get_object(Idstring("fire")) - }) - - if self._tasing_local_unit and mvector3.distance(shoot_from_pos, target_pos) < self._w_usage_tweak.tase_distance then - local record = managers.groupai:state():criminal_record(self._tasing_local_unit:key()) - if not record or record.status or self._tasing_local_unit:movement():chk_action_forbidden("hurt") or self._tasing_local_unit:movement():zipline_unit() then - self._expired = Network:is_server() - elseif not self:_obstructed(shoot_from_pos, target_pos) then - self._common_data.ext_network:send("action_tase_event", 3) - - self._attention.unit:character_damage():damage_tase({ attacker_unit = self._unit }) - CopDamage._notify_listeners("on_criminal_tased", self._unit, self._attention.unit) - - if not self._tasing_local_unit:base().is_local_player then - self._tasered_sound = self._unit:sound():play("tasered_3rd", nil) - end - self._ext_movement:play_redirect("recoil_auto") - self._shoot_t = nil - self._discharging = true - end - elseif not self._tasing_local_unit then - self._tasered_sound = self._unit:sound():play("tasered_3rd", nil) - self._ext_movement:play_redirect("recoil_auto") - self._shoot_t = nil + if self.is_obstructed(shoot_from_pos, target_pos, self._line_of_fire_slotmask, self._tase_radius) then + return end + + self._common_data.ext_network:send("action_tase_event", 3) + + self._tasing_local_unit:character_damage():damage_tase({ attacker_unit = self._unit }) + CopDamage._notify_listeners("on_criminal_tased", self._unit, self._tasing_local_unit) + + self._tasered_sound = not self._tasing_player and self._unit:sound():play("tasered_3rd", nil) + self._ext_movement:play_redirect("recoil_auto") + self._shoot_t = nil + self._discharging = true + else + self._tasered_sound = self._unit:sound():play("tasered_3rd", nil) + self._ext_movement:play_redirect("recoil_auto") + self._shoot_t = nil end end end diff --git a/lua/copactionwalk.lua b/lua/copactionwalk.lua index 6f72d17..623391f 100644 --- a/lua/copactionwalk.lua +++ b/lua/copactionwalk.lua @@ -1,21 +1,8 @@ --- Fix pathing start position (should always be our current position) -if Network:is_server() then - Hooks:PreHook(CopActionWalk, "init", "sh_init", function (self, action_desc, common_data) - local pos = common_data.pos - local from_pos = action_desc.nav_path[1] - if pos.x ~= from_pos.x or pos.y ~= from_pos.y then - table.insert(action_desc.nav_path, 1, mvector3.copy(common_data.pos)) - end - end) -end - - -- Helper function to get the final path position function CopActionWalk:get_destination_pos() return self._nav_point_pos(self._simplified_path and self._simplified_path[#self._simplified_path] or self._nav_path and self._nav_path[#self._nav_path]) end - -- Fix occasional incorrect animation speed local idstr_base = Idstring("base") function CopActionWalk:_adjust_move_anim(side, speed) @@ -31,7 +18,12 @@ function CopActionWalk:_adjust_move_anim(side, speed) if move_side and (side == move_side or self._matching_walk_anims[side][move_side]) then local seg_rel_t = self._machine:segment_relative_time(idstr_base) - if not self._walk_anim_lengths[anim_data.pose] or not self._walk_anim_lengths[anim_data.pose][self._stance.name] or not self._walk_anim_lengths[anim_data.pose][self._stance.name][speed] or not self._walk_anim_lengths[anim_data.pose][self._stance.name][speed][side] then + if + not self._walk_anim_lengths[anim_data.pose] + or not self._walk_anim_lengths[anim_data.pose][self._stance.name] + or not self._walk_anim_lengths[anim_data.pose][self._stance.name][speed] + or not self._walk_anim_lengths[anim_data.pose][self._stance.name][speed][side] + then return end @@ -46,4 +38,38 @@ function CopActionWalk:_adjust_move_anim(side, speed) end return redir_res -end \ No newline at end of file +end + +-- Fix navlink rubberbanding for clients +-- This function uses CopActionAct._get_act_index to determine the navlink action that is synced to clients +-- By temporarily replacing it with a function that returns 0, clients will not treat this walk action start as a navlink +-- The navlink animation is later synced when it actually happens +local _init_original = CopActionWalk._init +local _get_act_index_temp = function() + return 0 +end +function CopActionWalk:_init(...) + local _get_act_index = CopActionAct._get_act_index + CopActionAct._get_act_index = _get_act_index_temp + + local result = _init_original(self, ...) + + CopActionAct._get_act_index = _get_act_index + + self._nav_link_synched_with_start = nil + + return result +end + +if Network:is_client() then + return +end + +-- Fix pathing start position (should always be our current position) +Hooks:PreHook(CopActionWalk, "init", "sh_init", function(self, action_desc, common_data) + local pos = common_data.pos + local from_pos = action_desc.nav_path[1] + if pos.x ~= from_pos.x or pos.y ~= from_pos.y then + table.insert(action_desc.nav_path, 1, mvector3.copy(common_data.pos)) + end +end) diff --git a/lua/copbase.lua b/lua/copbase.lua index 460f5fd..7256e0b 100644 --- a/lua/copbase.lua +++ b/lua/copbase.lua @@ -1,13 +1,13 @@ -- Dynamically load throwable if we have one local unit_ids = Idstring("unit") -Hooks:PostHook(CopBase, "init", "sh_init", function (self) +Hooks:PostHook(CopBase, "init", "sh_init", function(self) local throwable = self._char_tweak.throwable if not throwable then return end local tweak_entry = tweak_data.blackmarket.projectiles[throwable] - local unit_name = Idstring(not Network:is_server() and tweak_entry.local_unit or tweak_entry.unit) + local unit_name = Idstring(Network:is_client() and tweak_entry.local_unit or tweak_entry.unit) local sprint_unit_name = tweak_entry.sprint_unit and Idstring(tweak_entry.sprint_unit) if not PackageManager:has(unit_ids, unit_name) then @@ -19,4 +19,41 @@ Hooks:PostHook(CopBase, "init", "sh_init", function (self) StreamHeist:log("Loading projectile sprint unit", throwable) managers.dyn_resource:load(unit_ids, sprint_unit_name, managers.dyn_resource.DYN_RESOURCES_PACKAGE) end -end) \ No newline at end of file +end) + +-- fix yufu wang hitbox +Hooks:PostHook(CopBase, "post_init", "hitbox_fix_post_init", function(self) + if self._tweak_table == "triad_boss" then + self._unit:body("head"--[[self._unit:character_damage()._head_body_name--]]):set_sphere_radius(15) + self._unit:body("body"):set_sphere_radius(22) + + self._unit:body("rag_LeftArm"):set_enabled(true) + self._unit:body("rag_LeftForeArm"):set_enabled(true) + + self._unit:body("rag_RightArm"):set_enabled(true) + self._unit:body("rag_RightForeArm"):set_enabled(true) + + self._unit:body("rag_LeftArm"):set_sphere_radius(11) + self._unit:body("rag_LeftForeArm"):set_sphere_radius(7) + self._unit:body("rag_RightArm"):set_sphere_radius(11) + self._unit:body("rag_RightForeArm"):set_sphere_radius(7) + + self._unit:body("rag_LeftUpLeg"):set_sphere_radius(10) + self._unit:body("rag_LeftLeg"):set_sphere_radius(7) + self._unit:body("rag_RightUpLeg"):set_sphere_radius(10) + self._unit:body("rag_RightLeg"):set_sphere_radius(7) + end +end) + +-- Check for weapon changes +if Network:is_client() then + return +end + +local weapon_mapping = StreamHeist:require("unit_weapons") +Hooks:PreHook(CopBase, "post_init", "sh_post_init", function(self) + self._default_weapon_id = weapon_mapping[self._unit:name():key()] or self._default_weapon_id + if type(self._default_weapon_id) == "table" then + self._default_weapon_id = table.random(self._default_weapon_id) + end +end) diff --git a/lua/copbrain.lua b/lua/copbrain.lua index 5c3c9ac..7667fbc 100644 --- a/lua/copbrain.lua +++ b/lua/copbrain.lua @@ -1,10 +1,18 @@ --- Set up boss logics +-- Set up logics and needed data CopBrain._logic_variants.mobster_boss = CopBrain._logic_variants.triad_boss CopBrain._logic_variants.chavez_boss = CopBrain._logic_variants.triad_boss CopBrain._logic_variants.hector_boss = CopBrain._logic_variants.triad_boss CopBrain._logic_variants.drug_lord_boss = CopBrain._logic_variants.triad_boss CopBrain._logic_variants.biker_boss = CopBrain._logic_variants.triad_boss +CopBrain._logic_variants.tank_elite = CopBrain._logic_variants.tank +CopBrain._logic_variants.phalanx_minion_break = CopBrain._logic_variants.city_swat +CopBrain._logic_variants.zeal_swat = CopBrain._logic_variants.city_swat +CopBrain._logic_variants.zeal_heavy_swat = CopBrain._logic_variants.city_swat +CopBrain._logic_variants.zeal_medic = CopBrain._logic_variants.city_swat +CopBrain._logic_variants.zeal_taser = CopBrain._logic_variants.taser +CopBrain._logic_variants.zeal_shield = clone(CopBrain._logic_variants.shield) +CopBrain._next_upd_t = 0 -- Fix spamming of grenades by units that dodge with grenades (Cloaker) local _chk_use_cover_grenade_original = CopBrain._chk_use_cover_grenade @@ -14,23 +22,63 @@ function CopBrain:_chk_use_cover_grenade(...) end end - --- Don't trigger damage callback from poison damage as it would make enemies go into shoot action --- when they stand inside a poison cloud, regardless of any targets being visible or not +-- Don't trigger damage callback from dot damage as it would make enemies go into shoot action +-- when they stand inside a poison cloud or molotov, regardless of any targets being visible or not local clbk_damage_original = CopBrain.clbk_damage function CopBrain:clbk_damage(my_unit, damage_info, ...) - return damage_info.variant ~= "poison" and clbk_damage_original(self, my_unit, damage_info, ...) + if damage_info.variant ~= "poison" and not damage_info.is_fire_dot_damage and not damage_info.is_molotov then + return clbk_damage_original(self, my_unit, damage_info, ...) + end end +-- Set Joker owner to keep follow objective correct +Hooks:PreHook(CopBrain, "convert_to_criminal", "sh_convert_to_criminal", function(self, mastermind_criminal) + self._logic_data.minion_owner = mastermind_criminal or managers.player:local_player() +end) + +-- Make surrender window slightly shorter and less random +Hooks:OverrideFunction(CopBrain, "on_surrender_chance", function(self) + local t = TimerManager:game():time() + + if self._logic_data.surrender_window then + self._logic_data.surrender_window.expire_t = t + self._logic_data.surrender_window.timeout_duration + self._logic_data.surrender_window.chance_mul = self._logic_data.surrender_window.chance_mul ^ 0.8 + managers.enemy:reschedule_delayed_clbk(self._logic_data.surrender_window.expire_clbk_id, self._logic_data.surrender_window.expire_t) + return + end + + -- Give between 2 and 3 extra shout interactions after the first + local window_duration = tweak_data.player.movement_state.interaction_delay * math.rand(2.5, 3.5) + local timeout_duration = math.rand(4, 8) + local expire_clbk_id = "CopBrain_sur_op" .. tostring(self._unit:key()) + self._logic_data.surrender_window = { + chance_mul = 0.05, + expire_clbk_id = expire_clbk_id, + window_expire_t = t + window_duration, + expire_t = t + window_duration + timeout_duration, + window_duration = window_duration, + timeout_duration = timeout_duration, + } + + managers.enemy:add_delayed_clbk(expire_clbk_id, callback(self, self, "clbk_surrender_chance_expired"), self._logic_data.surrender_window.expire_t) +end) + +-- Limit logic updates, there's no need to update it every frame +local update_original = CopBrain.update +function CopBrain:update(unit, t, ...) + if self._next_upd_t <= t then + self._next_upd_t = t + 1 / 30 + return update_original(self, unit, t, ...) + end +end -- If Iter is installed and streamlined path option is used, don't make any further changes if Iter and Iter.settings and Iter.settings.streamline_path then return end - -- Call pathing results callback in logic if it exists -Hooks:PostHook(CopBrain, "clbk_pathing_results", "sh_clbk_pathing_results", function (self) +Hooks:PostHook(CopBrain, "clbk_pathing_results", "sh_clbk_pathing_results", function(self) local current_logic = self._current_logic if current_logic.on_pathing_results then current_logic.on_pathing_results(self._logic_data) diff --git a/lua/copdamage.lua b/lua/copdamage.lua index d473bd1..6749bfa 100644 --- a/lua/copdamage.lua +++ b/lua/copdamage.lua @@ -3,6 +3,14 @@ -- Increasing the health granularity makes damage dealt more accurate to the actual weapon damage stats CopDamage._HEALTH_GRANULARITY = 8192 +-- Fixed critical hit mul and additional crit damage upgrade +function CopDamage:roll_critical_hit(attack_data) + if not self:can_be_critical(attack_data) or math.random() >= managers.player:critical_hit_chance() then + return false, attack_data.damage + end + + return true, attack_data.damage * (2 + managers.player:upgrade_value("weapon", "extra_crit_damage_mul", 0)) +end -- Make these functions check that the attacker unit is a player (to make sure NPC vs NPC melee doesn't crash) local _dismember_condition_original = CopDamage._dismember_condition @@ -25,27 +33,17 @@ function CopDamage:build_suppression(amount, ...) return build_suppression_original(self, amount == "max" and 2 or amount, ...) end --- Consistent crit damage (2x) (SHC) -function CopDamage:roll_critical_hit(attack_data) - if not self:can_be_critical(attack_data) or math.random() >= managers.player:critical_hit_chance() then - return false, attack_data.damage - end - - return true, attack_data.damage * 2 -end - -- Give flamethrowers a damage multiplier against dozers Hooks:PreHook(CopDamage, "damage_fire", "eclipse_damage_fire", function(self, attack_data) - if self._unit:base()._tweak_table == "tank" then - attack_data.damage = attack_data.damage * 2.5 - end + if self._unit:base()._tweak_table == "tank" then + attack_data.damage = attack_data.damage * 2.5 + end end) -- Counter strike aced stuns dozers local mvec_1 = Vector3() local mvec_2 = Vector3() -Hooks:OverrideFunction(CopDamage, "damage_melee", -function(self, attack_data) +Hooks:OverrideFunction(CopDamage, "damage_melee", function(self, attack_data) if self._dead or self._invulnerable then return end @@ -62,8 +60,8 @@ function(self, attack_data) local is_civlian = CopDamage.is_civilian(self._unit:base()._tweak_table) local is_gangster = CopDamage.is_gangster(self._unit:base()._tweak_table) local is_cop = not is_civlian and not is_gangster - local is_tank = is_cop and self._unit:base()._tweak_table == "tank" - local has_tank_knockdown = managers.player:has_enabled_cooldown_upgrade("cooldown", "counter_strike_eclipse") + local is_tank = is_cop and (self._unit:base()._tweak_table == "tank" or self._unit:base()._tweak_table == "tank_elite") + local has_tank_knockdown = managers.player:has_enabled_cooldown_upgrade("cooldown", "counter_strike_eclipse") local head = self._head_body_name and attack_data.col_ray.body and attack_data.col_ray.body:name() == self._ids_head_body_name local damage = attack_data.damage @@ -106,14 +104,14 @@ function(self, attack_data) if self:check_medic_heal() then result = { type = "healed", - variant = attack_data.variant + variant = attack_data.variant, } else damage_effect_percent = 1 attack_data.damage = self._health result = { type = "death", - variant = attack_data.variant + variant = attack_data.variant, } self:die(attack_data) @@ -124,15 +122,20 @@ function(self, attack_data) damage_effect = math.clamp(damage_effect, self._HEALTH_INIT_PRECENT, self._HEALTH_INIT) damage_effect_percent = math.ceil(damage_effect / self._HEALTH_INIT_PRECENT) damage_effect_percent = math.clamp(damage_effect_percent, 1, self._HEALTH_GRANULARITY) - local result_type = attack_data.shield_knock and self._char_tweak.damage.shield_knocked and "shield_knock" or attack_data.variant == "counter_tased" and "counter_tased" or attack_data.variant == "taser_tased" and "taser_tased" or attack_data.variant == "counter_spooc" and "expl_hurt" or self:get_damage_type(damage_effect_percent, "melee") or "fire_hurt" + local result_type = attack_data.shield_knock and self._char_tweak.damage.shield_knocked and "shield_knock" + or attack_data.variant == "counter_tased" and "counter_tased" + or attack_data.variant == "taser_tased" and "taser_tased" + or attack_data.variant == "counter_spooc" and "expl_hurt" + or self:get_damage_type(damage_effect_percent, "melee") + or "fire_hurt" -- If the player has counter strike aced and the target is a dozer, stun them - if is_tank and has_tank_knockdown then - result_type = "expl_hurt" + if is_tank and has_tank_knockdown then + result_type = "expl_hurt" managers.player:disable_cooldown_upgrade("cooldown", "counter_strike_eclipse") - end - result = { + end + result = { type = result_type, - variant = attack_data.variant + variant = attack_data.variant, } self:_apply_damage_to_health(damage) @@ -156,7 +159,7 @@ function(self, attack_data) head_shot = head, weapon_unit = attack_data.weapon_unit, name_id = attack_data.name_id, - variant = attack_data.variant + variant = attack_data.variant, } managers.statistics:killed_by_anyone(data) @@ -219,4 +222,4 @@ function(self, attack_data) self:_on_damage_received(attack_data) return result -end) \ No newline at end of file +end) diff --git a/lua/copinventory.lua b/lua/copinventory.lua index 19bec6e..3e2e96d 100644 --- a/lua/copinventory.lua +++ b/lua/copinventory.lua @@ -1,7 +1,27 @@ --- Add left hand align place for akimbo weapons -Hooks:PostHook(CopInventory, "init", "sh_init", function (self) +Hooks:PostHook(CopInventory, "init", "eclipse_init", function(self) + -- Add left hand align place for akimbo weapons self._align_places.left_hand = self._align_places.left_hand or { on_body = true, - obj3d_name = Idstring("a_weapon_left_front") + obj3d_name = Idstring("a_weapon_left_front"), } + + -- tweak table switch for shield break + if self._unit:base()._tweak_table == "phalanx_minion" then + self._shield_break_data = { + anim_global_switch = "cop", + tweak_table_name_switch = "phalanx_minion_break", + hurt_data = { hurt_type = "hurt" }, + } + end end) + +-- always glow cloakers by jarey +local _f_add_unit_by_name = CopInventory.add_unit_by_name +function CopInventory:add_unit_by_name(...) + _f_add_unit_by_name(self, ...) + if self._unit:base()._tweak_table == "spooc" and self._unit:damage() then + if self._unit:damage():has_sequence("turn_on_spook_lights") then + self._unit:damage():run_sequence_simple("turn_on_spook_lights") + end + end +end diff --git a/lua/coplogicarrest.lua b/lua/coplogicarrest.lua index cd43350..d9b33da 100644 --- a/lua/coplogicarrest.lua +++ b/lua/coplogicarrest.lua @@ -1,4 +1,4 @@ -- Stop shooting when entering arrest logic -Hooks:PostHook(CopLogicArrest, "enter", "sh_enter", function (data) +Hooks:PostHook(CopLogicArrest, "enter", "sh_enter", function(data) data.unit:movement():set_allow_fire(false) end) diff --git a/lua/coplogicattack.lua b/lua/coplogicattack.lua index ba49472..44b1b87 100644 --- a/lua/coplogicattack.lua +++ b/lua/coplogicattack.lua @@ -1,7 +1,10 @@ +local tmp_vec1 = Vector3() +local tmp_vec2 = Vector3() +local tmp_vec3 = Vector3() + -- Reuse function of idle logic to make enemies in an area aware of a player entering the area CopLogicAttack.on_area_safety = CopLogicIdle.on_area_safety - -- Remove some of the strict conditions for enemies shooting while on the move -- This will result in enemies opening fire more likely while moving -- Also greatly simplified the function @@ -26,16 +29,16 @@ function CopLogicAttack._upd_aim(data, my_data) end if not my_data.shooting and not my_data.spooc_attack and not data.unit:anim_data().reload and not data.unit:movement():chk_action_forbidden("action") then - my_data.shooting = data.unit:brain():action_request({ + my_data.shooting = data.brain:action_request({ body_part = 3, - type = "shoot" + type = "shoot", }) end else if my_data.shooting then - data.unit:brain():action_request({ + data.brain:action_request({ body_part = 3, - type = "idle" + type = "idle", }) end @@ -55,37 +58,46 @@ function CopLogicAttack._check_aim_shoot(data, my_data, focus_enemy, verified, n end local advancing = my_data.advancing and not my_data.advancing:stopping() - local running = data.unit:anim_data().run or advancing and my_data.advancing._cur_vel and my_data.advancing._cur_vel > 300 - local time_since_verification = focus_enemy.verified_t and data.t - focus_enemy.verified_t or math.huge - local weapon_range = data.internal_data.weapon_range or { close = 1000, far = 4000 } + local running = data.unit:anim_data().run or advancing and my_data.advancing:haste() == "run" + local verified_dt = focus_enemy.verified_t and data.t - focus_enemy.verified_t or math.huge + local weapon_range = my_data.weapon_range or { close = 750, optimal = 1500, far = 3000 } local firing_range = running and weapon_range.close or weapon_range.far + local enemy_dis = focus_enemy.verified_dis + local pushing = data.group and data.group.objective and (data.group.objective.open_fire or data.group.objective.moving_in) - local aim = not advancing or time_since_verification < math.lerp(4, 1, focus_enemy.verified_dis / firing_range) or focus_enemy.verified_dis < 800 or data.char_tweak.always_face_enemy - local shoot = aim and my_data.shooting and AIAttentionObject.REACT_SHOOT <= focus_enemy.reaction and time_since_verification < (running and 2 or 4) - local expected_pos = not shoot and focus_enemy.verified_dis < weapon_range.close and focus_enemy.m_head_pos or focus_enemy.last_verified_pos or focus_enemy.verified_pos + local expected_pos + local aim = data.char_tweak.always_face_enemy or not advancing or pushing or verified_dt < math.map_range(enemy_dis, 0, firing_range, 6, 3) + local shoot = aim and my_data.shooting and AIAttentionObject.REACT_SHOOT <= focus_enemy.reaction and verified_dt < (running and 2 or 4) if verified or nearly_visible then if not shoot and AIAttentionObject.REACT_SHOOT <= focus_enemy.reaction then - local last_sup_t = data.unit:character_damage():last_suppression_t() + local suppression_t = data.unit:character_damage():last_suppression_t() + local suppression_dt = suppression_t and data.t - suppression_t + local assault_dt = focus_enemy.criminal_record and focus_enemy.criminal_record.assault_t and data.t - focus_enemy.criminal_record.assault_t - if last_sup_t and data.t - last_sup_t < 7 * (running and 0.5 or 1) * (verified and 1 or 0.5) then + if suppression_dt and suppression_dt < 7 * (running and 0.5 or 1) * (verified and 1 or 0.5) then shoot = true - elseif verified and focus_enemy.verified_dis < firing_range then + elseif verified and enemy_dis < firing_range then shoot = true - elseif verified and focus_enemy.criminal_record and focus_enemy.criminal_record.assault_t and data.t - focus_enemy.criminal_record.assault_t < (running and 2 or 4) then + elseif verified and assault_dt and assault_dt < (running and 2 or 4) then shoot = true - elseif my_data.attitude == "engage" and my_data.firing and time_since_verification < 4 then + elseif my_data.attitude == "engage" and my_data.firing and verified_dt < 4 then shoot = true end end - aim = aim or shoot or focus_enemy.verified_dis < firing_range + aim = aim or shoot or enemy_dis < firing_range + else + if not shoot and focus_enemy.criminal_record then + expected_pos = focus_enemy.criminal_record.pos + else + expected_pos = focus_enemy.last_verified_pos or focus_enemy.verified_pos + end end return aim, shoot, expected_pos end - -- Pathing related fixes to stop spamming walk actions when the new position is the same as the current position local _find_retreat_position_original = CopLogicAttack._find_retreat_position function CopLogicAttack._find_retreat_position(from_pos, ...) @@ -99,8 +111,8 @@ function CopLogicAttack._chk_start_action_move_out_of_the_way(data, my_data) local from_pos = data.m_pos local reservation = { radius = 30, - position = from_pos, - filter = data.pos_rsrv_id + position = from_pos, + filter = data.pos_rsrv_id, } if managers.navigation:is_pos_free(reservation) then return @@ -113,7 +125,7 @@ function CopLogicAttack._chk_start_action_move_out_of_the_way(data, my_data) local path = { mvector3.copy(from_pos), - to_pos + to_pos, } CopLogicAttack._chk_request_action_walk_to_cover_shoot_pos(data, my_data, path, "run") end @@ -121,85 +133,223 @@ end -- Empty this function (path starting position is corrected in CopActionWalk as it covers all cases) function CopLogicAttack._correct_path_start_pos() end +-- Simplify function, navigation raycast is already done in CopLogicAttack._find_retreat_position +function CopLogicAttack._confirm_retreat_position(retreat_pos, threat_pos, threat_head_pos, threat_tracker) + mvector3.set(tmp_vec1, retreat_pos) + mvector3.set_z(tmp_vec1, retreat_pos.z + 140) + if not World:raycast("ray", tmp_vec1, threat_head_pos, "slot_mask", managers.slot:get_mask("AI_visibility"), "ray_type", "ai_vision") then + return retreat_pos + end +end -- Make moving back during combat depend on weapon range -function CopLogicAttack._chk_start_action_move_back(data, my_data, focus_enemy, engage) - local weapon_range = my_data.weapon_range or { close = 500 } - local close_range = weapon_range.close * 0.5 - if focus_enemy and focus_enemy.nav_tracker and focus_enemy.verified and focus_enemy.dis < close_range and CopLogicAttack._can_move(data) then - local from_pos = mvector3.copy(data.m_pos) - local threat_tracker = focus_enemy.nav_tracker - local threat_head_pos = focus_enemy.m_head_pos - local retreat_to = CopLogicAttack._find_retreat_position(from_pos, focus_enemy.m_pos, threat_head_pos, threat_tracker, 400, engage) - - if retreat_to then - CopLogicAttack._cancel_cover_pathing(data, my_data) - - my_data.advancing = data.unit:brain():action_request({ - type = "walk", - variant = "walk", - body_part = 2, - nav_path = { - from_pos, - retreat_to - } - }) +function CopLogicAttack._chk_start_action_move_back(data, my_data, focus_enemy, engage, range) + if not focus_enemy or not focus_enemy.nav_tracker or not focus_enemy.verified then + return + end - if my_data.advancing then - my_data.surprised = true - return true - end - end + local close_range = (my_data.weapon_range and (my_data.weapon_range[range] or my_data.weapon_range.close) or 800) * 0.5 + if focus_enemy.dis > close_range or not CopLogicAttack._can_move(data) then + return end -end + local from_pos = mvector3.copy(data.m_pos) + local threat_tracker = focus_enemy.nav_tracker + local threat_head_pos = focus_enemy.m_head_pos + local retreat_to = CopLogicAttack._find_retreat_position(from_pos, focus_enemy.m_pos, threat_head_pos, threat_tracker, close_range, engage) + if not retreat_to then + return + end --- Fix reinforce groups relocating due to using covers outside of their nav segments -local _update_cover_original = CopLogicAttack._update_cover -function CopLogicAttack._update_cover(data, ...) - if not data.objective or not data.objective.grp_objective or data.objective.grp_objective.type ~= "reenforce_area" then - return _update_cover_original(data, ...) + CopLogicAttack._cancel_cover_pathing(data, my_data) + + my_data.advancing = data.brain:action_request({ + type = "walk", + variant = "walk", + body_part = 2, + nav_path = { + from_pos, + retreat_to, + }, + }) + + if my_data.advancing then + my_data.surprised = true + return true end +end +-- Improve cover update +function CopLogicAttack._update_cover(data) local my_data = data.internal_data local best_cover = my_data.best_cover + local focus_enemy = data.attention_obj + local objective = data.objective - my_data.flank_cover = nil - - if not data.attention_obj or data.attention_obj.reaction < AIAttentionObject.REACT_COMBAT then - if best_cover and mvector3.distance_sq(best_cover[1][1], data.m_pos) > 10000 then + if not focus_enemy or not focus_enemy.nav_tracker or focus_enemy.reaction < AIAttentionObject.REACT_COMBAT or objective and objective.shield_cover_unit then + if best_cover and mvector3.distance_sq(best_cover[1][1], data.m_pos) > 100 ^ 2 then CopLogicAttack._set_best_cover(data, my_data, nil) end return end - local taking_cover = not my_data.moving_to_cover and not my_data.walking_to_cover_shoot_pos and not my_data.surprised - local can_take_cover = not my_data.surprised and not my_data.processing_cover_path and not my_data.charge_path_search_id - if not taking_cover and can_take_cover then - local threat_pos = data.attention_obj.m_pos - if not best_cover or not CopLogicAttack._verify_cover(best_cover[1], threat_pos, nil, nil) then - local dir = threat_pos - data.m_pos - mvector3.normalize(dir) - local found_cover = managers.navigation:find_cover_in_nav_seg_2(data.objective.area.nav_segs, data.m_pos, dir) - if found_cover and (not best_cover or CopLogicAttack._verify_cover(found_cover, threat_pos, nil, nil)) then - local better_cover = { - found_cover - } - - CopLogicAttack._set_best_cover(data, my_data, better_cover) - - local offset_pos, yaw = CopLogicAttack._get_cover_offset_pos(data, better_cover, threat_pos) - if offset_pos then - better_cover[5] = offset_pos - better_cover[6] = yaw + local taking_cover = my_data.moving_to_cover or my_data.walking_to_cover_shoot_pos or my_data.surprised + local processing_cover = my_data.processing_cover_path or my_data.charge_path_search_id + + if not taking_cover and not processing_cover then + local threat_pos = focus_enemy.nav_tracker:field_position() + local better_cover, best_cover_invalid + + if objective and objective.type == "follow" then + local near_pos = objective.follow_unit:movement():m_pos() + + if not best_cover or not CopLogicAttack._verify_follow_cover(best_cover[1], near_pos, threat_pos, 200, 1000) then + local nav_seg = objective.follow_unit:movement():nav_tracker():nav_segment() + local max_dis = objective.distance and objective.distance * 0.9 + local found_cover = managers.navigation:find_cover_in_nav_seg_3(nav_seg, max_dis, near_pos, threat_pos) + + if found_cover then + better_cover = { + found_cover, + } + end + end + else + local flank_cover = my_data.flank_cover and not my_data.flank_cover.failed and my_data.flank_cover + local min_dis = my_data.want_to_take_cover and my_data.weapon_range.close * 0.5 or nil + + if flank_cover or not best_cover or not CopLogicAttack._verify_cover(best_cover[1], threat_pos, min_dis, nil) then + local dir, near_pos, furthest_pos = tmp_vec1, tmp_vec2, tmp_vec3 + local optimal_dis = mvector3.direction(dir, threat_pos, data.m_pos) + local max_dis = my_data.weapon_range.far + + if flank_cover then + mvector3.rotate_with(dir, Rotation(flank_cover.angle)) + end + + if min_dis and optimal_dis < min_dis or optimal_dis > my_data.weapon_range.optimal * 1.2 then + optimal_dis = my_data.weapon_range.optimal + end + + mvector3.set(near_pos, threat_pos) + mvector3.add_scaled(near_pos, dir, optimal_dis) + + mvector3.set(furthest_pos, threat_pos) + mvector3.add_scaled(furthest_pos, dir, max_dis) + + local found_cover + if objective and objective.grp_objective and objective.grp_objective.type == "reenforce_area" then + local nav_seg = objective.area and objective.area.nav_segs or objective.nav_seg + found_cover = managers.navigation:find_cover_from_threat(nav_seg, optimal_dis, near_pos, threat_pos) + else + local angle = flank_cover and flank_cover.step or math.map_range(optimal_dis, 0, max_dis, 90, 60) + found_cover = managers.navigation:find_cover_in_cone_from_threat_pos_1(threat_pos, furthest_pos, near_pos, nil, angle, nil, nil, nil, data.pos_rsrv_id) + end + + if found_cover and CopLogicAttack._verify_cover(found_cover, threat_pos, min_dis, max_dis) then + better_cover = { + found_cover, + } + elseif flank_cover then + if math.sign(flank_cover.angle) == flank_cover.sign then + flank_cover.angle = -flank_cover.angle + else + flank_cover.angle = flank_cover.angle + flank_cover.step * flank_cover.sign + if math.abs(flank_cover.angle) > 90 then + flank_cover.failed = true + end + end + elseif best_cover then + best_cover_invalid = true end end end + + if better_cover then + CopLogicAttack._set_best_cover(data, my_data, better_cover) + + local offset_pos, yaw = CopLogicAttack._get_cover_offset_pos(data, better_cover, threat_pos) + if offset_pos then + better_cover[5] = offset_pos + better_cover[6] = yaw + end + elseif best_cover_invalid then + CopLogicAttack._set_best_cover(data, my_data, nil) + end + end + + if my_data.in_cover then + my_data.in_cover[3], my_data.in_cover[4] = CopLogicAttack._chk_covered(data, data.m_pos, focus_enemy.verified_pos, data.visibility_slotmask) + end +end + +-- Improve check for cover requirement +function CopLogicAttack._chk_wants_to_take_cover(data, my_data) + if not data.attention_obj or data.attention_obj.reaction < AIAttentionObject.REACT_COMBAT then + return + end + + if my_data.moving_to_cover or data.is_suppressed or my_data.attitude ~= "engage" then + return true + end + + if data.attention_obj.verified and data.attention_obj.dis < my_data.weapon_range.close * 0.5 then + return true + end + + if data.unit:inventory():equipped_unit():base():get_ammo_ratio() < 0.3 then + return true + end +end + +-- Improve Marshal combat movement, they really just need to try to keep their distance +function MarshalLogicAttack._upd_combat_movement(data) + local my_data = data.internal_data + local focus_enemy = data.attention_obj + + if data.logic.action_taken(data, my_data) or CopLogicAttack._upd_pose(data, my_data) then + return end - local in_cover = my_data.in_cover - if in_cover then - local threat_pos = data.attention_obj.verified_pos - in_cover[3], in_cover[4] = CopLogicAttack._chk_covered(data, data.m_pos, threat_pos, data.visibility_slotmask) + if data.unit:movement():chk_action_forbidden("walk") or not CopLogicAttack._can_move(data) then + return end -end \ No newline at end of file + + if focus_enemy.verified then + if CopLogicAttack._chk_start_action_move_back(data, my_data, focus_enemy, true, "optimal") then + return + end + + if data.important then + if data.is_suppressed and data.t - data.unit:character_damage():last_suppression_t() < 0.7 then + if CopLogicBase.chk_start_action_dodge(data, "scared") then + return + end + end + + if focus_enemy.is_person and focus_enemy.dis < 2000 and (data.group and data.group.size > 1 or math.random() < 0.5) then + local dodge + + if focus_enemy.is_local_player then + local e_movement_state = focus_enemy.unit:movement():current_state() + if not e_movement_state:_is_reloading() and not e_movement_state:_interacting() and not e_movement_state:is_equipping() then + dodge = true + end + else + local e_anim_data = focus_enemy.unit:anim_data() + if (e_anim_data.move or e_anim_data.idle) and not e_anim_data.reload then + dodge = true + end + end + + if dodge and focus_enemy.aimed_at and CopLogicBase.chk_start_action_dodge(data, "preemptive") then + return + end + end + end + end + + CopLogicAttack._chk_start_action_move_out_of_the_way(data, my_data) +end + +function MarshalLogicAttack.update_cover(data) end diff --git a/lua/coplogicbase.lua b/lua/coplogicbase.lua index 47d8484..a2437b3 100644 --- a/lua/coplogicbase.lua +++ b/lua/coplogicbase.lua @@ -1,7 +1,7 @@ -local math_random = math.random local mrot_y = mrotation.y local mvec3_add = mvector3.add local mvec3_dir = mvector3.direction +local mvec3_dis_sq = mvector3.distance_sq local mvec3_dot = mvector3.dot local mvec3_mul = mvector3.multiply local mvec3_neg = mvector3.negate @@ -12,7 +12,6 @@ local tmp_vec1 = Vector3() local tmp_vec2 = Vector3() local tmp_vec3 = Vector3() - -- Instant detection outside of stealth local _create_detected_attention_object_data_original = CopLogicBase._create_detected_attention_object_data function CopLogicBase._create_detected_attention_object_data(...) @@ -23,9 +22,8 @@ function CopLogicBase._create_detected_attention_object_data(...) return data end - -- Make shield_cover tactics stick closer to their shield tactics providers -Hooks:PreHook(CopLogicBase, "on_new_objective", "sh_on_new_objective", function (data, old_objective) +Hooks:PreHook(CopLogicBase, "on_new_objective", "sh_on_new_objective", function(data, old_objective) if not data.objective or data.objective.type ~= "defend_area" or not data.group or not data.tactics or not data.tactics.shield_cover then return end @@ -52,7 +50,6 @@ Hooks:PreHook(CopLogicBase, "on_new_objective", "sh_on_new_objective", function end end) - -- Remove follow unit as soon as it dies, not just after the body despawned function CopLogicBase.on_objective_unit_damaged(data, unit, attacker_unit) if unit:character_damage()._dead then @@ -60,7 +57,6 @@ function CopLogicBase.on_objective_unit_damaged(data, unit, attacker_unit) end end - -- Allow more dodge directions function CopLogicBase.chk_start_action_dodge(data, reason) if not data.char_tweak.dodge or not data.char_tweak.dodge.occasions[reason] then @@ -79,7 +75,7 @@ function CopLogicBase.chk_start_action_dodge(data, reason) data.dodge_chk_timeout_t = TimerManager:game():time() + 0.5 local dodge_tweak = data.char_tweak.dodge.occasions[reason] - if dodge_tweak.chance == 0 or dodge_tweak.chance < math_random() then + if dodge_tweak.chance == 0 or dodge_tweak.chance < math.random() then return end @@ -96,7 +92,7 @@ function CopLogicBase.chk_start_action_dodge(data, reason) local dodge_dir = mvector3.copy(enemy_dir) mvector3.cross(dodge_dir, enemy_dir, math.UP) - if math_random() < 0.5 then + if math.random() < 0.5 then mvec3_neg(dodge_dir) end @@ -104,7 +100,7 @@ function CopLogicBase.chk_start_action_dodge(data, reason) local ray_params = { trace = true, tracker_from = data.unit:movement():nav_tracker(), - pos_to = tmp_vec1 + pos_to = tmp_vec1, } mvec3_set(ray_params.pos_to, dodge_dir) @@ -143,7 +139,7 @@ function CopLogicBase.chk_start_action_dodge(data, reason) end -- Give enemies a chance to dodge backwards if dodging to the side is not possible or if dodging backwards has more space - if available_space < min_space or data.attention_obj and math_random() < math.max(0, 1 - data.attention_obj.dis / 500) then + if available_space < min_space or data.attention_obj and math.random() < math.max(0, 1 - data.attention_obj.dis / 500) then mvec3_set(ray_params.pos_to, enemy_dir) mvec3_mul(ray_params.pos_to, -prefered_space) mvec3_add(ray_params.pos_to, data.m_pos) @@ -171,9 +167,9 @@ function CopLogicBase.chk_start_action_dodge(data, reason) mrotation.x(data.unit:movement():m_rot(), tmp_vec1) local right_dot = mvec3_dot(dodge_dir, tmp_vec1) local fwd_dot = mvec3_dot(dodge_dir, data.unit:movement():m_fwd()) - local dodge_side = math.abs(fwd_dot) > 0.6 and (fwd_dot > 0 and "fwd" or "bwd") or right_dot > 0 and "r" or "l" + local dodge_side = math.abs(fwd_dot) > 0.6 and (fwd_dot > 0 and "fwd" or "bwd") or right_dot > 0 and "r" or "l" - local rand_nr = math_random() + local rand_nr = math.random() local total_chance = 0 local variation, variation_data for test_variation, test_variation_data in pairs(dodge_tweak.variations) do @@ -188,7 +184,7 @@ function CopLogicBase.chk_start_action_dodge(data, reason) local body_part = 1 local shoot_chance = variation_data.shoot_chance - if shoot_chance and shoot_chance > 0 and math_random() < shoot_chance then + if shoot_chance and shoot_chance > 0 and math.random() < shoot_chance then body_part = 2 end @@ -208,8 +204,8 @@ function CopLogicBase.chk_start_action_dodge(data, reason) dodge = -1, walk = -1, action = body_part == 1 and -1 or nil, - aim = body_part == 1 and -1 or nil - } + aim = body_part == 1 and -1 or nil, + }, } if variation ~= "side_step" then @@ -230,29 +226,56 @@ function CopLogicBase.chk_start_action_dodge(data, reason) return action end +-- Check for verified interrupt distance and remove bad marshal interrupt distance +function CopLogicBase.is_obstructed(data, objective, strictness, attention) + attention = attention or data.attention_obj + strictness = 1 - (strictness or 0) --- Check for minimum objective interruption distance -local is_obstructed_original = CopLogicBase.is_obstructed -function CopLogicBase.is_obstructed(data, objective, strictness, attention, ...) - local min_obj_interrupt_dis = data.char_tweak.min_obj_interrupt_dis - if not min_obj_interrupt_dis or not objective or not objective.interrupt_dis or objective.interrupt_dis < 0 then - return is_obstructed_original(data, objective, strictness, attention, ...) + if not objective or objective.is_default or (objective.in_place or not objective.nav_seg) and not objective.action then + return true, false end - attention = attention or data.attention_obj - if not attention or not attention.verified then - -- This is an additional multiplier, is_obstructed already halves when not visible, but for larger ranges thats not enough - min_obj_interrupt_dis = min_obj_interrupt_dis * 0.25 + if objective.interrupt_suppression and data.is_suppressed then + return true, true + end + + if objective.interrupt_health then + local health_ratio = data.unit:character_damage():health_ratio() + if health_ratio < 1 and health_ratio * strictness < objective.interrupt_health or data.unit:character_damage():dead() then + return true, true + end end - local interrupt_dis = objective.interrupt_dis - objective.interrupt_dis = math.max(interrupt_dis, min_obj_interrupt_dis) - local allow_trans, obj_failed = is_obstructed_original(data, objective, strictness, attention, ...) - objective.interrupt_dis = interrupt_dis + if objective.interrupt_dis then + if attention and (AIAttentionObject.REACT_COMBAT <= attention.reaction or data.cool and AIAttentionObject.REACT_SURPRISED <= attention.reaction) then + if objective.interrupt_dis == -1 then + return true, true + end - return allow_trans, obj_failed -end + if attention.verified and data.char_tweak.min_obj_interrupt_dis then + if attention.dis * strictness < data.char_tweak.min_obj_interrupt_dis then + return true, true + end + end + if math.abs(attention.m_pos.z - data.m_pos.z) < 250 then + if attention.dis * strictness * (attention.verified and 1 or 2) < objective.interrupt_dis then + return true, true + end + end + + if objective.pos and math.abs(attention.m_pos.z - objective.pos.z) < 250 then + if mvector3.distance(objective.pos, attention.m_pos) * strictness < objective.interrupt_dis then + return true, true + end + end + elseif objective.interrupt_dis == -1 and not data.cool then + return true, true + end + end + + return false, false +end -- Fix function not working accurately for clients/NPCs function CopLogicBase.chk_am_i_aimed_at(data, attention_obj, max_dot) @@ -275,4 +298,171 @@ function CopLogicBase.chk_am_i_aimed_at(data, attention_obj, max_dot) mvec3_dir(tmp_vec2, attention_obj.m_head_pos, data.unit:movement():m_com()) return max_dot < mvec3_dot(tmp_vec2, tmp_vec1) -end \ No newline at end of file +end + +-- Reduce maximum update delay +local _upd_attention_obj_detection_original = CopLogicBase._upd_attention_obj_detection +function CopLogicBase._upd_attention_obj_detection(...) + local delay = _upd_attention_obj_detection_original(...) + return math.min(0.5, delay) +end + +-- Fix incorrect checks and improve surrender conditions +function CopLogicBase._evaluate_reason_to_surrender(data, my_data, aggressor_unit) + local surrender_tweak = data.char_tweak.surrender + if not surrender_tweak then + return + end + + if surrender_tweak.base_chance >= 1 then + return 0 + end + + local t = data.t + + if data.surrender_window and data.surrender_window.window_expire_t < t then + return + end + + local hold_chance = 1 + local surrender_chk = { + health = function(health_surrender) + local health_ratio = data.unit:character_damage():health_ratio() + if health_ratio < 1 then + local min_setting, max_setting + + for k, v in pairs(health_surrender) do + if not min_setting or k < min_setting.k then + min_setting = { + k = k, + v = v, + } + end + + if not max_setting or max_setting.k < k then + max_setting = { + k = k, + v = v, + } + end + end + + if health_ratio < max_setting.k then + hold_chance = hold_chance * (1 - math.map_range_clamped(health_ratio, min_setting.k, max_setting.k, min_setting.v, max_setting.v)) + end + end + end, + + aggressor_dis = function(agg_dis_surrender) + local agg_dis = mvec3_dis_sq(data.m_pos, aggressor_unit:movement():m_pos()) + local min_setting, max_setting + + for k, v in pairs(agg_dis_surrender) do + if not min_setting or k < min_setting.k then + min_setting = { + k = k, + v = v, + } + end + + if not max_setting or max_setting.k < k then + max_setting = { + k = k, + v = v, + } + end + end + + if agg_dis < max_setting.k ^ 2 then + hold_chance = hold_chance * (1 - math.map_range_clamped(agg_dis, min_setting.k, max_setting.k, min_setting.v, max_setting.v)) + end + end, + + weapon_down = function(weap_down_surrender) + local anim_data = data.unit:anim_data() + if anim_data.reload or data.unit:inventory():equipped_unit():base():get_ammo_remaining_in_clip() == 0 then + hold_chance = hold_chance * (1 - weap_down_surrender) + elseif anim_data.hurt then + hold_chance = hold_chance * (1 - weap_down_surrender) + else + local stance_name = data.unit:movement():stance_name() + if stance_name == "ntl" then + hold_chance = hold_chance * (1 - weap_down_surrender) + elseif stance_name == "hos" then + hold_chance = hold_chance * (1 - weap_down_surrender * 0.25) + end + end + end, + + flanked = function(flanked_surrender) + local fwd_dot = mvec3_dot(data.unit:movement():m_rot():y(), tmp_vec1) + if fwd_dot < 0 then + hold_chance = hold_chance * (1 - flanked_surrender * math.abs(fwd_dot)) + end + end, + + unaware_of_aggressor = function(unaware_of_aggressor_surrender) + local att_info = data.detected_attention_objects[aggressor_unit:key()] + if not att_info or not att_info.identified or t - att_info.identified_t < 1 then + hold_chance = hold_chance * (1 - unaware_of_aggressor_surrender) + end + end, + + enemy_weap_cold = function(enemy_weap_cold_surrender) + if not managers.groupai:state():enemy_weapons_hot() then + hold_chance = hold_chance * (1 - enemy_weap_cold_surrender) + end + end, + + isolated = function(isolated_surrender) + if data.group and data.group.has_spawned and data.group.initial_size > 1 then + local has_support + for u_key, u_data in pairs(data.group.units) do + if u_key ~= data.key and mvec3_dis_sq(data.m_pos, u_data.m_pos) < 640000 then + has_support = true + break + end + end + + if not has_support then + hold_chance = hold_chance * (1 - isolated_surrender) + end + end + end, + + pants_down = function(pants_down_surrender) + local not_cool_t = data.unit:movement():not_cool_t() + if (not not_cool_t or t - not_cool_t < 1.5) and not managers.groupai:state():enemy_weapons_hot() then + hold_chance = hold_chance * (1 - pants_down_surrender) + end + end, + } + + for reason, reason_data in pairs(surrender_tweak.reasons) do + surrender_chk[reason](reason_data) + end + + if hold_chance > 1 - (surrender_tweak.significant_chance or 0) then + return 1 + end + + for factor, factor_data in pairs(surrender_tweak.factors) do + surrender_chk[factor](factor_data) + end + + if data.surrender_window then + hold_chance = hold_chance * (1 - data.surrender_window.chance_mul) + end + + if surrender_tweak.violence_timeout then + local violence_t = data.unit:character_damage():last_suppression_t() + if violence_t then + local violence_dt = t - violence_t + if violence_dt < surrender_tweak.violence_timeout then + hold_chance = hold_chance + (1 - hold_chance) * (1 - violence_dt / surrender_tweak.violence_timeout) + end + end + end + + return hold_chance < 1 and hold_chance +end diff --git a/lua/coplogicidle.lua b/lua/coplogicidle.lua index e88838f..f2292d3 100644 --- a/lua/coplogicidle.lua +++ b/lua/coplogicidle.lua @@ -38,7 +38,13 @@ function CopLogicIdle._chk_reaction_to_attention_object(data, attention_data, .. for u_key, other_crim_rec in pairs(managers.groupai:state():all_criminals()) do local other_crim_attention_info = data.detected_attention_objects[u_key] - if other_crim_attention_info and (other_crim_attention_info.is_deployable or other_crim_attention_info.verified and other_crim_rec.assault_t and data.t - other_crim_rec.assault_t < other_crim_rec.unit:base():arrest_settings().aggression_timeout) then + if + other_crim_attention_info + and ( + other_crim_attention_info.is_deployable + or other_crim_attention_info.verified and other_crim_rec.assault_t and data.t - other_crim_rec.assault_t < other_crim_rec.unit:base():arrest_settings().aggression_timeout + ) + then return attention_data.verified and REACT_COMBAT or attention_reaction end end @@ -50,25 +56,58 @@ function CopLogicIdle._chk_reaction_to_attention_object(data, attention_data, .. return math.min(attention_reaction, REACT_ARREST) end - -- Fix defend_area objectives being force relocated to areas with players in them -local _chk_relocate_original = CopLogicIdle._chk_relocate -function CopLogicIdle._chk_relocate(data, ...) +-- Fix lost follow objectives not refreshing for criminals in idle logic and Jokers in attack logic +-- Use the old defend_area behavior for the hunt objective for which it makes much more sense +function CopLogicIdle._chk_relocate(data) local objective = data.objective local objective_type = objective and objective.type + if objective_type == "follow" then - return _chk_relocate_original(data, ...) + local follow_unit = objective.follow_unit + local unit_pos = follow_unit:movement().get_walk_to_pos and follow_unit:movement():get_walk_to_pos() or follow_unit:movement():m_pos() + local dis_sq = mvector3.distance_sq(data.m_pos, unit_pos) + + if data.is_tied and objective.lose_track_dis and dis_sq > objective.lose_track_dis ^ 2 then + data.brain:set_objective(nil) + return true + end + + if objective.relocated_to and mvector3.equal(objective.relocated_to, unit_pos) then + return + elseif data.is_converted then + if not TeamAILogicIdle._check_should_relocate(data, data.internal_data, objective) then + return + end + elseif math.abs(unit_pos.z - data.m_pos.z) > 200 or objective.distance and dis_sq > objective.distance ^ 2 then + elseif managers.navigation:raycast({ pos_from = data.m_pos, pos_to = unit_pos }) then + elseif objective.shield_cover_unit and data.attention_obj and data.attention_obj.verified and data.attention_obj.reaction >= AIAttentionObject.REACT_AIM then + if mvector3.distance_sq(objective.relocated_to or data.m_pos, data.attention_obj.m_pos) > mvector3.distance_sq(unit_pos, data.attention_obj.m_pos) then + return + end + else + return + end + + objective.in_place = nil + objective.path_data = nil + objective.nav_seg = follow_unit:movement():nav_tracker():nav_segment() + objective.relocated_to = mvector3.copy(unit_pos) + + data.logic._exit(data.unit, "travel") + + return true elseif objective_type == "hunt" then - local objective_area = objective.area + local objective_area = objective.area or managers.groupai:state():get_area_from_nav_seg_id(objective.nav_seg or data.unit:movement():nav_tracker():nav_segment()) if not objective_area or next(objective_area.criminal.units) then return end local found_areas = { - [objective_area] = true + [objective_area] = true, } local areas_to_search = { - objective_area + objective_area, } local target_area @@ -100,10 +139,19 @@ function CopLogicIdle._chk_relocate(data, ...) data.logic._exit(data.unit, "travel") return true + elseif objective_type == "free" or not objective then + if data.cool or not data.is_converted and data.team.id ~= "criminal1" or data.path_fail_t and data.t - data.path_fail_t < 5 then + return + end + + local my_data = data.internal_data + + managers.groupai:state():on_criminal_jobless(data.unit) + + return my_data ~= data.internal_data end end - -- Improve and simplify attention handling -- Moved certain checks into their own functions for easier adjustments and improved target priority calculation -- Enemies no longer put low priority on reviving players and will prefer keeping their current target if there's a priority tie @@ -172,6 +220,11 @@ function CopLogicIdle._get_priority_attention(data, attention_objects, reaction_ target_priority_slot = target_priority_slot - 1 end + -- Focus on kingpin injector user + if weight_mul < 0.01 then + target_priority_slot = target_priority_slot - 1 + end + -- If we have murder tactic and criminal is downed or tased, focus on them if murder and reaction ~= AIAttentionObject.REACT_SPECIAL_ATTACK and (status == "electrified" or status == "disabled") then target_priority_slot = target_priority_slot - 1 @@ -264,4 +317,38 @@ function CopLogicIdle._get_attention_weight(attention_data, att_unit, distance) end end return 1 / weight_mul -end \ No newline at end of file +end + +-- Show hint to player when surrender is impossible +local on_intimidated_original = CopLogicIdle.on_intimidated +function CopLogicIdle.on_intimidated(data, amount, aggressor_unit, ...) + local surrender = on_intimidated_original(data, amount, aggressor_unit, ...) + if surrender or data.char_tweak.priority_shout or not data.team.foes.criminal1 or data.char_tweak.surrender == tweak_data.character.presets.surrender.special then + data._skip_surrender_hints = nil + return surrender + end + + local surrender_window_expired = data.surrender_window and data.surrender_window.window_expire_t < data.t + local too_many_hostages = not managers.groupai:state():has_room_for_police_hostage() + if not data.char_tweak.surrender or surrender_window_expired or too_many_hostages then + local peer = managers.network:session():peer_by_unit(aggressor_unit) + if not peer then + return + end + + if not data._skip_surrender_hints then + data._skip_surrender_hints = surrender_window_expired and 0 or 1 + end + + if data._skip_surrender_hints <= 0 then + if peer:id() == managers.network:session():local_peer():id() then + managers.hint:show_hint("convert_enemy_failed") + else + managers.network:session():send_to_peer(peer, "sync_show_hint", "convert_enemy_failed") + end + data._skip_surrender_hints = 3 + else + data._skip_surrender_hints = data._skip_surrender_hints - 1 + end + end +end diff --git a/lua/coplogicinactive.lua b/lua/coplogicinactive.lua index f02c6ae..2578501 100644 --- a/lua/coplogicinactive.lua +++ b/lua/coplogicinactive.lua @@ -1,6 +1,6 @@ -- Remove corpse attention settings on loud function CopLogicInactive.on_enemy_weapons_hot(data) - data.unit:brain():set_attention_settings(nil) + data.brain:set_attention_settings(nil) if data.unit:interaction():active() then data.unit:interaction():set_active(false, true, true) @@ -9,8 +9,8 @@ end function CopLogicInactive._register_attention(data) if data.unit:character_damage():dead() and not managers.groupai:state():enemy_weapons_hot() then - data.unit:brain():set_attention_settings({ corpse_sneak = true }) + data.brain:set_attention_settings({ corpse_sneak = true }) else - data.unit:brain():set_attention_settings(nil) + data.brain:set_attention_settings(nil) end end diff --git a/lua/coplogicintimidated.lua b/lua/coplogicintimidated.lua index 66c1f19..6b61335 100644 --- a/lua/coplogicintimidated.lua +++ b/lua/coplogicintimidated.lua @@ -1,8 +1,28 @@ --- Only allow hostage rescue if it's part of our tactics (or if we don't have any tactics to allow scripted cop/security spawns to rescue hostages) -local rescue_SO_verification_original = CopLogicIntimidated.rescue_SO_verification +-- Tweak hostage rescue conditions function CopLogicIntimidated.rescue_SO_verification(ignore_this, data, unit, ...) - local logic_data = unit:brain()._logic_data - if not logic_data or not logic_data.tactics or logic_data.tactics.rescue_hostages or logic_data.objective and logic_data.objective.grp_objective == "recon_area" then - return rescue_SO_verification_original(ignore_this, data, unit, ...) + if unit:movement():cool() then + return false end -end \ No newline at end of file + + if not unit:base():char_tweak().rescue_hostages then + return false + end + + if data.team.foes[unit:movement():team().id] then + return false + end + + local objective = unit:brain():objective() + if not objective or objective.type == "free" or not objective.area then + return true + end + + local nav_seg = data.unit:movement():nav_tracker():nav_segment() + if objective.area.nav_segs[nav_seg] or unit:movement():nav_tracker():nav_segment() == nav_seg then + return true + end + + if unit:movement():nav_tracker():nav_segment() == nav_seg then + return managers.groupai:state()._rescue_allowed + end +end diff --git a/lua/coplogicsniper.lua b/lua/coplogicsniper.lua index bbdab74..9111ea7 100644 --- a/lua/coplogicsniper.lua +++ b/lua/coplogicsniper.lua @@ -6,59 +6,36 @@ function CopLogicSniper._upd_aim(data, my_data) local shoot = focus_enemy and focus_enemy.verified and focus_enemy.reaction >= AIAttentionObject.REACT_SHOOT local anim_data = data.unit:anim_data() - local action_taken = my_data.turning or data.unit:movement():chk_action_forbidden("walk") - if not action_taken then + if not my_data.advancing and not my_data.turning and not data.unit:movement():chk_action_forbidden("walk") then + local can_crouch = anim_data.stand and (not data.char_tweak.allowed_poses or data.char_tweak.allowed_poses.crouch) + local can_stand = anim_data.crouch and (not data.char_tweak.allowed_poses or data.char_tweak.allowed_poses.stand) - if anim_data.reload and not anim_data.crouch and (not data.char_tweak.allowed_poses or data.char_tweak.allowed_poses.crouch) then - action_taken = CopLogicAttack._chk_request_action_crouch(data) - end - - if not action_taken then - if my_data.attitude == "engage" and not data.is_suppressed then - if focus_enemy then - if not focus_enemy.verified and not anim_data.reload then - if anim_data.crouch then - if (not data.char_tweak.allowed_poses or data.char_tweak.allowed_poses.stand) and not CopLogicSniper._chk_stand_visibility(data.m_pos, focus_enemy.m_head_pos, data.visibility_slotmask) then - CopLogicAttack._chk_request_action_stand(data) - end - elseif (not data.char_tweak.allowed_poses or data.char_tweak.allowed_poses.crouch) and not CopLogicSniper._chk_crouch_visibility(data.m_pos, focus_enemy.m_head_pos, data.visibility_slotmask) then - CopLogicAttack._chk_request_action_crouch(data) - end - end - elseif my_data.wanted_pose and not anim_data.reload then - if my_data.wanted_pose == "crouch" then - if not anim_data.crouch and (not data.char_tweak.allowed_poses or data.char_tweak.allowed_poses.crouch) then - action_taken = CopLogicAttack._chk_request_action_crouch(data) - end - elseif not anim_data.stand and (not data.char_tweak.allowed_poses or data.char_tweak.allowed_poses.stand) then - action_taken = CopLogicAttack._chk_request_action_stand(data) + if anim_data.reload or data.is_suppressed then + if can_crouch then + CopLogicAttack._chk_request_action_crouch(data) + end + elseif focus_enemy then + if focus_enemy.verified then + if my_data.attitude == "engage" or my_data.wanted_pose == "stand" then + if can_stand and not CopLogicSniper._chk_stand_visibility(data.m_pos, focus_enemy.m_head_pos, data.visibility_slotmask) then + CopLogicAttack._chk_request_action_stand(data) end - end - elseif focus_enemy then - if focus_enemy.verified and anim_data.stand and (not data.char_tweak.allowed_poses or data.char_tweak.allowed_poses.crouch) and CopLogicSniper._chk_crouch_visibility(data.m_pos, focus_enemy.m_head_pos, data.visibility_slotmask) then + elseif can_crouch and not CopLogicSniper._chk_crouch_visibility(data.m_pos, focus_enemy.m_head_pos, data.visibility_slotmask) then CopLogicAttack._chk_request_action_crouch(data) end - elseif my_data.wanted_pose and not anim_data.reload then - if my_data.wanted_pose == "crouch" then - if not anim_data.crouch and (not data.char_tweak.allowed_poses or data.char_tweak.allowed_poses.crouch) then - action_taken = CopLogicAttack._chk_request_action_crouch(data) - end - elseif not anim_data.stand and (not data.char_tweak.allowed_poses or data.char_tweak.allowed_poses.stand) then - action_taken = CopLogicAttack._chk_request_action_stand(data) - end + elseif can_stand and not CopLogicSniper._chk_stand_visibility(data.m_pos, focus_enemy.m_head_pos, data.visibility_slotmask) then + CopLogicAttack._chk_request_action_stand(data) + elseif can_crouch and not CopLogicSniper._chk_crouch_visibility(data.m_pos, focus_enemy.m_head_pos, data.visibility_slotmask) then + CopLogicAttack._chk_request_action_crouch(data) + end + elseif my_data.wanted_pose == "stand" then + if can_stand then + CopLogicAttack._chk_request_action_stand(data) + end + elseif my_data.wanted_pose == "crouch" then + if can_crouch then + CopLogicAttack._chk_request_action_crouch(data) end - end - end - - if my_data.reposition and not action_taken and not my_data.advancing then - local objective = data.objective - my_data.advance_path = { - mvector3.copy(data.m_pos), - mvector3.copy(objective.pos) - } - - if CopLogicTravel._chk_request_action_walk_to_advance_pos(data, my_data, objective.haste or "walk", objective.rot) then - action_taken = true end end @@ -74,16 +51,16 @@ function CopLogicSniper._upd_aim(data, my_data) end if not my_data.shooting and not data.unit:movement():chk_action_forbidden("action") then - my_data.shooting = data.unit:brain():action_request({ + my_data.shooting = data.brain:action_request({ body_part = 3, - type = "shoot" + type = "shoot", }) end else if my_data.shooting then - data.unit:brain():action_request({ + data.brain:action_request({ body_part = 3, - type = "idle" + type = "idle", }) end @@ -95,3 +72,29 @@ function CopLogicSniper._upd_aim(data, my_data) CopLogicAttack.aim_allow_fire(shoot, aim, data, my_data) end + +-- Return to objective position +Hooks:PostHook(CopLogicSniper, "action_complete_clbk", "sh_action_complete_clbk", function(data, action) + local action_type = action:type() + local my_data = data.internal_data + + if action_type ~= "hurt" and action_type ~= "dodge" and action_type ~= "act" then + return + end + + local objective = data.objective + if not objective or not objective.pos then + return + end + + if math.abs(objective.pos.x - data.m_pos.x) < 10 and math.abs(objective.pos.y - data.m_pos.y) < 10 then + return + end + + my_data.advance_path = { + mvector3.copy(data.m_pos), + mvector3.copy(objective.pos), + } + + CopLogicTravel._chk_request_action_walk_to_advance_pos(data, my_data, objective.haste or "walk", objective.rot) +end) diff --git a/lua/coplogictravel.lua b/lua/coplogictravel.lua index 37dc2c9..5369fb7 100644 --- a/lua/coplogictravel.lua +++ b/lua/coplogictravel.lua @@ -1,10 +1,6 @@ -local math_abs = math.abs -local math_random = math.random local mvec3_add = mvector3.add -local mvec3_copy = mvector3.copy local mvec3_cross = mvector3.cross local mvec3_dir = mvector3.direction -local mvec3_dis = mvector3.distance local mvec3_dis_sq = mvector3.distance_sq local mvec3_lerp = mvector3.lerp local mvec3_mul = mvector3.multiply @@ -15,34 +11,41 @@ local tmp_vec1 = Vector3() local tmp_vec2 = Vector3() local tmp_vec3 = Vector3() - -- Reuse function of idle logic to make enemies in an area aware of a player entering the area CopLogicTravel.on_area_safety = CopLogicIdle.on_area_safety - -- Update pathing immediately when receiving travel logic or pathing results Hooks:PostHook(CopLogicTravel, "enter", "sh_enter", CopLogicTravel.upd_advance) function CopLogicTravel.on_pathing_results(data) + local my_data = data.internal_data + + CopLogicTravel._upd_pathing(data, my_data) + + if data.internal_data ~= my_data then + return + end + CopLogicTravel.upd_advance(data) end --- Fix need for another queued task to update pathing or leaving cover on expired cover time --- Basically just does the needed checks before calling the original function to save on a queued update -Hooks:PreHook(CopLogicTravel, "upd_advance", "sh_upd_advance", function (data) +-- Sanity check for rare follow_unit crash +Hooks:PreHook(CopLogicTravel, "_begin_coarse_pathing", "sh__begin_coarse_pathing", function(data) + if data.objective.follow_unit and not alive(data.objective.follow_unit) then + data.objective.follow_unit = nil + end +end) + +-- Fix need for another queued task to update pathing after expired cover leave time +Hooks:PreHook(CopLogicTravel, "upd_advance", "sh_upd_advance", function(data) local unit = data.unit local my_data = data.internal_data local t = TimerManager:game():time() - if my_data.processing_advance_path or my_data.processing_coarse_path then - CopLogicTravel._upd_pathing(data, my_data) - elseif my_data.cover_leave_t then - if my_data.coarse_path and my_data.coarse_path_index == #my_data.coarse_path or my_data.cover_leave_t < t and not unit:movement():chk_action_forbidden("walk") and not data.unit:anim_data().reload then - my_data.cover_leave_t = nil - end + if my_data.cover_leave_t and my_data.cover_leave_t < t and not unit:movement():chk_action_forbidden("walk") and not data.unit:anim_data().reload then + my_data.cover_leave_t = nil end end) - -- Make groups move together (remove close to criminal check to avoid splitting groups) function CopLogicTravel.chk_group_ready_to_move(data, my_data) local my_objective = data.objective @@ -70,62 +73,65 @@ function CopLogicTravel.chk_group_ready_to_move(data, my_data) return true end - -- Find a random fallback position in the nav segment if no covers are available -- This is done to prevent enemies stacking in one spot if no positions next to walls are available -- Also add different positioning for shield_cover groups, sticking close to and behind their follow units local _get_exact_move_pos_original = CopLogicTravel._get_exact_move_pos function CopLogicTravel._get_exact_move_pos(data, nav_index, ...) local my_data = data.internal_data + local nav_manager = managers.navigation if alive(data.objective.shield_cover_unit) then if my_data.moving_to_cover then - managers.navigation:release_cover(my_data.moving_to_cover[1]) + nav_manager:release_cover(my_data.moving_to_cover[1]) my_data.moving_to_cover = nil end - return CopLogicTravel._get_pos_behind_unit(data, data.objective.shield_cover_unit, 50, 300) + local pos = CopLogicTravel._get_pos_behind_unit(data, data.objective.shield_cover_unit, 75, 300) + return pos or _get_exact_move_pos_original(data, nav_index, ...) end local coarse_path = my_data.coarse_path - if nav_index >= #coarse_path or data.objective.follow_unit then + if nav_index >= #coarse_path or data.objective.follow_unit or data.objective.path_style == "destination" then return _get_exact_move_pos_original(data, nav_index, ...) end if my_data.moving_to_cover then - managers.navigation:release_cover(my_data.moving_to_cover[1]) + nav_manager:release_cover(my_data.moving_to_cover[1]) my_data.moving_to_cover = nil end - -- Find cover (or fallback) point close to nav segment door when the coarse path isn't finished yet - local all_nav_segs = managers.navigation._nav_segments local nav_seg_id = coarse_path[nav_index][1] local next_nav_seg_id = coarse_path[nav_index + 1][1] - local next_area = managers.groupai:state():get_area_from_nav_seg_id(next_nav_seg_id) - local nav_seg_pos = all_nav_segs[nav_seg_id].pos - local to_pos = nav_seg_pos - - for neighbour_nav_seg_id, door_list in pairs(all_nav_segs[nav_seg_id].neighbours) do - if next_area.nav_segs[neighbour_nav_seg_id] and not all_nav_segs[neighbour_nav_seg_id].disabled then - local random_door_id = door_list[math_random(#door_list)] - if type(random_door_id) == "number" then - to_pos = managers.navigation._room_doors[random_door_id].center - else - to_pos = random_door_id:script_data().element:nav_link_end_pos() - end - break - end - end + local nav_seg_pos = nav_manager._nav_segments[nav_seg_id].pos + + -- Pick cover positions that are close to nav segment doors + local doors = nav_manager:find_segment_doors(nav_seg_id, function(seg_id) + return seg_id == next_nav_seg_id + end) + local door = table.random(doors) + local to_pos = door and door.center or coarse_path[nav_index][2] or nav_seg_pos - local cover = managers.navigation:find_cover_in_nav_seg_2(nav_seg_id, to_pos) + local cover = nav_manager:find_cover_in_nav_seg_2(nav_seg_id, to_pos) if cover then - managers.navigation:reserve_cover(cover, data.pos_rsrv_id) - my_data.moving_to_cover = { - cover - } + nav_manager:reserve_cover(cover, data.pos_rsrv_id) + my_data.moving_to_cover = { cover } to_pos = cover[1] else - to_pos = CopLogicTravel._get_pos_on_wall(to_pos == nav_seg_pos and to_pos or (nav_seg_pos + to_pos) * 0.5, 500) + mvector3.step(tmp_vec1, to_pos, nav_seg_pos, 200) + mvector3.set(tmp_vec2, math.UP) + mvector3.random_orthogonal(tmp_vec2) + mvector3.multiply(tmp_vec2, 100) + mvector3.add(tmp_vec1, tmp_vec2) + + local ray_params = { + pos_from = nav_seg_pos, + pos_to = tmp_vec1, + allow_entry = true, + trace = true, + } + nav_manager:raycast(ray_params) + to_pos = ray_params.trace[1] end return to_pos @@ -144,9 +150,9 @@ function CopLogicTravel._determine_destination_occupation(data, objective, ...) type = "defend", seg = objective.nav_seg, cover = { - cover + cover, }, - radius = objective.radius + radius = objective.radius, } else near_pos = CopLogicTravel._get_pos_on_wall(managers.navigation:find_random_position_in_segment(objective.nav_seg), 500) @@ -154,55 +160,49 @@ function CopLogicTravel._determine_destination_occupation(data, objective, ...) type = "defend", seg = objective.nav_seg, pos = near_pos, - radius = objective.radius + radius = objective.radius, } end end function CopLogicTravel._get_pos_behind_unit(data, unit, min_dis, max_dis) - local advancing = unit:brain() and unit:brain():is_advancing() + local threat_dir, threat_side, pos = tmp_vec1, tmp_vec2, tmp_vec3 local unit_movement = unit:movement() - local unit_pos = advancing or unit_movement:m_pos() - -- If target unit is advancing, add an offset so we don't run in front of it during advance - local offset = advancing and mvec3_dis(advancing, unit_movement:m_pos()) * 0.5 or 0 + local unit_pos = unit_movement.get_walk_to_pos and unit_movement:get_walk_to_pos() or unit_movement:m_pos() - -- Get the threat direction if data.attention_obj and data.attention_obj.reaction >= AIAttentionObject.REACT_AIM then - mvec3_dir(tmp_vec1, data.attention_obj.m_pos, unit_pos) + mvec3_dir(threat_dir, data.attention_obj.m_pos, unit_pos) else - mvec3_set(tmp_vec1, unit_movement.m_fwd and unit_movement:m_fwd() or unit_movement:m_head_rot():y()) - mvec3_neg(tmp_vec1) + mvec3_set(threat_dir, unit_movement.m_fwd and unit_movement:m_fwd() or unit_movement:m_head_rot():y()) + mvec3_neg(threat_dir) end - -- Threat direction side - mvec3_cross(tmp_vec2, tmp_vec1, math.UP) + mvec3_cross(threat_side, threat_dir, math.UP) local fallback_pos local rays = 7 local min_dis_sq = min_dis ^ 2 local nav_manager = managers.navigation local ray_params = { - allow_entry = false, trace = true, - pos_from = unit_pos + pos_from = unit_pos, + pos_to = pos, } local rsrv_desc = { - false, - 40 + radius = 40, } repeat - if math_random() < 0.5 then - mvec3_neg(tmp_vec2) + if math.random() < 0.5 then + mvec3_neg(threat_side) end -- Get a random vector between main threat direction and side threat direction - mvec3_lerp(tmp_vec3, tmp_vec1, tmp_vec2, math_random() * 0.5) - mvec3_normalize(tmp_vec3) - mvec3_mul(tmp_vec3, offset + math_random(min_dis, max_dis)) - mvec3_add(tmp_vec3, unit_pos) + mvec3_lerp(pos, threat_dir, threat_side, math.random() * 0.5) + mvec3_normalize(pos) + mvec3_mul(pos, math.random(min_dis, max_dis)) + mvec3_add(pos, unit_pos) - ray_params.pos_to = tmp_vec3 if not nav_manager:raycast(ray_params) or mvec3_dis_sq(ray_params.trace[1], unit_pos) > min_dis_sq then rsrv_desc.position = ray_params.trace[1] if nav_manager:is_pos_free(rsrv_desc) then @@ -215,77 +215,34 @@ function CopLogicTravel._get_pos_behind_unit(data, unit, min_dis, max_dis) rays = rays - 1 until rays <= 0 - return fallback_pos or unit_pos -end - - --- If Iter is installed and streamlined path option is used, don't make any further changes -if Iter and Iter.settings and Iter.settings.streamline_path then - return + return fallback_pos end - --- Take the direct path if possible and immediately start pathing instead of waiting for the next update (thanks to RedFlame) -function CopLogicTravel._check_start_path_ahead(data) - local my_data = data.internal_data - - if my_data.processing_advance_path then +-- Fix cover wait time being set to 0 if players aren't literally next to enemy +Hooks:PostHook(CopLogicTravel, "action_complete_clbk", "sh_action_complete_clbk", function(data, action) + if action:type() ~= "walk" then return end - local coarse_path = my_data.coarse_path - local next_index = my_data.coarse_path_index + 2 - local total_nav_points = #coarse_path - - if next_index > total_nav_points then + local my_data = data.internal_data + if not my_data.cover_leave_t then return end - local to_pos = data.logic._get_exact_move_pos(data, next_index) - local from_pos = data.pos_rsrv.move_dest.position - - if math_abs(from_pos.z - to_pos.z) < 100 and not managers.navigation:raycast({allow_entry = false, pos_from = from_pos, pos_to = to_pos}) then - my_data.advance_path = { - mvec3_copy(from_pos), - to_pos - } - - return + if not my_data.coarse_path or my_data.coarse_path_index == #my_data.coarse_path or data.objective and data.objective.follow_unit then + my_data.cover_leave_t = nil + elseif my_data.cover_leave_t == data.t then + my_data.cover_leave_t = data.t + (my_data.coarse_path_index == #my_data.coarse_path - 1 and 0.3 or math.rand(0.6, 1)) end +end) - my_data.processing_advance_path = true - local prio = data.logic.get_pathing_prio(data) - local nav_segs = CopLogicTravel._get_allowed_travel_nav_segs(data, my_data, to_pos) - - data.unit:brain():search_for_path_from_pos(my_data.advance_path_search_id, from_pos, to_pos, prio, nil, nav_segs) -end - -function CopLogicTravel._chk_start_pathing_to_next_nav_point(data, my_data) - if not CopLogicTravel.chk_group_ready_to_move(data, my_data) then - return - end - - local from_pos = data.unit:movement():nav_tracker():field_position() - local to_pos = CopLogicTravel._get_exact_move_pos(data, my_data.coarse_path_index + 1) - - if math_abs(from_pos.z - to_pos.z) < 100 and not managers.navigation:raycast({allow_entry = false, pos_from = from_pos, pos_to = to_pos}) then - my_data.advance_path = { - mvec3_copy(from_pos), - to_pos - } - - -- If we don't have to wait for the pathing results, immediately start advancing - CopLogicTravel._chk_begin_advance(data, my_data) - if my_data.advancing and my_data.path_ahead then - CopLogicTravel._check_start_path_ahead(data) - end - - return +-- Stop existing advancing action on exit to a new travel logic +-- This allows enemies to start their new path immediately instead of having to finish the old one +Hooks:PreHook(CopLogicTravel, "exit", "sh_exit", function(data, new_logic_name) + if new_logic_name == "travel" and data.internal_data.advancing and not data.unit:movement():chk_action_forbidden("idle") then + data.brain:action_request({ + body_part = 2, + type = "idle", + }) end - - my_data.processing_advance_path = true - local prio = data.logic.get_pathing_prio(data) - local nav_segs = CopLogicTravel._get_allowed_travel_nav_segs(data, my_data, to_pos) - - data.unit:brain():search_for_path(my_data.advance_path_search_id, to_pos, prio, nil, nav_segs) -end \ No newline at end of file +end) diff --git a/lua/copmovement.lua b/lua/copmovement.lua index 79a722f..cc09ded 100644 --- a/lua/copmovement.lua +++ b/lua/copmovement.lua @@ -1,3 +1,11 @@ +CopMovement._action_variants.tank_elite = CopMovement._action_variants.tank +CopMovement._action_variants.phalanx_minion_break = CopMovement._action_variants.city_swat +CopMovement._action_variants.zeal_swat = CopMovement._action_variants.city_swat +CopMovement._action_variants.zeal_heavy_swat = CopMovement._action_variants.city_swat +CopMovement._action_variants.zeal_medic = CopMovement._action_variants.city_swat +CopMovement._action_variants.zeal_shield = CopMovement._action_variants.shield +CopMovement._action_variants.zeal_taser = CopMovement._action_variants.taser + -- Fix enemies playing the suppressed stand-to-crouch animation when shot even if they are already crouching local play_redirect_original = CopMovement.play_redirect function CopMovement:play_redirect(redirect_name, ...) @@ -8,7 +16,6 @@ function CopMovement:play_redirect(redirect_name, ...) return result end - -- Fix attention synch not providing the old attention function CopMovement:synch_attention(attention) if attention and self._unit:character_damage():dead() then @@ -37,8 +44,11 @@ end -- counterstrike stuff Hooks:OverrideFunction(CopMovement, "damage_clbk", function(self, my_unit, damage_info) local hurt_type = damage_info.result.type - -- If it's a dozer and the hurt type is expl_hurt, use the medium hurt preset instead - if self._unit:base()._tweak_table == "tank" and hurt_type == "expl_hurt" then hurt_type = "hurt" end + -- If it's a dozer and the hurt type is expl_hurt, use the medium hurt preset instead + local is_tank = self._unit:base()._tweak_table == "tank" or self._unit:base()._tweak_table == "tank_elite" + if is_tank and hurt_type == "expl_hurt" then + hurt_type = "hurt" + end -- Original code if hurt_type == "stagger" then @@ -77,7 +87,7 @@ Hooks:OverrideFunction(CopMovement, "damage_clbk", function(self, my_unit, damag damage_info.variant = "melee" damage_info.result = { variant = "melee", - type = "shield_knock" + type = "shield_knock", } damage_info.shield_knock = true end @@ -113,7 +123,7 @@ Hooks:OverrideFunction(CopMovement, "damage_clbk", function(self, my_unit, damag aim = -1, action = -1, tase = -1, - walk = -1 + walk = -1, } if hurt_type == "bleedout" then @@ -135,7 +145,24 @@ Hooks:OverrideFunction(CopMovement, "damage_clbk", function(self, my_unit, damag local client_interrupt = nil - if Network:is_client() and (hurt_type == "light_hurt" or hurt_type == "hurt" and damage_info.variant ~= "tase" or hurt_type == "heavy_hurt" or hurt_type == "expl_hurt" or hurt_type == "shield_knock" or hurt_type == "counter_tased" or hurt_type == "taser_tased" or hurt_type == "counter_spooc" or hurt_type == "death" or hurt_type == "hurt_sick" or hurt_type == "fire_hurt" or hurt_type == "poison_hurt" or hurt_type == "concussion") then + if + Network:is_client() + and ( + hurt_type == "light_hurt" + or hurt_type == "hurt" and damage_info.variant ~= "tase" + or hurt_type == "heavy_hurt" + or hurt_type == "expl_hurt" + or hurt_type == "shield_knock" + or hurt_type == "counter_tased" + or hurt_type == "taser_tased" + or hurt_type == "counter_spooc" + or hurt_type == "death" + or hurt_type == "hurt_sick" + or hurt_type == "fire_hurt" + or hurt_type == "poison_hurt" + or hurt_type == "concussion" + ) + then client_interrupt = true end @@ -150,7 +177,7 @@ Hooks:OverrideFunction(CopMovement, "damage_clbk", function(self, my_unit, damag action_data = { body_part = 3, type = "healed", - client_interrupt = client_interrupt + client_interrupt = client_interrupt, } else action_data = { @@ -169,7 +196,7 @@ Hooks:OverrideFunction(CopMovement, "damage_clbk", function(self, my_unit, damag ignite_character = damage_info.ignite_character, start_dot_damage_roll = damage_info.start_dot_damage_roll, is_fire_dot_damage = damage_info.is_fire_dot_damage, - fire_dot_data = damage_info.fire_dot_data + fire_dot_data = damage_info.fire_dot_data, } end @@ -187,3 +214,20 @@ Hooks:OverrideFunction(CopMovement, "damage_clbk", function(self, my_unit, damag end end end) + +-- Fix head position update on suppression +Hooks:PreHook(CopMovement, "_upd_stance", "sh__upd_stance", function(self, t) + if self._stance.transition and self._stance.transition.next_upd_t < t then + self._force_head_upd = true + elseif self._suppression.transition and self._suppression.transition.next_upd_t < t then + self._force_head_upd = true + end +end) + +Hooks:PostHook(CopMovement, "_change_stance", "sh__change_stance", function(self) + self._force_head_upd = true +end) + +Hooks:PostHook(CopMovement, "on_suppressed", "sh_on_suppressed", function(self) + self._force_head_upd = true +end) diff --git a/lua/coreenvironmentcontrollermanager.lua b/lua/coreenvironmentcontrollermanager.lua index 9ce28c5..dc9c949 100644 --- a/lua/coreenvironmentcontrollermanager.lua +++ b/lua/coreenvironmentcontrollermanager.lua @@ -1,9 +1,7 @@ -local tmp_vec1 = Vector3() -local tmp_vec2 = Vector3() - +local tmp_vec = Vector3() -- Make flashbangs scale with look direction instead of a flat reduction at some certain angle -Hooks:OverrideFunction(CoreEnvironmentControllerManager, "test_line_of_sight", function (self, test_pos, min_distance, dot_distance, max_distance) +Hooks:OverrideFunction(CoreEnvironmentControllerManager, "test_line_of_sight", function(self, test_pos, min_distance, dot_distance, max_distance) local vp = managers.viewport:first_active_viewport() if not vp then @@ -12,9 +10,9 @@ Hooks:OverrideFunction(CoreEnvironmentControllerManager, "test_line_of_sight", f local camera = vp:camera() - camera:m_position(tmp_vec1) + camera:m_position(tmp_vec) - local dis = mvector3.direction(tmp_vec2, tmp_vec1, test_pos) + local dis = mvector3.direction(tmp_vec, tmp_vec, test_pos) if dis > max_distance then return 0 @@ -25,15 +23,13 @@ Hooks:OverrideFunction(CoreEnvironmentControllerManager, "test_line_of_sight", f end local cam_fwd = camera:rotation():y() - local dot_mul = (mvector3.dot(cam_fwd, tmp_vec2) + 1) / 2 + local dot_mul = (mvector3.dot(cam_fwd, tmp_vec) + 1) / 2 local dot_effect = dis > dot_distance and 1 or dis / dot_distance - local flash = (1 - math.max(dis - min_distance, 0) / (max_distance - min_distance)) * (dot_mul ^ dot_effect) - - return flash + return math.map_range_clamped(dis, min_distance, max_distance, 1, 0) * (dot_mul ^ dot_effect) end) -- Tone down the red screen on health hits function CoreEnvironmentControllerManager:set_health_effect_value(health_effect_value) self._health_effect_value = health_effect_value * 2 -end \ No newline at end of file +end diff --git a/lua/coreunitdamage.lua b/lua/coreunitdamage.lua index 0291226..0080fa0 100644 --- a/lua/coreunitdamage.lua +++ b/lua/coreunitdamage.lua @@ -1,12 +1,10 @@ -- Increase bulldozer armor health (SH) -Hooks:PostHook(CoreBodyDamage, "init", "sh_init", function (self) - if self._body_element and self._unit:base() and self._unit:base().has_tag and self._unit:base():has_tag("tank") then - self.tank_face_balance_mul = {3, 4, 5, 6} - local face_player_mul = managers.groupai:state():_get_balancing_multiplier(self.tank_face_balance_mul) +Hooks:PostHook(CoreBodyDamage, "init", "sh_init", function(self) + if self._body_element and self._unit:base() and self._unit:base().has_tag and (self._unit:base():has_tag("tank") or self._unit:base():has_tag("tank_elite")) then + local face_player_mul = managers.groupai:state():_get_balancing_multiplier(tweak_data.character.tank_armor_balance_mul) if not CoreBodyDamage._tank_armor_multiplier then CoreBodyDamage._tank_armor_multiplier = 1 / face_player_mul - CoreBodyDamage._tank_glass_multiplier = 1 / (face_player_mul * 2 / 3) end - self._body_element._damage_multiplier = self._body_element._name:find("glass") and CoreBodyDamage._tank_glass_multiplier or CoreBodyDamage._tank_armor_multiplier + self._body_element._damage_multiplier = self._body_element._name:find("glass") or CoreBodyDamage._tank_armor_multiplier end -end) \ No newline at end of file +end) diff --git a/lua/crimenetcontractgui.lua b/lua/crimenetcontractgui.lua new file mode 100644 index 0000000..3d046e8 --- /dev/null +++ b/lua/crimenetcontractgui.lua @@ -0,0 +1,1483 @@ +function CrimeNetContractGui:init(ws, fullscreen_ws, node) + self._ws = ws + self._fullscreen_ws = fullscreen_ws + self._panel = self._ws:panel():panel({ + layer = 51, + }) + self._fullscreen_panel = self._fullscreen_ws:panel():panel({ + layer = 50, + }) + + self._fullscreen_panel:rect({ + alpha = 0.75, + layer = 0, + color = Color.black, + }) + + self._node = node + local job_data = self._node:parameters().menu_component_data + self._customizable = job_data.customize_contract or false + self._smart_matchmaking = job_data.smart_matchmaking or false + local font_size = tweak_data.menu.pd2_small_font_size + local font = tweak_data.menu.pd2_small_font + local risk_color = tweak_data.screen_colors.risk + local padding = tweak_data.gui.crime_net.contract_gui.padding + local half_padding = 0.5 * padding + local double_padding = 2 * padding + local width = tweak_data.gui.crime_net.contract_gui.width + local height = tweak_data.gui.crime_net.contract_gui.height + local text_w = tweak_data.gui.crime_net.contract_gui.text_width + local contact_w = tweak_data.gui.crime_net.contract_gui.contact_width + local contact_h = contact_w / 1.7777777777777777 + local blur = self._fullscreen_panel:bitmap({ + texture = "guis/textures/test_blur_df", + render_template = "VertexColorTexturedBlur3D", + layer = 1, + w = self._fullscreen_ws:panel():w(), + h = self._fullscreen_ws:panel():h(), + }) + + local function func(o) + local start_blur = 0 + + over(0.6, function(p) + o:set_alpha(math.lerp(start_blur, 1, p)) + end) + end + + blur:animate(func) + + self._contact_text_header = self._panel:text({ + text = " ", + vertical = "top", + align = "left", + layer = 1, + font_size = tweak_data.menu.pd2_large_font_size, + font = tweak_data.menu.pd2_large_font, + color = tweak_data.screen_colors.text, + }) + local x, y, w, h = self._contact_text_header:text_rect() + + self._contact_text_header:set_size(width, h) + self._contact_text_header:set_center_x(self._panel:w() * 0.5) + + self._contract_panel = self._panel:panel({ + layer = 5, + h = height, + w = width, + x = self._contact_text_header:x(), + y = self._contact_text_header:bottom(), + }) + + self._contract_panel:set_center_y(self._panel:h() * 0.5) + self._contact_text_header:set_bottom(self._contract_panel:top()) + + if self._contact_text_header:y() < 0 then + local y_offset = -self._contact_text_header:y() + + self._contact_text_header:move(0, y_offset) + self._contract_panel:move(0, y_offset) + end + + if not job_data.job_id then + local bottom = self._contract_panel:bottom() + + self._contract_panel:set_h(160) + self._contract_panel:set_bottom(bottom) + self._contact_text_header:set_bottom(self._contract_panel:top()) + + local host_name = job_data.host_name or "" + local num_players = job_data.num_plrs or 1 + local server_text = managers.localization:to_upper_text("menu_lobby_server_title") .. " " .. host_name + local players_text = managers.localization:to_upper_text("menu_players_online", { + COUNT = tostring(num_players), + }) + + self._contact_text_header:set_text(server_text .. "\n" .. players_text) + self._contact_text_header:set_font(tweak_data.menu.pd2_medium_font_id) + self._contact_text_header:set_font_size(tweak_data.menu.pd2_medium_font_size) + + local x, y, w, h = self._contact_text_header:text_rect() + + self._contact_text_header:set_size(width, h) + self._contact_text_header:set_top(self._contract_panel:top()) + self._contact_text_header:move(padding, padding) + BoxGuiObject:new(self._contract_panel, { + sides = { + 1, + 1, + 1, + 1, + }, + }) + + self._step = 1 + self._steps = {} + + return + end + + BoxGuiObject:new(self._contract_panel, { + sides = { + 1, + 1, + 1, + 1, + }, + }) + + job_data.job_id = job_data.job_id or "ukrainian_job" + local narrative = tweak_data.narrative:job_data(job_data.job_id) + local narrative_chains = tweak_data.narrative:job_chain(job_data.job_id) + + self._contact_text_header:set_text(managers.localization:to_upper_text("menu_cn_contract_title", { + job = managers.localization:text(narrative.name_id), + })) + + local last_bottom = 0 + local contract_text = self._contract_panel:text({ + vertical = "top", + wrap = true, + align = "left", + wrap_word = true, + text = managers.localization:text(narrative.briefing_id), + w = text_w, + font_size = font_size, + font = font, + color = tweak_data.screen_colors.text, + x = padding, + y = padding, + }) + local _, _, _, h = contract_text:text_rect() + local scale = 1 + + if h + contract_text:top() > math.round(self._contract_panel:h() * 0.5) - font_size then + scale = (math.round(self._contract_panel:h() * 0.5) - font_size) / (h + contract_text:top()) + end + + contract_text:set_font_size(font_size * scale) + self:make_fine_text(contract_text) + + last_bottom = contract_text:bottom() + local is_job_ghostable = managers.job:is_job_ghostable(job_data.job_id) + + if is_job_ghostable then + local min_ghost_bonus, max_ghost_bonus = managers.job:get_job_ghost_bonus(job_data.job_id) + local min_ghost = math.round(min_ghost_bonus * 100) + local max_ghost = math.round(max_ghost_bonus * 100) + local min_string, max_string = nil + + if min_ghost == 0 and min_ghost_bonus ~= 0 then + min_string = string.format("%0.2f", math.abs(min_ghost_bonus * 100)) + else + min_string = tostring(math.abs(min_ghost)) + end + + if max_ghost == 0 and max_ghost_bonus ~= 0 then + max_string = string.format("%0.2f", math.abs(max_ghost_bonus * 100)) + else + max_string = tostring(math.abs(max_ghost)) + end + + local ghost_bonus_string = min_ghost_bonus == max_ghost_bonus and min_string or min_string .. "-" .. max_string + local ghostable_text = self._contract_panel:text({ + vertical = "top", + wrap = true, + align = "left", + wrap_word = true, + blend_mode = "add", + text = managers.localization:to_upper_text("menu_ghostable_job", { + bonus = ghost_bonus_string, + }), + w = text_w, + font_size = font_size, + font = font, + color = tweak_data.screen_colors.ghost_color, + }) + + ghostable_text:set_position(contract_text:x(), last_bottom + padding) + self:make_fine_text(ghostable_text) + + last_bottom = ghostable_text:bottom() + end + + if tweak_data.narrative:is_job_locked(job_data.job_id) then + local locked_text = self._contract_panel:text({ + font = tweak_data.menu.pd2_small_font, + font_size = font_size, + text = managers.localization:to_upper_text("bm_menu_vr_locked"), + color = tweak_data.screen_colors.important_1, + }) + + self:make_fine_text(locked_text) + locked_text:set_position(contract_text:x(), last_bottom + padding) + end + + local contact_panel = self._contract_panel:panel({ + w = contact_w, + h = contact_h, + x = text_w + double_padding, + y = padding, + }) + local contact_image = contact_panel:rect({ + color = Color(0.3, 0, 0, 0), + }) + local crimenet_videos = narrative.crimenet_videos + + if crimenet_videos then + local variant = math.random(#crimenet_videos) + + contact_panel:video({ + blend_mode = "add", + loop = true, + video = "movies/" .. crimenet_videos[variant], + width = contact_panel:w(), + height = contact_panel:h(), + color = tweak_data.screen_colors.button_stage_2, + }) + end + + local contact_text = self._contract_panel:text({ + text = managers.localization:to_upper_text(tweak_data.narrative.contacts[narrative.contact].name_id), + font_size = font_size, + font = font, + color = tweak_data.screen_colors.text, + }) + local x, y, w, h = contact_text:text_rect() + + contact_text:set_size(w, h) + contact_text:set_position(contact_panel:left(), contact_panel:bottom() + half_padding) + BoxGuiObject:new(contact_panel, { + sides = { + 1, + 1, + 1, + 1, + }, + }) + + local modifiers_text = self._contract_panel:text({ + name = "modifiers_text", + vertical = "top", + align = "left", + text = managers.localization:to_upper_text("menu_cn_modifiers"), + font = font, + font_size = font_size, + x = padding, + color = tweak_data.screen_colors.text, + w = text_w, + }) + + self:make_fine_text(modifiers_text) + modifiers_text:set_bottom(math.round(self._contract_panel:h() * 0.5 - font_size)) + + local next_top = modifiers_text:bottom() + local one_down_active = job_data.one_down == 1 + + if one_down_active then + local one_down_warning_text = self._contract_panel:text({ + name = "one_down_warning_text", + text = managers.localization:to_upper_text("menu_one_down"), + font = font, + font_size = font_size, + color = tweak_data.screen_colors.one_down, + }) + + self:make_fine_text(one_down_warning_text) + one_down_warning_text:set_top(next_top) + one_down_warning_text:set_left(double_padding) + + next_top = one_down_warning_text:bottom() + end + + local ghost_bonus_mul = managers.job:get_ghost_bonus() + local skill_bonus = managers.player:get_skill_exp_multiplier() + local infamy_bonus = managers.player:get_infamy_exp_multiplier() + local limited_bonus = managers.player:get_limited_exp_multiplier(job_data.job_id, nil) + local job_ghost = math.round(ghost_bonus_mul * 100) + local job_ghost_string = tostring(math.abs(job_ghost)) + local has_ghost_bonus = ghost_bonus_mul > 0 + + if job_ghost == 0 and ghost_bonus_mul ~= 0 then + job_ghost_string = string.format("%0.2f", math.abs(ghost_bonus_mul * 100)) + end + + local ghost_color = tweak_data.screen_colors.ghost_color + local ghost_warning_text = self._contract_panel:text({ + name = "ghost_color_warning_text", + vertical = "top", + word_wrap = true, + wrap = true, + align = "left", + blend_mode = "normal", + text = managers.localization:to_upper_text("menu_ghost_bonus", { + exp_bonus = job_ghost_string, + }), + font = font, + font_size = font_size, + color = ghost_color, + w = text_w, + }) + + self:make_fine_text(ghost_warning_text) + ghost_warning_text:set_top(next_top) + ghost_warning_text:set_left(double_padding) + ghost_warning_text:set_visible(has_ghost_bonus) + + if ghost_warning_text:visible() then + next_top = ghost_warning_text:bottom() + end + + local job_heat_value = managers.job:get_job_heat(job_data.job_id) or 0 + local ignore_heat = job_heat_value > 0 and self._customizable + local job_heat_mul = ignore_heat and 0 or managers.job:get_job_heat_multipliers(job_data.job_id) - 1 + local job_heat = math.round(job_heat_mul * 100) + local job_heat_string = tostring(math.abs(job_heat)) + local is_job_heated = job_heat ~= 0 or job_heat_mul ~= 0 + + if job_heat == 0 and job_heat_mul ~= 0 then + job_heat_string = string.format("%0.2f", math.abs(job_heat_mul * 100)) + end + + self._is_job_heated = is_job_heated + local heat_color = managers.job:get_job_heat_color(job_data.job_id) + local heat_text_id = "menu_heat_" .. (job_heat_mul > 0 and "warm" or job_heat_mul < 0 and "cold" or "ok") + local heat_warning_text = self._contract_panel:text({ + name = "heat_warning_text", + vertical = "top", + word_wrap = true, + wrap = true, + align = "left", + blend_mode = "normal", + text = managers.localization:to_upper_text(heat_text_id, { + job_heat = job_heat_string, + }), + font = font, + font_size = font_size, + color = heat_color, + w = text_w, + }) + + self:make_fine_text(heat_warning_text) + heat_warning_text:set_top(next_top) + heat_warning_text:set_left(double_padding) + heat_warning_text:set_visible(is_job_heated) + + self._heat_color = heat_color + + if heat_warning_text:visible() then + next_top = heat_warning_text:bottom() + end + + local pro_warning_text = self._contract_panel:text({ + name = "pro_warning_text", + vertical = "top", + word_wrap = true, + wrap = true, + align = "left", + blend_mode = "normal", + text = managers.localization:to_upper_text("menu_pro_warning"), + font = font, + font_size = font_size, + color = tweak_data.screen_colors.pro_color, + w = text_w, + }) + + self:make_fine_text(pro_warning_text) + pro_warning_text:set_h(pro_warning_text:h()) + pro_warning_text:set_left(double_padding) + pro_warning_text:set_top(next_top) + pro_warning_text:set_visible(narrative.professional) + + if pro_warning_text:visible() then + next_top = pro_warning_text:bottom() + end + + local is_christmas_job = managers.job:is_christmas_job(job_data.job_id) + local has_christmas_bonus = false + + if is_christmas_job then + local holiday_potential_bonus = managers.job:get_job_christmas_bonus(job_data.job_id) + local holiday_bonus_percentage = math.round(holiday_potential_bonus * 100) + has_christmas_bonus = holiday_bonus_percentage ~= 0 + + if has_christmas_bonus then + local holiday_string = tostring(holiday_bonus_percentage) + local holiday_text = self._contract_panel:text({ + vertical = "top", + wrap = true, + align = "left", + wrap_word = true, + blend_mode = "normal", + text = managers.localization:to_upper_text("holiday_warning_text", { + event_icon = managers.localization:get_default_macro("BTN_XMAS"), + bonus = holiday_string, + }), + w = text_w, + font_size = font_size, + font = font, + color = tweak_data.screen_colors.event_color, + }) + + holiday_text:set_position(double_padding, next_top) + self:make_fine_text(holiday_text) + + next_top = holiday_text:bottom() + end + end + + local any_modifier_available = heat_warning_text:visible() or one_down_active or pro_warning_text:visible() or ghost_warning_text:visible() + any_modifier_available = any_modifier_available or has_christmas_bonus + + modifiers_text:set_visible(any_modifier_available) + + local risk_title = self._contract_panel:text({ + font = font, + font_size = font_size, + text = managers.localization:to_upper_text("menu_risk"), + color = risk_color, + x = padding, + }) + + self:make_fine_text(risk_title) + risk_title:set_top(next_top) + + next_top = next_top + half_padding + local menu_risk_id = "menu_risk_pd" + + if job_data.difficulty == "hard" then + menu_risk_id = "menu_risk_swat" + elseif job_data.difficulty == "overkill" then + menu_risk_id = "menu_risk_fbi" + elseif job_data.difficulty == "overkill_145" then + menu_risk_id = "menu_risk_special" + elseif job_data.difficulty == "easy_wish" then + menu_risk_id = "menu_risk_easy_wish" + elseif job_data.difficulty == "overkill_290" then + menu_risk_id = "menu_risk_elite" + elseif job_data.difficulty == "sm_wish" then + menu_risk_id = "menu_risk_sm_wish" + end + + local risk_stats_panel = self._contract_panel:panel({ + name = "risk_stats_panel", + w = text_w, + x = padding, + }) + + risk_stats_panel:set_h(risk_title:h() + half_padding) + + local plvl = managers.experience:current_level() + local player_stars = math.max(math.ceil(plvl / 10), 1) + local job_stars = math.ceil(narrative.jc / 10) + local difficulty_stars = job_data.difficulty_id - 2 + local job_and_difficulty_stars = job_stars + difficulty_stars + local rsx = 15 + local risks = { + "risk_pd", + "risk_swat", + "risk_fbi", + "risk_death_squad", + "risk_sm_wish", + } + + local max_x = 0 + local max_y = 0 + + for i, name in ipairs(risks) do + if i ~= 1 then + local texture, rect = tweak_data.hud_icons:get_icon_data(name) + local active = false + local color = active and i ~= 1 and risk_color or Color.white + local alpha = active and 1 or 0.25 + local risk = self._contract_panel:bitmap({ + y = 0, + x = 0, + name = name, + texture = texture, + texture_rect = rect, + alpha = alpha, + color = color, + }) + + risk:set_x(rsx) + risk:set_top(math.round(risk_title:bottom())) + + rsx = rsx + risk:w() + 2 + local stat = managers.statistics:completed_job(job_data.job_id, tweak_data:index_to_difficulty(i + 1)) + local risk_stat = risk_stats_panel:text({ + align = "center", + name = name, + font = font, + font_size = font_size, + text = tostring(stat), + }) + + self:make_fine_text(risk_stat) + risk_stat:set_world_center_x(risk:world_center_x() - 1) + risk_stat:set_x(math.round(risk_stat:x())) + + local this_difficulty = i == difficulty_stars + 1 + active = i <= difficulty_stars + 1 + color = Color.white + alpha = 0.5 + + risk_stat:set_color(color) + risk_stat:set_alpha(alpha) + + max_y = math.max(max_y, risk:bottom()) + max_x = math.max(max_x, risk:right() + half_padding) + max_x = math.max(max_x, risk_stat:right() + risk_stats_panel:left() + padding) + end + end + + risk_stats_panel:set_top(math.round(max_y + 2)) + + local stat = managers.statistics:completed_job(job_data.job_id, tweak_data:index_to_difficulty(difficulty_stars + 2)) + local risk_text = self._contract_panel:text({ + vertical = "top", + name = "risk_text", + wrap = true, + align = "left", + word_wrap = true, + w = text_w - max_x, + text = managers.localization:to_upper_text(menu_risk_id) .. " " .. managers.localization:to_upper_text("menu_stat_job_completed", { + stat = tostring(stat), + }) .. " ", + font = font, + font_size = font_size, + color = risk_color, + x = max_x, + }) + + risk_text:set_top(math.round(risk_title:bottom())) + risk_text:set_h(risk_stats_panel:bottom() - risk_text:top()) + risk_text:hide() + + local potential_rewards_title = self._contract_panel:text({ + blend_mode = "add", + font = font, + font_size = font_size, + text = managers.localization:to_upper_text(self._customizable and "menu_potential_rewards_min" or "menu_potential_rewards", { + BTN_Y = managers.localization:btn_macro("menu_modify_item"), + }), + color = managers.menu:is_pc_controller() and self._customizable and tweak_data.screen_colors.button_stage_3 or tweak_data.screen_colors.text, + x = padding, + }) + + self:make_fine_text(potential_rewards_title) + potential_rewards_title:set_top(math.round(risk_stats_panel:bottom() + 4)) + + local jobpay_title = self._contract_panel:text({ + x = 20, + font = font, + font_size = font_size, + text = managers.localization:to_upper_text("cn_menu_contract_jobpay_header"), + color = tweak_data.screen_colors.text, + }) + + self:make_fine_text(jobpay_title) + jobpay_title:set_top(math.round(potential_rewards_title:bottom())) + + self._potential_rewards_title = potential_rewards_title + local experience_title = self._contract_panel:text({ + x = 20, + font = font, + font_size = font_size, + text = managers.localization:to_upper_text("menu_experience"), + color = tweak_data.screen_colors.text, + }) + + self:make_fine_text(experience_title) + experience_title:set_top(math.round(jobpay_title:bottom())) + + local sx = math.max(jobpay_title:right(), experience_title:right()) + sx = sx + 8 + local filled_star_rect = { + 0, + 32, + 32, + 32, + } + local empty_star_rect = { + 32, + 32, + 32, + 32, + } + local contract_visuals = job_data.contract_visuals or {} + local cy = experience_title:center_y() + local xp_min = contract_visuals.min_mission_xp and (type(contract_visuals.min_mission_xp) == "table" and contract_visuals.min_mission_xp[difficulty_stars + 1] or contract_visuals.min_mission_xp) + or 0 + local total_xp, dissected_xp = managers.experience:get_contract_xp_by_stars(job_data.job_id, job_stars, difficulty_stars, narrative.professional, #narrative_chains, { + ignore_heat = job_heat_value > 0 and self._customizable, + mission_xp = xp_min, + }) + local base_xp, risk_xp, heat_base_xp, heat_risk_xp, ghost_base_xp, ghost_risk_xp = unpack(dissected_xp) + local job_xp, add_xp, heat_add_xp, ghost_add_xp = self:_create_xp_appendices(sx, cy) + cy = jobpay_title:center_y() + local total_payout, base_payout, risk_payout = managers.money:get_contract_money_by_stars(job_stars, difficulty_stars, #narrative_chains, job_data.job_id) + local job_cash = self._contract_panel:text({ + name = "job_cash", + font = font, + font_size = font_size, + text = managers.experience:cash_string(0), + color = tweak_data.screen_colors.text, + }) + + self:make_fine_text(job_cash) + job_cash:set_x(sx) + job_cash:set_center_y(math.round(cy)) + + local add_cash = self._contract_panel:text({ + text = "", + name = "job_add_cash", + font = font, + font_size = font_size, + color = risk_color, + }) + + add_cash:set_text(" +" .. managers.experience:cash_string(math.round(0))) + self:make_fine_text(add_cash) + add_cash:set_x(math.round(job_cash:right())) + add_cash:set_center_y(math.round(cy)) + + local payday_money = math.round(total_payout) + local payday_text = self._contract_panel:text({ + name = "payday_text", + font = tweak_data.menu.pd2_large_font, + font_size = tweak_data.menu.pd2_large_font_size, + text = managers.localization:to_upper_text("menu_payday", { + MONEY = managers.experience:cash_string(0), + }), + color = tweak_data.screen_colors.text, + x = padding, + }) + + self:make_fine_text(payday_text) + payday_text:set_bottom(self._contract_panel:h() - padding) + + self._briefing_event = narrative.briefing_event + + if self._briefing_event then + self._briefing_len_panel = self._contract_panel:panel({ + w = contact_image:w(), + h = 2 * (font_size + padding), + }) + + self._briefing_len_panel:rect({ + blend_mode = "add", + name = "duration", + w = 0, + halign = "grow", + alpha = 0.6, + valign = "grow", + color = tweak_data.screen_colors.button_stage_3:with_alpha(0.2), + }) + self._briefing_len_panel:text({ + blend_mode = "add", + name = "text", + text = "", + layer = 1, + font = font, + font_size = font_size, + color = tweak_data.screen_colors.text, + x = padding, + y = padding, + }) + + local button_text = self._briefing_len_panel:text({ + blend_mode = "add", + name = "button_text", + text = " ", + layer = 1, + font = font, + font_size = font_size, + color = tweak_data.screen_colors.text, + x = padding, + y = padding, + }) + local _, _, _, h = button_text:text_rect() + + self._briefing_len_panel:set_h(2 * (h + padding)) + + if managers.menu:is_pc_controller() then + button_text:set_color(tweak_data.screen_colors.button_stage_3) + end + + BoxGuiObject:new(self._briefing_len_panel, { + sides = { + 1, + 1, + 1, + 1, + }, + }) + self._briefing_len_panel:set_position(contact_text:left(), contact_text:bottom() + padding) + end + + self._tabs = {} + self._pages = {} + self._active_page = nil + local tabs_panel = self._contract_panel:panel({ + y = 10, + w = contact_w, + h = contact_h, + x = text_w + 20, + }) + + tabs_panel:set_top((self._briefing_len_panel and self._briefing_len_panel:bottom() or contact_text:bottom()) + 10) + tabs_panel:set_visible(false) + + local pages_panel = self._contract_panel:panel({}) + + pages_panel:set_visible(false) + + local function add_tab(text_id) + local prev_tab = self._tabs[#self._tabs] + local tab_item = MenuGuiSmallTabItem:new(#self._tabs + 1, text_id, nil, self, 0, tabs_panel) + + table.insert(self._tabs, tab_item) + + if prev_tab then + tab_item._page_panel:set_left(prev_tab:next_page_position()) + end + + if #self._tabs == 1 then + tab_item:set_active(true) + + self._active_page = 1 + + tabs_panel:set_visible(true) + pages_panel:set_visible(true) + tabs_panel:set_h(tab_item._page_panel:bottom()) + pages_panel:set_size(contact_w, contact_h - tabs_panel:h()) + pages_panel:set_lefttop(tabs_panel:left(), tabs_panel:bottom() - 2) + BoxGuiObject:new(pages_panel, { + sides = { + 1, + 1, + 2, + 1, + }, + }) + managers.menu:active_menu().input:set_force_input(true) + end + + local page_panel = pages_panel:panel({}) + + page_panel:set_visible(tab_item:is_active()) + table.insert(self._pages, page_panel) + + return page_panel + end + + if job_data.mutators then + managers.mutators:set_crimenet_lobby_data(job_data.mutators) + + local mutator_tab_name = "menu_cn_" .. managers.mutators:get_enabled_active_mutator_category() .. "s" .. "_active" + local mutators_panel = add_tab(mutator_tab_name) + self._mutators_scroll = ScrollablePanel:new(mutators_panel, "mutators_scroll", { + padding = 0, + }) + local _y = half_padding + local mutators_list = {} + local last_item = nil + + for mutator_id, mutator_data in pairs(job_data.mutators) do + local mutator = managers.mutators:get_mutator_from_id(mutator_id) + + if mutator then + table.insert(mutators_list, mutator) + end + end + + table.sort(mutators_list, function(a, b) + return a:name() < b:name() + end) + + for i, mutator in ipairs(mutators_list) do + local mutator_text = self._mutators_scroll:canvas():text({ + name = "mutator_text_" .. tostring(i), + font = tweak_data.menu.pd2_small_font, + font_size = tweak_data.menu.pd2_small_font_size, + text = mutator:name(), + x = padding, + y = _y, + h = tweak_data.menu.pd2_small_font_size, + }) + _y = mutator_text:bottom() + 2 + last_item = mutator_text + end + + last_item:set_h(last_item:h() + padding) + self._mutators_scroll:update_canvas_size() + managers.mutators:set_crimenet_lobby_data(nil) + end + + if job_data.server == true then + local content_panel = add_tab("menu_cn_game_settings") + local _y = 7 + local add_back = true + + local function add_line(left_text, right_text) + if right_text == nil or left_text == nil then + return + end + + if add_back then + content_panel:rect({ + x = 8, + layer = -1, + y = _y, + h = tweak_data.menu.pd2_small_font_size, + w = content_panel:w() - 18, + color = Color.black:with_alpha(0.7), + }) + end + + add_back = not add_back + left_text = managers.localization:to_upper_text(left_text) + right_text = type(right_text) == "number" and tostring(right_text) or managers.localization:to_upper_text(right_text) + local left = content_panel:text({ + align = "left", + font = tweak_data.menu.pd2_small_font, + font_size = tweak_data.menu.pd2_small_font_size, + text = left_text, + x = padding, + y = _y, + h = tweak_data.menu.pd2_small_font_size, + w = content_panel:w() - double_padding, + color = Color(0.8, 0.8, 0.8), + }) + local right = content_panel:text({ + align = "right", + font = tweak_data.menu.pd2_small_font, + font_size = tweak_data.menu.pd2_small_font_size, + text = right_text .. " ", + x = padding, + y = _y, + h = tweak_data.menu.pd2_small_font_size, + w = content_panel:w() - 20, + color = Color(0.5, 0.5, 0.5), + }) + _y = math.max(left:bottom(), right:bottom()) + 2 + end + + local server_data = job_data.server_data + local tactics = { + "menu_plan_loud", + "menu_plan_stealth", + [-1.0] = "menu_any", + } + local kick = { + [0] = "menu_kick_disabled", + "menu_kick_server", + "menu_kick_vote", + } + local drop_in = { + [0] = "menu_off", + "menu_drop_in_on", + "menu_drop_in_prompt", + "menu_drop_in_stealth_prompt", + } + local permission = { + "menu_public_game", + "menu_friends_only_game", + "menu_private_game", + } + + add_line("menu_preferred_plan", tactics[server_data.job_plan]) + add_line("menu_kicking_allowed_option", kick[server_data.kick_option]) + add_line("menu_permission", permission[server_data.permission]) + add_line("menu_reputation_permission", server_data.min_level or 0) + add_line("menu_toggle_drop_in", drop_in[server_data.drop_in]) + end + + if job_data.mods then + local mods_presence = job_data.mods + + if mods_presence and mods_presence ~= "" and mods_presence ~= "1" then + local content_panel = add_tab("menu_cn_game_mods") + self._mods_tab = self._tabs[#self._tabs] + self._mods_scroll = ScrollablePanel:new(content_panel, "mods_scroll", { + padding = 0, + }) + self._mod_items = {} + local _y = 7 + local add_back = true + + local function add_line(id, text, ignore_back) + local canvas = self._mods_scroll:canvas() + + if add_back and not ignore_back then + canvas:rect({ + x = 8, + layer = -1, + y = _y, + h = tweak_data.menu.pd2_small_font_size, + w = canvas:w() - 18, + color = Color.black:with_alpha(0.7), + }) + end + + add_back = not add_back + text = string.upper(text) + local left_text = canvas:text({ + align = "left", + name = id, + font = tweak_data.menu.pd2_small_font, + font_size = tweak_data.menu.pd2_small_font_size, + text = text, + x = padding, + y = _y, + h = tweak_data.menu.pd2_small_font_size, + w = canvas:w() - double_padding, + color = Color(0.8, 0.8, 0.8), + }) + local highlight_text = canvas:text({ + blend_mode = "add", + align = "left", + visible = false, + name = id, + font = tweak_data.menu.pd2_small_font, + font_size = tweak_data.menu.pd2_small_font_size, + text = text, + x = padding, + y = _y, + h = tweak_data.menu.pd2_small_font_size, + w = canvas:w() - double_padding, + color = tweak_data.screen_colors.button_stage_2, + }) + _y = left_text:bottom() + 2 + + return left_text, highlight_text + end + + local splits = string.split(mods_presence, "|") + + for i = 1, #splits, 2 do + local text, highlight = add_line(splits[i + 1] or "", splits[i] or "") + + table.insert(self._mod_items, { + text, + highlight, + }) + end + + add_line("spacer", "", true) + self._mods_scroll:update_canvas_size() + end + end + + local days_multiplier = 0 + + for i = 1, #narrative_chains do + local day_mul = narrative.professional and tweak_data:get_value("experience_manager", "pro_day_multiplier", i) or tweak_data:get_value("experience_manager", "day_multiplier", i) + days_multiplier = days_multiplier + day_mul - 1 + end + + days_multiplier = 1 + days_multiplier / #narrative_chains + local last_day_mul = narrative.professional and tweak_data:get_value("experience_manager", "pro_day_multiplier", #narrative_chains) + or tweak_data:get_value("experience_manager", "day_multiplier", #narrative_chains) + self._data = { + job_cash = base_payout, + add_job_cash = risk_payout, + experience = base_xp, + add_experience = risk_xp, + heat_experience = heat_base_xp, + heat_add_experience = heat_risk_xp, + ghost_experience = ghost_base_xp, + ghost_add_experience = ghost_risk_xp, + num_stages_string = tostring(#narrative_chains) .. " x ", + payday_money = payday_money, + counted_job_cash = 0, + counted_job_xp = 0, + counted_risk_cash = 0, + counted_risk_xp = 0, + counted_heat_xp = 0, + counted_ghost_xp = 0, + counted_payday_money = 0, + stars = { + job_and_difficulty_stars = job_and_difficulty_stars, + job_stars = job_stars, + difficulty_stars = difficulty_stars, + }, + gui_objects = {}, + } + self._data.gui_objects.risk_stats_panel = risk_stats_panel + self._data.gui_objects.risk_text = risk_text + self._data.gui_objects.payday_text = payday_text + self._data.gui_objects.job_cash = job_cash + self._data.gui_objects.job_add_cash = add_cash + self._data.gui_objects.heat_add_xp = heat_add_xp + self._data.gui_objects.ghost_add_xp = ghost_add_xp + self._data.gui_objects.add_xp = add_xp + self._data.gui_objects.job_xp = job_xp + self._data.gui_objects.risks = { + "risk_pd", + "risk_swat", + "risk_fbi", + "risk_death_squad", + "risk_sm_wish", + } + + self._data.gui_objects.num_stars = 10 + self._wait_t = 0 + local reached_level_cap = managers.experience:reached_level_cap() + local levelup_text = reached_level_cap and managers.localization:to_upper_text("menu_reached_level_cap") + or managers.localization:to_upper_text("menu_levelup", { + levels = string.format("%0.1d%%", 0), + }) + local potential_level_up_text = self._contract_panel:text({ + blend_mode = "normal", + name = "potential_level_up_text", + visible = true, + layer = 3, + text = levelup_text, + font_size = tweak_data.menu.pd2_small_font_size, + font = tweak_data.menu.pd2_small_font, + color = tweak_data.hud_stats.potential_xp_color, + }) + + self:make_fine_text(potential_level_up_text) + potential_level_up_text:set_top(math.round(heat_add_xp:top())) + self:_update_xp_appendices() + + self._data.gui_objects.potential_level_up_text = potential_level_up_text + self._step = 1 + self._steps = { + "set_time", + "start_sound", + "start_counter", + "count_job_base", + "end_counter", + "count_difficulty_stars", + "start_counter", + "count_job_payday", + "end_counter", + "free_memory", + } + + if self._customizable then + if self._briefing_len_panel then + self._briefing_len_panel:hide() + end + + local premium_text = self._contract_panel:text({ + text = " ", + name = "premium_text", + wrap = true, + blend_mode = "add", + word_wrap = true, + font_size = font_size, + font = font, + color = tweak_data.screen_colors.button_stage_3, + }) + + premium_text:set_top(contact_text:bottom() + padding) + premium_text:set_left(contact_text:left()) + premium_text:set_w(contact_image:w()) + self._contact_text_header:set_text(managers.localization:to_upper_text("menu_cn_premium_buy_desc") .. ": " .. managers.localization:to_upper_text(narrative.name_id)) + + self._step = 1 + self._steps = { + "start_sound", + "set_all", + "free_memory", + } + elseif self._smart_matchmaking then + self._contact_text_header:set_text(managers.localization:to_upper_text("menu_smm_search_job") .. ": " .. managers.localization:to_upper_text(narrative.name_id)) + + self._step = 1 + self._steps = { + "set_time", + "start_sound", + "set_all", + "free_memory", + } + end + + self._current_job_star = 0 + self._current_difficulty_star = 0 + self._post_event_params = { + show_subtitle = false, + listener = { + end_of_event = true, + duration = true, + clbk = callback(self, self, "sound_event_callback"), + }, + } + + if not managers.menu:is_pc_controller() then + managers.menu:active_menu().input:deactivate_controller_mouse() + end + + self:_rec_round_object(self._panel) + + self._potential_show_max = false +end + +function CrimeNetContractGui:set_potential_rewards(show_max) + if self._step <= #self._steps then + return + end + + local job_data = self._node:parameters().menu_component_data + local narrative = tweak_data.narrative:job_data(job_data.job_id) + local narrative_chains = tweak_data.narrative:job_chain(job_data.job_id) + local job_stars = narrative.jc / 10 + local difficulty_stars = job_data.difficulty_id - 2 + local gui_panel = self._contract_panel + local potential_level_up_text = gui_panel:child("potential_level_up_text") + local job_heat_value = managers.job:get_job_heat(job_data.job_id) or 0 + local contract_visuals = job_data.contract_visuals or {} + local total_xp, dissected_xp, total_payout, base_payout, risk_payout = nil + + if show_max then + local xp_max = contract_visuals.max_mission_xp + and (type(contract_visuals.max_mission_xp) == "table" and contract_visuals.max_mission_xp[difficulty_stars + 1] or contract_visuals.max_mission_xp) + or 0 + total_xp, dissected_xp = managers.experience:get_contract_xp_by_stars(job_data.job_id, job_stars, difficulty_stars, job_data.professional, #narrative_chains, { + ignore_heat = job_heat_value > 0 and self._customizable, + mission_xp = xp_max, + }) + total_payout, base_payout, risk_payout = managers.money:get_contract_money_by_stars(job_stars, difficulty_stars, #narrative_chains, job_data.job_id, nil, { + mandatory_bags_value = contract_visuals.mandatory_bags_value and contract_visuals.mandatory_bags_value[difficulty_stars + 1], + bonus_bags_value = contract_visuals.bonus_bags_value and contract_visuals.bonus_bags_value[difficulty_stars + 1], + small_value = contract_visuals.small_value and contract_visuals.small_value[difficulty_stars + 1], + vehicle_value = contract_visuals.vehicle_value and contract_visuals.vehicle_value[difficulty_stars + 1], + }) + else + local xp_min = contract_visuals.min_mission_xp + and (type(contract_visuals.min_mission_xp) == "table" and contract_visuals.min_mission_xp[difficulty_stars + 1] or contract_visuals.min_mission_xp) + or 0 + total_xp, dissected_xp = managers.experience:get_contract_xp_by_stars(job_data.job_id, job_stars, difficulty_stars, job_data.professional, #narrative_chains, { + ignore_heat = job_heat_value > 0 and self._customizable, + mission_xp = xp_min, + }) + total_payout, base_payout, risk_payout = managers.money:get_contract_money_by_stars(job_stars, difficulty_stars, #narrative_chains, job_data.job_id) + end + + local base_xp, risk_xp, heat_base_xp, heat_risk_xp, ghost_base_xp, ghost_risk_xp = unpack(dissected_xp) + local num_stages_string = tostring(#narrative_chains) .. " x " + local xp = base_xp + local gui_xp = gui_panel:child("job_xp") + local gui_add_xp = gui_panel:child("add_xp") + local gui_heat_add_xp = gui_panel:child("heat_add_xp") + local gui_ghost_add_xp = gui_panel:child("ghost_add_xp") + local heat_xp = heat_base_xp + heat_risk_xp + local ghost_xp = ghost_base_xp + ghost_risk_xp + local risk_prefix = risk_xp >= 0 and " +" or " -" + local heat_prefix = heat_xp >= 0 and " +" or " -" + local ghost_prefix = ghost_xp >= 0 and " +" or " -" + local abs_risk_xp = math.abs(risk_xp) + local abs_heat_xp = math.abs(heat_xp) + local abs_ghost_xp = math.abs(ghost_xp) + + gui_xp:set_text(managers.money:add_decimal_marks_to_string(tostring(xp))) + self:make_fine_text(gui_xp) + gui_add_xp:set_text(risk_prefix .. managers.money:add_decimal_marks_to_string(tostring(abs_risk_xp))) + self:make_fine_text(gui_add_xp) + gui_heat_add_xp:set_text(heat_prefix .. managers.money:add_decimal_marks_to_string(tostring(abs_heat_xp))) + self:make_fine_text(gui_heat_add_xp) + gui_ghost_add_xp:set_text(ghost_prefix .. managers.money:add_decimal_marks_to_string(tostring(abs_ghost_xp))) + self:make_fine_text(gui_ghost_add_xp) + + if potential_level_up_text and not managers.experience:reached_level_cap() then + local gain_xp = base_xp + risk_xp + heat_xp + ghost_xp + local levels_gained = managers.experience:get_levels_gained_from_xp(gain_xp) + local levelup_text = managers.localization:to_upper_text("menu_levelup", { + levels = string.format("%0.1d%%", levels_gained * 100), + }) + + potential_level_up_text:set_text(levelup_text) + self:make_fine_text(potential_level_up_text) + self:_check_level_up(levels_gained) + end + + self:_update_xp_appendices() + + local job_cash = base_payout + local gui_job_cash = gui_panel:child("job_cash") + local gui_job_add_cash = gui_panel:child("job_add_cash") + + gui_job_cash:set_text(managers.experience:cash_string(job_cash)) + self:make_fine_text(gui_job_cash) + gui_job_add_cash:set_x(math.round(gui_job_cash:right())) + + local job_cash = risk_payout + local gui_job_add_cash = gui_panel:child("job_add_cash") + + gui_job_add_cash:set_text(" +" .. managers.experience:cash_string(job_cash)) + self:make_fine_text(gui_job_add_cash) + + local risks = { + "risk_swat", + "risk_fbi", + "risk_death_squad", + "risk_sm_wish", + } + + for i, risk in ipairs(risks) do + gui_panel:child(risk):set_alpha(i <= difficulty_stars and 1 or 0.25) + gui_panel:child(risk):set_color(i <= difficulty_stars and tweak_data.screen_colors.risk or Color.white) + + local this_difficulty = i == difficulty_stars + local active = i <= difficulty_stars + local color = active and tweak_data.screen_colors.risk or Color.white + local alpha = this_difficulty and 1 or 0.5 + + gui_panel:child("risk_stats_panel"):child(risk):set_color(color) + gui_panel:child("risk_stats_panel"):child(risk):set_alpha(alpha) + end + + gui_panel:child("risk_text"):show() + gui_panel:child("payday_text"):set_text(managers.localization:to_upper_text("menu_payday", { + MONEY = managers.experience:cash_string(total_payout), + })) + self:make_fine_text(gui_panel:child("payday_text")) + + local can_afford = managers.money:can_afford_buy_premium_contract(job_data.job_id, job_data.difficulty_id) + local text_id = MenuCallbackHandler:bang_active() and "menu_cn_premium_buy_fee_short" or "menu_cn_premium_buy_fee" + local text_string = managers.localization:to_upper_text(text_id, { + contract_fee = "##" .. managers.experience:cash_string(managers.money:get_cost_of_premium_contract(job_data.job_id, job_data.difficulty_id)) .. "##", + }) + local text_dissected = utf8.characters(text_string) + local idsp = Idstring("#") + local start_ci = {} + local end_ci = {} + local first_ci = true + + for i, c in ipairs(text_dissected) do + if Idstring(c) == idsp then + local next_c = text_dissected[i + 1] + + if next_c and Idstring(next_c) == idsp then + if first_ci then + table.insert(start_ci, i) + else + table.insert(end_ci, i) + end + + first_ci = not first_ci + end + end + end + + if #start_ci == #end_ci then + for i = 1, #start_ci do + start_ci[i] = start_ci[i] - ((i - 1) * 4 + 1) + end_ci[i] = end_ci[i] - (i * 4 - 1) + end + end + + text_string = string.gsub(text_string, "##", "") + local premium_text = gui_panel:child("premium_text") + + if alive(premium_text) then + premium_text:set_text(text_string) + premium_text:clear_range_color(1, utf8.len(text_string)) + + if #start_ci ~= #end_ci then + Application:error("CrimeNetContractGui: Not even amount of ##'s in skill description string!", #start_ci, #end_ci) + else + for i = 1, #start_ci do + premium_text:set_range_color(start_ci[i], end_ci[i], i == 1 and not can_afford and tweak_data.screen_colors.pro_color or tweak_data.screen_colors.button_stage_2) + end + end + end +end + +function CrimeNetContractGui:set_all(t, dt) + local job_data = self._node:parameters().menu_component_data + local narrative = tweak_data.narrative:job_data(job_data.job_id) + local narrative_chains = tweak_data.narrative:job_chain(job_data.job_id) + local job_stars = narrative.jc / 10 + local difficulty_stars = job_data.difficulty_id - 2 + local gui_panel = self._contract_panel + local potential_level_up_text = gui_panel:child("potential_level_up_text") + local job_heat_value = managers.job:get_job_heat(job_data.job_id) or 0 + local contract_visuals = job_data.contract_visuals or {} + local xp_min = contract_visuals.min_mission_xp and (type(contract_visuals.min_mission_xp) == "table" and contract_visuals.min_mission_xp[difficulty_stars + 1] or contract_visuals.min_mission_xp) + or 0 + local total_xp, dissected_xp = managers.experience:get_contract_xp_by_stars(job_data.job_id, job_stars, difficulty_stars, job_data.professional, #narrative_chains, { + ignore_heat = job_heat_value > 0 and self._customizable, + mission_xp = xp_min, + }) + local total_payout, base_payout, risk_payout = managers.money:get_contract_money_by_stars(job_stars, difficulty_stars, #narrative_chains, job_data.job_id) + local base_xp, risk_xp, heat_base_xp, heat_risk_xp, ghost_base_xp, ghost_risk_xp = unpack(dissected_xp) + local num_stages_string = tostring(#narrative_chains) .. " x " + local xp = base_xp + local gui_xp = gui_panel:child("job_xp") + local gui_add_xp = gui_panel:child("add_xp") + local gui_heat_add_xp = gui_panel:child("heat_add_xp") + local gui_ghost_add_xp = gui_panel:child("ghost_add_xp") + local heat_xp = heat_base_xp + heat_risk_xp + local ghost_xp = ghost_base_xp + ghost_risk_xp + local risk_prefix = risk_xp >= 0 and " +" or " -" + local heat_prefix = heat_xp >= 0 and " +" or " -" + local ghost_prefix = ghost_xp >= 0 and " +" or " -" + local abs_risk_xp = math.abs(risk_xp) + local abs_heat_xp = math.abs(heat_xp) + local abs_ghost_xp = math.abs(ghost_xp) + + gui_xp:set_text(managers.money:add_decimal_marks_to_string(tostring(xp))) + self:make_fine_text(gui_xp) + gui_add_xp:set_text(risk_prefix .. managers.money:add_decimal_marks_to_string(tostring(abs_risk_xp))) + self:make_fine_text(gui_add_xp) + gui_heat_add_xp:set_text(heat_prefix .. managers.money:add_decimal_marks_to_string(tostring(abs_heat_xp))) + self:make_fine_text(gui_heat_add_xp) + gui_ghost_add_xp:set_text(ghost_prefix .. managers.money:add_decimal_marks_to_string(tostring(abs_ghost_xp))) + self:make_fine_text(gui_ghost_add_xp) + + local job_cash = base_payout + local gui_job_cash = gui_panel:child("job_cash") + local gui_job_add_cash = gui_panel:child("job_add_cash") + + gui_job_cash:set_text(managers.experience:cash_string(job_cash)) + self:make_fine_text(gui_job_cash) + gui_job_add_cash:set_x(math.round(gui_job_cash:right())) + + if potential_level_up_text and not managers.experience:reached_level_cap() then + local gain_xp = base_xp + risk_xp + heat_xp + ghost_xp + local levels_gained = managers.experience:get_levels_gained_from_xp(gain_xp) + local levelup_text = managers.localization:to_upper_text("menu_levelup", { + levels = string.format("%0.1d%%", levels_gained * 100), + }) + + potential_level_up_text:set_text(levelup_text) + self:make_fine_text(potential_level_up_text) + self:_check_level_up(levels_gained) + end + + self:_update_xp_appendices() + + local job_cash = risk_payout + local gui_job_add_cash = gui_panel:child("job_add_cash") + + gui_job_add_cash:set_text(" +" .. managers.experience:cash_string(job_cash)) + self:make_fine_text(gui_job_add_cash) + + local risks = { + "risk_swat", + "risk_fbi", + "risk_death_squad", + "risk_sm_wish", + } + + for i, risk in ipairs(risks) do + gui_panel:child(risk):set_alpha(i <= difficulty_stars and 1 or 0.25) + gui_panel:child(risk):set_color(i <= difficulty_stars and tweak_data.screen_colors.risk or Color.white) + + local this_difficulty = i == difficulty_stars + local active = i <= difficulty_stars + local color = active and tweak_data.screen_colors.risk or Color.white + local alpha = this_difficulty and 1 or 0.5 + + gui_panel:child("risk_stats_panel"):child(risk):set_color(color) + gui_panel:child("risk_stats_panel"):child(risk):set_alpha(alpha) + end + + gui_panel:child("risk_text"):show() + gui_panel:child("payday_text"):set_text(managers.localization:to_upper_text("menu_payday", { + MONEY = managers.experience:cash_string(total_payout), + })) + self:make_fine_text(gui_panel:child("payday_text")) + + local can_afford = managers.money:can_afford_buy_premium_contract(job_data.job_id, job_data.difficulty_id) + local text_id = MenuCallbackHandler:bang_active() and "menu_cn_premium_buy_fee_short" or "menu_cn_premium_buy_fee" + local text_string = managers.localization:to_upper_text(text_id, { + contract_fee = "##" .. managers.experience:cash_string(managers.money:get_cost_of_premium_contract(job_data.job_id, job_data.difficulty_id)) .. "##", + }) + local text_dissected = utf8.characters(text_string) + local idsp = Idstring("#") + local start_ci = {} + local end_ci = {} + local first_ci = true + + for i, c in ipairs(text_dissected) do + if Idstring(c) == idsp then + local next_c = text_dissected[i + 1] + + if next_c and Idstring(next_c) == idsp then + if first_ci then + table.insert(start_ci, i) + else + table.insert(end_ci, i) + end + + first_ci = not first_ci + end + end + end + + if #start_ci == #end_ci then + for i = 1, #start_ci do + start_ci[i] = start_ci[i] - ((i - 1) * 4 + 1) + end_ci[i] = end_ci[i] - (i * 4 - 1) + end + end + + text_string = string.gsub(text_string, "##", "") + + if alive(gui_panel:child("premium_text")) then + gui_panel:child("premium_text"):set_text(text_string) + gui_panel:child("premium_text"):clear_range_color(1, utf8.len(text_string)) + + if #start_ci ~= #end_ci then + Application:error("CrimeNetContractGui: Not even amount of ##'s in skill description string!", #start_ci, #end_ci) + else + for i = 1, #start_ci do + gui_panel:child("premium_text"):set_range_color(start_ci[i], end_ci[i], i == 1 and not can_afford and tweak_data.screen_colors.pro_color or tweak_data.screen_colors.button_stage_2) + end + end + end + + if self._step == 2 then + self._step = self._step + 1 + end +end + +function CrimeNetContractGui:set_difficulty_id(difficulty_id) + local job_data = self._node:parameters().menu_component_data + local diffs = { + "easy", + "normal", + "hard", + "overkill", + "overkill_145", + "easy_wish", + "overkill_290", + "sm_wish", + } + job_data.difficulty_id = difficulty_id + job_data.difficulty = diffs[difficulty_id] + local menu_risk_id = "menu_risk_pd" + + if job_data.difficulty == "hard" then + menu_risk_id = "menu_risk_swat" + elseif job_data.difficulty == "overkill" then + menu_risk_id = "menu_risk_fbi" + elseif job_data.difficulty == "overkill_145" then + menu_risk_id = "menu_risk_special" + elseif job_data.difficulty == "easy_wish" then + menu_risk_id = "menu_risk_easy_wish" + elseif job_data.difficulty == "overkill_290" then + menu_risk_id = "menu_risk_elite" + elseif job_data.difficulty == "sm_wish" then + menu_risk_id = "menu_risk_sm_wish" + end + + local stat = managers.statistics:completed_job(job_data.job_id, tweak_data:index_to_difficulty(difficulty_id)) + local risk_text = self._contract_panel:child("risk_text") + + risk_text:set_text(managers.localization:to_upper_text(menu_risk_id) .. " " .. managers.localization:to_upper_text("menu_stat_job_completed", { + stat = tostring(stat), + }) .. " ") + + local _, _, _, h = risk_text:text_rect() + + risk_text:set_h(h) + self:set_potential_rewards(self._potential_show_max) +end diff --git a/lua/crimenetmanager.lua b/lua/crimenetmanager.lua new file mode 100644 index 0000000..f4543d7 --- /dev/null +++ b/lua/crimenetmanager.lua @@ -0,0 +1,17 @@ +function CrimeNetGui:four_stars(job, inside) + local stars_panel = job.side_panel:child("stars_panel") + + if inside.job_id then + stars_panel:child(4):hide() -- thanks james for this + stars_panel:child(5):hide() + end +end + +local gui = CrimeNetGui._create_job_gui +function CrimeNetGui:_create_job_gui(data, type, fixed_x, fixed_y, fixed_location) + local gui_data = gui(self, data, type, fixed_x, fixed_y, fixed_location) + + self:four_stars(gui_data, data) + + return gui_data +end diff --git a/lua/criminalactionwalk.lua b/lua/criminalactionwalk.lua new file mode 100644 index 0000000..c667501 --- /dev/null +++ b/lua/criminalactionwalk.lua @@ -0,0 +1,33 @@ +-- Apply default carry speed upgrade to bots and make them always use forward speed +function CriminalActionWalk:_get_max_walk_speed() + local speed = CriminalActionWalk.super._get_max_walk_speed(self) + + local carry_id = self._ext_movement:carry_id() + if not carry_id then + return speed + end + + local carry_upgrade = managers.player:upgrade_value("carry", "movement_speed_multiplier", 1) + local speed_modifier = math.clamp(tweak_data.carry.types[tweak_data.carry[carry_id].type].move_speed_modifier * carry_upgrade, 0, 1) + + speed = deep_clone(speed) + for k, v in pairs(speed) do + speed[k] = v * speed_modifier + end + + return speed +end + +function CriminalActionWalk:_get_current_max_walk_speed() + local speed = CriminalActionWalk.super._get_current_max_walk_speed(self, "fwd") + + local carry_id = self._ext_movement:carry_id() + if not carry_id then + return speed + end + + local carry_upgrade = managers.player:upgrade_value("carry", "movement_speed_multiplier", 1) + local speed_modifier = math.clamp(tweak_data.carry.types[tweak_data.carry[carry_id].type].move_speed_modifier * carry_upgrade, 0, 1) + + return speed * speed_modifier +end diff --git a/lua/criminalsmanager.lua b/lua/criminalsmanager.lua new file mode 100644 index 0000000..e9f18c2 --- /dev/null +++ b/lua/criminalsmanager.lua @@ -0,0 +1,5 @@ +if Global.level_data and (Global.level_data.level_id == "short2_stage1" or Global.level_data.level_id == "short2_stage2b") then + CriminalsManager.MAX_NR_TEAM_AI = 2 +else + CriminalsManager.MAX_NR_TEAM_AI = 1 +end diff --git a/lua/dlcmanager.lua b/lua/dlcmanager.lua new file mode 100644 index 0000000..d0215e4 --- /dev/null +++ b/lua/dlcmanager.lua @@ -0,0 +1,7 @@ +function GenericDLCManager:has_mrwi_deck() + return false +end + +function GenericDLCManager:has_mrwi_deck_equipped_mimicing(choice) + return false +end diff --git a/lua/dramatweakdata.lua b/lua/dramatweakdata.lua index cfae45c..9f42709 100644 --- a/lua/dramatweakdata.lua +++ b/lua/dramatweakdata.lua @@ -1,8 +1,8 @@ function DramaTweakData:init() self:_create_table_structure() - self.drama_actions = {criminal_hurt = 0.2, criminal_dead = 0.2, criminal_disabled = 0.1} - self.drama_balance_mul = {1.2, 0.8, 0.6, 0.35} + self.drama_actions = { criminal_hurt = 0.2, criminal_dead = 0.2, criminal_disabled = 0.1 } + self.drama_balance_mul = { 1.2, 0.8, 0.6, 0.35 } self.decay_period = 30 self.max_dis = 6000 self.max_dis_mul = 0.5 diff --git a/lua/drill.lua b/lua/drill.lua index cf18f63..95fba5d 100644 --- a/lua/drill.lua +++ b/lua/drill.lua @@ -1,6 +1,5 @@ -- Make autorepair reroll everytime the drill is jammed and fix swapped values -Hooks:PreHook(Drill, "set_jammed", "shc_set_jammed", function (self, jammed) - +Hooks:PreHook(Drill, "set_jammed", "shc_set_jammed", function(self, jammed) if Network:is_server() and jammed and not self._jammed then local current_auto_repair_level_1 = self._skill_upgrades.auto_repair_level_1 or 0 local current_auto_repair_level_2 = self._skill_upgrades.auto_repair_level_2 or 0 @@ -30,4 +29,4 @@ function Drill:set_autorepair(state, jammed) self._autorepair_clbk_id = "Drill_autorepair" .. tostring(self._unit:key()) managers.enemy:add_delayed_clbk(self._autorepair_clbk_id, callback(self, self, "clbk_autorepair"), TimerManager:game():time() + 5 + 5 * math.random()) end -end \ No newline at end of file +end diff --git a/lua/dynamicresourcemanager.lua b/lua/dynamicresourcemanager.lua new file mode 100644 index 0000000..aa0d42f --- /dev/null +++ b/lua/dynamicresourcemanager.lua @@ -0,0 +1,17 @@ +local ids_unit = Idstring("unit") +Hooks:PostHook(DynamicResourceManager, "preload_units", "sh_preload_units", function(self) + local function load_unit(path) + self:load(ids_unit, Idstring(path), self.DYN_RESOURCES_PACKAGE) + self:load(ids_unit, Idstring(path .. "_husk"), self.DYN_RESOURCES_PACKAGE) + end + + StreamHeist:log("Loading custom units...") + + if PackageManager:loaded("packages/sm_wish") then + StreamHeist:log("Zeal package loaded, loading custom Zeal units...") + load_unit("units/pd2_dlc_gitgud/characters/ene_zeal_swat_2/ene_zeal_swat_2") + load_unit("units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy_2/ene_zeal_swat_heavy_2") + load_unit("units/pd2_dlc_gitgud/characters/ene_zeal_medic_m4/ene_zeal_medic_m4") + load_unit("units/pd2_dlc_gitgud/characters/ene_zeal_medic_r870/ene_zeal_medic_r870") + end +end) diff --git a/lua/elementareatrigger.lua b/lua/elementareatrigger.lua index 62a79b3..ede21da 100644 --- a/lua/elementareatrigger.lua +++ b/lua/elementareatrigger.lua @@ -49,20 +49,20 @@ Hooks:PostHook(ElementAreaTrigger, "on_set_enabled", "sh_on_set_enabled", check_ -- Point of no return escape zones only need the players who aren't downed to trigger the escape local old_project_instigators = ElementAreaTrigger.project_instigators function ElementAreaTrigger:project_instigators() - local instigators = old_project_instigators(self) + local instigators = old_project_instigators(self) - if Network:is_client() then - if self._values.instigator == "criminals_not_downed" then - table.insert(instigators, managers.player:player_unit()) - return instigators - end - end + if Network:is_client() then + if self._values.instigator == "criminals_not_downed" then + table.insert(instigators, managers.player:player_unit()) + return instigators + end + end - if self._values.instigator == "criminals_not_downed" then - table.insert(instigators, managers.player:player_unit()) - end + if self._values.instigator == "criminals_not_downed" then + table.insert(instigators, managers.player:player_unit()) + end - return instigators + return instigators end function ElementAreaTrigger:project_amount_inside() @@ -91,16 +91,16 @@ function ElementAreaTrigger:project_amount_inside() counter = counter + 1 end end - elseif self._values.instigator == "criminals_not_downed" then - counter = 0 - - for _, criminal in pairs(managers.groupai:state():all_char_criminals()) do - for _, instigator in pairs(self._inside) do - if criminal.unit == instigator and not criminal.unit:movement():downed() then - counter = counter + 1 - end - end - end + elseif self._values.instigator == "criminals_not_downed" then + counter = 0 + + for _, criminal in pairs(managers.groupai:state():all_player_criminals()) do + for _, instigator in pairs(self._inside) do + if criminal.unit == instigator and not criminal.unit:movement():downed() then + counter = counter + 1 + end + end + end end return counter @@ -123,16 +123,16 @@ function ElementAreaTrigger:project_amount_all() end return i - elseif self._values.instigator == "criminals_not_downed" then - local i = 0 + elseif self._values.instigator == "criminals_not_downed" then + local i = 0 - for _, data in pairs(managers.groupai:state():all_char_criminals()) do - if not data.unit:movement():downed() then - i = i + 1 - end - end + for _, data in pairs(managers.groupai:state():all_player_criminals()) do + if not data.unit:movement():downed() then + i = i + 1 + end + end - return i + return i end return managers.network:session() and managers.network:session():amount_of_alive_players() or 0 diff --git a/lua/elementspawnenemydummy.lua b/lua/elementspawnenemydummy.lua index fa044d0..84cd8a9 100644 --- a/lua/elementspawnenemydummy.lua +++ b/lua/elementspawnenemydummy.lua @@ -1,49 +1,97 @@ -- Don't replace spawns on custom enemy spawner map -local level_id = Global.game_settings and Global.game_settings.level_id +local level_id = Global.game_settings and Global.game_settings.level_id if Global.editor_mode or level_id == "modders_devmap" or level_id == "Enemy_Spawner" then StreamHeist:log("Editor/Spawner mode is active, spawn group fixes disabled") return end +-- Map to correct incorrect faction spawns +local enemy_replacements = { + normal = { + swat_1 = "units/payday2/characters/ene_swat_1/ene_swat_1", + swat_2 = "units/payday2/characters/ene_swat_2/ene_swat_2", + swat_3 = "units/payday2/characters/ene_swat_1/ene_swat_1", + heavy_1 = "units/payday2/characters/ene_swat_1/ene_swat_1", + heavy_2 = "units/payday2/characters/ene_swat_2/ene_swat_2", + shield = "units/payday2/characters/ene_shield_2/ene_shield_2", + sniper = "units/payday2/characters/ene_sniper_1/ene_sniper_1", + dozer_1 = "units/payday2/characters/ene_bulldozer_1/ene_bulldozer_1", + }, + overkill = { + swat_1 = "units/payday2/characters/ene_fbi_swat_1/ene_fbi_swat_1", + swat_2 = "units/payday2/characters/ene_fbi_swat_2/ene_fbi_swat_2", + swat_3 = "units/payday2/characters/ene_fbi_swat_1/ene_fbi_swat_1", + heavy_1 = "units/payday2/characters/ene_fbi_swat_1/ene_fbi_swat_1", + heavy_2 = "units/payday2/characters/ene_fbi_swat_2/ene_fbi_swat_2", + shield = "units/payday2/characters/ene_shield_1/ene_shield_1", + sniper = "units/payday2/characters/ene_sniper_1/ene_sniper_1", + dozer_1 = "units/payday2/characters/ene_bulldozer_1/ene_bulldozer_1", + }, + overkill_145 = { + swat_1 = "units/payday2/characters/ene_fbi_swat_1/ene_fbi_swat_1", + swat_2 = "units/payday2/characters/ene_fbi_swat_2/ene_fbi_swat_2", + swat_3 = "units/payday2/characters/ene_fbi_swat_1/ene_fbi_swat_1", + heavy_1 = "units/payday2/characters/ene_fbi_heavy_1/ene_fbi_heavy_1", + heavy_2 = "units/payday2/characters/ene_fbi_heavy_r870/ene_fbi_heavy_r870", + shield = "units/payday2/characters/ene_shield_1/ene_shield_1", + sniper = "units/payday2/characters/ene_sniper_2/ene_sniper_2", + dozer_1 = "units/payday2/characters/ene_bulldozer_1/ene_bulldozer_1", + }, + easy_wish = { + swat_1 = "units/payday2/characters/ene_city_swat_1/ene_city_swat_1", + swat_2 = "units/payday2/characters/ene_city_swat_2/ene_city_swat_2", + swat_3 = "units/payday2/characters/ene_city_swat_3/ene_city_swat_3", + heavy_1 = "units/payday2/characters/ene_fbi_heavy_1/ene_fbi_heavy_1", + heavy_2 = "units/payday2/characters/ene_fbi_heavy_r870/ene_fbi_heavy_r870", + shield = "units/payday2/characters/ene_shield_1/ene_shield_1", + sniper = "units/payday2/characters/ene_sniper_2/ene_sniper_2", + dozer_1 = "units/payday2/characters/ene_bulldozer_1/ene_bulldozer_1", + }, +} +enemy_replacements.hard = enemy_replacements.normal local enemy_mapping = { - [Idstring("units/payday2/characters/ene_bulldozer_1/ene_bulldozer_1"):key()] = "units/payday2/characters/ene_bulldozer_1/ene_bulldozer_1", - [Idstring("units/payday2/characters/ene_bulldozer_2/ene_bulldozer_2"):key()] = "units/payday2/characters/ene_bulldozer_1/ene_bulldozer_1", - [Idstring("units/payday2/characters/ene_bulldozer_3/ene_bulldozer_3"):key()] = "units/payday2/characters/ene_bulldozer_3/ene_bulldozer_3", - [Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_bulldozer_2/ene_zeal_bulldozer_2"):key()] = "units/payday2/characters/ene_bulldozer_1/ene_bulldozer_1", - [Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_bulldozer_3/ene_zeal_bulldozer_3"):key()] = "units/payday2/characters/ene_bulldozer_1/ene_bulldozer_1", - [Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_bulldozer/ene_zeal_bulldozer"):key()] = "units/payday2/characters/ene_bulldozer_3/ene_bulldozer_3", - [Idstring("units/payday2/characters/ene_zeal_cloaker/ene_zeal_cloaker"):key()] = "units/payday2/characters/ene_spook_1/ene_spook_1", - [Idstring("units/payday2/characters/ene_zeal_tazer/ene_zeal_tazer"):key()] = "units/payday2/characters/ene_tazer_1/ene_tazer_1", - [Idstring("units/payday2/characters/ene_city_heavy_g36/ene_city_heavy_g36"):key()] = "units/payday2/characters/ene_fbi_heavy_1/ene_fbi_heavy_1", - [Idstring("units/payday2/characters/ene_city_heavy_r870/ene_city_heavy_r870"):key()] = "units/payday2/characters/ene_fbi_heavy_r870/ene_fbi_heavy_r870", - [Idstring("units/payday2/characters/ene_city_shield/ene_city_shield"):key()] = "units/payday2/characters/ene_shield_1/ene_shield_1", - [Idstring("units/payday2/characters/ene_city_swat_1/ene_city_swat_1"):key()] = "units/payday2/characters/ene_city_swat_1/ene_city_swat_1", - [Idstring("units/payday2/characters/ene_city_swat_2/ene_city_swat_2"):key()] = "units/payday2/characters/ene_city_swat_2/ene_city_swat_2", - [Idstring("units/payday2/characters/ene_city_swat_3/ene_city_swat_3"):key()] = "units/payday2/characters/ene_city_swat_3/ene_city_swat_3", - [Idstring("units/payday2/characters/ene_city_swat_r870/ene_city_swat_r870"):key()] = "units/payday2/characters/ene_city_swat_2/ene_city_swat_2", - [Idstring("units/payday2/characters/ene_fbi_heavy_1/ene_fbi_heavy_1"):key()] = "units/payday2/characters/ene_fbi_swat_1/ene_fbi_swat_1", - [Idstring("units/payday2/characters/ene_fbi_heavy_r870/ene_fbi_heavy_r870"):key()] = "units/payday2/characters/ene_fbi_swat_2/ene_fbi_swat_2", - [Idstring("units/payday2/characters/ene_fbi_swat_1/ene_fbi_swat_1"):key()] = "units/payday2/characters/ene_fbi_swat_1/ene_fbi_swat_1", - [Idstring("units/payday2/characters/ene_fbi_swat_2/ene_fbi_swat_2"):key()] = "units/payday2/characters/ene_fbi_swat_2/ene_fbi_swat_2", - [Idstring("units/payday2/characters/ene_shield_1/ene_shield_1"):key()] = "units/payday2/characters/ene_shield_1/ene_shield_1", - [Idstring("units/payday2/characters/ene_shield_2/ene_shield_2"):key()] = "units/payday2/characters/ene_shield_1/ene_shield_1", - [Idstring("units/payday2/characters/ene_sniper_1/ene_sniper_1"):key()] = "units/payday2/characters/ene_sniper_2/ene_sniper_2", - [Idstring("units/payday2/characters/ene_sniper_2/ene_sniper_2"):key()] = "units/payday2/characters/ene_sniper_2/ene_sniper_2", - [Idstring("units/payday2/characters/ene_swat_1/ene_swat_1"):key()] = "units/payday2/characters/ene_city_swat_3/ene_city_swat_3", - [Idstring("units/payday2/characters/ene_swat_2/ene_swat_2"):key()] = "units/payday2/characters/ene_city_swat_1/ene_city_swat_1", - [Idstring("units/payday2/characters/ene_swat_heavy_1/ene_swat_heavy_1"):key()] = "units/payday2/characters/ene_fbi_swat_1/ene_fbi_swat_1", - [Idstring("units/payday2/characters/ene_swat_heavy_r870/ene_swat_heavy_r870"):key()] = "units/payday2/characters/ene_fbi_swat_2/ene_fbi_swat_2", - [Idstring("units/payday2/characters/ene_tazer_1/ene_tazer_1"):key()] = "units/payday2/characters/ene_tazer_1/ene_tazer_1", - [Idstring("units/payday2/characters/ene_spook_1/ene_spook_1"):key()] = "units/payday2/characters/ene_spook_1/ene_spook_1", - [Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy/ene_zeal_swat_heavy"):key()] = "units/payday2/characters/ene_city_swat_3/ene_city_swat_3", - [Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_swat_shield/ene_zeal_swat_shield"):key()] = "units/payday2/characters/ene_shield_1/ene_shield_1", - [Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_swat/ene_zeal_swat"):key()] = "units/payday2/characters/ene_city_swat_3/ene_city_swat_3" + [Idstring("units/payday2/characters/ene_bulldozer_1/ene_bulldozer_1"):key()] = "dozer_1", + [Idstring("units/payday2/characters/ene_bulldozer_2/ene_bulldozer_2"):key()] = "dozer_1", + [Idstring("units/payday2/characters/ene_bulldozer_3/ene_bulldozer_3"):key()] = "dozer_1", + [Idstring("units/payday2/characters/ene_city_heavy_g36/ene_city_heavy_g36"):key()] = "heavy_1", + [Idstring("units/payday2/characters/ene_city_heavy_r870/ene_city_heavy_r870"):key()] = "heavy_2", + [Idstring("units/payday2/characters/ene_city_shield/ene_city_shield"):key()] = "shield", + [Idstring("units/payday2/characters/ene_city_swat_1/ene_city_swat_1"):key()] = "swat_1", + [Idstring("units/payday2/characters/ene_city_swat_2/ene_city_swat_2"):key()] = "swat_2", + [Idstring("units/payday2/characters/ene_city_swat_3/ene_city_swat_3"):key()] = "swat_3", + [Idstring("units/payday2/characters/ene_city_swat_r870/ene_city_swat_r870"):key()] = "swat_2", + [Idstring("units/payday2/characters/ene_fbi_heavy_1/ene_fbi_heavy_1"):key()] = "heavy_1", + [Idstring("units/payday2/characters/ene_fbi_heavy_r870/ene_fbi_heavy_r870"):key()] = "heavy_2", + [Idstring("units/payday2/characters/ene_fbi_swat_1/ene_fbi_swat_1"):key()] = "swat_1", + [Idstring("units/payday2/characters/ene_fbi_swat_2/ene_fbi_swat_2"):key()] = "swat_2", + [Idstring("units/payday2/characters/ene_shield_1/ene_shield_1"):key()] = "shield", + [Idstring("units/payday2/characters/ene_shield_2/ene_shield_2"):key()] = "shield", + [Idstring("units/payday2/characters/ene_sniper_1/ene_sniper_1"):key()] = "sniper", + [Idstring("units/payday2/characters/ene_sniper_2/ene_sniper_2"):key()] = "sniper", + [Idstring("units/payday2/characters/ene_swat_1/ene_swat_1"):key()] = "swat_1", + [Idstring("units/payday2/characters/ene_swat_2/ene_swat_2"):key()] = "swat_2", + [Idstring("units/payday2/characters/ene_swat_heavy_1/ene_swat_heavy_1"):key()] = "heavy_1", + [Idstring("units/payday2/characters/ene_swat_heavy_r870/ene_swat_heavy_r870"):key()] = "heavy_2", + [Idstring("units/payday2/characters/ene_medic_m4/ene_medic_m4"):key()] = "medic_1", + [Idstring("units/payday2/characters/ene_medic_r870/ene_medic_r870"):key()] = "medic_2", + [Idstring("units/payday2/characters/ene_tazer_1/ene_tazer_1"):key()] = "taser", + [Idstring("units/payday2/characters/ene_spook_1/ene_spook_1"):key()] = "cloaker", + [Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy/ene_zeal_swat_heavy"):key()] = "heavy_1", + [Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_swat_shield/ene_zeal_swat_shield"):key()] = "shield", + [Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_swat/ene_zeal_swat"):key()] = "swat_1", } +local difficulty +if tweak_data.levels[level_id] and tweak_data.levels[level_id].group_ai_state == "skirmish" then + difficulty = "normal" +else + difficulty = Global.game_settings and Global.game_settings.difficulty or "normal" +end Hooks:PostHook(ElementSpawnEnemyDummy, "init", "sh_init", function(self) - local mapped_unit = enemy_mapping[self._enemy_name:key()] + local mapped_name = enemy_mapping[self._enemy_name:key()] + local mapped_unit = enemy_replacements[difficulty] and enemy_replacements[difficulty][mapped_name] local mapped_unit_ids = mapped_unit and Idstring(mapped_unit) if mapped_unit_ids and mapped_unit_ids ~= self._enemy_name then self._enemy_name = mapped_unit_ids end -end) \ No newline at end of file +end) diff --git a/lua/elementspawnenemygroup.lua b/lua/elementspawnenemygroup.lua index 26bc158..d8ca62c 100644 --- a/lua/elementspawnenemygroup.lua +++ b/lua/elementspawnenemygroup.lua @@ -1,5 +1,5 @@ -- Remove some dodgy code for forced group spawns, forcing spawn groups has been fixed in GroupAIStateBesiege:force_spawn_group -Hooks:OverrideFunction(ElementSpawnEnemyGroup, "on_executed", function (self, instigator) +Hooks:OverrideFunction(ElementSpawnEnemyGroup, "on_executed", function(self, instigator) if not self._values.enabled then return end @@ -17,7 +17,7 @@ Hooks:OverrideFunction(ElementSpawnEnemyGroup, "on_executed", function (self, in for i = 1, self:get_random_table_value(self._group_data.amount) do local element = self._spawn_points[self:_get_spawn_point(i)] element:produce({ - team = self._values.team + team = self._values.team, }) end end @@ -35,18 +35,18 @@ local groupsOLD = { "tac_shield_wall", "tac_swat_rifle_flank", "tac_shield_wall_ranged", - "tac_bull_rush" + "tac_bull_rush", } local twat_captain = { Phalanx = true, - single_spooc = true + single_spooc = true, } Hooks:PostHook(ElementSpawnEnemyGroup, "_finalize_values", "eclipse__finalize_values", function(self) if self._values.preferred_spawn_groups and #self._values.preferred_spawn_groups == #groupsOLD and table.contains_all(self._values.preferred_spawn_groups, groupsOLD) then self._values.preferred_spawn_groups = {} - for name,_ in pairs(tweak_data.group_ai.enemy_spawn_groups) do + for name, _ in pairs(tweak_data.group_ai.enemy_spawn_groups) do if not table.contains(self._values.preferred_spawn_groups, name) and not twat_captain[name] then table.insert(self._values.preferred_spawn_groups, name) end diff --git a/lua/elementspecialobjective.lua b/lua/elementspecialobjective.lua index 328ff5c..bbbebe8 100644 --- a/lua/elementspecialobjective.lua +++ b/lua/elementspecialobjective.lua @@ -1,3 +1,10 @@ +-- Keep position saved for AI SOs to fix some older map scripting +Hooks:PreHook(ElementSpecialObjective, "_finalize_values", "sh__finalize_values", function(self, values) + if self:value("so_action"):begins("AI") and values.path_style == "destination" then + self._AI_SO_pos = values.position + end +end) + -- Keep hunt and search as actual objective types instead of making it defend_area -- This is done to be able to differentiate between those objectives and make hunt work properly (search is currently unused) local get_objective_original = ElementSpecialObjective.get_objective @@ -9,6 +16,11 @@ function ElementSpecialObjective:get_objective(...) if objective_type == "hunt" or objective_type == "search" then objective.type = objective_type end + + if not objective.nav_seg and self._AI_SO_pos then + objective.nav_seg = managers.navigation:get_nav_seg_from_pos(self._AI_SO_pos) + objective.area = managers.groupai:state():get_area_from_nav_seg_id(objective.nav_seg) + end end return objective diff --git a/lua/enemymanager.lua b/lua/enemymanager.lua index c7fb3d5..23b720f 100644 --- a/lua/enemymanager.lua +++ b/lua/enemymanager.lua @@ -1,11 +1,10 @@ -- Make medics require line of sight to heal and optimize function -Hooks:OverrideFunction(EnemyManager, "get_nearby_medic", function (self, unit) +Hooks:OverrideFunction(EnemyManager, "get_nearby_medic", function(self, unit) if self:is_civilian(unit) then return end - local gstate = managers.groupai:state() - local medics = gstate._special_units.medic + local medics = managers.groupai:state()._special_units.medic if not medics then return end @@ -13,9 +12,29 @@ Hooks:OverrideFunction(EnemyManager, "get_nearby_medic", function (self, unit) local radius_sq = tweak_data.medic.radius ^ 2 local unit_pos = unit:movement():m_head_pos() local unit_data = self._enemy_data.unit_data + local vision_slot_mask = managers.slot:get_mask("AI_visibility") + + local function is_valid(medic) + if not medic or medic.unit == unit then + return false + end + + local anim_data = medic.unit:anim_data() + if anim_data.hurt or anim_data.act then + return false + end + + local medic_pos = medic.unit:movement():m_head_pos() + if mvector3.distance_sq(medic_pos, unit_pos) > radius_sq then + return false + end + + return not World:raycast("ray", unit_pos, medic_pos, "slot_mask", vision_slot_mask, "report") + end + for u_key, _ in pairs(medics) do local medic = unit_data[u_key] - if medic and unit ~= medic.unit and mvector3.distance_sq(medic.m_pos, unit_pos) <= radius_sq and not World:raycast("ray", unit_pos, medic.unit:movement():m_head_pos(), "slot_mask", managers.slot:get_mask("AI_visibility"), "report") then + if is_valid(medic) then return medic.unit end end diff --git a/lua/enveffecttweakdata.lua b/lua/enveffecttweakdata.lua index 701516b..c4704ba 100644 --- a/lua/enveffecttweakdata.lua +++ b/lua/enveffecttweakdata.lua @@ -20,8 +20,8 @@ function EnvEffectTweakData:molotov_fire() dot_damage = 7.5, dot_length = 6, dot_trigger_max_distance = 3000, - dot_tick_period = 0.25 - } + dot_tick_period = 0.25, + }, } return params @@ -46,8 +46,8 @@ function EnvEffectTweakData:incendiary_fire() dot_damage = 7.5, dot_length = 6, dot_trigger_max_distance = 3000, - dot_tick_period = 0.25 - } + dot_tick_period = 0.25, + }, } return params @@ -72,9 +72,9 @@ function EnvEffectTweakData:incendiary_fire_arbiter() dot_damage = 7.5, dot_length = 6, dot_trigger_max_distance = 3000, - dot_tick_period = 0.25 - } + dot_tick_period = 0.25, + }, } return params -end \ No newline at end of file +end diff --git a/lua/environmentfire.lua b/lua/environmentfire.lua index d0b2e9d..adca0f8 100644 --- a/lua/environmentfire.lua +++ b/lua/environmentfire.lua @@ -31,7 +31,7 @@ function EnvironmentFire:on_spawn(data, normal, user_unit, added_time, range_mul feedback_range = data.range * 2, sound_event_burning = data.sound_event_burning, sound_event_impact_duration = data.sound_event_impact_duration, - sound_event_duration = data.burn_duration + added_time + sound_event_duration = data.burn_duration + added_time, } self._data = data self._normal = normal @@ -190,9 +190,9 @@ function EnvironmentFire:_do_damage() owner = self._unit, alert_radius = self._fire_alert_radius, fire_dot_data = self._fire_dot_data, - is_molotov = self._is_molotov + is_molotov = self._is_molotov, }) end self._burn_tick_counter = 0 -end \ No newline at end of file +end diff --git a/lua/eventjobstweakdata.lua b/lua/eventjobstweakdata.lua new file mode 100644 index 0000000..e432131 --- /dev/null +++ b/lua/eventjobstweakdata.lua @@ -0,0 +1,3 @@ +Hooks:PostHook(EventJobsTweakData, "init", "eclipse_init", function(self) + table.delete(self.challenges, self.challenges[19]) -- no shredding christmas sidejob to avoid crashes +end) diff --git a/lua/fpcameraplayerbase.lua b/lua/fpcameraplayerbase.lua index 4555641..2eb9101 100644 --- a/lua/fpcameraplayerbase.lua +++ b/lua/fpcameraplayerbase.lua @@ -1,6 +1,5 @@ -- No cloaker camera tilt -function FPCameraPlayerBase:clbk_aim_assist(col_ray) -end +function FPCameraPlayerBase:clbk_aim_assist(col_ray) end -- static recoil that actually feels good function FPCameraPlayerBase:stop_shooting(wait) @@ -10,7 +9,7 @@ function FPCameraPlayerBase:stop_shooting(wait) end function FPCameraPlayerBase:recoil_kick(up, down, left, right) - if math.abs(self._recoil_kick.accumulated) < 200 then + if math.abs(self._recoil_kick.accumulated) < 2000 then local v = math.lerp(up, down, math.random()) self._recoil_kick.accumulated = (self._recoil_kick.accumulated or 0) + v self._recoil_kick.ret = v @@ -20,3 +19,61 @@ function FPCameraPlayerBase:recoil_kick(up, down, left, right) self._recoil_kick.h.accumulated = (self._recoil_kick.h.accumulated or 0) + h self._recoil_kick.h.ret = h end + +-- Spray pattern implementation +Hooks:PostHook(FPCameraPlayerBase, "init", "spray_init", function(self) + -- Some fields to initialize + self._recoil_recovery_t = 0 + self._pattern_index = 1 + self._persist_pattern_index = 1 + self._persist_pattern_back = 1 + self._h_recoil_cushion = 0 +end) +Hooks:PostHook(FPCameraPlayerBase, "update", "spray_update", function(self, unit, t, dt) + -- Count the time since the player last shot + if self._recoil_recovery_t > 0 then + self._recoil_recovery_t = self._recoil_recovery_t - dt + end +end) +function FPCameraPlayerBase:pattern_recoil_kick(pattern, persist_pattern, recoil_multiplier, recoil_recovery) + -- If the player hasn't shot in 1/3rd of second reset the recoil pattern + if self._recoil_recovery_t <= 0 then + self._pattern_index = 1 + end + self._recoil_recovery_t = recoil_recovery + -- Stability affects spray patterns + -- Bruteforce way but idc + -- Do the first part of the spray pattern + if self._pattern_index <= #pattern then + local v = math.lerp(pattern[self._pattern_index].up * recoil_multiplier, pattern[self._pattern_index].down * recoil_multiplier, math.random()) + self._recoil_kick.accumulated = (self._recoil_kick.accumulated or 0) + v + + local h = math.lerp(pattern[self._pattern_index].left * recoil_multiplier, pattern[self._pattern_index].right * recoil_multiplier, math.random()) + self._recoil_kick.h.accumulated = (self._recoil_kick.h.accumulated or 0) + h + + self._pattern_index = self._pattern_index + 1 + else -- Second part of the spray pattern (persist pattern) + if self._persist_pattern_index >= #persist_pattern then + self._persist_pattern_index = 1 + end + -- Reverse horizontal spray after a threshold + -- Add a cushion in case the recoil gets stuck too far in one direction + if math.abs(self._recoil_kick.h.accumulated) >= 7 and self._h_recoil_cushion == 0 then + self._h_recoil_cushion = 3 + self._persist_pattern_back = -self._persist_pattern_back + end + local v = math.lerp(persist_pattern[self._persist_pattern_index].up * recoil_multiplier, persist_pattern[self._persist_pattern_index].down * recoil_multiplier, math.random()) + self._recoil_kick.accumulated = (self._recoil_kick.accumulated or 0) + v + + local h = math.lerp(persist_pattern[self._persist_pattern_index].left * recoil_multiplier, persist_pattern[self._persist_pattern_index].right * recoil_multiplier, math.random()) + self._recoil_kick.h.accumulated = (self._recoil_kick.h.accumulated or 0) + h * self._persist_pattern_back + + self._persist_pattern_index = self._persist_pattern_index + 1 + if self._h_recoil_cushion ~= 0 then + self._h_recoil_cushion = self._h_recoil_cushion - 1 + end + end + + self._recoil_kick.ret = 0 + self._recoil_kick.h.ret = 0 +end diff --git a/lua/fraggrenade.lua b/lua/fraggrenade.lua index 52b24ff..b6ec840 100644 --- a/lua/fraggrenade.lua +++ b/lua/fraggrenade.lua @@ -1,2 +1 @@ -function FragGrenade:bullet_hit() -end \ No newline at end of file +function FragGrenade:bullet_hit() end diff --git a/lua/gamesetup.lua b/lua/gamesetup.lua new file mode 100644 index 0000000..3ea00e5 --- /dev/null +++ b/lua/gamesetup.lua @@ -0,0 +1,9 @@ +Hooks:PostHook(GameSetup, "load_packages", "sh_load_packages", function(self) + local difficulty = Global.game_settings and Global.game_settings.difficulty or "normal" + local difficulty_index = tweak_data:difficulty_to_index(difficulty) + if difficulty_index and difficulty_index == 6 and not PackageManager:loaded("packages/sm_wish") then + StreamHeist:log("Loading ZEAL package") + table.insert(self._loaded_diff_packages, "packages/sm_wish") + PackageManager:load("packages/sm_wish") + end +end) diff --git a/lua/grenadebase.lua b/lua/grenadebase.lua index b1fcb89..6f3d7aa 100644 --- a/lua/grenadebase.lua +++ b/lua/grenadebase.lua @@ -1,4 +1,3 @@ - if weapon_id then - managers.statistics:shot_fired({ - }) - end \ No newline at end of file +if weapon_id then + managers.statistics:shot_fired({}) +end diff --git a/lua/groupaimanager.lua b/lua/groupaimanager.lua new file mode 100644 index 0000000..075ac9e --- /dev/null +++ b/lua/groupaimanager.lua @@ -0,0 +1,29 @@ +function GroupAIManager:set_state(name) + if name == "empty" then + self._state = GroupAIStateEmpty:new() + elseif name == "street" then + self._state = GroupAIStateStreet:new() + elseif name == "besiege" or name == "airport" or name == "zombie_apocalypse" then + local level_tweak = managers.job:current_level_data() + self._state = GroupAIStateBesiege:new(level_tweak and level_tweak.group_ai_state or "besiege") + elseif name == "ponr" then + self._state = GroupAIStatePonr:new("ponr", self._state) + else + Application:error("[GroupAIManager:set_state] inexistent state name", name) + + return + end + + self._state_name = name +end + +function GroupAIManager:state_names() + return { + "empty", + "airport", + "besiege", + "street", + "zombie_apocalypse", + "ponr", + } +end diff --git a/lua/groupaistatebase.lua b/lua/groupaistatebase.lua index eb14ef6..be2cd65 100644 --- a/lua/groupaistatebase.lua +++ b/lua/groupaistatebase.lua @@ -16,61 +16,111 @@ function GroupAIStateBase:criminal_hurt_drama(unit, attacker, dmg_percent) end local _update_whitelist = { - "hox_1", - "hox_2", - "red2", - "spa", - "pal", - "flat", - "rvd2" + hox_1 = true, + hox_2 = true, + red2 = true, -- fwb + spa = true, -- 10-10 + flat = true, -- proom + dinner = true, -- slouse + pbr2 = true, -- bos + peta2 = true, -- goat2 + vit = true, -- whouse + des = true, -- hrock + election_day_3 = true, + election_day_3_skip1 = true, + election_day_3_skip2 = true, + run = true, -- hstreet + bph = true, -- hisland + big = true, -- bigbank + wwh = true, -- kolyaskindeal + bex = true, -- sanmartini + pex = true, -- bijuana + pent = true, -- mmaster } -local function check_whitelist(id) - for _, level in pairs(_update_whitelist) do - if level == id then - return true - end - end - - return false -end - -- Code from Dr. Newbie local _old_update_point_of_no_return = GroupAIStateBase._update_point_of_no_return function GroupAIStateBase:_update_point_of_no_return(t, dt) - local get_mission_script_element = function(id) - for name, script in pairs(managers.mission:scripts()) do - if script:element(id) then - return script:element(id) - end - end - end - - local level_id = managers.job:has_active_job() and managers.job:current_level_id() or "" - - if check_whitelist(level_id) then - self._point_of_no_return_timer = self._point_of_no_return_timer - dt - end - - if not self._point_of_no_return_id or not get_mission_script_element(self._point_of_no_return_id) then - if self._point_of_no_return_timer <= 0 then - managers.network:session():send_to_peers("mission_ended", false, 0) - game_state_machine:change_state_by_name("gameoverscreen") - else - managers.hud:feed_point_of_no_return_timer(self._point_of_no_return_timer) - end - else - _old_update_point_of_no_return(self, t, dt) - end + local get_mission_script_element = function(id) + for name, script in pairs(managers.mission:scripts()) do + if script:element(id) then + return script:element(id) + end + end + end + + local level_id = managers.job:has_active_job() and managers.job:current_level_id() or "" + + if _update_whitelist[level_id] then + self._point_of_no_return_timer = self._point_of_no_return_timer - dt + end + + if self._point_of_no_return_id == -1 or not get_mission_script_element(self._point_of_no_return_id) then + if self._point_of_no_return_timer <= 0 then + if Network:is_server() then + managers.groupai:set_state("ponr") + self:set_difficulty(1) + end + self:remove_point_of_no_return_timer(-1) + else + managers.hud:feed_point_of_no_return_timer(self._point_of_no_return_timer) + end + else + _old_update_point_of_no_return(self, t, dt) + end end -- End code from Dr. Newbie +function GroupAIStateBase:chk_allow_drop_in() + if not self._allow_dropin then + return false + end + + return true +end + -- Set up needed variables -Hooks:PostHook(GroupAIStateBase, "init", "sh_init", function (self) +Hooks:PostHook(GroupAIStateBase, "init", "sh_init", function(self) self._next_police_upd_task = 0 + self._next_group_spawn_t = {} + self._marking_sentries = {} end) +-- Make elite dozers register as specials +local function register_special_types(gstate) + gstate._special_unit_types.tank_elite = true + gstate._special_unit_mappings = { + tank_elite = { "tank" }, + } +end + +Hooks:PostHook(GroupAIStateBase, "_init_misc_data", "sh__init_misc_data", register_special_types) +Hooks:PostHook(GroupAIStateBase, "on_simulation_started", "sh_on_simulation_started", register_special_types) + +local register_special_unit_original = GroupAIStateBase.register_special_unit +function GroupAIStateBase:register_special_unit(u_key, category_name, ...) + local mapping = self._special_unit_mappings[category_name] + if mapping then + for _, v in pairs(mapping) do + register_special_unit_original(self, u_key, v, ...) + end + else + register_special_unit_original(self, u_key, category_name, ...) + end +end + +local unregister_special_unit_original = GroupAIStateBase.unregister_special_unit +function GroupAIStateBase:unregister_special_unit(u_key, category_name, ...) + local mapping = self._special_unit_mappings[category_name] + if mapping then + for _, v in pairs(mapping) do + unregister_special_unit_original(self, u_key, v, ...) + end + else + unregister_special_unit_original(self, u_key, category_name, ...) + end +end -- Restore scripted cloaker spawn noise local _process_recurring_grp_SO_original = GroupAIStateBase._process_recurring_grp_SO @@ -83,7 +133,7 @@ function GroupAIStateBase:_process_recurring_grp_SO(...) end -- Log time when criminals enter an area to use for the teargas check -Hooks:PreHook(GroupAIStateBase, "on_criminal_nav_seg_change", "sh_on_criminal_nav_seg_change", function (self, unit, nav_seg_id) +Hooks:PreHook(GroupAIStateBase, "on_criminal_nav_seg_change", "sh_on_criminal_nav_seg_change", function(self, unit, nav_seg_id) local u_sighting = self._criminals[unit:key()] if not u_sighting or u_sighting.ai then return @@ -92,7 +142,9 @@ Hooks:PreHook(GroupAIStateBase, "on_criminal_nav_seg_change", "sh_on_criminal_na local prev_area = u_sighting.area local area = self:get_area_from_nav_seg_id(nav_seg_id) if prev_area and prev_area ~= area then - if table.count(prev_area.criminal.units, function (c_data) return not c_data.ai end) <= 1 then + if table.count(prev_area.criminal.units, function(c_data) + return not c_data.ai + end) <= 1 then prev_area.criminal_left_t = self._t prev_area.old_criminal_entered_t = prev_area.criminal_entered_t prev_area.criminal_entered_t = nil @@ -107,24 +159,9 @@ Hooks:PreHook(GroupAIStateBase, "on_criminal_nav_seg_change", "sh_on_criminal_na end end) - --- Register Winters and minions as soon as they spawn, not just after they reach their objective or take damage --- This fixes instances of Winters not leaving the map because the phalanx is broken up before he is registered -Hooks:PostHook(GroupAIStateBase, "on_enemy_registered", "sh_on_enemy_registered", function (self, unit) - if self._phalanx_spawn_group and not self._phalanx_spawn_group.has_spawned then - local logics = unit:brain()._logics - if logics == CopBrain._logic_variants.phalanx_minion then - self:register_phalanx_minion(unit) - elseif logics == CopBrain._logic_variants.phalanx_vip then - self:register_phalanx_vip(unit) - end - end -end) - - -- Delay spawn points when enemies die close to them -Hooks:PostHook(GroupAIStateBase, "on_enemy_unregistered", "sh_on_enemy_unregistered", function (self, unit) - if not Network:is_server() or not unit:character_damage():dead() then +Hooks:PostHook(GroupAIStateBase, "on_enemy_unregistered", "sh_on_enemy_unregistered", function(self, unit) + if Network:is_client() or not unit:character_damage():dead() then return end @@ -138,11 +175,9 @@ Hooks:PostHook(GroupAIStateBase, "on_enemy_unregistered", "sh_on_enemy_unregiste return end - local u_pos = e_data.m_pos - local spawn_pos = spawn_point:value("position") - local dist_sq = mvector3.distance_sq(spawn_pos, u_pos) - local max_dist_sq = 1000000 - if dist_sq > max_dist_sq then + local max_dis = 1000 + local dis = mvector3.distance(spawn_point:value("position"), e_data.m_pos) + if dis > max_dis then return end @@ -152,7 +187,7 @@ Hooks:PostHook(GroupAIStateBase, "on_enemy_unregistered", "sh_on_enemy_unregiste if group.spawn_pts then for _, point in pairs(group.spawn_pts) do if point.mission_element == spawn_point then - local delay_t = self._t + math.lerp(8, 0, dist_sq / max_dist_sq) + local delay_t = self._t + math.lerp(tweak_data.group_ai.spawn_kill_cooldown, 0, dis / max_dis) group.delay_t = math.max(group.delay_t, delay_t) return end @@ -163,35 +198,34 @@ Hooks:PostHook(GroupAIStateBase, "on_enemy_unregistered", "sh_on_enemy_unregiste end end) - --- Fix this function doing nothing -function GroupAIStateBase:_merge_coarse_path_by_area(coarse_path) - local i_nav_seg = #coarse_path - local area, last_area - while i_nav_seg > 0 and #coarse_path > 2 do - area = self:get_area_from_nav_seg_id(coarse_path[i_nav_seg][1]) - if last_area and last_area == area then - table.remove(coarse_path, i_nav_seg) - else - last_area = area +-- Ignore disabled criminals for area safety checks +function GroupAIStateBase:is_area_safe(area) + for _, u_data in pairs(self._criminals) do + if u_data.status ~= "disabled" and u_data.status ~= "dead" and area.nav_segs[u_data.tracker:nav_segment()] then + return end - i_nav_seg = i_nav_seg - 1 end + return true end +function GroupAIStateBase:is_area_safe_assault(area) + for _, u_data in pairs(self._char_criminals) do + if u_data.status ~= "disabled" and u_data.status ~= "dead" and area.nav_segs[u_data.tracker:nav_segment()] then + return + end + end + return true +end --- Check nav segment safety directly instead of area safety function GroupAIStateBase:is_nav_seg_safe(nav_seg) for _, u_data in pairs(self._criminals) do - if u_data.tracker:nav_segment() == nav_seg then + if u_data.status ~= "disabled" and u_data.status ~= "dead" and u_data.tracker:nav_segment() == nav_seg then return end end - return true end - -- Don't count recon as assault force and vice versa function GroupAIStateBase:_count_police_force(task_name) local amount = 0 @@ -204,16 +238,73 @@ function GroupAIStateBase:_count_police_force(task_name) return amount end - -- Set accurate criminal position -Hooks:PostHook(GroupAIStateBase, "criminal_spotted", "sh_criminal_spotted", function (self, unit) +Hooks:PostHook(GroupAIStateBase, "criminal_spotted", "sh_criminal_spotted", function(self, unit) local u_sighting = self._criminals[unit:key()] mvector3.set(u_sighting.pos, u_sighting.m_det_pos) end) -Hooks:PostHook(GroupAIStateBase, "on_criminal_nav_seg_change", "sh_on_criminal_nav_seg_change", function (self, unit) +Hooks:PostHook(GroupAIStateBase, "on_criminal_nav_seg_change", "sh_on_criminal_nav_seg_change", function(self, unit) local u_sighting = self._criminals[unit:key()] if u_sighting then mvector3.set(u_sighting.pos, u_sighting.m_det_pos) end end) + +-- Make jokers follow their actual owner instead of the closest player +local _determine_objective_for_criminal_AI_original = GroupAIStateBase._determine_objective_for_criminal_AI +function GroupAIStateBase:_determine_objective_for_criminal_AI(unit, ...) + local logic_data = unit:brain()._logic_data + if logic_data.is_converted and alive(logic_data.minion_owner) then + return { + type = "follow", + scan = true, + follow_unit = logic_data.minion_owner, + } + end + + return _determine_objective_for_criminal_AI_original(self, unit, ...) +end + +-- Adjust objective data for rescue and steal SOs +Hooks:PreHook(GroupAIStateBase, "add_special_objective", "sh_add_special_objective", function(self, id, objective_data) + if type(id) ~= "string" or not id:match("^carrysteal") and not id:match("^rescue") then + return + end + + objective_data.interval = 4 + objective_data.search_dis_sq = 4000000 + objective_data.objective.interrupt_dis = 600 + objective_data.objective.interrupt_health = 0.8 + objective_data.objective.pose = nil +end) + +-- Fully count all criminals for the balancing multiplier +function GroupAIStateBase:_get_balancing_multiplier(balance_multipliers) + return balance_multipliers[math.clamp(table.size(self._char_criminals), 1, #balance_multipliers)] +end + +-- Setup sentry marking via host +function GroupAIStateBase:register_marking_sentry(unit) + if unit:base().sentry_gun and unit:base():has_marking() then + self._marking_sentries[unit:key()] = unit + EclipseDebug:log(1, "Marking sentry set!") + end +end + +-- Remove sentries from marking table on destruction +-- Have to work around this bc sh framework :weirdge: +function GroupAIStateBase:unregister_marking_sentry(unit) + if unit:base().sentry_gun and unit:base():has_marking() then + self._marking_sentries[unit:key()] = nil + end +end + +-- Do sentry marking if you are the host +Hooks:PostHook(GroupAIStateBase, "update", "eclipse_sentry_update", function(self, t, dt) + if Network:is_server() then + for _, sentry in pairs(self._marking_sentries) do + sentry:base():_update_omniscience(t, dt) + end + end +end) diff --git a/lua/groupaistatebesiege.lua b/lua/groupaistatebesiege.lua index 763f685..3b5af0b 100644 --- a/lua/groupaistatebesiege.lua +++ b/lua/groupaistatebesiege.lua @@ -1,18 +1,11 @@ -local math_min = math.min -local math_lerp = math.lerp -local math_map_range = math.map_range -local math_random = math.random -local table_insert = table.insert -local table_remove = table.remove local mvec_add = mvector3.add local mvec_cpy = mvector3.copy +local mvec_dir = mvector3.direction local mvec_dis = mvector3.distance local mvec_dis_sq = mvector3.distance_sq +local mvec_lerp = mvector3.lerp local mvec_mul = mvector3.multiply local mvec_set = mvector3.set -local mvec_set_l = mvector3.set_length -local mvec_set_z = mvector3.set_z -local mvec_sub = mvector3.subtract local tmp_vec1 = Vector3() local tmp_vec2 = Vector3() @@ -27,25 +20,23 @@ function GroupAIStateBesiege:_begin_assault_task(...) local assault_task = self._task_data.assault local anticipation_duration = self:_get_anticipation_duration(self._tweak_data.assault.anticipation_duration, assault_task.was_first) assault_task.phase_end_t = self._t + anticipation_duration - assault_task.is_hesitating = true end end Hooks:PostHook(GroupAIStateBesiege, "_end_regroup_task", "eclipse_end_regroup_task", function(self) local assault_task = self._task_data.assault - if self._hostage_headcount > 0 then - local assault_task = self._task_data.assault + if self._hostage_headcount > 0 then local hesitation_delay = self:_get_difficulty_dependent_value(self._tweak_data.assault.hostage_hesitation_delay) - local hostage_situation_skill = managers.player:upgrade_value("team", "hostage_situation", 0) - assault_task.is_hesitating = true - if assault_task.next_dispatch_t then - assault_task.voice_delay = assault_task.next_dispatch_t - self._t - assault_task.next_dispatch_t = assault_task.next_dispatch_t + hesitation_delay + hostage_situation_skill - end + local hostage_situation = managers.player:upgrade_value("team", "hostage_situation", 0) + local hostage_multiplier = math.clamp(self._hostage_headcount, 1, 4) + assault_task.is_hesitating = true + if assault_task.next_dispatch_t then + assault_task.voice_delay = assault_task.next_dispatch_t - self._t + assault_task.next_dispatch_t = assault_task.next_dispatch_t + (hesitation_delay + hostage_situation) * hostage_multiplier + end end end) - -- Fix reenforce group delay local _begin_reenforce_task_original = GroupAIStateBesiege._begin_reenforce_task function GroupAIStateBesiege:_begin_reenforce_task(...) @@ -56,7 +47,6 @@ function GroupAIStateBesiege:_begin_reenforce_task(...) self._task_data.reenforce.next_dispatch_t = next_dispatch_t end - -- Old fade behavior but less abusable local _upd_assault_task_original = GroupAIStateBesiege._upd_assault_task function GroupAIStateBesiege:_upd_assault_task(...) @@ -154,18 +144,127 @@ function GroupAIStateBesiege:_upd_assault_task(...) self:_assign_enemy_groups_to_assault(task_data.phase) end +-- Improve reenforce task handling to allow dynamic scaling of dispatch times +function GroupAIStateBesiege:_upd_reenforce_tasks() + local reenforce_tasks = self._task_data.reenforce.tasks + local overshot_groups = {} + local undershot_tasks = {} + + -- Check reenforce tasks + for i = #reenforce_tasks, 1, -1 do + local task_data = reenforce_tasks[i] + local force_settings = task_data.target_area.factors.force + local force_required = force_settings and force_settings.force or 0 + local force_occupied = 0 + + local occupied_groups = {} + for _, group in pairs(self._groups) do + if (group.objective.target_area or group.objective.area) == task_data.target_area and group.objective.type == "reenforce_area" then + local size = group.has_spawned and group.size or group.initial_size + force_occupied = force_occupied + size + table.insert(occupied_groups, { + group = group, + size = size, + }) + end + end + + if force_occupied > force_required then + table.sort(occupied_groups, function(a, b) + return a.size < b.size + end) + for _, group_data in pairs(occupied_groups) do + force_occupied = force_occupied - group_data.size + if force_occupied < force_required then + break + else + table.insert(overshot_groups, group_data.group) + end + end + elseif force_occupied < force_required then + if not self._task_data.regroup.active and self._task_data.assault.phase ~= "fade" and self:is_area_safe(task_data.target_area) then + table.insert(undershot_tasks, task_data) + end + end + + if force_required == 0 then + table.remove(reenforce_tasks, i) + end + end + + -- Handle areas that need reenforce + for _, task_data in pairs(undershot_tasks) do + local overshot_group = table.remove(overshot_groups) + if overshot_group then + overshot_group.objective.target_area = task_data.target_area + elseif self._task_data.reenforce.next_dispatch_t < self._t then + local spawned + + if task_data.use_spawn_event then + task_data.use_spawn_event = false + spawned = self:_try_use_task_spawn_event(self._t, task_data.target_area, "reenforce") + end + + if not spawned and not next(self._spawning_groups) then + local spawn_group, spawn_group_type = self:_find_spawn_group_near_area(task_data.target_area, self._tweak_data.reenforce.groups) + if spawn_group then + self:_spawn_in_group(spawn_group, spawn_group_type, { + attitude = "avoid", + scan = true, + pose = "stand", + type = "reenforce_area", + stance = "hos", + area = spawn_group.area, + target_area = task_data.target_area, + }) + spawned = true + end + end + + -- Adjust next reinforce dispatch time based on the amount of tasks still needed + if spawned then + self._task_data.reenforce.next_dispatch_t = self._t + self:_get_difficulty_dependent_value(self._tweak_data.reenforce.interval) / #undershot_tasks + break + end + else + break + end + end + + -- Retire overshot groups + for _, group in pairs(overshot_groups) do + if group.has_spawned then + self:_assign_group_to_retire(group) + end + end + + self:_assign_enemy_groups_to_reenforce() +end --- Add an alternate in_place check to prevent enemy groups from getting stuck +-- Improve in_place check to prevent enemy groups from getting stuck +-- Moved these functions that were all pretty much identical to a helper function function GroupAIStateBesiege:_assign_enemy_groups_to_assault(phase) + self:_assign_enemy_groups_to_task(phase, "assault_area", self._set_assault_objective_to_group) +end + +function GroupAIStateBesiege:_assign_enemy_groups_to_recon(phase) + self:_assign_enemy_groups_to_task(phase, "recon_area", self._set_recon_objective_to_group) +end + +function GroupAIStateBesiege:_assign_enemy_groups_to_reenforce(phase) + self:_assign_enemy_groups_to_task(phase, "reenforce_area", self._set_reenforce_objective_to_group) +end + +function GroupAIStateBesiege:_assign_enemy_groups_to_task(phase, objective_type, objective_func) for _, group in pairs(self._groups) do - if group.has_spawned and group.objective.type == "assault_area" then + if group.has_spawned and group.objective.type == objective_type then if group.objective.moving_out then local done_moving for _, u_data in pairs(group.units) do local objective = u_data.unit:brain():objective() if objective and objective.grp_objective == group.objective then - if objective.in_place or objective.area and objective.area.nav_segs[u_data.unit:movement():nav_tracker():nav_segment()] then + if objective.in_place then done_moving = true else done_moving = false @@ -183,118 +282,99 @@ function GroupAIStateBesiege:_assign_enemy_groups_to_assault(phase) end if not group.objective.moving_in then - self:_set_assault_objective_to_group(group, phase) + objective_func(self, group, phase) end end end end - -- Improve and heavily simplify objective assignment code, fix pull back and open fire objectives -- Basically, a lot of this function was needlessly complex and had oversights or incorrect conditions -Hooks:OverrideFunction(GroupAIStateBesiege, "_set_assault_objective_to_group", function (self, group, phase) +Hooks:OverrideFunction(GroupAIStateBesiege, "_set_assault_objective_to_group", function(self, group, phase) local phase_is_anticipation = phase == "anticipation" local current_objective = group.objective - local approach, open_fire, pull_back, charge + local approach, open_fire, pull_back local obstructed_area = self:_chk_group_areas_tresspassed(group) local group_leader_u_key, group_leader_u_data = self._determine_group_leader(group.units) + local tactics_map = group_leader_u_data and group_leader_u_data.tactics_map or {} local in_place_duration = group.in_place_t and self._t - group.in_place_t or 0 - local tactics_map = {} + local objective_area = current_objective.area - if group_leader_u_data and group_leader_u_data.tactics then - for _, tactic_name in ipairs(group_leader_u_data.tactics) do - tactics_map[tactic_name] = true - end + -- Clear objective tactic if it no longer fits + if current_objective.tactic and not tactics_map[current_objective.tactic] then + current_objective.tactic = nil + end - if current_objective.tactic and not tactics_map[current_objective.tactic] then - current_objective.tactic = nil + -- Check deathguard + if tactics_map.deathguard and not phase_is_anticipation then + if current_objective.tactic == "deathguard" then + local u_data = alive(current_objective.follow_unit) and self._char_criminals[current_objective.follow_unit:key()] + if u_data and u_data.status and current_objective.area.nav_segs[u_data.seg] then + return + end end - for i_tactic, tactic_name in ipairs(group_leader_u_data.tactics) do - if tactic_name == "deathguard" and not phase_is_anticipation then - if current_objective.tactic == tactic_name then - for u_key, u_data in pairs(self._char_criminals) do - if u_data.status and current_objective.follow_unit == u_data.unit then - if current_objective.area.nav_segs[u_data.tracker:nav_segment()] then - return - end - end - end - end - - local closest_crim_u_data, closest_crim_dis_sq - for u_key, u_data in pairs(self._char_criminals) do - if u_data.status then - local closest_u_id, closest_u_data, closest_u_dis_sq = self._get_closest_group_unit_to_pos(u_data.m_pos, group.units) - if closest_u_dis_sq and (not closest_crim_dis_sq or closest_u_dis_sq < closest_crim_dis_sq) then - closest_crim_u_data = u_data - closest_crim_dis_sq = closest_u_dis_sq - end - end + local closest_crim_u_data + local closest_crim_dis_sq = math.huge + for _, u_data in pairs(self._char_criminals) do + if u_data.status then + local _, _, closest_u_dis_sq = self._get_closest_group_unit_to_pos(u_data.m_pos, group.units) + if closest_u_dis_sq and closest_u_dis_sq < closest_crim_dis_sq then + closest_crim_u_data = u_data + closest_crim_dis_sq = closest_u_dis_sq end + end + end - if closest_crim_u_data then - local coarse_path = managers.navigation:search_coarse({ - id = "GroupAI_deathguard", - from_tracker = group_leader_u_data.unit:movement():nav_tracker(), - to_tracker = closest_crim_u_data.tracker, - access_pos = self._get_group_acces_mask(group) - }) - - if coarse_path then - local grp_objective = { - distance = 800, - type = "assault_area", - attitude = "engage", - tactic = "deathguard", - moving_in = true, - follow_unit = closest_crim_u_data.unit, - area = self:get_area_from_nav_seg_id(coarse_path[#coarse_path][1]), - coarse_path = coarse_path - } - group.is_chasing = true - - self:_set_objective_to_enemy_group(group, grp_objective) - self:_voice_deathguard_start(group) - return - end - end - elseif tactic_name == "charge" and not current_objective.moving_out then - charge = true + if closest_crim_u_data then + local coarse_path = managers.navigation:search_coarse({ + id = "GroupAI_deathguard", + from_tracker = group_leader_u_data.tracker, + to_tracker = closest_crim_u_data.tracker, + access_pos = self._get_group_acces_mask(group), + }) + + if coarse_path then + self:_voice_deathguard_start(group) + self:_set_objective_to_enemy_group(group, { + distance = 800, + type = "assault_area", + attitude = "engage", + pose = "stand", + tactic = "deathguard", + moving_in = true, + follow_unit = closest_crim_u_data.unit, + area = self:get_area_from_nav_seg_id(coarse_path[#coarse_path][1]), + coarse_path = coarse_path, + }) + return end end end - local objective_area = current_objective.area - if obstructed_area then + if current_objective.open_fire then + approach = not current_objective.moving_out and (tactics_map.charge or not tactics_map.ranged_fire or in_place_duration > 10) and not self:_can_group_see_target(group) + elseif (phase_is_anticipation or obstructed_area or tactics_map.ranged_fire) and self:_can_group_see_target(group, tactics_map.flank and "optimal" or "far") then if phase_is_anticipation then - -- If we run into enemies during anticipation, pull back - pull_back = true - elseif current_objective.moving_out then - -- If we run into enemies while moving out, open fire - if not current_objective.open_fire then - open_fire = true - objective_area = obstructed_area - end - elseif not current_objective.pushed or charge and not current_objective.charge then - -- If we've been in position for a while or haven't seen enemies, approach - approach = not self:_can_group_see_target(group) - end - elseif not current_objective.moving_out then - -- If we aren't moving out to an objective, open fire if we have ranged_fire tactics and see an enemy, otherwise approach - approach = charge or group.is_chasing or not tactics_map.ranged_fire or not self:_can_group_see_target(group) - open_fire = not approach and not current_objective.open_fire - elseif tactics_map.ranged_fire and not current_objective.open_fire and self:_can_group_see_target(group, true) then - -- If we see an enemy while moving out and have the ranged_fire tactics, open fire - local forwardmost_i_nav_point = self:_get_group_forwardmost_coarse_path_index(group) - if forwardmost_i_nav_point then + pull_back = obstructed_area + open_fire = not pull_back + elseif obstructed_area then + objective_area = obstructed_area + open_fire = true + else + local forwardmost_i_nav_point = self:_get_group_forwardmost_coarse_path_index(group) + if forwardmost_i_nav_point then + objective_area = self:get_area_from_nav_seg_id(current_objective.coarse_path[forwardmost_i_nav_point][1]) + end open_fire = true - objective_area = self:get_area_from_nav_seg_id(current_objective.coarse_path[forwardmost_i_nav_point][1]) end + elseif not current_objective.moving_out then + approach = true end if open_fire then - local grp_objective = { + self:_voice_open_fire_start(group) + self:_set_objective_to_enemy_group(group, { attitude = "engage", pose = "stand", type = "assault_area", @@ -305,35 +385,32 @@ Hooks:OverrideFunction(GroupAIStateBesiege, "_set_assault_objective_to_group", f coarse_path = { { objective_area.pos_nav_seg, - mvector3.copy(objective_area.pos) - } - } - } - - self:_set_objective_to_enemy_group(group, grp_objective) - self:_voice_open_fire_start(group) + mvector3.copy(objective_area.pos), + }, + }, + }) elseif approach then local assault_area, assault_path, assault_from local to_search_areas = { - objective_area + objective_area, } local found_areas = { - [objective_area] = objective_area + [objective_area] = objective_area, } local group_access_mask = self._get_group_acces_mask(group) local flank_chance = 2 -- First is shortest path to criminal, second is first actual flank path repeat - local search_area = table_remove(to_search_areas, 1) - if next(search_area.criminal.units) then + local search_area = table.remove(to_search_areas, 1) + if next(search_area.criminal.units) and not self:is_area_safe_assault(search_area) then local flank = tactics_map.flank and found_areas[search_area] ~= objective_area - if not flank or math_random() < flank_chance then + if not flank or math.random() < flank_chance then local new_assault_path = managers.navigation:search_coarse({ id = "GroupAI_assault", from_seg = objective_area.pos_nav_seg, - to_seg = search_area.pos_nav_seg, + to_seg = flank and found_areas[search_area].pos_nav_seg or search_area.pos_nav_seg, access_pos = group_access_mask, - verify_clbk = callback(self, self, "is_nav_seg_safe") + verify_clbk = callback(self, self, "is_nav_seg_safe"), }) if new_assault_path then @@ -353,7 +430,7 @@ Hooks:OverrideFunction(GroupAIStateBesiege, "_set_assault_objective_to_group", f else for _, other_area in pairs(search_area.neighbours) do if not found_areas[other_area] then - table_insert(to_search_areas, other_area) + table.insert(to_search_areas, other_area) found_areas[other_area] = search_area end end @@ -362,68 +439,53 @@ Hooks:OverrideFunction(GroupAIStateBesiege, "_set_assault_objective_to_group", f if assault_area and assault_path then local push = assault_from == objective_area - local move_out = not push if push then + if phase_is_anticipation or tactics_map.no_push then + return + end + local detonate_pos - local c_key = charge and table.random_key(assault_area.criminal.units) + local c_key = tactics_map.charge and table.random_key(assault_area.criminal.units) if c_key then - detonate_pos = assault_area.criminal.units[c_key].unit:movement():m_pos() + detonate_pos = mvec_cpy(assault_area.criminal.units[c_key].m_pos) end -- Check which grenade to use to push, grenade use is required for the push to be initiated -- If grenade isn't available, push regardless anyway after a short delay - if self:_chk_group_use_grenade(assault_area, group, detonate_pos) then - move_out = true - elseif charge or group.ignore_grenade_check_t and group.ignore_grenade_check_t <= self._t then - move_out = true + if not self:_chk_group_use_grenade(assault_area, group, detonate_pos) then + if not group.ignore_grenade_check_t then + local delay = tweak_data.group_ai.no_grenade_push_delay * (tactics_map.charge and 0.25 or 1) + group.ignore_grenade_check_t = self._t + math.map_range_clamped(table.size(assault_area.criminal.units), 1, 4, delay, delay * 0.5) + return + elseif group.ignore_grenade_check_t > self._t then + return + end end - if move_out then - self:_voice_move_in_start(group) - elseif not group.ignore_grenade_check_t then - group.ignore_grenade_check_t = self._t + math.map_range_clamped(table.size(assault_area.criminal.units), 1, 4, 8, 1) - end + self:_voice_move_in_start(group) else -- If we aren't pushing, we go to one area before the criminal area - -- If we are supposed to flank, calculate the path to the area we want to flank from - local new_assault_path = tactics_map.flank and managers.navigation:search_coarse({ - id = "GroupAI_assault", - from_seg = objective_area.pos_nav_seg, - to_seg = assault_from.pos_nav_seg, - access_pos = group_access_mask, - verify_clbk = callback(self, self, "is_nav_seg_safe") - }) - if new_assault_path then - self:_merge_coarse_path_by_area(new_assault_path) - assault_path = new_assault_path - elseif tactics_map.flank then - StreamHeist:warn(group.id, "failed to find flank path to assault area!") - elseif #assault_path > 2 and assault_area.nav_segs[assault_path[#assault_path][1]] then - table_remove(assault_path) + if #assault_path > 2 and assault_area.nav_segs[assault_path[#assault_path][1]] then + table.remove(assault_path) end assault_area = assault_from end - if move_out then - local grp_objective = { - type = "assault_area", - stance = "hos", - area = assault_area, - coarse_path = assault_path, - pose = push and "crouch" or "stand", - attitude = push and "engage" or "avoid", - moving_in = push, - open_fire = push, - pushed = push, - charge = charge, - interrupt_dis = charge and 0 - } - group.is_chasing = group.is_chasing or push - - self:_set_objective_to_enemy_group(group, grp_objective) - end - elseif in_place_duration > 15 and not self:_can_group_see_target(group) then + self:_set_objective_to_enemy_group(group, { + type = "assault_area", + stance = "hos", + area = assault_area, + coarse_path = assault_path, + pose = push and "crouch" or "stand", + attitude = push and "engage" or "avoid", + moving_in = push, + open_fire = push, + pushed = push, + charge = tactics_map.charge, + interrupt_dis = tactics_map.charge and 0, + }) + elseif not current_objective.assigned_t and in_place_duration > 15 and not self:_can_group_see_target(group) then -- Log and remove groups that get stuck local element_id = group.spawn_group_element and group.spawn_group_element._id or 0 local element_name = group.spawn_group_element and group.spawn_group_element._editor_name or "" @@ -454,41 +516,40 @@ Hooks:OverrideFunction(GroupAIStateBesiege, "_set_assault_objective_to_group", f end if retreat_area then - local new_grp_objective = { + self:_set_objective_to_enemy_group(group, { attitude = "avoid", stance = "hos", pose = "crouch", type = "assault_area", area = retreat_area, + open_fire = true, coarse_path = { { retreat_area.pos_nav_seg, - mvector3.copy(retreat_area.pos) - } - } - } - group.is_chasing = nil - - self:_set_objective_to_enemy_group(group, new_grp_objective) + mvector3.copy(retreat_area.pos), + }, + }, + }) end end end) -- Helper to check if any group member has visuals on their focus target function GroupAIStateBesiege:_can_group_see_target(group, limit_range) - local logic_data, focus_enemy for _, u_data in pairs(group.units) do - logic_data = u_data.unit:brain()._logic_data - focus_enemy = logic_data and logic_data.attention_obj - if focus_enemy and focus_enemy.reaction >= AIAttentionObject.REACT_AIM and focus_enemy.verified then - if not limit_range or focus_enemy.verified_dis < (logic_data.internal_data.weapon_range and logic_data.internal_data.weapon_range.far or 3000) then - return true + local logic_data = u_data.unit:brain()._logic_data + if logic_data.objective and logic_data.objective.grp_objective == group.objective then + local focus_enemy = logic_data.attention_obj + if focus_enemy and focus_enemy.verified and focus_enemy.reaction > AIAttentionObject.REACT_AIM then + local weapon_range = logic_data.internal_data.weapon_range + if not limit_range or focus_enemy.dis < (weapon_range and weapon_range[limit_range] or 3000) then + return u_data + end end end end end - -- Add custom grenade usage function function GroupAIStateBesiege:_chk_group_use_grenade(assault_area, group, detonate_pos) local task_data = self._task_data.assault @@ -498,7 +559,7 @@ function GroupAIStateBesiege:_chk_group_use_grenade(assault_area, group, detonat local grenade_types = { smoke_grenade = (not task_data.smoke_grenade_next_t or task_data.smoke_grenade_next_t < self._t) or nil, - flash_grenade = (not task_data.flash_grenade_next_t or task_data.flash_grenade_next_t < self._t) or nil + flash_grenade = (not task_data.flash_grenade_next_t or task_data.flash_grenade_next_t < self._t) or nil, } local grenade_candidates = {} for grenade_type, _ in pairs(grenade_types) do @@ -513,80 +574,74 @@ function GroupAIStateBesiege:_chk_group_use_grenade(assault_area, group, detonat return end - local candidate = table.random(grenade_candidates) - local grenade_type = candidate[1] - local grenade_user = candidate[2] + local grenade_type, grenade_user = unpack(table.random(grenade_candidates)) + local door_pos local detonate_offset, detonate_offset_pos = tmp_vec1, tmp_vec2 - if detonate_pos then - mvec_set(detonate_offset, grenade_user.m_pos) - mvec_sub(detonate_offset, detonate_pos) - else - local nav_seg = managers.navigation._nav_segments[grenade_user.tracker:nav_segment()] - for neighbour_nav_seg_id, door_list in pairs(nav_seg.neighbours) do - local area = self:get_area_from_nav_seg_id(neighbour_nav_seg_id) - if task_data.target_areas[1].nav_segs[neighbour_nav_seg_id] or next(area.criminal.units) then - local random_door_id = door_list[math_random(#door_list)] - if type(random_door_id) == "number" then - detonate_pos = managers.navigation._room_doors[random_door_id].center - else - detonate_pos = random_door_id:script_data().element:nav_link_end_pos() - end - break + local nav_seg = managers.navigation._nav_segments[grenade_user.tracker:nav_segment()] + for neighbour_nav_seg_id, door_list in pairs(nav_seg.neighbours) do + if assault_area.nav_segs[neighbour_nav_seg_id] then + local random_door_id = door_list[math.random(#door_list)] + if type(random_door_id) == "number" then + door_pos = managers.navigation._room_doors[random_door_id].center + else + door_pos = random_door_id:script_data().element:nav_link_end_pos() end + break end + end - if not detonate_pos then - return - end - - mvec_set(detonate_offset, assault_area.pos) - mvec_sub(detonate_offset, detonate_pos) + -- Offset grenade a bit to avoid spawning exactly on the player/door + if detonate_pos and grenade_type == "flash_grenade" then + mvec_dir(detonate_offset, detonate_pos, door_pos or grenade_user.m_pos) + mvec_mul(detonate_offset, math.random(200, 400)) + elseif door_pos then + detonate_pos = door_pos + mvec_dir(detonate_offset_pos, detonate_pos, assault_area.pos) + mvec_mul(detonate_offset_pos, math.random(50, 150)) + mvec_dir(detonate_offset, grenade_user.m_pos, assault_area.pos) + mvec_mul(detonate_offset, math.random(150)) + mvec_add(detonate_offset, detonate_offset_pos) + else + return end - local ray_mask = managers.slot:get_mask("world_geometry") + mvec_set(detonate_offset_pos, detonate_pos) + mvec_add(detonate_offset_pos, detonate_offset) -- If players camp a specific area for too long, turn a smoke grenade into a teargas grenade instead local use_teargas - local can_use_teargas = grenade_type == "smoke_grenade" and assault_area.criminal_entered_t and table.size(assault_area.neighbours) <= 2 - if can_use_teargas and math_random() < (self._t - assault_area.criminal_entered_t - 60) / 180 then - mvec_set(detonate_offset_pos, math.UP) - mvec_mul(detonate_offset_pos, 1000) - mvec_add(detonate_offset_pos, assault_area.pos) - if World:raycast("ray", assault_area.pos, detonate_offset_pos, "slot_mask", ray_mask, "report") then - mvec_set(detonate_offset_pos, assault_area.pos) - mvec_set_z(detonate_offset_pos, detonate_offset_pos.z + 100) - use_teargas = true - end - end + if grenade_type == "smoke_grenade" and assault_area.criminal_entered_t and table.size(assault_area.neighbours) <= 2 then + local teargas_chance_times = tweak_data.group_ai.cs_grenade_chance_times or { 60, 240 } + local teargas_chance = math.map_range(self._t - assault_area.criminal_entered_t, teargas_chance_times[1], teargas_chance_times[2], 0, 1) + if math.random() < teargas_chance then + local teargas_pos = managers.navigation:find_random_position_in_segment(assault_area.pos_nav_seg) + mvec_lerp(teargas_pos, teargas_pos, assault_area.pos, 0.75) + + local c_key = table.random_key(assault_area.criminal.units) + if c_key then + mvec_lerp(teargas_pos, teargas_pos, assault_area.criminal.units[c_key].m_pos, 0.5) + end - if not use_teargas then - -- Offset grenade a bit to avoid spawning exactly on the player/door - mvec_set_z(detonate_offset, math.max(detonate_offset.z, 0)) - mvec_set_l(detonate_offset, math_random(250, 400)) - mvec_set(detonate_offset_pos, detonate_pos) - mvec_add(detonate_offset_pos, detonate_offset) + mvec_set(detonate_offset, math.UP) + mvec_mul(detonate_offset, 1000) + mvec_add(detonate_offset, teargas_pos) - local ray = World:raycast("ray", detonate_pos, detonate_offset_pos, "slot_mask", ray_mask) - if ray then - mvec_set_l(detonate_offset, math.max(0, ray.distance - 50)) - mvec_set(detonate_offset_pos, detonate_pos) - mvec_add(detonate_offset_pos, detonate_offset) + if World:raycast("ray", teargas_pos, detonate_offset, "slot_mask", managers.slot:get_mask("world_geometry"), "report") then + assault_area.criminal_entered_t = assault_area.criminal_entered_t - teargas_chance_times[2] + detonate_offset_pos = teargas_pos + use_teargas = true + end end end - -- Raycast down to place grenade on ground - mvec_set(detonate_offset, math.DOWN) - mvec_mul(detonate_offset, 1000) - mvec_add(detonate_offset, detonate_offset_pos) - - local ground_ray = World:raycast("ray", detonate_offset_pos, detonate_offset, "slot_mask", ray_mask) - detonate_pos = ground_ray and ground_ray.hit_position or detonate_offset_pos + -- Make sure the grenade stays inside AI navigation (on the ground) + local grenade_tracker = managers.navigation:create_nav_tracker(detonate_offset_pos) + detonate_pos = grenade_tracker:field_position() + managers.navigation:destroy_nav_tracker(grenade_tracker) local timeout if use_teargas then - assault_area.criminal_entered_t = nil - self:detonate_cs_grenade(detonate_pos, mvec_cpy(grenade_user.m_pos), tweak_data.group_ai.cs_grenade_lifetime or 10) timeout = tweak_data.group_ai.cs_grenade_timeout or tweak_data.group_ai.smoke_and_flash_grenade_timeout @@ -604,25 +659,27 @@ function GroupAIStateBesiege:_chk_group_use_grenade(assault_area, group, detonat task_data.use_smoke = false -- Minimum grenade cooldown - task_data.use_smoke_timer = self._t + 10 + task_data.use_smoke_timer = self._t + tweak_data.group_ai.min_grenade_timeout -- Individual grenade cooldowns - task_data[grenade_type .. "_next_t"] = self._t + math_lerp(timeout[1], timeout[2], math_random()) + task_data[grenade_type .. "_next_t"] = self._t + math.lerp(timeout[1], timeout[2], math.random()) return true end +-- Fix grenades being synced twice (sync is already done in GroupAIStateBase:detonate_world_smoke_grenade) +function GroupAIStateBesiege:detonate_smoke_grenade(detonate_pos, shooter_pos, duration, flashbang) + self:sync_smoke_grenade(detonate_pos, shooter_pos, duration, flashbang) +end -- Keep recon groups around during anticipation -- Making them retreat only afterwards gives them more time to complete their objectives local _assign_recon_groups_to_retire_original = GroupAIStateBesiege._assign_recon_groups_to_retire function GroupAIStateBesiege:_assign_recon_groups_to_retire(...) - if self._task_data.assault.phase == "anticipation" then - return + if not self._rescue_allowed then + return _assign_recon_groups_to_retire_original(self, ...) end - return _assign_recon_groups_to_retire_original(self, ...) end - -- Tweak importance of spawn group distance in spawn group weight based on the groups to spawn -- Also slightly optimized this function to properly check all areas function GroupAIStateBesiege:_find_spawn_group_near_area(target_area, allowed_groups, target_pos, max_dis, verify_clbk) @@ -647,7 +704,7 @@ function GroupAIStateBesiege:_find_spawn_group_near_area(target_area, allowed_gr access_pos = "swat", from_seg = spawn_group.nav_seg, to_seg = target_area.pos_nav_seg, - id = dis_id + id = dis_id, }) if path and #path >= 2 then @@ -664,19 +721,13 @@ function GroupAIStateBesiege:_find_spawn_group_near_area(target_area, allowed_gr end end - if self._graph_distance_cache[dis_id] then - local my_dis = self._graph_distance_cache[dis_id] - if my_dis < max_dis then - local spawn_group_id = spawn_group.mission_element:id() - valid_spawn_groups[spawn_group_id] = spawn_group - valid_spawn_group_distances[spawn_group_id] = my_dis - if my_dis < shortest_dis then - shortest_dis = my_dis - end - if my_dis > longest_dis then - longest_dis = my_dis - end - end + local my_dis = self._graph_distance_cache[dis_id] + if my_dis and my_dis < max_dis then + local spawn_group_id = spawn_group.mission_element:id() + valid_spawn_groups[spawn_group_id] = spawn_group + valid_spawn_group_distances[spawn_group_id] = my_dis + shortest_dis = my_dis < shortest_dis and my_dis or shortest_dis + longest_dis = my_dis > longest_dis and my_dis or longest_dis end end end @@ -692,7 +743,7 @@ function GroupAIStateBesiege:_find_spawn_group_near_area(target_area, allowed_gr local low_weight = allowed_groups == self._tweak_data.reenforce.groups and 0.1 or allowed_groups == self._tweak_data.recon.groups and 0.4 or 0.7 local single_choice = longest_dis == shortest_dis for i, dis in pairs(valid_spawn_group_distances) do - local my_wgt = single_choice and 1 or math_map_range(dis, shortest_dis, longest_dis, 1, low_weight) + local my_wgt = single_choice and 1 or math.map_range(dis, shortest_dis, longest_dis, 1, low_weight) local my_spawn_group = valid_spawn_groups[i] local my_group_types = my_spawn_group.mission_element:spawn_groups() my_spawn_group.distance = dis @@ -706,7 +757,6 @@ function GroupAIStateBesiege:_find_spawn_group_near_area(target_area, allowed_gr return self:_choose_best_group(candidate_groups, total_weight) end - -- Reorder task updates so groups that have finished spawning immediately get their objectives instead of waiting for the next update function GroupAIStateBesiege:_upd_police_activity() if self._police_activity_blocked or not self._ai_enabled then @@ -715,24 +765,27 @@ function GroupAIStateBesiege:_upd_police_activity() self:_upd_SO() self:_upd_grp_SO() + + if not self._enemy_weapons_hot then + return + end + + self:_claculate_drama_value() + self:_check_spawn_phalanx() self:_check_phalanx_group_has_spawned() self:_check_phalanx_damage_reduction_increase() -- Do _upd_group_spawning and _begin_new_tasks before the various task updates - if self._enemy_weapons_hot then - self:_claculate_drama_value() - self:_upd_group_spawning() - self:_begin_new_tasks() - self:_upd_regroup_task() - self:_upd_reenforce_tasks() - self:_upd_recon_tasks() - self:_upd_assault_task() - self:_upd_groups() - end + self:_upd_group_spawning() + self:_begin_new_tasks() + self:_upd_regroup_task() + self:_upd_reenforce_tasks() + self:_upd_recon_tasks() + self:_upd_assault_task() + self:_upd_groups() end - -- Update police activity in consistent intervals -- Don't use the task queue for it since this function is called in GroupAIStateBesiege:update anyways function GroupAIStateBesiege:_queue_police_upd_task() @@ -742,7 +795,6 @@ function GroupAIStateBesiege:_queue_police_upd_task() end end - -- Overhaul group spawning and fix forced group spawns not actually forcing the entire group to spawn -- Group spawning now always spawns the entire group at once but uses a cooldown that prevents any regular group spawns -- for a number of seconds equal to the amount of spawned units @@ -753,15 +805,25 @@ function GroupAIStateBesiege:force_spawn_group(...) self._force_next_group_spawn = nil end -Hooks:OverrideFunction(GroupAIStateBesiege, "_perform_group_spawning", function (self, spawn_task, force, use_last) +function GroupAIStateBesiege:_is_spawn_task_type_on_cooldown(spawn_task) + local group_objective_type = spawn_task.group.objective.type + return self._next_group_spawn_t[group_objective_type] and self._next_group_spawn_t[group_objective_type] > self._t +end + +function GroupAIStateBesiege:_set_spawn_task_type_cooldown(spawn_task, cooldown) + local group_objective_type = spawn_task.group.objective.type + self._next_group_spawn_t[group_objective_type] = self._t + cooldown +end + +Hooks:OverrideFunction(GroupAIStateBesiege, "_perform_group_spawning", function(self, spawn_task, force, use_last) -- Prevent regular group spawning if cooldown is active unless it's a forced spawn - if self._next_group_spawn_t and self._next_group_spawn_t > self._t and not force and not self._force_next_group_spawn then + if self:_is_spawn_task_type_on_cooldown(spawn_task) and not force and not self._force_next_group_spawn then return end local produce_data = { name = true, - spawn_ai = {} + spawn_ai = {}, } local unit_categories = tweak_data.group_ai.unit_categories local current_unit_type = tweak_data.levels:get_ai_group_type() @@ -771,17 +833,17 @@ Hooks:OverrideFunction(GroupAIStateBesiege, "_perform_group_spawning", function local hopeless = true for _, sp_data in ipairs(spawn_points) do local category = unit_categories[u_type_name] - if (sp_data.accessibility == "any" or category.access[sp_data.accessibility]) and (not sp_data.amount or sp_data.amount > 0) and sp_data.mission_element:enabled() then + local has_access = sp_data.accessibility == "any" or category.access[sp_data.accessibility] + if has_access and (not sp_data.amount or sp_data.amount > 0) and sp_data.mission_element:enabled() then hopeless = false if sp_data.delay_t < self._t then - local units = category.unit_types[current_unit_type] - produce_data.name = units[math.random(#units)] + produce_data.name = table.random(category.unit_types[current_unit_type]) produce_data.name = managers.modifiers:modify_value("GroupAIStateBesiege:SpawningUnit", produce_data.name) + local spawned_unit = sp_data.mission_element:produce(produce_data) - local u_key = spawned_unit:key() - local objective = nil + local objective if spawn_task.objective then objective = self.clone_objective(spawn_task.objective) else @@ -795,17 +857,14 @@ Hooks:OverrideFunction(GroupAIStateBesiege, "_perform_group_spawning", function objective.grp_objective = spawn_task.group.objective end + local u_key = spawned_unit:key() local u_data = self._police[u_key] self:set_enemy_assigned(objective.area, u_key) if spawn_entry.tactics then u_data.tactics = spawn_entry.tactics - u_data.tactics_map = {} - - for _, tactic_name in ipairs(u_data.tactics) do - u_data.tactics_map[tactic_name] = true - end + u_data.tactics_map = table.list_to_set(spawn_entry.tactics) end spawned_unit:brain():set_spawn_entry(spawn_entry, u_data.tactics_map) @@ -818,7 +877,6 @@ Hooks:OverrideFunction(GroupAIStateBesiege, "_perform_group_spawning", function if objective.element then objective.element:clbk_objective_administered(spawned_unit) end - spawned_unit:brain():set_objective(objective) else spawned_unit:brain():set_followup_objective(objective) @@ -883,12 +941,12 @@ Hooks:OverrideFunction(GroupAIStateBesiege, "_perform_group_spawning", function self._groups[spawn_task.group.id] = nil end - -- Set a dynamic enemy spawnrate that depends on player count and enemy group size + -- Set a dynamic enemy spawnrate that scales with player count and difficulty value local spawn_rate_player_mul = self:_get_balancing_multiplier(self._tweak_data.assault.spawnrate_balance_mul) - self._next_group_spawn_t = self._t + spawn_task.group.size * spawn_rate_player_mul + local spawn_rate = self:_get_difficulty_dependent_value(self._tweak_data.assault.spawnrate) + self:_set_spawn_task_type_cooldown(spawn_task, spawn_task.group.size * spawn_rate * spawn_rate_player_mul) end) - -- Save spawn group element in group description for debugging stuck groups local _spawn_in_group_original = GroupAIStateBesiege._spawn_in_group function GroupAIStateBesiege:_spawn_in_group(spawn_group, ...) @@ -899,7 +957,6 @@ function GroupAIStateBesiege:_spawn_in_group(spawn_group, ...) end end - -- Make push use a unique voiceline, add retreat lines function GroupAIStateBesiege:_voice_move_in_start(group) for u_key, unit_data in pairs(group.units) do @@ -917,19 +974,16 @@ function GroupAIStateBesiege:_voice_retreat(group) end end -Hooks:PostHook(GroupAIStateBesiege, "_assign_group_to_retire", "sh__assign_group_to_retire", function (self, group) - self:_voice_retreat(group) -end) +Hooks:PostHook(GroupAIStateBesiege, "_assign_group_to_retire", "sh__assign_group_to_retire", GroupAIStateBesiege._voice_retreat) -- When scripted spawns are assigned to group ai, use a generic group type instead of using their category as type -- This ensures they are not retired immediatley cause they are not part of assault/recon group types -Hooks:OverrideFunction(GroupAIStateBesiege, "assign_enemy_to_group_ai", function (self, unit, team_id) - local assault_active = self._task_data.assault.active +Hooks:OverrideFunction(GroupAIStateBesiege, "assign_enemy_to_group_ai", function(self, unit, team_id) local area = self:get_area_from_nav_seg_id(unit:movement():nav_tracker():nav_segment()) local grp_objective = { - type = assault_active and "assault_area" or "recon_area", + type = self._task_data.assault.active and "assault_area" or "recon_area", area = area, - moving_out = false + moving_out = false, } local objective = unit:brain():objective() @@ -940,7 +994,7 @@ Hooks:OverrideFunction(GroupAIStateBesiege, "assign_enemy_to_group_ai", function local group = self:_create_group({ size = 1, - type = assault_active and "custom_assault" or "custom_recon" + type = self._task_data.assault.active and "custom_assault" or "custom_recon", }) group.team = self._teams[team_id] group.objective = grp_objective @@ -950,11 +1004,293 @@ Hooks:OverrideFunction(GroupAIStateBesiege, "assign_enemy_to_group_ai", function self:set_enemy_assigned(area, unit:key()) end) - -- Fix for potential crash when a group objective does not have a coarse path local _get_group_forwardmost_coarse_path_index_original = GroupAIStateBesiege._get_group_forwardmost_coarse_path_index function GroupAIStateBesiege:_get_group_forwardmost_coarse_path_index(group, ...) if group.objective and group.objective.coarse_path then return _get_group_forwardmost_coarse_path_index_original(self, group, ...) end -end \ No newline at end of file +end + +-- Simplify reenforce objective assignment and prevent them from being stuck at spawn +Hooks:OverrideFunction(GroupAIStateBesiege, "_set_reenforce_objective_to_group", function(self, group) + if not group.has_spawned then + return + end + + if group.obstructed_t and group.obstructed_t > self._t then + return + end + + local current_objective = group.objective + local objective_area = current_objective.area + local target_area = current_objective.target_area + if not target_area or current_objective.moving_out or objective_area == target_area then + return + end + + local obstructed + local search_params = { + id = "GroupAI_reenforce", + from_seg = objective_area.pos_nav_seg, + to_seg = target_area.pos_nav_seg, + access_pos = self._get_group_acces_mask(group), + verify_clbk = callback(self, self, "is_nav_seg_safe"), + } + + local coarse_path = managers.navigation:search_coarse(search_params) + if coarse_path then + self:_merge_coarse_path_by_area(coarse_path) + elseif current_objective.obstructed then + group.obstructed_t = self._t + 6 + return + else + search_params.verify_clbk = nil + coarse_path = managers.navigation:search_coarse(search_params) + + if not coarse_path then + return + end + + self:_merge_coarse_path_by_area(coarse_path) + + local is_safe = true + for i = 1, #coarse_path do + if is_safe then + is_safe = self:is_nav_seg_safe(coarse_path[i][1]) + else + table.remove(coarse_path) + end + end + + if #coarse_path <= 2 then + return + end + + obstructed = true + end + + local move_in = objective_area.neighbours[target_area.id] + if not move_in then + table.remove(coarse_path) + elseif next(target_area.criminal.units) then + local u_key, u_data = self._determine_group_leader(group.units) + local tactics_map = u_data and u_data.tactics_map or {} + if tactics_map.no_push then + return + elseif self:_can_group_see_target(group, "close") then + return + elseif not self:_chk_group_use_grenade(target_area, group) then + if not group.in_place_t or self._t - group.in_place_t < tweak_data.group_ai.no_grenade_push_delay * 0.5 then + return + end + end + end + + self:_set_objective_to_enemy_group(group, { + scan = true, + pose = move_in and "crouch" or "stand", + type = "reenforce_area", + stance = "hos", + attitude = move_in and "engage" or "avoid", + moving_in = move_in and true, + obstructed = obstructed, + area = self:get_area_from_nav_seg_id(coarse_path[#coarse_path][1]), + target_area = target_area, + coarse_path = coarse_path, + }) +end) + +-- Simplify and improve recon objective assignment +Hooks:OverrideFunction(GroupAIStateBesiege, "_set_recon_objective_to_group", function(self, group) + if not group.has_spawned then + return + end + + local current_objective = group.objective + local objective_area = current_objective.area + local target_area = current_objective.target_area or objective_area + if target_area and (target_area.loot or target_area.hostages) then + if current_objective.moving_out then + if not current_objective.coarse_path then + return + end + + local safe = true + local forwardmost_i_nav_point = self:_get_group_forwardmost_coarse_path_index(group) or #current_objective.coarse_path + for i = forwardmost_i_nav_point + 1, #current_objective.coarse_path do + if not self:is_nav_seg_safe(current_objective.coarse_path[i][1]) then + objective_area = self:get_area_from_nav_seg_id(current_objective.coarse_path[forwardmost_i_nav_point][1]) + safe = false + break + end + end + + if safe then + return + end + elseif objective_area == target_area then + return + end + end + + local coarse_path + local to_search_areas = { + objective_area, + } + local found_areas = { + [objective_area] = objective_area, + } + local group_access_mask = self._get_group_acces_mask(group) + + local base_score = 1 + local target_area_score = math.huge + + local function area_score(area, base) + if not area or not area.loot and not area.hostages then + return math.huge + end + local score = base + table.size(area.police.units) + if next(area.criminal.units) then + score = score * 1.5 + end + if area == target_area then + score = score * 0.75 + end + return score + end + + repeat + local search_area = table.remove(to_search_areas, 1) + local search_area_score = area_score(search_area, base_score) + + if search_area_score < target_area_score then + local new_recon_path = managers.navigation:search_coarse({ + id = "GroupAI_recon", + from_seg = objective_area.pos_nav_seg, + to_seg = search_area.pos_nav_seg, + access_pos = group_access_mask, + verify_clbk = callback(self, self, "is_nav_seg_safe"), + }) + + if new_recon_path then + self:_merge_coarse_path_by_area(new_recon_path) + coarse_path = new_recon_path + target_area = search_area + target_area_score = search_area_score + end + end + + if not next(search_area.criminal.units) then + for _, other_area in pairs(search_area.neighbours) do + if not found_areas[other_area] then + table.insert(to_search_areas, other_area) + found_areas[other_area] = search_area + end + end + end + + base_score = base_score + 1 + until #to_search_areas == 0 + + if not coarse_path then + return + end + + local move_in = objective_area.neighbours[target_area.id] + if not move_in then + table.remove(coarse_path) + elseif next(target_area.criminal.units) then + local u_key, u_data = self._determine_group_leader(group.units) + local tactics_map = u_data and u_data.tactics_map or {} + if tactics_map.no_push then + return + elseif self:_can_group_see_target(group, "close") then + return + elseif not self:_chk_group_use_grenade(target_area, group) then + if not group.in_place_t or self._t - group.in_place_t < tweak_data.group_ai.no_grenade_push_delay * 0.5 then + return + end + end + end + + self:_set_objective_to_enemy_group(group, { + scan = true, + type = "recon_area", + stance = "hos", + pose = move_in and "crouch" or "stand", + attitude = "avoid", + moving_in = move_in, + area = self:get_area_from_nav_seg_id(coarse_path[#coarse_path][1]), + target_area = target_area, + coarse_path = coarse_path, + }) +end) + +-- Spawn events are probably not used anywhere, but for the sake of correctness, fix this function +-- All the functions that call this expect it to return true when it's used +function GroupAIStateBase:_try_use_task_spawn_event(t, target_area, task_type, target_pos, force) + target_pos = target_pos or target_area.pos + + local max_dis_sq = 3000 ^ 2 + for _, event_data in pairs(self._spawn_events) do + if (event_data.task_type == task_type or event_data.task_type == "any") and mvec_dis_sq(target_pos, event_data.pos) < max_dis_sq then + if force or math.random() < event_data.chance then + self._anticipated_police_force = self._anticipated_police_force + event_data.amount + self._police_force = self._police_force + event_data.amount + self:_use_spawn_event(event_data) + return true + else + event_data.chance = math.min(1, event_data.chance + event_data.chance_inc) + end + end + end +end + +-- New designated ponr ai state +GroupAIStatePonr = GroupAIStatePonr or class(GroupAIStateBesiege) + +function GroupAIStatePonr:init(state, data) + -- Grab all information from the previous ai state + for k, v in pairs(data) do + if type(v) ~= "function" then + self[k] = v + end + end + -- Set the group ai tweak table + self._tweak_data = tweak_data.group_ai["ponr"] + self:_parse_teammate_comments() + self:sync_assault_mode(false) + self:set_bain_state(true) + self:set_difficulty(1) + self:_set_rescue_state(true) + self:_init_unit_type_filters() + self:_init_team_tables() + if Network:is_server() and managers.navigation:is_data_ready() then + self:_queue_police_upd_task() + end + self:force_end_assault_phase(true) +end + +function GroupAIStatePonr._extract_group_desc_structure(spawn_entry_outer, valid_unit_entries) + for spawn_entry_key, spawn_entry in ipairs(spawn_entry_outer) do + if spawn_entry.unit then + table.insert(valid_unit_entries, clone(spawn_entry)) + else + GroupAIStatePonr._extract_group_desc_structure(spawn_entry, valid_unit_entries) + end + end + + for spawn_entry_key, spawn_entry in pairs(spawn_entry_outer) do + if (type(spawn_entry_key) ~= "number" or spawn_entry_key > #spawn_entry_outer) and #spawn_entry ~= 0 then + local i_rand = math.random(#spawn_entry) + local rand_branch = spawn_entry[i_rand] + + if rand_branch.unit then + table.insert(valid_unit_entries, clone(rand_branch)) + else + GroupAIStatePonr._extract_group_desc_structure(rand_branch, valid_unit_entries) + end + end + end +end diff --git a/lua/groupaitweakdata.lua b/lua/groupaitweakdata.lua index 88d2a2d..0619061 100644 --- a/lua/groupaitweakdata.lua +++ b/lua/groupaitweakdata.lua @@ -1,1163 +1,2009 @@ --- Increase tickrate to 90 -Hooks:PostHook(GroupAITweakData, "init", "eclipse_init", function (self) - self.ai_tick_rate = 1 / 90 +-- Fix retreat chatter using the wrong voiceline and make voiclines used for pushing unique +Hooks:PostHook(GroupAITweakData, "_init_chatter_data", "sh__init_chatter_data", function(self) + self.enemy_chatter.retreat.queue = "m01" + self.enemy_chatter.retreat.duration = { 4, 8 } + self.enemy_chatter.push = { + radius = 1000, + max_nr = 0, + queue = "pus", + group_min = 0, + duration = { 5, 10 }, + interval = { 0.75, 1.2 }, + } end) Hooks:PostHook(GroupAITweakData, "_init_unit_categories", "eclipse__init_unit_categories", function(self, difficulty_index) - if difficulty_index >= 7 then + if difficulty_index == 2 then self.special_unit_spawn_limits = { - tank = 2, + tank = 0, + taser = 0, + spooc = 0, + shield = 2, + medic = 0, + } + elseif difficulty_index == 3 then + self.special_unit_spawn_limits = { + tank = 0, + taser = 1, + spooc = 0, + shield = 2, + medic = 0, + } + elseif difficulty_index == 4 then + self.special_unit_spawn_limits = { + tank = 1, + taser = 2, + spooc = 0, + shield = 2, + medic = 0, + } + elseif difficulty_index == 5 then + self.special_unit_spawn_limits = { + tank = 1, taser = 3, spooc = 2, + shield = 2, + medic = 3, + } + elseif difficulty_index == 6 then + self.special_unit_spawn_limits = { + tank = 2, + taser = 3, + spooc = 3, shield = 4, - medic = 1 + medic = 4, } end -local access_type_walk_only = { - walk = true -} -local access_type_all = { - acrobatic = true, - walk = true -} + local access_type_walk_only = { + walk = true, + } + local access_type_all = { + acrobatic = true, + walk = true, + } + + -- UNIT CATEGORIES + -- Honestly if only i could be arsed to actually support all factions + + -- SWAT Rifleman + self.unit_categories.beat_cop = { + unit_types = { + america = { + Idstring("units/payday2/characters/ene_cop_1/ene_cop_1"), + Idstring("units/payday2/characters/ene_cop_2/ene_cop_2"), + Idstring("units/payday2/characters/ene_cop_3/ene_cop_3"), + Idstring("units/payday2/characters/ene_cop_4/ene_cop_4"), + }, + russia = { + Idstring("units/payday2/characters/ene_cop_1/ene_cop_1"), + Idstring("units/payday2/characters/ene_cop_2/ene_cop_2"), + Idstring("units/payday2/characters/ene_cop_3/ene_cop_3"), + Idstring("units/payday2/characters/ene_cop_4/ene_cop_4"), + }, + zombie = { + Idstring("units/payday2/characters/ene_cop_1/ene_cop_1"), + Idstring("units/payday2/characters/ene_cop_2/ene_cop_2"), + Idstring("units/payday2/characters/ene_cop_3/ene_cop_3"), + Idstring("units/payday2/characters/ene_cop_4/ene_cop_4"), + }, + murkywater = { + Idstring("units/payday2/characters/ene_cop_1/ene_cop_1"), + Idstring("units/payday2/characters/ene_cop_2/ene_cop_2"), + Idstring("units/payday2/characters/ene_cop_3/ene_cop_3"), + Idstring("units/payday2/characters/ene_cop_4/ene_cop_4"), + }, + federales = { + Idstring("units/payday2/characters/ene_cop_1/ene_cop_1"), + Idstring("units/payday2/characters/ene_cop_2/ene_cop_2"), + Idstring("units/payday2/characters/ene_cop_3/ene_cop_3"), + Idstring("units/payday2/characters/ene_cop_4/ene_cop_4"), + }, + }, + access = access_type_all, + } + + -- SWAT Rifleman + self.unit_categories.swat_m4 = { + unit_types = { + america = { + Idstring("units/payday2/characters/ene_swat_1/ene_swat_1"), + }, + russia = { + Idstring("units/payday2/characters/ene_swat_1/ene_swat_1"), + }, + zombie = { + Idstring("units/payday2/characters/ene_swat_1/ene_swat_1"), + }, + murkywater = { + Idstring("units/payday2/characters/ene_swat_1/ene_swat_1"), + }, + federales = { + Idstring("units/payday2/characters/ene_swat_1/ene_swat_1"), + }, + }, + access = access_type_all, + } + + -- SWAT Shotgunner + self.unit_categories.swat_r870 = { + unit_types = { + america = { + Idstring("units/payday2/characters/ene_swat_2/ene_swat_2"), + }, + russia = { + Idstring("units/payday2/characters/ene_swat_2/ene_swat_2"), + }, + zombie = { + Idstring("units/payday2/characters/ene_swat_2/ene_swat_2"), + }, + murkywater = { + Idstring("units/payday2/characters/ene_swat_2/ene_swat_2"), + }, + federales = { + Idstring("units/payday2/characters/ene_swat_2/ene_swat_2"), + }, + }, + access = access_type_all, + } --- UNIT CATEGORIES --- Honestly if only i could be arsed to actually support all factions + -- SWAT Shield + self.unit_categories.swat_shield = { + special_type = "shield", + unit_types = { + america = { + Idstring("units/payday2/characters/ene_shield_2/ene_shield_2"), + }, + russia = { + Idstring("units/payday2/characters/ene_shield_2/ene_shield_2"), + }, + zombie = { + Idstring("units/payday2/characters/ene_shield_2/ene_shield_2"), + }, + murkywater = { + Idstring("units/payday2/characters/ene_shield_2/ene_shield_2"), + }, + federales = { + Idstring("units/payday2/characters/ene_shield_2/ene_shield_2"), + }, + }, + access = access_type_walk_only, + } -- FBI Riflemen - self.unit_categories.rifleman_fbi = { - unit_types = { - america = { - Idstring("units/payday2/characters/ene_fbi_swat_1/ene_fbi_swat_1") - }, - russia = { - Idstring("units/payday2/characters/ene_fbi_swat_1/ene_fbi_swat_1") - }, - zombie = { - Idstring("units/payday2/characters/ene_fbi_swat_1/ene_fbi_swat_1") - }, - murkywater = { - Idstring("units/payday2/characters/ene_fbi_swat_1/ene_fbi_swat_1") - }, - federales = { - Idstring("units/payday2/characters/ene_fbi_swat_1/ene_fbi_swat_1") - } - }, - access = access_type_all - } + self.unit_categories.fbi_m4 = { + unit_types = { + america = { + Idstring("units/payday2/characters/ene_fbi_swat_1/ene_fbi_swat_1"), + }, + russia = { + Idstring("units/payday2/characters/ene_fbi_swat_1/ene_fbi_swat_1"), + }, + zombie = { + Idstring("units/payday2/characters/ene_fbi_swat_1/ene_fbi_swat_1"), + }, + murkywater = { + Idstring("units/payday2/characters/ene_fbi_swat_1/ene_fbi_swat_1"), + }, + federales = { + Idstring("units/payday2/characters/ene_fbi_swat_1/ene_fbi_swat_1"), + }, + }, + access = access_type_all, + } - self.unit_categories.shotgunner_fbi = { - unit_types = { - america = { - Idstring("units/payday2/characters/ene_fbi_swat_2/ene_fbi_swat_2") - }, - russia = { - Idstring("units/payday2/characters/ene_fbi_swat_2/ene_fbi_swat_2") - }, - zombie = { - Idstring("units/payday2/characters/ene_fbi_swat_2/ene_fbi_swat_2") - }, - murkywater = { - Idstring("units/payday2/characters/ene_fbi_swat_2/ene_fbi_swat_2") - }, - federales = { - Idstring("units/payday2/characters/ene_fbi_swat_2/ene_fbi_swat_2") - } - }, - access = access_type_all - } + -- FBI Shotgunners + self.unit_categories.fbi_r870 = { + unit_types = { + america = { + Idstring("units/payday2/characters/ene_fbi_swat_2/ene_fbi_swat_2"), + }, + russia = { + Idstring("units/payday2/characters/ene_fbi_swat_2/ene_fbi_swat_2"), + }, + zombie = { + Idstring("units/payday2/characters/ene_fbi_swat_2/ene_fbi_swat_2"), + }, + murkywater = { + Idstring("units/payday2/characters/ene_fbi_swat_2/ene_fbi_swat_2"), + }, + federales = { + Idstring("units/payday2/characters/ene_fbi_swat_2/ene_fbi_swat_2"), + }, + }, + access = access_type_all, + } + + -- Balaclava HRU + self.unit_categories.balaclava = { + unit_types = { + america = { + Idstring("units/payday2/characters/ene_fbi_3/ene_fbi_3"), + }, + russia = { + Idstring("units/payday2/characters/ene_fbi_3/ene_fbi_3"), + }, + zombie = { + Idstring("units/payday2/characters/ene_fbi_3/ene_fbi_3"), + }, + murkywater = { + Idstring("units/payday2/characters/ene_fbi_3/ene_fbi_3"), + }, + federales = { + Idstring("units/payday2/characters/ene_fbi_3/ene_fbi_3"), + }, + }, + access = access_type_all, + } + + -- Whiteshirt HRU + self.unit_categories.whiteshirt = { + unit_types = { + america = { + Idstring("units/payday2/characters/ene_fbi_2/ene_fbi_2"), + }, + russia = { + Idstring("units/payday2/characters/ene_fbi_2/ene_fbi_2"), + }, + zombie = { + Idstring("units/payday2/characters/ene_fbi_2/ene_fbi_2"), + }, + murkywater = { + Idstring("units/payday2/characters/ene_fbi_2/ene_fbi_2"), + }, + federales = { + Idstring("units/payday2/characters/ene_fbi_2/ene_fbi_2"), + }, + }, + access = access_type_all, + } + + -- FBI Shield + self.unit_categories.shield_fbi = { + special_type = "shield", + unit_types = { + america = { + Idstring("units/payday2/characters/ene_shield_1/ene_shield_1"), + }, + russia = { + Idstring("units/payday2/characters/ene_shield_1/ene_shield_1"), + }, + zombie = { + Idstring("units/payday2/characters/ene_shield_1/ene_shield_1"), + }, + murkywater = { + Idstring("units/payday2/characters/ene_shield_1/ene_shield_1"), + }, + federales = { + Idstring("units/payday2/characters/ene_shield_1/ene_shield_1"), + }, + }, + access = access_type_walk_only, + } -- FBI Heavy Riflemen - self.unit_categories.heavy_rifleman_fbi = { - unit_types = { - america = { - Idstring("units/payday2/characters/ene_fbi_heavy_1/ene_fbi_heavy_1") - }, - russia = { - Idstring("units/payday2/characters/ene_fbi_heavy_1/ene_fbi_heavy_1") - }, - zombie = { - Idstring("units/payday2/characters/ene_fbi_heavy_1/ene_fbi_heavy_1") - }, - murkywater = { - Idstring("units/payday2/characters/ene_fbi_heavy_1/ene_fbi_heavy_1") - }, - federales = { - Idstring("units/payday2/characters/ene_fbi_heavy_1/ene_fbi_heavy_1") - } - }, - access = access_type_all - } + self.unit_categories.heavy_fbi_m4 = { + unit_types = { + america = { + Idstring("units/payday2/characters/ene_fbi_heavy_1/ene_fbi_heavy_1"), + }, + russia = { + Idstring("units/payday2/characters/ene_fbi_heavy_1/ene_fbi_heavy_1"), + }, + zombie = { + Idstring("units/payday2/characters/ene_fbi_heavy_1/ene_fbi_heavy_1"), + }, + murkywater = { + Idstring("units/payday2/characters/ene_fbi_heavy_1/ene_fbi_heavy_1"), + }, + federales = { + Idstring("units/payday2/characters/ene_fbi_heavy_1/ene_fbi_heavy_1"), + }, + }, + access = access_type_all, + } + -- FBI Heavy Shotgunners - self.unit_categories.heavy_shotgunner_fbi = { - unit_types = { - america = { - Idstring("units/payday2/characters/ene_fbi_heavy_r870/ene_fbi_heavy_r870") - }, - russia = { - Idstring("units/payday2/characters/ene_fbi_heavy_r870/ene_fbi_heavy_r870") - }, - zombie = { - Idstring("units/payday2/characters/ene_fbi_heavy_r870/ene_fbi_heavy_r870") - }, - murkywater = { - Idstring("units/payday2/characters/ene_fbi_heavy_r870/ene_fbi_heavy_r870") - }, - federales = { - Idstring("units/payday2/characters/ene_fbi_heavy_r870/ene_fbi_heavy_r870") - } - }, - access = access_type_all - } + self.unit_categories.heavy_fbi_r870 = { + unit_types = { + america = { + Idstring("units/payday2/characters/ene_fbi_heavy_r870/ene_fbi_heavy_r870"), + }, + russia = { + Idstring("units/payday2/characters/ene_fbi_heavy_r870/ene_fbi_heavy_r870"), + }, + zombie = { + Idstring("units/payday2/characters/ene_fbi_heavy_r870/ene_fbi_heavy_r870"), + }, + murkywater = { + Idstring("units/payday2/characters/ene_fbi_heavy_r870/ene_fbi_heavy_r870"), + }, + federales = { + Idstring("units/payday2/characters/ene_fbi_heavy_r870/ene_fbi_heavy_r870"), + }, + }, + access = access_type_all, + } -- GenSec Long Range Riflemen - self.unit_categories.rifleman_lr_elite = { - unit_types = { - america = { - Idstring("units/payday2/characters/ene_city_swat_1/ene_city_swat_1") - }, - russia = { - Idstring("units/payday2/characters/ene_city_swat_1/ene_city_swat_1") - }, - zombie = { - Idstring("units/payday2/characters/ene_city_swat_1/ene_city_swat_1") - }, - murkywater = { - Idstring("units/payday2/characters/ene_city_swat_1/ene_city_swat_1") - }, - federales = { - Idstring("units/payday2/characters/ene_city_swat_1/ene_city_swat_1") - } - }, - access = access_type_all - } + self.unit_categories.g36_elite = { + unit_types = { + america = { + Idstring("units/payday2/characters/ene_city_swat_1/ene_city_swat_1"), + }, + russia = { + Idstring("units/payday2/characters/ene_city_swat_1/ene_city_swat_1"), + }, + zombie = { + Idstring("units/payday2/characters/ene_city_swat_1/ene_city_swat_1"), + }, + murkywater = { + Idstring("units/payday2/characters/ene_city_swat_1/ene_city_swat_1"), + }, + federales = { + Idstring("units/payday2/characters/ene_city_swat_1/ene_city_swat_1"), + }, + }, + access = access_type_all, + } -- GenSec Shotgunners - self.unit_categories.shotgunner_elite = { - unit_types = { - america = { - Idstring("units/payday2/characters/ene_city_swat_2/ene_city_swat_2") - }, - russia = { - Idstring("units/payday2/characters/ene_city_swat_2/ene_city_swat_2") - }, - zombie = { - Idstring("units/payday2/characters/ene_city_swat_2/ene_city_swat_2") - }, - murkywater = { - Idstring("units/payday2/characters/ene_city_swat_2/ene_city_swat_2") - }, - federales = { - Idstring("units/payday2/characters/ene_city_swat_2/ene_city_swat_2") - } - }, - access = access_type_all - } + self.unit_categories.benelli_elite = { + unit_types = { + america = { + Idstring("units/payday2/characters/ene_city_swat_2/ene_city_swat_2"), + }, + russia = { + Idstring("units/payday2/characters/ene_city_swat_2/ene_city_swat_2"), + }, + zombie = { + Idstring("units/payday2/characters/ene_city_swat_2/ene_city_swat_2"), + }, + murkywater = { + Idstring("units/payday2/characters/ene_city_swat_2/ene_city_swat_2"), + }, + federales = { + Idstring("units/payday2/characters/ene_city_swat_2/ene_city_swat_2"), + }, + }, + access = access_type_all, + } -- GenSec Short Range Riflemen - self.unit_categories.rifleman_sr_elite = { - unit_types = { - america = { - Idstring("units/payday2/characters/ene_city_swat_3/ene_city_swat_3") - }, - russia = { - Idstring("units/payday2/characters/ene_city_swat_3/ene_city_swat_3") - }, - zombie = { - Idstring("units/payday2/characters/ene_city_swat_3/ene_city_swat_3") - }, - murkywater = { - Idstring("units/payday2/characters/ene_city_swat_3/ene_city_swat_3") - }, - federales = { - Idstring("units/payday2/characters/ene_city_swat_3/ene_city_swat_3") - } - }, - access = access_type_all - } + self.unit_categories.ump_elite = { + unit_types = { + america = { + Idstring("units/payday2/characters/ene_city_swat_3/ene_city_swat_3"), + }, + russia = { + Idstring("units/payday2/characters/ene_city_swat_3/ene_city_swat_3"), + }, + zombie = { + Idstring("units/payday2/characters/ene_city_swat_3/ene_city_swat_3"), + }, + murkywater = { + Idstring("units/payday2/characters/ene_city_swat_3/ene_city_swat_3"), + }, + federales = { + Idstring("units/payday2/characters/ene_city_swat_3/ene_city_swat_3"), + }, + }, + access = access_type_all, + } - -- Blue Rifleman - self.unit_categories.swat_rifle = { - unit_types = { - america = { - Idstring("units/payday2/characters/ene_swat_1/ene_swat_1") - }, - russia = { - Idstring("units/payday2/characters/ene_swat_1/ene_swat_1") - }, - zombie = { - Idstring("units/payday2/characters/ene_swat_1/ene_swat_1") - }, - murkywater = { - Idstring("units/payday2/characters/ene_swat_1/ene_swat_1") - }, - federales = { - Idstring("units/payday2/characters/ene_swat_1/ene_swat_1") - } - }, - access = access_type_all - } + -- Elite Shield + self.unit_categories.shield_elite = { + special_type = "shield", + unit_types = { + america = { + Idstring("units/pd2_dlc_vip/characters/ene_phalanx_1/ene_phalanx_1"), + }, + russia = { + Idstring("units/pd2_dlc_vip/characters/ene_phalanx_1/ene_phalanx_1"), + }, + zombie = { + Idstring("units/pd2_dlc_vip/characters/ene_phalanx_1/ene_phalanx_1"), + }, + murkywater = { + Idstring("units/pd2_dlc_vip/characters/ene_phalanx_1/ene_phalanx_1"), + }, + federales = { + Idstring("units/pd2_dlc_vip/characters/ene_phalanx_1/ene_phalanx_1"), + }, + }, + access = access_type_walk_only, + } - -- Blue Rifleman - self.unit_categories.swat_shotgun = { - unit_types = { - america = { - Idstring("units/payday2/characters/ene_swat_2/ene_swat_2") - }, - russia = { - Idstring("units/payday2/characters/ene_swat_2/ene_swat_2") - }, - zombie = { - Idstring("units/payday2/characters/ene_swat_2/ene_swat_2") - }, - murkywater = { - Idstring("units/payday2/characters/ene_swat_2/ene_swat_2") - }, - federales = { - Idstring("units/payday2/characters/ene_swat_2/ene_swat_2") - } - }, - access = access_type_all - } + -- Taser + self.unit_categories.taser_unit = { + special_type = "taser", + unit_types = { + america = { + Idstring("units/payday2/characters/ene_tazer_1/ene_tazer_1"), + }, + russia = { + Idstring("units/payday2/characters/ene_tazer_1/ene_tazer_1"), + }, + zombie = { + Idstring("units/payday2/characters/ene_tazer_1/ene_tazer_1"), + }, + murkywater = { + Idstring("units/payday2/characters/ene_tazer_1/ene_tazer_1"), + }, + federales = { + Idstring("units/payday2/characters/ene_tazer_1/ene_tazer_1"), + }, + }, + access = access_type_all, + } - -- Balaclava HRU - self.unit_categories.balaclava = { - unit_types = { - america = { - Idstring("units/payday2/characters/ene_fbi_3/ene_fbi_3") - }, - russia = { - Idstring("units/payday2/characters/ene_fbi_3/ene_fbi_3") - }, - zombie = { - Idstring("units/payday2/characters/ene_fbi_3/ene_fbi_3") - }, - murkywater = { - Idstring("units/payday2/characters/ene_fbi_3/ene_fbi_3") - }, - federales = { - Idstring("units/payday2/characters/ene_fbi_3/ene_fbi_3") - } - }, - access = access_type_all - } + -- Medic + self.unit_categories.medic_unit = { + special_type = "medic", + unit_types = { + america = { + Idstring("units/payday2/characters/ene_medic_m4/ene_medic_m4"), + Idstring("units/payday2/characters/ene_medic_r870/ene_medic_r870"), + }, + russia = { + Idstring("units/payday2/characters/ene_medic_m4/ene_medic_m4"), + Idstring("units/payday2/characters/ene_medic_r870/ene_medic_r870"), + }, + zombie = { + Idstring("units/payday2/characters/ene_medic_m4/ene_medic_m4"), + Idstring("units/payday2/characters/ene_medic_r870/ene_medic_r870"), + }, + murkywater = { + Idstring("units/payday2/characters/ene_medic_m4/ene_medic_m4"), + Idstring("units/payday2/characters/ene_medic_r870/ene_medic_r870"), + }, + federales = { + Idstring("units/payday2/characters/ene_medic_m4/ene_medic_m4"), + Idstring("units/payday2/characters/ene_medic_r870/ene_medic_r870"), + }, + }, + access = access_type_all, + } - -- Whiteshirt HRU - self.unit_categories.whiteshirt = { - unit_types = { - america = { - Idstring("units/payday2/characters/ene_fbi_2/ene_fbi_2") - }, - russia = { - Idstring("units/payday2/characters/ene_fbi_2/ene_fbi_2") - }, - zombie = { - Idstring("units/payday2/characters/ene_fbi_2/ene_fbi_2") - }, - murkywater = { - Idstring("units/payday2/characters/ene_fbi_2/ene_fbi_2") - }, - federales = { - Idstring("units/payday2/characters/ene_fbi_2/ene_fbi_2") - } - }, - access = access_type_all - } + -- FBI Dozers + self.unit_categories.tank_fbi = { + special_type = "tank", + unit_types = { + america = { + Idstring("units/payday2/characters/ene_bulldozer_1/ene_bulldozer_1"), + Idstring("units/payday2/characters/ene_bulldozer_2/ene_bulldozer_2"), + }, + russia = { + Idstring("units/payday2/characters/ene_bulldozer_1/ene_bulldozer_1"), + Idstring("units/payday2/characters/ene_bulldozer_2/ene_bulldozer_2"), + }, + zombie = { + Idstring("units/payday2/characters/ene_bulldozer_1/ene_bulldozer_1"), + Idstring("units/payday2/characters/ene_bulldozer_2/ene_bulldozer_2"), + }, + murkywater = { + Idstring("units/payday2/characters/ene_bulldozer_1/ene_bulldozer_1"), + Idstring("units/payday2/characters/ene_bulldozer_2/ene_bulldozer_2"), + }, + federales = { + Idstring("units/payday2/characters/ene_bulldozer_1/ene_bulldozer_1"), + Idstring("units/payday2/characters/ene_bulldozer_2/ene_bulldozer_2"), + }, + }, + access = access_type_all, + } - -- FBI Shield - self.unit_categories.shield_fbi = { - special_type = "shield", - unit_types = { - america = { - Idstring("units/payday2/characters/ene_shield_1/ene_shield_1") - }, - russia = { - Idstring("units/payday2/characters/ene_shield_1/ene_shield_1") - }, - zombie = { - Idstring("units/payday2/characters/ene_shield_1/ene_shield_1") - }, - murkywater = { - Idstring("units/payday2/characters/ene_shield_1/ene_shield_1") - }, - federales = { - Idstring("units/payday2/characters/ene_shield_1/ene_shield_1") - } - }, - access = access_type_walk_only - } + -- Elite Dozers + self.unit_categories.tank_elite = { + special_type = "tank", + unit_types = { + america = { + Idstring("units/payday2/characters/ene_bulldozer_3/ene_bulldozer_3"), + Idstring("units/pd2_dlc_drm/characters/ene_bulldozer_minigun_classic/ene_bulldozer_minigun_classic"), + }, + russia = { + Idstring("units/payday2/characters/ene_bulldozer_3/ene_bulldozer_3"), + Idstring("units/pd2_dlc_drm/characters/ene_bulldozer_minigun_classic/ene_bulldozer_minigun_classic"), + }, + zombie = { + Idstring("units/payday2/characters/ene_bulldozer_3/ene_bulldozer_3"), + Idstring("units/pd2_dlc_drm/characters/ene_bulldozer_minigun_classic/ene_bulldozer_minigun_classic"), + }, + murkywater = { + Idstring("units/payday2/characters/ene_bulldozer_3/ene_bulldozer_3"), + Idstring("units/pd2_dlc_drm/characters/ene_bulldozer_minigun_classic/ene_bulldozer_minigun_classic"), + }, + federales = { + Idstring("units/payday2/characters/ene_bulldozer_3/ene_bulldozer_3"), + Idstring("units/pd2_dlc_drm/characters/ene_bulldozer_minigun_classic/ene_bulldozer_minigun_classic"), + }, + }, + access = access_type_all, + } - -- Taser - self.unit_categories.taser_unit = { - special_type = "taser", - unit_types = { - america = { - Idstring("units/payday2/characters/ene_tazer_1/ene_tazer_1") - }, - russia = { - Idstring("units/payday2/characters/ene_tazer_1/ene_tazer_1") - }, - zombie = { - Idstring("units/payday2/characters/ene_tazer_1/ene_tazer_1") - }, - murkywater = { - Idstring("units/payday2/characters/ene_tazer_1/ene_tazer_1") - }, - federales = { - Idstring("units/payday2/characters/ene_tazer_1/ene_tazer_1") - } - }, - access = access_type_all - } + -- Cloakers + self.unit_categories.cloaker = { + special_type = "spooc", + unit_types = { + america = { + Idstring("units/payday2/characters/ene_spook_1/ene_spook_1"), + }, + russia = { + Idstring("units/payday2/characters/ene_spook_1/ene_spook_1"), + }, + zombie = { + Idstring("units/payday2/characters/ene_spook_1/ene_spook_1"), + }, + murkywater = { + Idstring("units/payday2/characters/ene_spook_1/ene_spook_1"), + }, + federales = { + Idstring("units/payday2/characters/ene_spook_1/ene_spook_1"), + }, + }, + access = access_type_all, + } - -- Medic - self.unit_categories.medic_unit = { - special_type = "medic", - unit_types = { - america = { - Idstring("units/payday2/characters/ene_medic_m4/ene_medic_m4"), - Idstring("units/payday2/characters/ene_medic_r870/ene_medic_r870") - }, - russia = { - Idstring("units/payday2/characters/ene_medic_m4/ene_medic_m4"), - Idstring("units/payday2/characters/ene_medic_r870/ene_medic_r870") - }, - zombie = { - Idstring("units/payday2/characters/ene_medic_m4/ene_medic_m4"), - Idstring("units/payday2/characters/ene_medic_r870/ene_medic_r870") - }, - murkywater = { - Idstring("units/payday2/characters/ene_medic_m4/ene_medic_m4"), - Idstring("units/payday2/characters/ene_medic_r870/ene_medic_r870") - }, - federales = { - Idstring("units/payday2/characters/ene_medic_m4/ene_medic_m4"), - Idstring("units/payday2/characters/ene_medic_r870/ene_medic_r870") - } - }, - access = access_type_all - } + -- ZEAL Rifleman + self.unit_categories.shepheard_zeal = { + unit_types = { + america = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_swat/ene_zeal_swat"), + }, + russia = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_swat/ene_zeal_swat"), + }, + zombie = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_swat/ene_zeal_swat"), + }, + murkywater = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_swat/ene_zeal_swat"), + }, + federales = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_swat/ene_zeal_swat"), + }, + }, + access = access_type_all, + } - -- Green / Saiga Dozers - self.unit_categories.tank_fbi = { - special_type = "tank", - unit_types = { - america = { - Idstring("units/payday2/characters/ene_bulldozer_1/ene_bulldozer_1"), - Idstring("units/payday2/characters/ene_bulldozer_2/ene_bulldozer_2") - }, - russia = { - Idstring("units/payday2/characters/ene_bulldozer_1/ene_bulldozer_1"), - Idstring("units/payday2/characters/ene_bulldozer_2/ene_bulldozer_2") - }, - zombie = { - Idstring("units/payday2/characters/ene_bulldozer_1/ene_bulldozer_1"), - Idstring("units/payday2/characters/ene_bulldozer_2/ene_bulldozer_2") - }, - murkywater = { - Idstring("units/payday2/characters/ene_bulldozer_1/ene_bulldozer_1"), - Idstring("units/payday2/characters/ene_bulldozer_2/ene_bulldozer_2") - }, - federales = { - Idstring("units/payday2/characters/ene_bulldozer_1/ene_bulldozer_1"), - Idstring("units/payday2/characters/ene_bulldozer_2/ene_bulldozer_2") - } - }, - access = access_type_all - } + -- ZEAL Shotgunner + self.unit_categories.ksg_zeal = { + unit_types = { + america = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_swat_2/ene_zeal_swat_2"), + }, + russia = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_swat_2/ene_zeal_swat_2"), + }, + zombie = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_swat_2/ene_zeal_swat_2"), + }, + murkywater = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_swat_2/ene_zeal_swat_2"), + }, + federales = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_swat_2/ene_zeal_swat_2"), + }, + }, + access = access_type_all, + } - -- Lmg Dozers - self.unit_categories.tank_elite = { - special_type = "tank", - unit_types = { - america = { - Idstring("units/payday2/characters/ene_bulldozer_3/ene_bulldozer_3") - }, - russia = { - Idstring("units/payday2/characters/ene_bulldozer_3/ene_bulldozer_3") - }, - zombie = { - Idstring("units/payday2/characters/ene_bulldozer_3/ene_bulldozer_3") - }, - murkywater = { - Idstring("units/payday2/characters/ene_bulldozer_3/ene_bulldozer_3") - }, - federales = { - Idstring("units/payday2/characters/ene_bulldozer_3/ene_bulldozer_3") - } - }, - access = access_type_all - } + -- ZEAL Heavy Rifleman + self.unit_categories.rifle_heavy_zeal = { + unit_types = { + america = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy/ene_zeal_swat_heavy"), + }, + russia = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy/ene_zeal_swat_heavy"), + }, + zombie = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy/ene_zeal_swat_heavy"), + }, + murkywater = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy/ene_zeal_swat_heavy"), + }, + federales = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy/ene_zeal_swat_heavy"), + }, + }, + access = access_type_all, + } - -- Cloakers - self.unit_categories.cloaker = { - unit_types = { - america = { - Idstring( "units/payday2/characters/ene_spook_1/ene_spook_1" ) - }, - russia = { - Idstring( "units/payday2/characters/ene_spook_1/ene_spook_1" ) - }, - zombie = { - Idstring( "units/payday2/characters/ene_spook_1/ene_spook_1" ) - }, - murkywater = { - Idstring( "units/payday2/characters/ene_spook_1/ene_spook_1" ) - }, - federales = { - Idstring( "units/payday2/characters/ene_spook_1/ene_spook_1" ) - } - }, - access = access_type_all - } + -- ZEAL Heavy Shotgunner + self.unit_categories.ksg_heavy_zeal = { + unit_types = { + america = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy_2/ene_zeal_swat_heavy_2"), + }, + russia = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy_2/ene_zeal_swat_heavy_2"), + }, + zombie = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy_2/ene_zeal_swat_heavy_2"), + }, + murkywater = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy_2/ene_zeal_swat_heavy_2"), + }, + federales = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_swat_heavy_2/ene_zeal_swat_heavy_2"), + }, + }, + access = access_type_all, + } -end) + -- ZEAL Medic + self.unit_categories.zeal_medic = { + special_type = "medic", + unit_types = { + america = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_medic_m4/ene_zeal_medic_m4"), + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_medic_r870/ene_zeal_medic_r870"), + }, + russia = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_medic_m4/ene_zeal_medic_m4"), + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_medic_r870/ene_zeal_medic_r870"), + }, + zombie = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_medic_m4/ene_zeal_medic_m4"), + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_medic_r870/ene_zeal_medic_r870"), + }, + murkywater = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_medic_m4/ene_zeal_medic_m4"), + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_medic_r870/ene_zeal_medic_r870"), + }, + federales = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_medic_m4/ene_zeal_medic_m4"), + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_medic_r870/ene_zeal_medic_r870"), + }, + }, + access = access_type_all, + } -Hooks:PostHook(GroupAITweakData, "_init_enemy_spawn_groups", "eclipse__init_enemy_spawn_groups", function(self, difficulty_index) - -- Tactic Presets - -- GenSec presets are more advanced than FBI ones - -- Only GenSec enemies can flank as well, this should make flanking less predictable and far scarier - self._tactics = { - Phalanx_minion = { - "ranged_fire" + -- ZEAL Taser + self.unit_categories.zeal_taser = { + special_type = "taser", + unit_types = { + america = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_tazer/ene_zeal_tazer"), + }, + russia = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_tazer/ene_zeal_tazer"), + }, + zombie = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_tazer/ene_zeal_tazer"), + }, + murkywater = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_tazer/ene_zeal_tazer"), + }, + federales = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_tazer/ene_zeal_tazer"), + }, + }, + access = access_type_all, + } + + -- ZEAL Cloaker + self.unit_categories.zeal_spooc = { + special_type = "spooc", + unit_types = { + america = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_cloaker/ene_zeal_cloaker"), + }, + russia = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_cloaker/ene_zeal_cloaker"), + }, + zombie = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_cloaker/ene_zeal_cloaker"), + }, + murkywater = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_cloaker/ene_zeal_cloaker"), + }, + federales = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_cloaker/ene_zeal_cloaker"), + }, + }, + access = access_type_all, + } + + -- ZEAL Shield + self.unit_categories.zeal_shield = { + special_type = "shield", + unit_types = { + america = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_swat_shield/ene_zeal_swat_shield"), + }, + russia = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_swat_shield/ene_zeal_swat_shield"), + }, + zombie = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_swat_shield/ene_zeal_swat_shield"), + }, + murkywater = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_swat_shield/ene_zeal_swat_shield"), + }, + federales = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_swat_shield/ene_zeal_swat_shield"), + }, + }, + access = access_type_walk_only, + } + + -- ZEAL Dozers + self.unit_categories.zeal_tank = { + special_type = "tank", + unit_types = { + america = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_bulldozer/ene_zeal_bulldozer"), + Idstring("units/pd2_dlc_drm/characters/ene_bulldozer_minigun/ene_bulldozer_minigun"), + }, + russia = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_bulldozer/ene_zeal_bulldozer"), + Idstring("units/pd2_dlc_drm/characters/ene_bulldozer_minigun/ene_bulldozer_minigun"), + }, + zombie = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_bulldozer/ene_zeal_bulldozer"), + Idstring("units/pd2_dlc_drm/characters/ene_bulldozer_minigun/ene_bulldozer_minigun"), + }, + murkywater = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_bulldozer/ene_zeal_bulldozer"), + Idstring("units/pd2_dlc_drm/characters/ene_bulldozer_minigun/ene_bulldozer_minigun"), + }, + federales = { + Idstring("units/pd2_dlc_gitgud/characters/ene_zeal_bulldozer/ene_zeal_bulldozer"), + Idstring("units/pd2_dlc_drm/characters/ene_bulldozer_minigun/ene_bulldozer_minigun"), + }, + }, + access = access_type_all, + } +end) + +Hooks:PostHook(GroupAITweakData, "_init_enemy_spawn_groups", "eclipse__init_enemy_spawn_groups", function(self, difficulty_index) + -- Tactic Presets + -- GenSec presets are more advanced than FBI ones + -- Only GenSec enemies can flank as well, this makes flanking less predictable and defines them as the superior faction + self._tactics = { + beat_cop = { + "ranged_fire", + "no_push", + }, + swat_assault = { + "shield_cover", + }, + swat_shield = { + "shield", + }, + fbi_rifle = { + "shield_cover", + "ranged_fire", + }, + fbi_shotgun = { + "charge", + }, + fbi_special = { + "flash_grenade", + "shield_cover", + "murder", + }, + fbi_shield = { + "shield", + "ranged_fire", + }, + fbi_tank = { + "charge", + "flash_grenade", + }, + elite_ranged = { + "ranged_fire", + "smoke_grenade", + "flash_grenade", + "shield_cover", + }, + elite_assault = { + "smoke_grenade", + "flash_grenade", + "shield_cover", + "deathguard", + "murder", + }, + elite_shotgun = { + "charge", + "flash_grenade", + "shield_cover", + "deathguard", + "murder", + }, + elite_flank = { + "flank", + "smoke_grenade", + "flash_grenade", + "murder", + }, + elite_shield = { + "shield", + "no_push", + "ranged_fire", + "smoke_grenade", + }, + elite_shield_charge = { + "shield", + "charge", + "smoke_grenade", + }, + elite_special = { + "ranged_fire", + "shield_cover", + "smoke_grenade", + "flash_grenade", + "murder", + }, + elite_special_flank = { + "flank", + "smoke_grenade", + "flash_grenade", + "murder", + }, + elite_tank = { + "flash_grenade", + "smoke_grenade", + "deathguard", + "murder", + }, + spooc_charge = { + "charge", + "flash_grenade", + "smoke_grenade", + "deathguard", + }, + spooc_flank = { + "flank", + "flash_grenade", + "smoke_grenade", + }, + reenforce_aggressive = { + "charge", + "flash_grenade", + "smoke_grenade", + }, + reenforce_passive = { + "ranged_fire", + "flash_grenade", + "smoke_grenade", + "no_push", + }, + recon_attack = { + "ranged_fire", + "smoke_grenade", + }, + recon_rescue = { + "flank", + "flash_grenade", + "no_push", + }, + } + + -- соси хуй кк? + self.enemy_spawn_groups = {} + + -- Custom spawngroups + self.enemy_spawn_groups.beat_cops = { + amount = { + 2, + 3, + }, + spawn = { + { + amount_min = 2, + freq = 0.5, + amount_max = 3, + rank = 1, + unit = "beat_cop", + tactics = self._tactics.beat_cop, + }, + }, + } + self.enemy_spawn_groups.blue_swats = { + amount = { + 2, + 3, + }, + spawn = { + { + amount_min = 1, + freq = 0.75, + amount_max = 3, + rank = 1, + unit = "swat_m4", + tactics = self._tactics.swat_assault, + }, + { + freq = 0.35, + amount_max = 2, + rank = 1, + unit = "swat_r870", + tactics = self._tactics.swat_assault, + }, + }, + } + self.enemy_spawn_groups.swat_shields = { + amount = { + 3, + 3, + }, + spawn = { + { + amount_min = 2, + freq = 1, + rank = 1, + unit = "swat_m4", + tactics = self._tactics.swat_assault, + }, + { + amount_min = 1, + freq = 1, + amount_max = 1, + rank = 2, + unit = "swat_shield", + tactics = self._tactics.swat_shield, + }, + }, + } + self.enemy_spawn_groups.swat_tasers = { + amount = { + 2, + 3, + }, + spawn = { + { + amount_min = 1, + freq = 0.5, + amount_max = 2, + rank = 1, + unit = "swat_m4", + tactics = self._tactics.swat_assault, + }, + { + freq = 0.5, + amount_max = 1, + rank = 2, + unit = "swat_shield", + tactics = self._tactics.swat_shield, + }, + { + amount_min = 1, + freq = 1, + amount_max = 1, + rank = 3, + unit = "taser_unit", + tactics = self._tactics.swat_assault, + }, + }, + } + self.enemy_spawn_groups.swat_tanks = { + amount = { + 1, + 2, + }, + spawn = { + { + amount_min = 1, + freq = 1, + amount_max = 1, + rank = 2, + unit = "tank_fbi", + tactics = self._tactics.fbi_tank, + }, + { + amount_min = 1, + freq = 1, + amount_max = 1, + rank = 1, + unit = "swat_m4", + tactics = self._tactics.swat_assault, + }, + }, + } + self.enemy_spawn_groups.fbi_lights = { + amount = { + 3, + 4, + }, + spawn = { + { + freq = 0.75, + amount_max = 3, + rank = 1, + unit = "fbi_m4", + tactics = self._tactics.fbi_rifle, + }, + { + freq = 0.75, + amount_max = 3, + rank = 1, + unit = "fbi_r870", + tactics = self._tactics.fbi_shotgun, + }, + { + freq = 0.35, + amount_max = 1, + rank = 2, + unit = "whiteshirt", + tactics = self._tactics.elite_ranged, + }, + }, + } + self.enemy_spawn_groups.fbi_heavies = { + amount = { + 3, + 4, + }, + spawn = { + { + freq = 0.75, + amount_max = 3, + rank = 1, + unit = "heavy_fbi_m4", + tactics = self._tactics.fbi_rifle, + }, + { + freq = 0.75, + amount_max = 3, + rank = 1, + unit = "heavy_fbi_r870", + tactics = self._tactics.fbi_shotgun, + }, + { + freq = 0.35, + amount_max = 1, + rank = 2, + unit = "medic_unit", + tactics = self._tactics.fbi_special, + }, + }, + } + self.enemy_spawn_groups.fbi_shields = { + amount = { + 3, + 4, + }, + spawn = { + { + amount_min = 2, + freq = 2, + amount_max = 2, + rank = 3, + unit = "shield_fbi", + tactics = self._tactics.fbi_shield, + }, + { + freq = 0.35, + amount_max = 1, + rank = 2, + unit = "taser_unit", + tactics = self._tactics.fbi_special, + }, + { + amount_min = 1, + freq = 1, + amount_max = 1, + rank = 1, + unit = "heavy_fbi_m4", + tactics = self._tactics.fbi_rifle, + }, + }, + } + self.enemy_spawn_groups.fbi_tanks = { + amount = { + 3, + 3, + }, + spawn = { + { + amount_min = 1, + freq = 1, + amount_max = 1, + rank = 3, + unit = "tank_fbi", + tactics = self._tactics.fbi_tank, + }, + { + amount_min = 1, + freq = 1, + amount_max = 1, + rank = 2, + unit = "taser_unit", + tactics = self._tactics.fbi_special, + }, + { + amount_min = 1, + freq = 1, + amount_max = 1, + rank = 1, + unit = "heavy_fbi_m4", + tactics = self._tactics.fbi_rifle, + }, + }, + } + self.enemy_spawn_groups.gensec_cqc_lights = { + amount = { + 3, + 4, + }, + spawn = { + { + freq = 0.66, + amount_max = 3, + rank = 2, + unit = "benelli_elite", + tactics = self._tactics.elite_shotgun, + }, + { + freq = 0.66, + amount_max = 3, + rank = 2, + unit = "ump_elite", + tactics = self._tactics.elite_assault, + }, + { + freq = 0.15, + amount_max = 1, + rank = 3, + unit = "cloaker", + tactics = self._tactics.spooc_charge, + }, + }, + } + self.enemy_spawn_groups.gensec_ranged_lights = { + amount = { + 3, + 4, + }, + spawn = { + { + freq = 0.66, + amount_max = 3, + rank = 2, + unit = "g36_elite", + tactics = self._tactics.elite_ranged, + }, + { + freq = 0.66, + amount_max = 3, + rank = 2, + unit = "ump_elite", + tactics = self._tactics.elite_assault, + }, + { + freq = 0.15, + amount_max = 1, + rank = 3, + unit = "taser_unit", + tactics = self._tactics.elite_special, + }, + }, + } + self.enemy_spawn_groups.gensec_shields = { + amount = { + 3, + 4, + }, + spawn = { + { + amount_min = 1, + freq = 2, + amount_max = 1, + rank = 3, + unit = "shield_elite", + tactics = self._tactics.elite_shield, + }, + { + freq = 0.35, + amount_max = 1, + rank = 2, + unit = "taser_unit", + tactics = self._tactics.elite_special, + }, + { + amount_min = 1, + freq = 1, + amount_max = 1, + rank = 1, + unit = "g36_elite", + tactics = self._tactics.elite_ranged, + }, + { + freq = 0.75, + amount_max = 1, + rank = 1, + unit = "heavy_fbi_m4", + tactics = self._tactics.elite_ranged, + }, + }, + } + self.enemy_spawn_groups.gensec_flankers = { + amount = { + 3, + 4, + }, + spawn = { + { + freq = 0.2, + amount_max = 1, + rank = 3, + unit = "taser_unit", + tactics = self._tactics.elite_special_flank, + }, + { + freq = 0.66, + amount_max = 3, + rank = 2, + unit = "ump_elite", + tactics = self._tactics.elite_flank, + }, + { + freq = 0.66, + amount_max = 3, + rank = 2, + unit = "benelli_elite", + tactics = self._tactics.elite_flank, + }, + }, + } + self.enemy_spawn_groups.gensec_tasers = { + amount = { + 3, + 4, + }, + spawn = { + { + amount_min = 2, + freq = 2, + amount_max = 2, + rank = 3, + unit = "taser_unit", + tactics = self._tactics.elite_special, + }, + { + freq = 0.33, + amount_max = 1, + rank = 2, + unit = "medic_unit", + tactics = self._tactics.elite_special, + }, + { + freq = 0.15, + amount_max = 1, + rank = 2, + unit = "cloaker", + tactics = self._tactics.spooc_charge, + }, + { + freq = 0.75, + amount_max = 2, + rank = 1, + unit = "ump_elite", + tactics = self._tactics.elite_assault, + }, + }, + } + self.enemy_spawn_groups.gensec_tanks = { + amount = { + 4, + 4, + }, + spawn = { + { + amount_min = 1, + freq = 1, + amount_max = 1, + rank = 3, + unit = "tank_elite", + tactics = self._tactics.elite_tank, + }, + { + amount_min = 1, + freq = 1, + amount_max = 1, + rank = 2, + unit = "taser_unit", + tactics = self._tactics.elite_special, + }, + { + amount_min = 1, + freq = 1, + amount_max = 1, + rank = 1, + unit = "medic_unit", + tactics = self._tactics.elite_special, + }, + { + amount_min = 1, + freq = 1, + amount_max = 1, + rank = 1, + unit = "heavy_fbi_m4", + tactics = self._tactics.fbi_rifle, + }, + }, + } + self.enemy_spawn_groups.spoocs = { + amount = { + 2, + 2, + }, + spawn = { + { + freq = 2, + amount_min = 2, + amount_max = 2, + rank = 1, + unit = "cloaker", + tactics = self._tactics.spooc_flank, + }, + }, + } + self.enemy_spawn_groups.zeal_lights_charge = { + amount = { + 3, + 4, + }, + spawn = { + { + freq = 0.66, + amount_max = 3, + rank = 1, + unit = "ksg_zeal", + tactics = self._tactics.elite_shotgun, + }, + { + freq = 0.66, + amount_max = 3, + rank = 1, + unit = "shepheard_zeal", + tactics = self._tactics.elite_assault, + }, + { + freq = 0.5, + amount_max = 1, + rank = 2, + unit = "zeal_shield", + tactics = self._tactics.elite_shield_charge, + }, + }, + } + self.enemy_spawn_groups.zeal_lights_flank = { + amount = { + 3, + 4, + }, + spawn = { + { + freq = 0.66, + amount_max = 3, + rank = 1, + unit = "ksg_zeal", + tactics = self._tactics.elite_flank, + }, + { + freq = 0.66, + amount_max = 3, + rank = 1, + unit = "shepheard_zeal", + tactics = self._tactics.elite_flank, + }, + { + freq = 0.33, + amount_max = 1, + rank = 2, + unit = "zeal_spooc", + tactics = self._tactics.spooc_flank, + }, + { + freq = 0.33, + amount_max = 1, + rank = 2, + unit = "zeal_taser", + tactics = self._tactics.elite_special_flank, + }, + }, + } + self.enemy_spawn_groups.zeal_heavies_ranged = { + amount = { + 3, + 4, + }, + spawn = { + { + freq = 0.66, + amount_max = 2, + rank = 1, + unit = "rifle_heavy_zeal", + tactics = self._tactics.elite_ranged, + }, + { + freq = 0.6, + amount_max = 1, + rank = 2, + unit = "zeal_shield", + tactics = self._tactics.elite_shield, + }, + { + freq = 0.4, + amount_max = 1, + rank = 3, + unit = "zeal_taser", + tactics = self._tactics.elite_special, + }, + }, + } + self.enemy_spawn_groups.zeal_heavies_charge = { + amount = { + 3, + 4, }, - Phalanx_vip = { - "ranged_fire" + spawn = { + { + freq = 0.66, + amount_max = 2, + rank = 1, + unit = "ksg_heavy_zeal", + tactics = self._tactics.elite_shotgun, + }, + { + freq = 0.66, + amount_max = 2, + rank = 1, + unit = "rifle_heavy_zeal", + tactics = self._tactics.elite_assault, + }, + { + freq = 0.6, + amount_max = 1, + rank = 2, + unit = "zeal_shield", + tactics = self._tactics.elite_shield_charge, + }, + { + freq = 0.2, + amount_max = 1, + rank = 2, + unit = "zeal_medic", + tactics = self._tactics.elite_special, + }, }, - fbi_rifle = { - "shield_cover", - "flash_grenade" + } + self.enemy_spawn_groups.zeal_tasers = { + amount = { + 3, + 4, }, - fbi_shotgun = { - "charge", - "shield_cover", - "flash_grenade" + spawn = { + { + amount_min = 2, + freq = 1, + amount_max = 2, + rank = 3, + unit = "zeal_taser", + tactics = self._tactics.elite_special, + }, + { + freq = 0.66, + amount_max = 1, + rank = 1, + unit = "rifle_heavy_zeal", + tactics = self._tactics.elite_assault, + }, + { + freq = 0.66, + amount_max = 1, + rank = 2, + unit = "ksg_heavy_zeal", + tactics = self._tactics.elite_shotgun, + }, + { + freq = 0.2, + amount_max = 1, + rank = 2, + unit = "zeal_medic", + tactics = self._tactics.elite_special, + }, }, - fbi_shield = { - "charge", - "shield" + } + self.enemy_spawn_groups.zeal_shields = { + amount = { + 3, + 4, }, - fbi_special = { - "shield_cover", - "flash_grenade" + spawn = { + { + amount_min = 1, + freq = 1, + amount_max = 1, + rank = 1, + unit = "zeal_shield", + tactics = self._tactics.elite_shield, + }, + { + amount_min = 1, + freq = 1, + amount_max = 1, + rank = 1, + unit = "shield_elite", + tactics = self._tactics.elite_shield, + }, + { + freq = 0.66, + amount_max = 1, + rank = 1, + unit = "ksg_zeal", + tactics = self._tactics.elite_special, + }, + { + freq = 0.66, + amount_max = 1, + rank = 1, + unit = "shepheard_zeal", + tactics = self._tactics.elite_special, + }, }, - fbi_tank = { - "charge", - "flash_grenade" + } + self.enemy_spawn_groups.zeal_tanks = { + amount = { + 3, + 4, }, - elite_long_range = { - "ranged_fire", - "smoke_grenade", - "rescue_hostages", - "shield_cover" + spawn = { + { + amount_min = 1, + freq = 1, + amount_max = 1, + rank = 3, + unit = "zeal_tank", + tactics = self._tactics.elite_tank, + }, + { + amount_min = 1, + freq = 1, + amount_max = 1, + rank = 2, + unit = "zeal_medic", + tactics = self._tactics.elite_special, + }, + { + freq = 0.66, + amount_max = 1, + rank = 2, + unit = "zeal_shield", + tactics = self._tactics.elite_shield_charge, + }, + { + freq = 0.66, + amount_max = 1, + rank = 1, + unit = "ksg_heavy_zeal", + tactics = self._tactics.elite_shotgun, + }, }, - elite_assault = { - "flash_grenade", - "smoke_grenade", - "rescue_hostages", - "shield_cover", - "deathguard", - "murder" + } + self.enemy_spawn_groups.zeal_spoocs = { + amount = { + 3, + 3, }, - elite_shotgun = { - "charge", - "flash_grenade", - "rescue_hostages", - "shield_cover", - "deathguard", - "murder" + spawn = { + { + freq = 3, + amount_min = 3, + amount_max = 3, + rank = 1, + unit = "zeal_spooc", + tactics = self._tactics.spooc_flank, + }, }, - elite_flank = { - "flank", - "flash_grenade", - "smoke_grenade", - "rescue_hostages", - "deathguard", - "murder" + } + self.enemy_spawn_groups.reenforce_sneaky = { + amount = { + 4, + 4, }, - elite_shield = { - "charge", - "shield", - "flash_grenade" + spawn = { + { + amount_min = 2, + freq = 2, + amount_max = 2, + rank = 2, + unit = "cloaker", + tactics = self._tactics.reenforce_aggressive, + }, + { + amount_min = 2, + freq = 2, + amount_max = 2, + rank = 1, + unit = "whiteshirt", + tactics = self._tactics.reenforce_aggressive, + }, }, - elite_special = { - "ranged_fire", - "shield_cover", - "flash_grenade", - "rescue_hostages", - "deathguard", - "murder" + } + self.enemy_spawn_groups.reenforce_common = { + amount = { + 4, + 4, }, - elite_special_flank = { - "flank", - "shield_cover", - "flash_grenade", - "rescue_hostages", - "deathguard", - "murder" + spawn = { + { + amount_min = 2, + freq = 2, + amount_max = 2, + rank = 1, + unit = "balaclava", + tactics = self._tactics.reenforce_aggressive, + }, + { + amount_min = 2, + freq = 2, + amount_max = 2, + rank = 1, + unit = "whiteshirt", + tactics = self._tactics.reenforce_passive, + }, }, - elite_tank_ranged = { - "ranged_fire", - "flash_grenade", - "smoke_grenade", - "deathguard", - "shield", - "murder" + } + self.enemy_spawn_groups.recon_hrt = { + amount = { + 4, + 4, }, - spooc = { - "flank", - "flash_grenade", - "smoke_grenade", - "deathguard" + spawn = { + { + amount_min = 2, + freq = 1, + amount_max = 2, + rank = 1, + unit = "balaclava", + tactics = self._tactics.recon_rescue, + }, + { + amount_min = 2, + freq = 1, + amount_max = 2, + rank = 1, + unit = "whiteshirt", + tactics = self._tactics.recon_rescue, + }, }, - reenforce_aggressive = { - "charge", - "flash_grenade", - "smoke_grenade", + } + self.enemy_spawn_groups.recon_aggressive = { + amount = { + 3, + 3, }, - reenforce_passive = { - "ranged_fire", - "flash_grenade", - "smoke_grenade", + spawn = { + { + amount_min = 1, + freq = 1, + amount_max = 1, + rank = 1, + unit = "swat_r870", + tactics = self._tactics.recon_attack, + }, + { + amount_min = 1, + freq = 1, + amount_max = 1, + rank = 1, + unit = "swat_m4", + tactics = self._tactics.recon_attack, + }, + { + amount_min = 1, + freq = 1, + amount_max = 1, + rank = 2, + unit = "taser_unit", + tactics = self._tactics.recon_attack, + }, }, - recon_attack = { - "ranged_fire", - "smoke_grenade" + } + self.enemy_spawn_groups.single_spooc = { + amount = { + 1, + 1, + }, + spawn = { + { + freq = 1, + amount_min = 1, + rank = 1, + unit = "cloaker", + tactics = self._tactics.spooc, + }, }, - recon_rescue = { - "flank", - "flash_grenade", - "rescue_hostages" - } } + self.enemy_spawn_groups.FBI_spoocs = self.enemy_spawn_groups.single_spooc +end) - -- соси хуй кк? - self.enemy_spawn_groups = {} +-- get rid of marshals +function GroupAITweakData:_init_enemy_spawn_groups_level() end - -- Custom spawngroups - if difficulty_index >= 7 then - self.enemy_spawn_groups.common_charge = { - amount = { - 4, - 5 - }, - spawn = { - { - amount_min = 1, - freq = 0.75, - amount_max = 2, - rank = 1, - unit = "rifleman_fbi", - tactics = self._tactics.fbi_rifle - }, - { - amount_min = 1, - freq = 0.75, - amount_max = 2, - rank = 1, - unit = "shotgunner_fbi", - tactics = self._tactics.fbi_shotgun - }, - { - amount_min = 0, - freq = 0.75, - amount_max = 1, - rank = 2, - unit = "heavy_rifleman_fbi", - tactics = self._tactics.fbi_rifle - }, - { - amount_min = 0, - freq = 0.75, - amount_max = 1, - rank = 2, - unit = "heavy_shotgunner_fbi", - tactics = self._tactics.fbi_shotgun - }, - { - amount_min = 0, - freq = 0.5, - amount_max = 1, - rank = 3, - unit = "taser_unit", - tactics = self._tactics.fbi_special - } - } +Hooks:PostHook(GroupAITweakData, "_init_task_data", "eclipse__init_task_data", function(self, difficulty_index) + -- difficulty scaling + local f = ((difficulty_index ^ 2) / (difficulty_index * 3)) + + -- Assault Data + -- AI Tickrate + self.ai_tickrate = 1 / (30 * f) + + -- BESIEGE -- + + -- PHASES -- + + -- Sustain + self.besiege.assault.sustain_duration_min = { 40 * f, 75 * f, 105 * f } + self.besiege.assault.sustain_duration_max = { 40 * f, 75 * f, 105 * f } + self.besiege.assault.sustain_duration_balance_mul = { 1, 1, 1, 1 } + + -- Control + self.besiege.assault.delay = { 40 / f, 30 / f, 15 / f } + self.besiege.assault.hostage_hesitation_delay = { 10, 5, 2.5 } + + -- SPAWNS -- + + -- Spawncap + self.besiege.assault.force = { 4, 9, math.min(10, 7.5 * f) } + self.besiege.assault.force_balance_mul = { 1.5, 1.5, 1.75, 2 } + + -- Spawnrate + self.spawn_kill_cooldown = 8 / (math.sqrt(f)) + self.besiege.assault.spawnrate = { 1.6 / (math.sqrt(f)), 1.4 / (math.sqrt(f)), 1.2 / (math.sqrt(f)) } + self.besiege.assault.spawnrate_balance_mul = { 2, 1.6, 1.4, 1.2 } + + -- Spawnpool + self.besiege.assault.force_pool = { + 1000, + 1000, + 1000, -- increase to make it impossible to exhaust considering increased length + } + + -- RECON / REENFORCE -- + + -- Reenforce spawn interval + self.besiege.reenforce.interval = { 60 / f, 40 / f, 20 / f } + + -- Recon spawn interval and spawncap + self.besiege.recon.interval_variation = 0 + self.besiege.recon.interval = { 20 / f, 16 / f, 10 / f } + self.besiege.recon.force = { 2, 4, 6 } + + -- GRENADES -- + -- global + self.min_grenade_timeout = 20 / f + self.no_grenade_push_delay = 8 / (math.sqrt(f)) + + -- flash + self.flash_grenade.light_color = Vector3(255, 255, 255) + self.flash_grenade.light_range = 500 + self.flash_grenade_timeout = { 30 / f, 40 / f } + self.flash_grenade.timer = 2 / f + + -- smoke & gas + self.smoke_grenade_timeout = { 40 / f, 50 / f } + self.smoke_grenade_lifetime = 10 * f + self.cs_grenade_timeout = { 110 / f, 150 / f } + self.cs_grenade_lifetime = 10 * f + + -- Spawngroups + if difficulty_index == 2 then + self.besiege.assault.groups = { + beat_cops = { 0.5, 0.4, 0.25 }, + blue_swats = { 1, 1, 1 }, + swat_shields = { 0, 0, 0.25 }, } - self.enemy_spawn_groups.common_shield = { - amount = { - 4, - 4 - }, - spawn = { - { - amount_min = 2, - freq = 2, - amount_max = 2, - rank = 3, - unit = "shield_fbi", - tactics = self._tactics.fbi_shield - }, - { - amount_min = 0, - freq = 0.5, - amount_max = 2, - rank = 1, - unit = "shotgunner_fbi", - tactics = self._tactics.fbi_shotgun - }, - { - amount_min = 0, - freq = 0.5, - amount_max = 2, - rank = 1, - unit = "rifleman_fbi", - tactics = self._tactics.fbi_rifle - }, - { - amount_min = 0, - freq = 0.65, - amount_max = 1, - rank = 1, - unit = "heavy_rifleman_fbi", - tactics = self._tactics.fbi_rifle - }, - { - amount_min = 0, - freq = 0.65, - amount_max = 1, - rank = 1, - unit = "heavy_shotgunner_fbi", - tactics = self._tactics.fbi_shotgun - } - } + self.besiege.recon.groups = { + blue_swats = { 0, 0, 0 }, } - self.enemy_spawn_groups.common_tank = { - amount = { - 3, - 4 - }, - spawn = { - { - amount_min = 1, - freq = 2, - amount_max = 1, - rank = 3, - unit = "tank_fbi", - tactics = self._tactics.fbi_tank - }, - { - amount_min = 1, - freq = 1, - amount_max = 1, - rank = 2, - unit = "shield_fbi", - tactics = self._tactics.fbi_rifle - }, - { - amount_min = 0, - freq = 0.5, - amount_max = 1, - rank = 2, - unit = "taser_unit", - tactics = self._tactics.fbi_special - }, - { - amount_min = 0, - freq = 0.75, - amount_max = 2, - rank = 1, - unit = "heavy_shotgunner_fbi", - tactics = self._tactics.fbi_shotgun - } - } + self.besiege.reenforce.groups = { + blue_swats = { 1, 1, 1 }, } - self.enemy_spawn_groups.uncommon_charge = { - amount = { - 4, - 5 - }, - spawn = { - { - amount_min = 0, - freq = 0.75, - amount_max = 2, - rank = 1, - unit = "rifleman_fbi", - tactics = self._tactics.fbi_rifle - }, - { - amount_min = 0, - freq = 0.75, - amount_max = 2, - rank = 1, - unit = "shotgunner_fbi", - tactics = self._tactics.fbi_shotgun - }, - { - amount_min = 0, - freq = 0.75, - amount_max = 1, - rank = 2, - unit = "rifleman_sr_elite", - tactics = self._tactics.elite_assault - }, - { - amount_min = 0, - freq = 0.75, - amount_max = 1, - rank = 2, - unit = "shotgunner_elite", - tactics = self._tactics.elite_shotgun - }, - { - amount_min = 0, - freq = 0.5, - amount_max = 1, - rank = 3, - unit = "taser_unit", - tactics = self._tactics.fbi_special - } - } + elseif difficulty_index == 3 then + self.besiege.assault.groups = { + beat_cops = { 0.5, 0.25, 0 }, + blue_swats = { 0.8, 0.8, 1 }, + swat_shields = { 0, 0.125, 0.2 }, + swat_tasers = { 0, 0.1, 0.15 }, + fbi_lights = { 0, 0.1, 0.3 }, } - self.enemy_spawn_groups.elite_shieldg = { - amount = { - 4, - 4 - }, - spawn = { - { - amount_min = 2, - freq = 2, - amount_max = 2, - rank = 3, - unit = "shield_fbi", - tactics = self._tactics.elite_shield - }, - { - amount_min = 0, - freq = 0.75, - amount_max = 2, - rank = 2, - unit = "rifleman_sr_elite", - tactics = self._tactics.elite_assault - }, - { - amount_min = 0, - freq = 0.75, - amount_max = 2, - rank = 2, - unit = "shotgunner_elite", - tactics = self._tactics.elite_shotgun - } - } + self.besiege.recon.groups = { + beat_cops = { 1, 1, 1 }, } - self.enemy_spawn_groups.elite_flanklight = { - amount = { - 3, - 4 - }, - spawn = { - { - amount_min = 0, - freq = 0.2, - amount_max = 1, - rank = 3, - unit = "taser_unit", - tactics = self._tactics.elite_special_flank - }, - { - amount_min = 1, - freq = 0.66, - amount_max = 2, - rank = 2, - unit = "rifleman_sr_elite", - tactics = self._tactics.elite_flank - }, - { - amount_min = 1, - freq = 0.66, - amount_max = 2, - rank = 2, - unit = "shotgunner_elite", - tactics = self._tactics.elite_flank - } - } + self.besiege.reenforce.groups = { + blue_swats = { 1, 1, 1 }, } - self.enemy_spawn_groups.elite_long_range = { - amount = { - 3, - 3 - }, - spawn = { - { - amount_min = 3, - freq = 1, - amount_max = 3, - rank = 3, - unit = "rifleman_lr_elite", - tactics = self._tactics.elite_long_range - } - } + elseif difficulty_index == 4 then + self.besiege.assault.groups = { + blue_swats = { 0, 0.8, 0.75 }, + swat_shields = { 0, 0.125, 0.2 }, + swat_tasers = { 0, 0.1, 0.2 }, + swat_tanks = { 0, 0.01, 0.06 }, + fbi_lights = { 0, 1, 1.5 }, + fbi_heavies = { 0, 0.25, 0.75 }, } - self.enemy_spawn_groups.elite_heavy_charge = { - amount = { - 4, - 4 - }, - spawn = { - { - amount_min = 0, - freq = 1, - amount_max = 2, - rank = 2, - unit = "heavy_shotgunner_fbi", - tactics = self._tactics.elite_assault - }, - { - amount_min = 0, - freq = 1, - amount_max = 2, - rank = 2, - unit = "heavy_rifleman_fbi", - tactics = self._tactics.elite_shotgun - }, - { - amount_min = 2, - freq = 2, - amount_max = 2, - rank = 3, - unit = "taser_unit", - tactics = self._tactics.elite_special - } - } + self.besiege.recon.groups = { + recon_hrt = { 1, 1, 1 }, + recon_aggressive = { 0.66, 0.66, 0.66 }, } - self.enemy_spawn_groups.elite_tank = { - amount = { - 3, - 4 - }, - spawn = { - { - amount_min = 1, - freq = 1, - amount_max = 1, - rank = 3, - unit = "tank_elite", - tactics = self._tactics.elite_tank_ranged - }, - { - amount_min = 1, - freq = 0.66, - amount_max = 2, - rank = 2, - unit = "taser_unit", - tactics = self._tactics.elite_special - }, - { - amount_min = 1, - freq = 1, - amount_max = 1, - rank = 1, - unit = "medic_unit", - tactics = self._tactics.elite_special - } - } + self.besiege.reenforce.groups = { + reenforce_common = { 0.3, 0.3, 0.3 }, } - self.enemy_spawn_groups.cloaker_group = { - amount = { - 2, - 2 - }, - spawn = { - { - freq = 3, - amount_min = 2, - amount_max = 2, - rank = 1, - unit = "cloaker", - tactics = self._tactics.spooc - } - } + elseif difficulty_index == 5 then + self.besiege.assault.groups = { + blue_swats = { 1, 0.45, 0.25 }, + swat_shields = { 0.3, 0.3, 0.2 }, + swat_tasers = { 0.25, 0.25, 0.25 }, + fbi_lights = { 1.5, 1.5, 1.5 }, + fbi_heavies = { 0.3, 0.5, 1 }, + fbi_shields = { 0.2, 0.2, 0.3 }, + fbi_tanks = { 0, 0.02, 0.1 }, + spoocs = { 0, 0.03, 0.06 }, } - self.enemy_spawn_groups.reenforce_tank = { - amount = { - 3, - 5 - }, - spawn = { - { - amount_min = 1, - freq = 1, - amount_max = 1, - rank = 3, - unit = "tank_fbi", - tactics = self._tactics.reenforce_aggressive - }, - { - amount_min = 1, - freq = 0.5, - amount_max = 2, - rank = 2, - unit = "taser_unit", - tactics = self._tactics.reenforce_aggressive - }, - { - amount_min = 1, - freq = 0.66, - amount_max = 2, - rank = 1, - unit = "shotgunner_elite", - tactics = self._tactics.reenforce_aggressive - } - } + self.besiege.recon.groups = { + recon_hrt = { 1, 1, 1 }, + recon_aggressive = { 0.66, 0.66, 0.66 }, } - self.enemy_spawn_groups.reenforce_sneaky = { - amount = { - 4, - 4 - }, - spawn = { - { - amount_min = 2, - freq = 2, - amount_max = 2, - rank = 2, - unit = "cloaker", - tactics = self._tactics.reenforce_aggressive - }, - { - amount_min = 2, - freq = 2, - amount_max = 2, - rank = 1, - unit = "rifleman_sr_elite", - tactics = self._tactics.reenforce_passive - } - } + self.besiege.reenforce.groups = { + reenforce_common = { 0.3, 0.3, 0.3 }, + reenforce_sneaky = { 0, 0.05, 0.1 }, } - self.enemy_spawn_groups.reenforce_common = { - amount = { - 3, - 4 - }, - spawn = { - { - amount_min = 2, - freq = 1, - amount_max = 2, - rank = 2, - unit = "rifleman_fbi", - tactics = self._tactics.reenforce_passive - }, - { - amount_min = 1, - freq = 2, - amount_max = 1, - rank = 2, - unit = "rifleman_lr_elite", - tactics = self._tactics.reenforce_passive - }, - { - amount_min = 1, - freq = 1, - amount_max = 1, - rank = 1, - unit = "whiteshirt", - tactics = self._tactics.reenforce_passive - } - } + elseif difficulty_index == 6 then + self.besiege.assault.groups = { + blue_swats = { 1, 0.3, 0 }, + fbi_lights = { 1.75, 1.5, 0 }, + fbi_heavies = { 0.5, 0.75, 1.25 }, + fbi_shields = { 0.3, 0.3, 0.3 }, + fbi_tanks = { 0, 0.02, 0.13 }, + gensec_cqc_lights = { 0.3, 0.3, 0.5 }, + gensec_ranged_lights = { 0.3, 0.3, 0.5 }, + gensec_flankers = { 0.25, 0.25, 0.45 }, + gensec_tasers = { 0.15, 0.15, 0.3 }, + gensec_shields = { 0.00, 0.00, 0.15 }, + gensec_tanks = { 0, 0, 0.1 }, + spoocs = { 0, 0.045, 0.09 }, } - self.enemy_spawn_groups.recon_hrt = { - amount = { - 4, - 4 - }, - spawn = { - { - amount_min = 2, - freq = 1, - amount_max = 2, - rank = 1, - unit = "balaclava", - tactics = self._tactics.recon_rescue - }, - { - amount_min = 2, - freq = 1, - amount_max = 2, - rank = 1, - unit = "whiteshirt", - tactics = self._tactics.recon_rescue - } - } + self.besiege.recon.groups = { + recon_hrt = { 1, 1, 1 }, + recon_aggressive = { 0.66, 0.66, 0.66 }, } - self.enemy_spawn_groups.recon_aggressive = { - amount = { - 3, - 3 - }, - spawn = { - { - amount_min = 1, - freq = 1, - amount_max = 1, - rank = 1, - unit = "swat_shotgun", - tactics = self._tactics.recon_attack - }, - { - amount_min = 1, - freq = 1, - amount_max = 1, - rank = 1, - unit = "swat_rifle", - tactics = self._tactics.recon_attack - }, - { - amount_min = 1, - freq = 1, - amount_max = 1, - rank = 2, - unit = "taser_unit", - tactics = self._tactics.recon_attack - } - } + self.besiege.reenforce.groups = { + reenforce_common = { 0.3, 0.3, 0.3 }, + reenforce_sneaky = { 0.2, 0.2, 0.2 }, } end - self.enemy_spawn_groups.single_spooc = { - amount = { - 1, - 1 - }, - spawn = { - { - freq = 1, - amount_min = 1, - rank = 1, - unit = "cloaker", - tactics = self._tactics.spooc - } - } + + -- PONR -- + self.ponr = deep_clone(self.besiege) + + local job = Global.level_data and Global.level_data.level_id + local short_ponr_heists = { + bph, + red2, + bex, + pex, + glace, } - self.enemy_spawn_groups.FBI_spoocs = self.enemy_spawn_groups.single_spooc - -- remove marshals - if self.enemy_spawn_groups.marshal_squad then - self.enemy_spawn_groups.marshal_squad.max_nr_simultaneous_groups = 0 - end -end) + -- Sustain + self.ponr.assault.sustain_duration_min = { 900, 900, 900 } + self.ponr.assault.sustain_duration_max = { 900, 900, 900 } -Hooks:PostHook(GroupAITweakData, "_init_task_data", "eclipse__init_task_data", function(self, difficulty_index, difficulty) - local is_console = SystemInfo:platform() ~= Idstring("WIN32") - if difficulty_index == 7 or 8 then - self.besiege.recurring_group_SO = { - recurring_cloaker_spawn = { - retire_delay = 30, - interval = { - 20, - 40 - } - }, - recurring_spawn_1 = {interval = { - 30, - 60 - }} - } - end + -- Control + self.ponr.assault.delay = { 20, 20, 20 } + self.ponr.assault.hostage_hesitation_delay = { 10, 7.5, 5 } - -- Assault Data - -- PHASES -- - - -- Sustain - self.besiege.assault.sustain_duration_min = {70, 140, 210} - self.besiege.assault.sustain_duration_max = {70, 140, 210} - self.besiege.assault.sustain_duration_balance_mul = {1, 1, 1, 1} - - -- Control - self.besiege.assault.delay = {20, 15, 7.5} - - -- SPAWNS -- - - -- Spawncap - self.besiege.assault.force = {10, 14, 18} - self.besiege.assault.force_balance_mul = {1.2, 1.4, 1.6, 1.8} - - -- Spawnrate - if difficulty_index == 7 then - self.besiege.assault.spawnrate_balance_mul = {1.3, 1, 0.85, 0.7} - elseif difficulty_index == 8 then - self.besiege.assault.spawnrate_balance_mul = {1.2, 0.9, 0.7, 0.5} - end - - -- RECON / REENFORCE -- - - -- Make reenforce spawngroups spawn faster - self.besiege.reenforce.interval = {45, 35, 25} - - -- Make recon spawngroups spawn faster and increase their spawncap - self.besiege.recon.interval_variation = 0 - self.besiege.recon.interval = {10, 8, 5} - self.besiege.recon.force = {2, 6, 10} - - -- GRENADES -- - --shared - self.flash_grenade.light_color = Vector3(255, 255, 255) - self.flash_grenade.light_range = 500 - self.smoke_grenade_timeout = {20, 30} - self.cs_grenade_timeout = {55, 75} - - if difficulty_index == 7 then - self.flash_grenade_timeout = {12, 18} - self.flash_grenade.timer = 1.35 - self.smoke_grenade_lifetime = 12 - self.cs_grenade_lifetime = 15 - - elseif difficulty_index == 8 then - self.flash_grenade_timeout = {10, 15} - self.flash_grenade.timer = 1 - self.smoke_grenade_lifetime = 15 - self.cs_grenade_lifetime = 20 + if job and short_ponr_heists[job] then + self.ponr.assault.delay = { 5, 5, 5 } + self.ponr.assault.hostage_hesitation_delay = { 0, 0, 0 } end + -- Spawncap & Spawnrate + self.ponr.assault.force = { 4, 9, math.min(12, 8 * f) } + self.ponr.assault.spawnrate = { 1.4 / (math.sqrt(f)), 1.2 / (math.sqrt(f)), 1 / (math.sqrt(f)) } + + -- Recon + self.ponr.recon.groups = {} + self.ponr.recon.force = { 0, 0, 0 } -- no recon after ponr ran out + -- Spawngroups - if difficulty_index >= 7 then + if difficulty_index == 2 then + self.ponr.assault.groups = { + beat_cops = { 0.5, 0.25, 0 }, + blue_swats = { 0.8, 0.8, 1 }, + swat_shields = { 0, 0.125, 0.2 }, + swat_tasers = { 0, 0.1, 0.15 }, + fbi_lights = { 0, 0.1, 0.3 }, + } + elseif difficulty_index == 3 then + self.ponr.assault.groups = { + blue_swats = { 0, 0.8, 0.75 }, + swat_shields = { 0, 0.125, 0.2 }, + swat_tasers = { 0, 0.1, 0.2 }, + swat_tanks = { 0, 0.01, 0.06 }, + fbi_lights = { 0, 1, 1.5 }, + fbi_heavies = { 0, 0.25, 0.75 }, + } + self.ponr.reenforce.groups = { + reenforce_common = { 0.3, 0.3, 0.3 }, + } + elseif difficulty_index == 4 then self.besiege.assault.groups = { - common_charge = {1, 1, 1}, - common_shield = {0.66, 0.66, 0.66}, - common_tank = {0, 0.02, 0.16}, - uncommon_charge = {0.75, 0.75, 0.75}, - elite_flanklight = {0.45, 0.45, 0.45}, - elite_shieldg = {0.45, 0.45, 0.45}, - elite_long_range = {0.4, 0.4, 0.4}, - elite_heavy_charge = {0.35, 0.35, 0.35}, - elite_tank = {0, 0, 0.1}, - cloaker_group = {0, 0.08, 0.12}, - single_spooc = {0, 0, 0}, - Phalanx = {0, 0, 0}, - marshal_squad = {0, 0, 0}, - custom_assault = {0, 0, 0} + blue_swats = { 1, 0.45, 0.25 }, + swat_shields = { 0.3, 0.3, 0.2 }, + swat_tasers = { 0.25, 0.25, 0.25 }, + fbi_lights = { 1.5, 1.5, 1.5 }, + fbi_heavies = { 0.3, 0.5, 1 }, + fbi_shields = { 0.2, 0.2, 0.3 }, + fbi_tanks = { 0, 0.02, 0.1 }, + spoocs = { 0, 0.03, 0.06 }, } - self.besiege.recon.groups = { - recon_hrt = {1, 1, 1}, - recon_aggressive = {0.66, 0.66, 0.66}, - single_spooc = {0, 0, 0}, - Phalanx = {0, 0, 0}, - marshal_squad = {0, 0, 0}, - custom_recon = {0, 0, 0} + self.besiege.reenforce.groups = { + reenforce_common = { 0.3, 0.3, 0.3 }, + reenforce_sneaky = { 0, 0.05, 0.1 }, + } + elseif difficulty_index == 5 then + self.besiege.assault.groups = { + blue_swats = { 1, 0.3, 0 }, + fbi_lights = { 1.75, 1.5, 0 }, + fbi_heavies = { 0.5, 0.75, 1.25 }, + fbi_shields = { 0.3, 0.3, 0.3 }, + fbi_tanks = { 0, 0.02, 0.13 }, + gensec_cqc_lights = { 0.3, 0.3, 0.5 }, + gensec_ranged_lights = { 0.3, 0.3, 0.5 }, + gensec_flankers = { 0.25, 0.25, 0.45 }, + gensec_tasers = { 0.15, 0.15, 0.3 }, + gensec_shields = { 0.00, 0.00, 0.15 }, + gensec_tanks = { 0, 0, 0.1 }, + spoocs = { 0, 0.045, 0.09 }, } self.besiege.reenforce.groups = { - reenforce_common = {0.3, 0.3, 0.3}, - reenforce_sneaky = {0.2, 0.2, 0.2}, - reenforce_tank = {0.15, 0.15, 0.15} + reenforce_common = { 0.3, 0.3, 0.3 }, + reenforce_sneaky = { 0.2, 0.2, 0.2 }, + } + elseif difficulty_index == 6 then + self.ponr.assault.groups = { + zeal_lights_charge = { 1, 1, 1 }, + zeal_lights_flank = { 0.75, 0.75, 0.75 }, + zeal_heavies_ranged = { 0.55, 0.55, 0.55 }, + zeal_heavies_charge = { 0.55, 0.55, 0.55 }, + zeal_shields = { 0.4, 0.4, 0.4 }, + zeal_tasers = { 0.2, 0.2, 0.2 }, + zeal_tanks = { 0.1, 0.1, 0.1 }, + zeal_spoocs = { 0.1, 0.1, 0.1 }, + } + self.ponr.reenforce.groups = { + zeal_lights_charge = { 1, 1, 1 }, + zeal_heavies_ranged = { 0.6, 0.6, 0.6 }, + zeal_heavies_charge = { 0.6, 0.6, 0.6 }, + zeal_tasers = { 0.3, 0.3, 0.3 }, + zeal_spoocs = { 0.2, 0.2, 0.2 }, } end - -- Hoxd1 is a shit heist - if Global.level_data and Global.level_data.level_id == "hox_1" then - self.besiege.assault.spawnrate_balance_mul = {2.5, 1.75, 1.33, 1} - end + -- misc + self.besiege.assault.groups.single_spooc = { 0, 0, 0 } + self.besiege.assault.groups.Phalanx = { 0, 0, 0 } + self.besiege.assault.groups.marshal_squad = { 0, 0, 0 } + self.besiege.assault.groups.custom_assault = { 0, 0, 0 } + self.besiege.recon.groups.single_spooc = { 0, 0, 0 } + self.besiege.recon.groups.Phalanx = { 0, 0, 0 } + self.besiege.recon.groups.marshal_squad = { 0, 0, 0 } + self.besiege.recon.groups.custom_assault = { 0, 0, 0 } + self.ponr.assault.groups.single_spooc = { 0, 0, 0 } + self.ponr.assault.groups.Phalanx = { 0, 0, 0 } + self.ponr.assault.groups.marshal_squad = { 0, 0, 0 } + self.ponr.assault.groups.custom_assault = { 0, 0, 0 } + self.ponr.recon.groups.single_spooc = { 0, 0, 0 } + self.ponr.recon.groups.Phalanx = { 0, 0, 0 } + self.ponr.recon.groups.marshal_squad = { 0, 0, 0 } + self.ponr.recon.groups.custom_assault = { 0, 0, 0 } -- nuke captain self.phalanx.spawn_chance = { @@ -1165,7 +2011,7 @@ Hooks:PostHook(GroupAITweakData, "_init_task_data", "eclipse__init_task_data", f start = 0, respawn_delay = 300000, increase = 0, - max = 1 + max = 1, } self.street = deep_clone(self.besiege) diff --git a/lua/guitweakdata.lua b/lua/guitweakdata.lua new file mode 100644 index 0000000..431b471 --- /dev/null +++ b/lua/guitweakdata.lua @@ -0,0 +1,41 @@ +Hooks:PostHook(GuiTweakData, "init", "eclipse_init", function(self) + self.buy_weapon_categories = { + primaries = { + { + "assault_rifle", + }, + { + "shotgun", + }, + { + "lmg", + }, + { + "snp", + }, + { + "akimbo", + }, + { + "wpn_special", + }, + }, + secondaries = { + { + "pistol", + }, + { + "smg", + }, + { + "shotgun", + }, + { + "snp", + }, + { + "wpn_special", + }, + }, + } +end) diff --git a/lua/hudassaultcorner.lua b/lua/hudassaultcorner.lua new file mode 100644 index 0000000..2daaccd --- /dev/null +++ b/lua/hudassaultcorner.lua @@ -0,0 +1,94 @@ +if StreamHeist.settings.ponr_assault_text then + function HUDAssaultCorner:sync_start_assault(assault_number) + if self._point_of_no_return or self._casing then + return + end + + local color = self._assault_color + local diff_i = tweak_data:difficulty_to_index(Global.game_settings and Global.game_settings.difficulty or "normal") + + if managers.groupai:state_name() == "ponr" then + if diff_i == 6 then + color = Color.red + else + color = self._vip_assault_color + end + end + + if self._assault_mode == "phalanx" then + color = self._vip_assault_color + end + + self:_update_assault_hud_color(color) + self:set_assault_wave_number(assault_number) + + self._start_assault_after_hostage_offset = true + + self:_set_hostage_offseted(true) + end + + function HUDAssaultCorner:_get_assault_strings() + local diff_i = tweak_data:difficulty_to_index(Global.game_settings and Global.game_settings.difficulty or "normal") + + if self._assault_mode == "normal" then + if managers.job:current_difficulty_stars() > 0 then + local ids_risk = Idstring("risk") + + if managers.groupai:state_name() == "ponr" then + if diff_i == 6 then + return { + "hud_assault_ponr", + "hud_assault_end_line", + ids_risk, + "hud_assault_end_line", + "hud_assault_zeal_ponr", + "hud_assault_end_line", + ids_risk, + "hud_assault_end_line", + } + else + return { + "hud_assault_ponr", + "hud_assault_end_line", + ids_risk, + "hud_assault_end_line", + "hud_assault_normal_ponr", + "hud_assault_end_line", + ids_risk, + "hud_assault_end_line", + } + end + else + return { + "hud_assault_assault", + "hud_assault_end_line", + ids_risk, + "hud_assault_end_line", + "hud_assault_assault", + "hud_assault_end_line", + ids_risk, + "hud_assault_end_line", + } + end + else + if managers.groupai:state_name() == "ponr" then + return { + "hud_assault_ponr", + "hud_assault_end_line", + "hud_assault_normal_ponr", + "hud_assault_end_line", + } + else + return { + "hud_assault_assault", + "hud_assault_end_line", + "hud_assault_assault", + "hud_assault_end_line", + "hud_assault_assault", + "hud_assault_end_line", + } + end + end + end + end +end diff --git a/lua/hudmissionbriefing.lua b/lua/hudmissionbriefing.lua new file mode 100644 index 0000000..3a7ebef --- /dev/null +++ b/lua/hudmissionbriefing.lua @@ -0,0 +1,610 @@ +function HUDMissionBriefing:init(hud, workspace) + self._backdrop = MenuBackdropGUI:new(workspace) + + if not _G.IS_VR then + self._backdrop:create_black_borders() + end + + self._hud = hud + self._workspace = workspace + self._singleplayer = Global.game_settings.single_player + local bg_font = tweak_data.menu.pd2_massive_font + local title_font = tweak_data.menu.pd2_large_font + local content_font = tweak_data.menu.pd2_medium_font + local text_font = tweak_data.menu.pd2_small_font + local bg_font_size = tweak_data.menu.pd2_massive_font_size + local title_font_size = tweak_data.menu.pd2_large_font_size + local content_font_size = tweak_data.menu.pd2_medium_font_size + local text_font_size = tweak_data.menu.pd2_small_font_size + local interupt_stage = managers.job:interupt_stage() + self._background_layer_one = self._backdrop:get_new_background_layer() + self._background_layer_two = self._backdrop:get_new_background_layer() + self._background_layer_three = self._backdrop:get_new_background_layer() + self._foreground_layer_one = self._backdrop:get_new_foreground_layer() + + self._backdrop:set_panel_to_saferect(self._background_layer_one) + self._backdrop:set_panel_to_saferect(self._foreground_layer_one) + + self._ready_slot_panel = self._foreground_layer_one:panel({ + name = "player_slot_panel", + w = self._foreground_layer_one:w() / 2, + h = text_font_size * 4 + 20, + }) + + self._ready_slot_panel:set_bottom(self._foreground_layer_one:h() - 70) + self._ready_slot_panel:set_right(self._foreground_layer_one:w()) + + if not self._singleplayer then + local voice_icon, voice_texture_rect = tweak_data.hud_icons:get_icon_data("mugshot_talk") + + for i = 1, tweak_data.max_players do + local color_id = i + local color = tweak_data.chat_colors[color_id] or tweak_data.chat_colors[#tweak_data.chat_colors] + local slot_panel = self._ready_slot_panel:panel({ + x = 10, + name = "slot_" .. tostring(i), + h = text_font_size, + y = (i - 1) * text_font_size + 10, + w = self._ready_slot_panel:w() - 20, + }) + local criminal = slot_panel:text({ + name = "criminal", + align = "left", + blend_mode = "add", + vertical = "center", + font_size = text_font_size, + font = text_font, + color = color, + text = tweak_data.gui.LONGEST_CHAR_NAME, + }) + local voice = slot_panel:bitmap({ + name = "voice", + visible = false, + x = 10, + layer = 2, + texture = voice_icon, + texture_rect = voice_texture_rect, + w = voice_texture_rect[3], + h = voice_texture_rect[4], + color = color, + }) + local name = slot_panel:text({ + vertical = "center", + name = "name", + w = 256, + align = "left", + blend_mode = "add", + rotation = 360, + layer = 1, + text = managers.localization:text("menu_lobby_player_slot_available") .. " ", + font = text_font, + font_size = text_font_size, + color = color:with_alpha(0.5), + h = text_font_size, + }) + local status = slot_panel:text({ + vertical = "center", + name = "status", + w = 256, + align = "right", + blend_mode = "add", + text = " ", + visible = false, + layer = 1, + font = text_font, + font_size = text_font_size, + h = text_font_size, + color = tweak_data.screen_colors.text:with_alpha(0.5), + }) + local infamy = slot_panel:bitmap({ + w = 16, + name = "infamy", + h = 16, + visible = false, + y = 1, + layer = 2, + color = color, + }) + local detection = slot_panel:panel({ + name = "detection", + visible = false, + layer = 2, + w = slot_panel:h(), + h = slot_panel:h(), + }) + local detection_ring_left_bg = detection:bitmap({ + blend_mode = "add", + name = "detection_left_bg", + alpha = 0.2, + texture = "guis/textures/pd2/mission_briefing/inv_detection_meter", + w = detection:w(), + h = detection:h(), + }) + local detection_ring_right_bg = detection:bitmap({ + blend_mode = "add", + name = "detection_right_bg", + alpha = 0.2, + texture = "guis/textures/pd2/mission_briefing/inv_detection_meter", + w = detection:w(), + h = detection:h(), + }) + + detection_ring_right_bg:set_texture_rect(detection_ring_right_bg:texture_width(), 0, -detection_ring_right_bg:texture_width(), detection_ring_right_bg:texture_height()) + + local detection_ring_left = detection:bitmap({ + blend_mode = "add", + name = "detection_left", + texture = "guis/textures/pd2/mission_briefing/inv_detection_meter", + render_template = "VertexColorTexturedRadial", + layer = 1, + w = detection:w(), + h = detection:h(), + }) + local detection_ring_right = detection:bitmap({ + blend_mode = "add", + name = "detection_right", + texture = "guis/textures/pd2/mission_briefing/inv_detection_meter", + render_template = "VertexColorTexturedRadial", + layer = 1, + w = detection:w(), + h = detection:h(), + }) + + detection_ring_right:set_texture_rect(detection_ring_right:texture_width(), 0, -detection_ring_right:texture_width(), detection_ring_right:texture_height()) + + local detection_value = slot_panel:text({ + text = " ", + name = "detection_value", + align = "left", + blend_mode = "add", + vertical = "center", + font_size = text_font_size, + font = text_font, + color = color, + }) + + detection:set_left(slot_panel:w() * 0.65) + detection_value:set_left(detection:right() + 2) + detection_value:set_visible(detection:visible()) + + local _, _, w, _ = criminal:text_rect() + + voice:set_left(w + 2) + criminal:set_w(w) + criminal:set_align("right") + criminal:set_text("") + name:set_left(voice:right() + 2) + status:set_right(slot_panel:w()) + infamy:set_left(name:x()) + end + + BoxGuiObject:new(self._ready_slot_panel, { + sides = { + 1, + 1, + 1, + 1, + }, + }) + end + + if not managers.job:has_active_job() then + return + end + + self._current_contact_data = managers.job:current_contact_data() + self._current_level_data = managers.job:current_level_data() + self._current_stage_data = managers.job:current_stage_data() + self._current_job_data = managers.job:current_job_data() + self._current_job_chain = managers.job:current_job_chain_data() + self._job_class = self._current_job_data and self._current_job_data.jc or 0 + local show_contact_gui = true + + if managers.crime_spree:is_active() then + self._backdrop:set_pattern("guis/textures/pd2/mission_briefing/bain/bd_pattern", 0.1, "add") + + show_contact_gui = false + end + + if show_contact_gui then + local contact_gui = self._background_layer_two:gui(self._current_contact_data.assets_gui, {}) + local contact_pattern = contact_gui:has_script() and contact_gui:script().pattern + + if contact_pattern then + self._backdrop:set_pattern(contact_pattern, 0.1, "add") + end + end + + local padding_y = 60 + self._paygrade_panel = self._background_layer_one:panel({ + w = 210, + h = 70, + y = padding_y, + }) + local pg_text = self._foreground_layer_one:text({ + name = "pg_text", + vertical = "center", + h = 32, + align = "right", + text = utf8.to_upper(managers.localization:text("menu_risk")), + y = padding_y, + font_size = content_font_size, + font = content_font, + color = tweak_data.screen_colors.text, + }) + local _, _, w, h = pg_text:text_rect() + + pg_text:set_size(w, h) + + self._paygrade_text = pg_text + local job_stars = managers.job:current_job_stars() + local job_and_difficulty_stars = managers.job:current_job_and_difficulty_stars() + local difficulty_stars = managers.job:current_difficulty_stars() + local filled_star_rect = { + 0, + 32, + 32, + 32, + } + local empty_star_rect = { + 32, + 32, + 32, + 32, + } + local num_stars = 0 + local x = 0 + local y = 0 + local star_size = 18 + local panel_w = 0 + local panel_h = 0 + local risk_color = tweak_data.screen_colors.risk + local risks = { + "risk_swat", + "risk_fbi", + "risk_death_squad", + "risk_sm_wish", + } + + for i, name in ipairs(risks) do + local texture, rect = tweak_data.hud_icons:get_icon_data(name) + local active = i <= difficulty_stars + local color = active and risk_color or tweak_data.screen_colors.text + local alpha = active and 1 or 0.25 + local risk = self._paygrade_panel:bitmap({ + y = 0, + x = 0, + name = name, + texture = texture, + texture_rect = rect, + alpha = alpha, + color = color, + }) + + risk:set_position(x, y) + + x = x + risk:w() + 0 + panel_w = math.max(panel_w, risk:right()) + panel_h = math.max(panel_h, risk:h()) + end + + pg_text:set_color(risk_color) + self._paygrade_panel:set_h(panel_h) + self._paygrade_panel:set_w(panel_w) + self._paygrade_panel:set_right(self._background_layer_one:w()) + pg_text:set_right(self._paygrade_panel:left()) + + if Global.game_settings.one_down then + local one_down_text = self._foreground_layer_one:text({ + name = "one_down_text", + text = managers.localization:to_upper_text("menu_one_down"), + font = content_font, + font_size = content_font_size, + color = tweak_data.screen_colors.one_down, + }) + local _, _, w, h = one_down_text:text_rect() + + one_down_text:set_size(w, h) + one_down_text:set_righttop(pg_text:left() - 10, pg_text:top()) + end + + if managers.skirmish:is_skirmish() then + self._paygrade_panel:set_visible(false) + pg_text:set_visible(false) + + local min, max = managers.skirmish:wave_range() + local wave_range_text = self._foreground_layer_one:text({ + name = "wave_range", + vertical = "center", + h = 32, + align = "right", + text = managers.localization:to_upper_text("menu_skirmish_wave_range", { + min = min, + max = max, + }), + y = padding_y, + font_size = content_font_size, + font = content_font, + color = tweak_data.screen_colors.skirmish_color, + }) + + managers.hud:make_fine_text(wave_range_text) + wave_range_text:set_right(self._background_layer_one:w()) + end + + self._job_schedule_panel = self._background_layer_one:panel({ + h = 70, + w = self._background_layer_one:w() / 2, + }) + + self._job_schedule_panel:set_right(self._foreground_layer_one:w()) + self._job_schedule_panel:set_top(padding_y + content_font_size + 15) + + if interupt_stage then + self._job_schedule_panel:set_alpha(0.2) + + if not tweak_data.levels[interupt_stage].bonus_escape then + self._interupt_panel = self._background_layer_one:panel({ + h = 125, + w = self._background_layer_one:w() / 2, + }) + local interupt_text = self._interupt_panel:text({ + name = "job_text", + vertical = "top", + h = 80, + font_size = 70, + align = "left", + layer = 5, + text = utf8.to_upper(managers.localization:text("menu_escape")), + font = bg_font, + color = tweak_data.screen_colors.important_1, + }) + local _, _, w, h = interupt_text:text_rect() + + interupt_text:set_size(w, h) + interupt_text:rotate(-15) + interupt_text:set_center(self._interupt_panel:w() / 2, self._interupt_panel:h() / 2) + self._interupt_panel:set_shape(self._job_schedule_panel:shape()) + end + end + + local num_stages = self._current_job_chain and #self._current_job_chain or 0 + local day_color = tweak_data.screen_colors.item_stage_1 + local chain = self._current_job_chain and self._current_job_chain or {} + local js_w = self._job_schedule_panel:w() / 7 + local js_h = self._job_schedule_panel:h() + + for i = 1, 7 do + local day_font = text_font + local day_font_size = text_font_size + day_color = tweak_data.screen_colors.item_stage_1 + + if num_stages < i then + day_color = tweak_data.screen_colors.item_stage_3 + elseif i == managers.job:current_stage() then + day_font = content_font + day_font_size = content_font_size + end + + local day_text = self._job_schedule_panel:text({ + vertical = "center", + align = "center", + blend_mode = "add", + name = "day_" .. tostring(i), + text = utf8.to_upper(managers.localization:text("menu_day_short", { + day = tostring(i), + })), + font_size = day_font_size, + font = day_font, + w = js_w, + h = js_h, + color = day_color, + }) + + day_text:set_left(i == 1 and 0 or self._job_schedule_panel:child("day_" .. tostring(i - 1)):right()) + + local ghost = self._job_schedule_panel:bitmap({ + texture = "guis/textures/pd2/cn_minighost", + h = 16, + blend_mode = "add", + w = 16, + name = "ghost_" .. tostring(i), + color = tweak_data.screen_colors.ghost_color, + }) + + ghost:set_center(day_text:center_x(), day_text:center_y() + day_text:h() * 0.25) + + local ghost_visible = i <= num_stages and managers.job:is_job_stage_ghostable(managers.job:current_real_job_id(), i) + + ghost:set_visible(ghost_visible) + + if ghost_visible then + self:_apply_ghost_color(ghost, i, not Network:is_server()) + end + end + + local stage_crossed_icon = { + texture = "guis/textures/pd2/mission_briefing/calendar_xo", + texture_rect = { + 0, + 0, + 80, + 64, + }, + } + local stage_circled_icon = { + texture = "guis/textures/pd2/mission_briefing/calendar_xo", + texture_rect = { + 80, + 0, + 80, + 64, + }, + } + + for i = 1, managers.job:current_stage() or 0 do + local icon = i == managers.job:current_stage() and stage_circled_icon or stage_crossed_icon + local stage_marker = self._job_schedule_panel:bitmap({ + h = 64, + layer = 1, + w = 80, + name = "stage_done_" .. tostring(i), + texture = icon.texture, + texture_rect = icon.texture_rect, + rotation = math.rand(-10, 10), + }) + + stage_marker:set_center(self._job_schedule_panel:child("day_" .. tostring(i)):center()) + stage_marker:move(math.random(4) - 2, math.random(4) - 2) + end + + if managers.job:has_active_job() then + local payday_stamp = self._job_schedule_panel:bitmap({ + texture = "guis/textures/pd2/mission_briefing/calendar_xo", + name = "payday_stamp", + h = 64, + layer = 2, + w = 96, + texture_rect = { + 160, + 0, + 96, + 64, + }, + rotation = math.rand(-5, 5), + }) + + payday_stamp:set_center(self._job_schedule_panel:child("day_" .. tostring(num_stages)):center()) + payday_stamp:move(math.random(4) - 2 - 7, math.random(4) - 2 + 8) + + if payday_stamp:rotation() == 0 then + payday_stamp:set_rotation(1) + end + end + + local job_overview_text = self._foreground_layer_one:text({ + name = "job_overview_text", + vertical = "bpttom", + align = "left", + text = utf8.to_upper(managers.localization:text("menu_job_overview")), + h = content_font_size, + font_size = content_font_size, + font = content_font, + color = tweak_data.screen_colors.text, + }) + local _, _, w, h = job_overview_text:text_rect() + + job_overview_text:set_size(w, h) + job_overview_text:set_leftbottom(self._job_schedule_panel:left(), pg_text:bottom()) + job_overview_text:set_y(math.round(job_overview_text:y())) + + self._job_overview_text = job_overview_text + + self._paygrade_panel:set_center_y(job_overview_text:center_y()) + pg_text:set_center_y(job_overview_text:center_y()) + pg_text:set_y(math.round(pg_text:y())) + + if pg_text:left() <= job_overview_text:right() + 15 then + pg_text:move(0, -pg_text:h()) + self._paygrade_panel:move(0, -pg_text:h()) + end + + local text = utf8.to_upper(managers.localization:text(self._current_contact_data.name_id) .. ": " .. managers.localization:text(self._current_job_data.name_id)) + local text_align, text_len = nil + + if managers.crime_spree:is_active() then + local level_id = Global.game_settings.level_id + local name_id = level_id and tweak_data.levels[level_id] and tweak_data.levels[level_id].name_id + local mission = managers.crime_spree:get_mission() + text = managers.localization:to_upper_text(name_id) .. ": " + text_len = utf8.len(text) + text = text .. "+" .. managers.localization:text("menu_cs_level", { + level = mission and mission.add or 0, + }) + text_align = "right" + end + + if managers.skirmish:is_skirmish() then + if managers.skirmish:is_weekly_skirmish() then + text = managers.localization:to_upper_text("menu_weekly_skirmish") + else + text = managers.localization:to_upper_text("menu_skirmish") + end + end + + local job_text = self._foreground_layer_one:text({ + vertical = "top", + name = "job_text", + text = text, + align = text_align or "left", + font_size = title_font_size, + font = title_font, + color = tweak_data.screen_colors.text, + }) + + if managers.crime_spree:is_active() then + job_text:set_range_color(text_len, utf8.len(text), tweak_data.screen_colors.crime_spree_risk) + end + + if not text_align then + local big_text = self._background_layer_three:text({ + vertical = "top", + name = "job_text", + alpha = 0.4, + text = text, + align = text_align or "left", + font_size = bg_font_size, + font = bg_font, + color = tweak_data.screen_colors.button_stage_1, + }) + + big_text:set_world_center_y(self._foreground_layer_one:child("job_text"):world_center_y()) + big_text:set_world_x(self._foreground_layer_one:child("job_text"):world_x()) + big_text:move(-13, 9) + self._backdrop:animate_bg_text(big_text) + end + + if managers.job:current_job_data().name_id == "heist_rvd" then + local day_1_text = self._job_schedule_panel:child("day_1") + local day_1_sticker = self._job_schedule_panel:bitmap({ + texture = "guis/dlcs/rvd/textures/pd2/mission_briefing/day2", + h = 48, + w = 96, + rotation = 360, + layer = 2, + }) + + day_1_sticker:set_center(day_1_text:center()) + day_1_sticker:move(math.random(4) - 2, math.random(4) - 2) + + local day_2_text = self._job_schedule_panel:child("day_2") + local day_2_sticker = self._job_schedule_panel:bitmap({ + texture = "guis/dlcs/rvd/textures/pd2/mission_briefing/day1", + h = 48, + w = 96, + rotation = 360, + layer = 2, + }) + + day_2_sticker:set_center(day_2_text:center()) + day_2_sticker:move(math.random(4) - 2, math.random(4) - 2) + end + + if managers.crime_spree:is_active() then + self._paygrade_panel:set_visible(false) + self._job_schedule_panel:set_visible(false) + self._paygrade_text:set_visible(false) + self._job_overview_text:set_visible(false) + end + + if managers.skirmish:is_skirmish() then + self._job_schedule_panel:set_visible(false) + + self._skirmish_progress = SkirmishBriefingProgress:new(self._background_layer_one, { + x = self._job_schedule_panel:x(), + y = self._job_schedule_panel:y(), + w = self._job_schedule_panel:width(), + h = self._job_schedule_panel:height(), + }) + end +end diff --git a/lua/hudstageendscreen.lua b/lua/hudstageendscreen.lua new file mode 100644 index 0000000..2c95569 --- /dev/null +++ b/lua/hudstageendscreen.lua @@ -0,0 +1,576 @@ +local function make_fine_text(text) + local x, y, w, h = text:text_rect() + + text:set_size(w, h) + text:set_position(math.round(text:x()), math.round(text:y())) + + return x, y, w, h +end + +function HUDStageEndScreen:init(hud, workspace) + self._backdrop = MenuBackdropGUI:new(workspace) + + if not _G.IS_VR then + self._backdrop:create_black_borders() + end + + self._hud = hud + self._workspace = workspace + self._singleplayer = Global.game_settings.single_player + local bg_font = tweak_data.menu.pd2_massive_font + local title_font = tweak_data.menu.pd2_large_font + local content_font = tweak_data.menu.pd2_medium_font + local small_font = tweak_data.menu.pd2_small_font + local bg_font_size = tweak_data.menu.pd2_massive_font_size + local title_font_size = tweak_data.menu.pd2_large_font_size + local content_font_size = tweak_data.menu.pd2_medium_font_size + local small_font_size = tweak_data.menu.pd2_small_font_size + local massive_font = bg_font + local large_font = title_font + local medium_font = content_font + local massive_font_size = bg_font_size + local large_font_size = title_font_size + local medium_font_size = content_font_size + self._background_layer_safe = self._backdrop:get_new_background_layer() + self._background_layer_full = self._backdrop:get_new_background_layer() + self._foreground_layer_safe = self._backdrop:get_new_foreground_layer() + self._foreground_layer_full = self._backdrop:get_new_foreground_layer() + + self._backdrop:set_panel_to_saferect(self._background_layer_safe) + self._backdrop:set_panel_to_saferect(self._foreground_layer_safe) + + if managers.job:has_active_job() then + local current_contact_data = managers.job:current_contact_data() + local contact_gui = current_contact_data and self._background_layer_full:gui(current_contact_data.assets_gui, { + empty = true, + }) + local contact_pattern = contact_gui and contact_gui:has_script() and contact_gui:script().pattern + + if contact_pattern then + self._backdrop:set_pattern(contact_pattern) + end + end + + local padding_y = 0 + self._paygrade_panel = self._background_layer_safe:panel({ + w = 210, + h = 70, + y = padding_y, + }) + local pg_text = self._foreground_layer_safe:text({ + name = "pg_text", + vertical = "center", + h = 32, + align = "right", + text = utf8.to_upper(managers.localization:text("menu_risk")), + y = padding_y, + font_size = content_font_size, + font = content_font, + color = tweak_data.screen_colors.text, + }) + local _, _, w, h = pg_text:text_rect() + + pg_text:set_size(w, h) + + local job_stars = managers.job:has_active_job() and managers.job:current_job_stars() or 1 + local job_and_difficulty_stars = managers.job:has_active_job() and managers.job:current_job_and_difficulty_stars() or 1 + local difficulty_stars = managers.job:has_active_job() and managers.job:current_difficulty_stars() or 0 + local risk_color = tweak_data.screen_colors.risk + local risks = { + "risk_swat", + "risk_fbi", + "risk_death_squad", + "risk_sm_wish", + } + + local panel_w = 0 + local panel_h = 0 + local x = 0 + local y = 0 + + for i, name in ipairs(risks) do + local texture, rect = tweak_data.hud_icons:get_icon_data(name) + local active = i <= difficulty_stars + local color = active and risk_color or tweak_data.screen_colors.text + local alpha = active and 1 or 0.25 + local risk = self._paygrade_panel:bitmap({ + y = 0, + x = 0, + name = name, + texture = texture, + texture_rect = rect, + alpha = alpha, + color = color, + }) + + risk:set_position(x, y) + + x = x + risk:w() + 0 + panel_w = math.max(panel_w, risk:right()) + panel_h = math.max(panel_h, risk:h()) + end + + pg_text:set_color(risk_color) + self._paygrade_panel:set_h(panel_h) + self._paygrade_panel:set_w(panel_w) + self._paygrade_panel:set_right(self._background_layer_safe:w()) + pg_text:set_right(self._paygrade_panel:left()) + pg_text:set_center_y(self._paygrade_panel:center_y()) + pg_text:set_y(math.round(pg_text:y())) + + if managers.skirmish:is_skirmish() then + self._paygrade_panel:set_visible(false) + pg_text:set_visible(false) + + local min, max = managers.skirmish:wave_range() + local wave_range_text = self._foreground_layer_safe:text({ + name = "wave_range", + vertical = "center", + h = 32, + align = "right", + text = managers.localization:to_upper_text("menu_skirmish_wave_range", { + min = min, + max = max, + }), + y = padding_y, + font_size = content_font_size, + font = content_font, + color = tweak_data.screen_colors.skirmish_color, + }) + + managers.hud:make_fine_text(wave_range_text) + wave_range_text:set_right(self._background_layer_safe:w()) + end + + self._stage_name = managers.job:current_level_id() and managers.localization:to_upper_text(tweak_data.levels[managers.job:current_level_id()].name_id) or "" + + if managers.skirmish:is_skirmish() then + if managers.skirmish:is_weekly_skirmish() then + self._stage_name = managers.localization:to_upper_text("menu_weekly_skirmish") + else + self._stage_name = managers.localization:to_upper_text("menu_skirmish") + end + end + + self._foreground_layer_safe:text({ + name = "stage_text", + vertical = "center", + align = "left", + text = self._stage_name, + h = title_font_size, + font_size = title_font_size, + font = title_font, + color = tweak_data.screen_colors.text, + }) + + local bg_text = self._background_layer_full:text({ + name = "stage_text", + vertical = "top", + alpha = 0.4, + align = "left", + text = self._stage_name, + h = bg_font_size, + font_size = bg_font_size, + font = bg_font, + color = tweak_data.screen_colors.button_stage_3, + }) + + bg_text:set_world_center_y(self._foreground_layer_safe:child("stage_text"):world_center_y()) + bg_text:set_world_x(self._foreground_layer_safe:child("stage_text"):world_x()) + bg_text:move(-13, 9) + self._backdrop:animate_bg_text(bg_text) + + self._coins_backpanel = self._background_layer_safe:panel({ + name = "coins_backpanel", + y = 70, + w = self._background_layer_safe:w() / 2 - 10, + h = self._background_layer_safe:h() / 2, + }) + self._coins_forepanel = self._foreground_layer_safe:panel({ + name = "coins_forepanel", + y = 70, + w = self._foreground_layer_safe:w() / 2 - 10, + h = self._foreground_layer_safe:h() / 2, + }) + local level_progress_text = self._coins_forepanel:text({ + vertical = "top", + name = "coin_progress_text", + align = "left", + y = 10, + x = 10, + text = managers.localization:to_upper_text("menu_es_coins_progress"), + h = content_font_size + 2, + font_size = content_font_size, + font = content_font, + color = tweak_data.screen_colors.text, + }) + local _, _, lw, lh = level_progress_text:text_rect() + + level_progress_text:set_size(lw, lh) + + local coins_bg_circle = self._coins_backpanel:bitmap({ + texture = "guis/textures/pd2/endscreen/exp_ring", + name = "bg_progress_circle", + alpha = 0.6, + blend_mode = "normal", + h = self._coins_backpanel:h() - content_font_size, + w = self._coins_backpanel:h() - content_font_size, + y = content_font_size, + color = Color.black, + }) + self._coins_circle = self._coins_backpanel:bitmap({ + texture = "guis/textures/pd2/endscreen/exp_ring", + name = "progress_circle", + blend_mode = "add", + render_template = "VertexColorTexturedRadial", + layer = 1, + h = self._coins_backpanel:h() - content_font_size, + w = self._coins_backpanel:h() - content_font_size, + y = content_font_size, + color = Color(0, 1, 1), + }) + self._coins_text = self._coins_forepanel:text({ + name = "coins_text", + vertical = "center", + align = "center", + text = "", + font_size = bg_font_size, + font = bg_font, + h = self._coins_backpanel:h() - content_font_size, + w = self._coins_backpanel:h() - content_font_size, + y = content_font_size, + color = tweak_data.screen_colors.text, + }) + self._coins_box = BoxGuiObject:new(self._coins_backpanel, { + sides = { + 1, + 1, + 1, + 1, + }, + }) + self._lp_backpanel = self._background_layer_safe:panel({ + name = "lp_backpanel", + y = 70, + w = self._background_layer_safe:w() / 2 - 10, + h = self._background_layer_safe:h() / 2, + }) + self._lp_forepanel = self._foreground_layer_safe:panel({ + name = "lp_forepanel", + y = 70, + w = self._foreground_layer_safe:w() / 2 - 10, + h = self._foreground_layer_safe:h() / 2, + }) + local level_progress_text = self._lp_forepanel:text({ + vertical = "top", + name = "level_progress_text", + align = "left", + y = 10, + x = 10, + text = managers.localization:to_upper_text("menu_es_level_progress"), + h = content_font_size + 2, + font_size = content_font_size, + font = content_font, + color = tweak_data.screen_colors.text, + }) + local _, _, lw, lh = level_progress_text:text_rect() + + level_progress_text:set_size(lw, lh) + + local lp_bg_circle = self._lp_backpanel:bitmap({ + texture = "guis/textures/pd2/endscreen/exp_ring", + name = "bg_progress_circle", + alpha = 0.6, + blend_mode = "normal", + h = self._lp_backpanel:h() - content_font_size, + w = self._lp_backpanel:h() - content_font_size, + y = content_font_size, + color = Color.black, + }) + self._prestige_lp_circle = self._lp_backpanel:bitmap({ + texture = "guis/textures/pd2/exp_ring_purple", + name = "bg_infamy_progress_circle", + blend_mode = "add", + render_template = "VertexColorTexturedRadial", + layer = -1, + x = lp_bg_circle:x(), + y = lp_bg_circle:y(), + h = lp_bg_circle:h(), + w = lp_bg_circle:w(), + color = Color(0, 1, 1), + }) + self._lp_circle = self._lp_backpanel:bitmap({ + texture = "guis/textures/pd2/endscreen/exp_ring", + name = "progress_circle", + blend_mode = "add", + render_template = "VertexColorTexturedRadial", + layer = 1, + h = self._lp_backpanel:h() - content_font_size, + w = self._lp_backpanel:h() - content_font_size, + y = content_font_size, + color = Color(0, 1, 1), + }) + self._lp_text = self._lp_forepanel:text({ + name = "level_text", + vertical = "center", + align = "center", + text = "", + font_size = bg_font_size, + font = bg_font, + h = self._lp_backpanel:h() - content_font_size, + w = self._lp_backpanel:h() - content_font_size, + y = content_font_size, + color = tweak_data.screen_colors.text, + }) + self._lp_curr_xp = self._lp_forepanel:text({ + vertical = "top", + name = "current_xp", + align = "left", + text = managers.localization:to_upper_text("menu_es_current_xp"), + h = small_font_size, + font_size = small_font_size, + font = small_font, + color = tweak_data.screen_colors.text, + }) + self._lp_xp_gained = self._lp_forepanel:text({ + vertical = "top", + name = "xp_gained", + align = "left", + text = managers.localization:to_upper_text("menu_es_xp_gained"), + h = content_font_size, + font_size = content_font_size, + font = content_font, + color = tweak_data.screen_colors.text, + }) + self._lp_next_level = self._lp_forepanel:text({ + vertical = "top", + name = "next_level", + align = "left", + text = managers.localization:to_upper_text("menu_es_next_level"), + h = small_font_size, + font_size = small_font_size, + font = small_font, + color = tweak_data.screen_colors.text, + }) + self._lp_skill_points = self._lp_forepanel:text({ + vertical = "top", + name = "skill_points", + align = "left", + text = managers.localization:to_upper_text("menu_es_skill_points_gained"), + h = small_font_size, + font_size = small_font_size, + font = small_font, + color = tweak_data.screen_colors.text, + }) + self._lp_xp_curr = self._lp_forepanel:text({ + text = "", + vertical = "top", + name = "c_xp", + align = "left", + h = small_font_size, + font_size = small_font_size, + font = small_font, + color = tweak_data.screen_colors.text, + }) + self._lp_xp_gain = self._lp_forepanel:text({ + text = "", + vertical = "top", + name = "xp_g", + align = "left", + h = content_font_size, + font_size = content_font_size, + font = content_font, + color = tweak_data.screen_colors.text, + }) + self._lp_xp_nl = self._lp_forepanel:text({ + text = "", + vertical = "top", + name = "xp_nl", + align = "left", + h = small_font_size, + font_size = small_font_size, + font = small_font, + color = tweak_data.screen_colors.text, + }) + self._lp_sp_gain = self._lp_forepanel:text({ + text = "0", + vertical = "center", + name = "sp_g", + align = "left", + h = small_font_size, + font_size = small_font_size, + font = small_font, + color = tweak_data.screen_colors.text, + }) + local _, _, cw, ch = self._lp_curr_xp:text_rect() + local _, _, gw, gh = self._lp_xp_gained:text_rect() + local _, _, nw, nh = self._lp_next_level:text_rect() + local _, _, sw, sh = self._lp_skill_points:text_rect() + ch = ch - 2 + nh = nh - 2 + sh = sh - 2 + local w = math.ceil(math.max(cw, gw, nw, sw)) + 20 + local squeeze_more_pixels = false + + if w > 170 then + squeeze_more_pixels = true + end + + self._num_skill_points_gained = 0 + self._lp_sp_info = self._lp_forepanel:text({ + vertical = "top", + name = "sp_info", + wrap = true, + align = "left", + word_wrap = true, + text = managers.localization:text("menu_es_skill_points_info", { + SKILL_MENU = managers.localization:to_upper_text("menu_skilltree"), + }), + h = small_font_size, + font_size = small_font_size, + font = small_font, + color = tweak_data.screen_colors.text, + }) + + self._lp_sp_info:grow(-self._lp_circle:right() - 10, 0) + + local _, _, iw, ih = self._lp_sp_info:text_rect() + + self._lp_sp_info:set_h(ih) + self._lp_sp_info:set_leftbottom(self._lp_circle:right() + 0, self._lp_forepanel:h() - 10) + + local w = self._lp_forepanel:w() - self._lp_sp_info:x() - 90 + local number_text_x = self._lp_sp_info:left() + w + + self._lp_skill_points:set_size(sw, sh) + self._lp_skill_points:set_left(self._lp_sp_info:left()) + self._lp_skill_points:set_bottom(self._lp_sp_info:top()) + self._lp_sp_gain:set_left(number_text_x) + self._lp_sp_gain:set_top(self._lp_skill_points:top()) + self._lp_sp_gain:set_size(self._lp_forepanel:w() - self._lp_sp_gain:left() - 10, sh) + self._lp_next_level:set_size(nw, nh) + self._lp_next_level:set_left(self._lp_sp_info:left()) + self._lp_next_level:set_bottom(self._lp_skill_points:top()) + self._lp_xp_nl:set_left(number_text_x) + self._lp_xp_nl:set_top(self._lp_next_level:top()) + self._lp_xp_nl:set_size(self._lp_forepanel:w() - self._lp_xp_nl:left() - 10, nh) + self._lp_curr_xp:set_size(cw, ch) + self._lp_curr_xp:set_left(self._lp_sp_info:left()) + self._lp_curr_xp:set_bottom(self._lp_next_level:top()) + self._lp_xp_curr:set_left(number_text_x) + self._lp_xp_curr:set_top(self._lp_curr_xp:top()) + self._lp_xp_curr:set_size(self._lp_forepanel:w() - self._lp_xp_curr:left() - 10, ch) + self._lp_xp_gained:set_size(gw, gh) + self._lp_xp_gained:set_left(self._lp_curr_xp:left()) + self._lp_xp_gained:set_bottom(self._lp_curr_xp:top()) + self._lp_xp_gain:set_left(number_text_x) + self._lp_xp_gain:set_top(self._lp_xp_gained:top()) + self._lp_xp_gain:set_size(self._lp_forepanel:w() - self._lp_xp_gain:left() - 10, gh) + self._lp_xp_gained:set_bottom(math.round(self._lp_forepanel:h() / 2)) + self._lp_curr_xp:set_top(self._lp_xp_gained:bottom()) + self._lp_next_level:set_top(self._lp_curr_xp:bottom()) + self._lp_skill_points:set_top(self._lp_next_level:bottom()) + self._lp_sp_info:set_top(self._lp_skill_points:bottom()) + self._lp_xp_gain:set_top(self._lp_xp_gained:top()) + self._lp_xp_curr:set_top(self._lp_curr_xp:top()) + self._lp_xp_nl:set_top(self._lp_next_level:top()) + self._lp_sp_gain:set_top(self._lp_skill_points:top()) + + if squeeze_more_pixels then + lp_bg_circle:move(-20, 0) + self._lp_circle:move(-20, 0) + self._prestige_lp_circle:move(-20, 0) + self._lp_text:move(-20, 0) + self._lp_curr_xp:move(-30, 0) + self._lp_xp_gained:move(-30, 0) + self._lp_next_level:move(-30, 0) + self._lp_skill_points:move(-30, 0) + self._lp_sp_info:move(-30, 0) + end + + self._box = BoxGuiObject:new(self._lp_backpanel, { + sides = { + 1, + 1, + 1, + 1, + }, + }) + + WalletGuiObject.set_wallet(self._foreground_layer_safe) + WalletGuiObject.hide_wallet() + + self._package_forepanel = self._foreground_layer_safe:panel({ + alpha = 1, + name = "package_forepanel", + y = 70, + w = self._foreground_layer_safe:w() / 2 - 10, + h = self._foreground_layer_safe:h() / 2 - 70 - 10, + }) + + self._package_forepanel:set_right(self._foreground_layer_safe:w()) + self._package_forepanel:text({ + text = "", + name = "title_text", + y = 10, + x = 10, + font = content_font, + font_size = content_font_size, + }) + + local package_box_panel = self._foreground_layer_safe:panel() + + package_box_panel:set_shape(self._package_forepanel:shape()) + package_box_panel:set_layer(self._package_forepanel:layer()) + + self._package_box = BoxGuiObject:new(package_box_panel, { + sides = { + 1, + 1, + 1, + 1, + }, + }) + self._package_items = {} + + self:clear_stage() + + if self._data then + self:start_experience_gain() + end + + for i, child in ipairs(self._lp_forepanel:children()) do + if child.text then + local text = child:text() + + child:set_text(string.gsub(text, ":", "")) + end + end + + local skip_panel = self._foreground_layer_safe:panel({ + name = "skip_forepanel", + y = 70, + w = self._foreground_layer_safe:w() / 2 - 10, + h = self._foreground_layer_safe:h() / 2, + }) + local macros = { + BTN_SPEED = managers.localization:btn_macro("menu_challenge_claim", true), + } + + if not managers.menu:is_pc_controller() then + macros.BTN_SPEED = managers.localization:get_default_macro("BTN_SWITCH_WEAPON") + end + + self._skip_text = skip_panel:text({ + name = "skip_text", + visible = false, + alpha = 0.5, + font = small_font, + font_size = small_font_size, + text = managers.localization:to_upper_text("menu_stageendscreen_speed_up", macros), + }) + + make_fine_text(self._skip_text) + self._skip_text:set_right(skip_panel:w() - 10) + self._skip_text:set_bottom(skip_panel:h() - 10) +end diff --git a/lua/huskcopbase.lua b/lua/huskcopbase.lua index 5507f4b..566d4d6 100644 --- a/lua/huskcopbase.lua +++ b/lua/huskcopbase.lua @@ -1,5 +1,29 @@ -- "Fuck clients apparently" (c) RedFlame -- fixes cops on clients not derendering when they should Hooks:PostHook(HuskCopBase, "post_init", "eclipse__post_init", function(self) - self._allow_invisible = true -end) \ No newline at end of file + self._allow_invisible = true +end) + +-- fix yufu wang hitbox +Hooks:PostHook(HuskCopBase, "post_init", "hitbox_fix_post_init", function(self) + if self._tweak_table == "triad_boss" then + self._unit:body("head"--[[self._unit:character_damage()._head_body_name--]]):set_sphere_radius(15) + self._unit:body("body"):set_sphere_radius(22) + + self._unit:body("rag_LeftArm"):set_enabled(true) + self._unit:body("rag_LeftForeArm"):set_enabled(true) + + self._unit:body("rag_RightArm"):set_enabled(true) + self._unit:body("rag_RightForeArm"):set_enabled(true) + + self._unit:body("rag_LeftArm"):set_sphere_radius(11) + self._unit:body("rag_LeftForeArm"):set_sphere_radius(7) + self._unit:body("rag_RightArm"):set_sphere_radius(11) + self._unit:body("rag_RightForeArm"):set_sphere_radius(7) + + self._unit:body("rag_LeftUpLeg"):set_sphere_radius(10) + self._unit:body("rag_LeftLeg"):set_sphere_radius(7) + self._unit:body("rag_RightUpLeg"):set_sphere_radius(10) + self._unit:body("rag_RightLeg"):set_sphere_radius(7) + end +end) diff --git a/lua/huskplayerdamage.lua b/lua/huskplayerdamage.lua index 610c59f..5022c63 100644 --- a/lua/huskplayerdamage.lua +++ b/lua/huskplayerdamage.lua @@ -1,35 +1,35 @@ -- Friendly Fire function HuskPlayerDamage:damage_bullet(attack_data) - if managers.mutators:is_mutator_active(MutatorFriendlyFire) or Global.game_settings and Global.game_settings.one_down then - self:_send_damage_to_owner(attack_data) - end + if managers.mutators:is_mutator_active(MutatorFriendlyFire) or Global.game_settings and Global.game_settings.one_down then + self:_send_damage_to_owner(attack_data) + end end function HuskPlayerDamage:damage_melee(attack_data) - if managers.mutators:is_mutator_active(MutatorFriendlyFire) or Global.game_settings and Global.game_settings.one_down then - self:_send_damage_to_owner(attack_data) - end + if managers.mutators:is_mutator_active(MutatorFriendlyFire) or Global.game_settings and Global.game_settings.one_down then + self:_send_damage_to_owner(attack_data) + end end function HuskPlayerDamage:damage_fire(attack_data) - if managers.mutators:is_mutator_active(MutatorFriendlyFire) or Global.game_settings and Global.game_settings.one_down then - attack_data.damage = attack_data.damage * 0.2 + if managers.mutators:is_mutator_active(MutatorFriendlyFire) or Global.game_settings and Global.game_settings.one_down then + attack_data.damage = attack_data.damage * 0.2 - self:_send_damage_to_owner(attack_data) - end + self:_send_damage_to_owner(attack_data) + end end function HuskPlayerDamage:_send_damage_to_owner(attack_data) - local peer_id = managers.criminals:character_peer_id_by_unit(self._unit) - local damage = math.min(10, attack_data.damage * 0.5) + local peer_id = managers.criminals:character_peer_id_by_unit(self._unit) + local damage = math.min(10, attack_data.damage * 0.5) - managers.network:session():send_to_peers("sync_friendly_fire_damage", peer_id, attack_data.attacker_unit, damage, attack_data.variant) + managers.network:session():send_to_peers("sync_friendly_fire_damage", peer_id, attack_data.attacker_unit, damage, attack_data.variant) - if attack_data.attacker_unit == managers.player:player_unit() then - managers.hud:on_hit_confirmed() - end + if attack_data.attacker_unit == managers.player:player_unit() then + managers.hud:on_hit_confirmed() + end - if managers.mutators:is_mutator_active(MutatorFriendlyFire) then - managers.job:set_memory("trophy_flawless", true, false) - end + if managers.mutators:is_mutator_active(MutatorFriendlyFire) then + managers.job:set_memory("trophy_flawless", true, false) + end end diff --git a/lua/husktankcopdamage.lua b/lua/husktankcopdamage.lua deleted file mode 100644 index 7fb7678..0000000 --- a/lua/husktankcopdamage.lua +++ /dev/null @@ -1,18 +0,0 @@ --- Make shotgun pellets prioritize dozer's faceplate and visor (from the fixes) -local origfunc = HuskTankCopDamage and HuskTankCopDamage.is_head or nil -function HuskTankCopDamage:is_head(body, ...) - local head = origfunc and origfunc(self, body, ...) or HuskTankCopDamage.super.is_head(self, body, ...) - - if not head and body - and (not TheFixes or TheFixes.shotgun_dozer_face) - then - local bn = body:name():key() - if bn == 'f46eb16d189339da' - or bn == 'f260d73afd0c74fe' - then - head = true - end - end - - return head -end diff --git a/lua/incendiarygrenade.lua b/lua/incendiarygrenade.lua index 1ead16e..163b78f 100644 --- a/lua/incendiarygrenade.lua +++ b/lua/incendiarygrenade.lua @@ -1,2 +1 @@ -function IncendiaryGrenade:bullet_hit() -end \ No newline at end of file +function IncendiaryGrenade:bullet_hit() end diff --git a/lua/infamynewtweakdata.lua b/lua/infamynewtweakdata.lua new file mode 100644 index 0000000..ec87983 --- /dev/null +++ b/lua/infamynewtweakdata.lua @@ -0,0 +1,3 @@ +Hooks:PostHook(InfamyTweakData, "init", "eclipse_init", function(self) + self.items.infamy_root.upgrades.skilltree.multiplier = 1 +end) diff --git a/lua/ingamecontractgui.lua b/lua/ingamecontractgui.lua new file mode 100644 index 0000000..f457fac --- /dev/null +++ b/lua/ingamecontractgui.lua @@ -0,0 +1,441 @@ +function IngameContractGui:init(ws, node) + local padding = SystemInfo:platform() == Idstring("WIN32") and 10 or 5 + self._panel = ws:panel():panel({ + w = math.round(ws:panel():w() * 0.6), + h = math.round(ws:panel():h() * 1), + }) + + self._panel:set_y(math.max(tweak_data.menu.pd2_medium_font_size, CoreMenuRenderer.Renderer.border_height)) + self._panel:grow(0, -(self._panel:y() + tweak_data.menu.pd2_medium_font_size)) + + self._node = node + local job_data = managers.job:current_job_data() + local job_chain = managers.job:current_job_chain_data() + + if job_data and managers.job:current_job_id() == "safehouse" and Global.mission_manager.saved_job_values.playedSafeHouseBefore or managers.job:current_job_id() == "chill" then + self._panel:set_visible(false) + end + + local contract_text = self._panel:text({ + text = "", + vertical = "bottom", + rotation = 360, + layer = 1, + font = tweak_data.menu.pd2_large_font, + font_size = tweak_data.menu.pd2_large_font_size, + color = tweak_data.screen_colors.text, + }) + + contract_text:set_text(self:get_text("cn_menu_contract_header") .. " " .. (job_data and self:get_text(job_data.name_id) or "")) + contract_text:set_bottom(5) + + local text_panel = self._panel:panel({ + layer = 1, + w = self._panel:w() - padding * 2, + h = self._panel:h() - padding * 2, + }) + + text_panel:set_left(padding) + text_panel:set_top(padding) + + local briefing_title = text_panel:text({ + text = "", + font = tweak_data.menu.pd2_medium_font, + font_size = tweak_data.menu.pd2_medium_font_size, + color = tweak_data.screen_colors.text, + }) + + briefing_title:set_text(self:get_text("menu_briefing")) + managers.hud:make_fine_text(briefing_title) + + local font_size = tweak_data.menu.pd2_small_font_size + local text = job_data and managers.localization:text(job_data.briefing_id) or "" + local briefing_description = text_panel:text({ + name = "briefing_description", + vertical = "top", + h = 128, + wrap = true, + align = "left", + word_wrap = true, + text = text, + font = tweak_data.menu.pd2_small_font, + font_size = font_size, + color = tweak_data.screen_colors.text, + }) + local _, _, _, h = briefing_description:text_rect() + + briefing_description:set_h(h) + briefing_description:set_top(briefing_title:bottom()) + + local is_job_ghostable = managers.job:is_job_ghostable(managers.job:current_job_id()) + local ghostable_text = nil + + if is_job_ghostable then + local min_ghost_bonus, max_ghost_bonus = managers.job:get_job_ghost_bonus(managers.job:current_job_id()) + local min_ghost = math.round(min_ghost_bonus * 100) + local max_ghost = math.round(max_ghost_bonus * 100) + local min_string, max_string = nil + + if min_ghost == 0 and min_ghost_bonus ~= 0 then + min_string = string.format("%0.2f", math.abs(min_ghost_bonus * 100)) + else + min_string = tostring(math.abs(min_ghost)) + end + + if max_ghost == 0 and max_ghost_bonus ~= 0 then + max_string = string.format("%0.2f", math.abs(max_ghost_bonus * 100)) + else + max_string = tostring(math.abs(max_ghost)) + end + + local ghost_bonus_string = min_ghost_bonus == max_ghost_bonus and min_string or min_string .. "-" .. max_string + ghostable_text = text_panel:text({ + blend_mode = "add", + vertical = "top", + wrap = true, + align = "left", + wrap_word = true, + text = managers.localization:to_upper_text("menu_ghostable_job", { + bonus = ghost_bonus_string, + }), + font_size = tweak_data.menu.pd2_small_font_size, + font = tweak_data.menu.pd2_small_font, + color = tweak_data.screen_colors.ghost_color, + }) + + ghostable_text:set_position(briefing_description:x(), briefing_description:bottom() + padding) + managers.hud:make_fine_text(ghostable_text) + end + + local modifiers_text = text_panel:text({ + name = "modifiers_text", + align = "left", + vertical = "top", + text = managers.localization:to_upper_text("menu_cn_modifiers"), + font = tweak_data.menu.pd2_small_font, + font_size = font_size, + color = tweak_data.screen_colors.text, + }) + + managers.hud:make_fine_text(modifiers_text) + modifiers_text:set_bottom(text_panel:h() * 0.5 - tweak_data.menu.pd2_small_font_size) + + local next_top = modifiers_text:bottom() + local one_down_warning_text = nil + + local job_heat_mul = managers.job:get_job_heat_multipliers(managers.job:current_job_id()) - 1 + local job_heat = math.round(job_heat_mul * 100) + local job_heat_string = tostring(math.abs(job_heat)) + local is_job_heated = job_heat ~= 0 or job_heat_mul ~= 0 + + if job_heat == 0 and job_heat_mul ~= 0 then + job_heat_string = string.format("%0.2f", math.abs(job_heat_mul * 100)) + end + + local ghost_bonus_mul = managers.job:get_ghost_bonus() + local job_ghost = math.round(ghost_bonus_mul * 100) + local job_ghost_string = tostring(math.abs(job_ghost)) + local has_ghost_bonus = managers.job:has_ghost_bonus() + + if job_ghost == 0 and ghost_bonus_mul ~= 0 then + job_ghost_string = string.format("%0.2f", math.abs(ghost_bonus_mul * 100)) + end + + local ghost_warning_text = nil + + if has_ghost_bonus then + local ghost_color = tweak_data.screen_colors.ghost_color + ghost_warning_text = text_panel:text({ + name = "ghost_color_warning_text", + vertical = "top", + word_wrap = true, + wrap = true, + align = "left", + blend_mode = "normal", + text = managers.localization:to_upper_text("menu_ghost_bonus", { + exp_bonus = job_ghost_string, + }), + font = tweak_data.menu.pd2_small_font, + font_size = tweak_data.menu.pd2_small_font_size, + color = ghost_color, + }) + + managers.hud:make_fine_text(ghost_warning_text) + ghost_warning_text:set_top(next_top) + ghost_warning_text:set_left(10) + + next_top = ghost_warning_text:bottom() + end + + local heat_warning_text = nil + local heat_color = managers.job:get_job_heat_color(managers.job:current_job_id()) + + if is_job_heated then + local job_heat_text_id = "menu_heat_" .. (job_heat_mul > 0 and "warm" or job_heat_mul < 0 and "cold" or "ok") + heat_warning_text = text_panel:text({ + name = "heat_warning_text", + vertical = "top", + word_wrap = true, + wrap = true, + align = "left", + text = managers.localization:to_upper_text(job_heat_text_id, { + job_heat = job_heat_string, + }), + font = tweak_data.menu.pd2_small_font, + font_size = font_size, + color = heat_color, + }) + + managers.hud:make_fine_text(heat_warning_text) + heat_warning_text:set_top(next_top) + heat_warning_text:set_left(10) + + next_top = heat_warning_text:bottom() + end + + local pro_warning_text = nil + + if Global.game_settings.one_down then + pro_warning_text = text_panel:text({ + name = "pro_warning_text", + vertical = "top", + h = 128, + wrap = true, + align = "left", + word_wrap = true, + text = self:get_text("menu_pro_warning"), + font = tweak_data.menu.pd2_small_font, + font_size = font_size, + color = tweak_data.screen_colors.pro_color, + }) + + managers.hud:make_fine_text(pro_warning_text) + pro_warning_text:set_h(pro_warning_text:h()) + pro_warning_text:set_top(next_top) + pro_warning_text:set_left(10) + + next_top = pro_warning_text:bottom() + end + + local is_christmas_job = managers.job:is_christmas_job(managers.job:current_job_id()) + local has_christmas_bonus = false + + if is_christmas_job then + local holiday_potential_bonus = managers.job:get_job_christmas_bonus(managers.job:current_job_id()) + local holiday_bonus_percentage = math.round(holiday_potential_bonus * 100) + has_christmas_bonus = holiday_bonus_percentage ~= 0 + + if has_christmas_bonus then + local holiday_string = tostring(holiday_bonus_percentage) + local holiday_text = text_panel:text({ + vertical = "top", + wrap = true, + align = "left", + wrap_word = true, + text = managers.localization:to_upper_text("holiday_warning_text", { + event_icon = managers.localization:get_default_macro("BTN_XMAS"), + bonus = holiday_string, + }), + font_size = tweak_data.menu.pd2_small_font_size, + font = tweak_data.menu.pd2_small_font, + color = tweak_data.screen_colors.event_color, + }) + + holiday_text:set_position(10, next_top) + managers.hud:make_fine_text(holiday_text) + + next_top = holiday_text:bottom() + end + end + + next_top = next_top + 5 + local any_modifier_available = heat_warning_text or pro_warning_text or ghost_warning_text or one_down_warning_text + any_modifier_available = any_modifier_available or has_christmas_bonus + + modifiers_text:set_visible(any_modifier_available) + + local risk_color = tweak_data.screen_colors.risk + local risk_title = text_panel:text({ + font = tweak_data.menu.pd2_small_font, + font_size = font_size, + text = self:get_text("menu_risk"), + color = risk_color, + }) + + managers.hud:make_fine_text(risk_title) + risk_title:set_top(next_top) + risk_title:set_visible(job_data and true or false) + + local menu_risk_id = "menu_risk_pd" + + if Global.game_settings.difficulty == "hard" then + menu_risk_id = "menu_risk_swat" + elseif Global.game_settings.difficulty == "overkill" then + menu_risk_id = "menu_risk_fbi" + elseif Global.game_settings.difficulty == "overkill_145" then + menu_risk_id = "menu_risk_special" + elseif Global.game_settings.difficulty == "easy_wish" then + menu_risk_id = "menu_risk_easy_wish" + elseif Global.game_settings.difficulty == "overkill_290" then + menu_risk_id = "menu_risk_elite" + elseif Global.game_settings.difficulty == "sm_wish" then + menu_risk_id = "menu_risk_sm_wish" + end + + local risk_stats_panel = text_panel:panel({ + name = "risk_stats_panel", + }) + + risk_stats_panel:set_h(risk_title:h() + 5) + + if job_data then + local job_stars = managers.job:current_job_stars() + local job_and_difficulty_stars = managers.job:current_job_and_difficulty_stars() + local difficulty_stars = managers.job:current_difficulty_stars() + local job_id = managers.job:current_job_id() + local rsx = 15 + local risks = { + "risk_pd", + "risk_swat", + "risk_fbi", + "risk_death_squad", + "risk_sm_wish", + } + + local max_y = 0 + local max_x = 0 + + for i, name in ipairs(risks) do + if i ~= 1 then + local texture, rect = tweak_data.hud_icons:get_icon_data(name) + local active = i <= difficulty_stars + 1 + local color = active and i ~= 1 and risk_color or tweak_data.screen_colors.text + local alpha = active and 1 or 0.25 + local risk = text_panel:bitmap({ + y = 0, + x = 0, + texture = texture, + texture_rect = rect, + alpha = alpha, + color = color, + }) + + risk:set_x(rsx) + risk:set_top(math.round(risk_title:bottom())) + + rsx = rsx + risk:w() + 2 + local stat = managers.statistics:completed_job(job_id, tweak_data:index_to_difficulty(i + 1)) + local risk_stat = risk_stats_panel:text({ + align = "center", + name = name, + font = tweak_data.menu.pd2_small_font, + font_size = font_size, + text = tostring(stat), + }) + + managers.hud:make_fine_text(risk_stat) + risk_stat:set_world_center_x(risk:world_center_x()) + + local this_difficulty = i == difficulty_stars + 1 + active = i <= difficulty_stars + 1 + color = active and risk_color or Color.white + + if this_difficulty then + alpha = 1 + elseif active then + alpha = 0.5 + else + alpha = 0.25 + end + + risk_stat:set_color(color) + risk_stat:set_alpha(alpha) + + max_y = math.max(max_y, risk:bottom()) + max_x = math.max(max_x, risk:right() + 5) + max_x = math.max(max_x, risk_stat:right() + risk_stats_panel:left() + 10) + end + end + + risk_stats_panel:set_top(math.round(max_y + 2)) + + local stat = managers.statistics:completed_job(job_id, tweak_data:index_to_difficulty(difficulty_stars + 2)) + local risk_text = text_panel:text({ + name = "risk_text", + wrap = true, + align = "left", + vertical = "top", + word_wrap = true, + x = max_x, + w = text_panel:w() - max_x, + h = text_panel:h(), + text = self:get_text(menu_risk_id) .. " " .. managers.localization:to_upper_text("menu_stat_job_completed", { + stat = tostring(stat), + }) .. " ", + font = tweak_data.hud_stats.objective_desc_font, + font_size = font_size, + color = risk_color, + }) + + risk_text:set_top(math.round(risk_title:bottom() + 4)) + risk_text:set_h(risk_stats_panel:bottom() - risk_text:top()) + + local show_max = self._node and self._node:parameters().show_potential_max or false + local potential_rewards_title = text_panel:text({ + blend_mode = "add", + font = tweak_data.menu.pd2_small_font, + font_size = font_size, + text = self:get_text(show_max and "menu_potential_rewards_max" or "menu_potential_rewards_min", { + BTN_Y = managers.localization:btn_macro("menu_modify_item"), + }), + color = managers.menu:is_pc_controller() and tweak_data.screen_colors.button_stage_3 or tweak_data.screen_colors.text, + }) + + managers.hud:make_fine_text(potential_rewards_title) + potential_rewards_title:set_top(risk_stats_panel:bottom() + 4) + + local jobpay_title = text_panel:text({ + x = 10, + font = tweak_data.menu.pd2_small_font, + font_size = font_size, + text = managers.localization:to_upper_text("cn_menu_contract_jobpay_header"), + color = tweak_data.screen_colors.text, + }) + + managers.hud:make_fine_text(jobpay_title) + jobpay_title:set_top(math.round(potential_rewards_title:bottom())) + + local experience_title = text_panel:text({ + x = 10, + font = tweak_data.menu.pd2_small_font, + font_size = font_size, + text = self:get_text("menu_experience"), + color = tweak_data.screen_colors.text, + }) + + managers.hud:make_fine_text(experience_title) + experience_title:set_top(math.round(jobpay_title:bottom())) + + self._potential_rewards_title = potential_rewards_title + self._jobpay_title = jobpay_title + self._experience_title = experience_title + self._text_panel = text_panel + self._rewards_panel = text_panel:panel({ + name = "rewards_panel", + }) + self._potential_show_max = show_max + + self:set_potential_rewards(show_max) + end + + self:_rec_round_object(self._panel) + + self._sides = BoxGuiObject:new(self._panel, { + sides = { + 1, + 1, + 1, + 1, + }, + }) +end diff --git a/lua/interactiontweakdata.lua b/lua/interactiontweakdata.lua index 97183c9..1c4a6c1 100644 --- a/lua/interactiontweakdata.lua +++ b/lua/interactiontweakdata.lua @@ -1,6 +1,6 @@ -Hooks:PostHook(InteractionTweakData, "init", "eclipse_init", function (self) - self.revive.timer = 4.5 - self.drill_upgrade.timer = 0 - self.lance_upgrade.timer = 0 - self.gen_int_saw_upgrade.timer = 0 -end) \ No newline at end of file +Hooks:PostHook(InteractionTweakData, "init", "eclipse_init", function(self) + self.revive.timer = 4.5 + self.drill_upgrade.timer = 0 + self.lance_upgrade.timer = 0 + self.gen_int_saw_upgrade.timer = 0 +end) diff --git a/lua/medicdamage.lua b/lua/medicdamage.lua index 6a856da..4e87d16 100644 --- a/lua/medicdamage.lua +++ b/lua/medicdamage.lua @@ -1,8 +1,8 @@ local heal_unit_orig = MedicDamage.heal_unit function MedicDamage:heal_unit(...) - if self._unit:movement():chk_action_forbidden("action") then - return false - end + if self._unit:movement():chk_action_forbidden("action") then + return false + end - return heal_unit_orig(self, ...) -end \ No newline at end of file + return heal_unit_orig(self, ...) +end diff --git a/lua/meleeweaponstweakdata.lua b/lua/meleeweaponstweakdata.lua new file mode 100644 index 0000000..5164662 --- /dev/null +++ b/lua/meleeweaponstweakdata.lua @@ -0,0 +1,54 @@ +-- Rebalance melee weapons based on their range, concealment and speed +Hooks:PostHook(BlackMarketTweakData, "_init_melee_weapons", "shc__init_melee_weapons", function(self) + local min_conceal, max_conceal = 30, 0 + local min_range, max_range = 300, 0 + local min_expire, max_expire = 10, 0 + for _, data in pairs(self.melee_weapons) do + if data.stats.concealment then + min_conceal = data.stats.concealment < min_conceal and data.stats.concealment or min_conceal + max_conceal = data.stats.concealment > max_conceal and data.stats.concealment or max_conceal + end + + if data.stats.range then + min_range = data.stats.range < min_range and data.stats.range or min_range + max_range = data.stats.range > max_range and data.stats.range or max_range + end + + local expire = (data.expire_t + data.repeat_expire_t) * 0.5 + min_expire = expire < min_expire and expire or min_expire + max_expire = expire > max_expire and expire or max_expire + end + + local reference = self.melee_weapons.iceaxe + local ref_dmg_min = reference.stats.min_damage + local ref_dmg_max = reference.stats.max_damage + local ref_charge_t = reference.stats.charge_time + local ref_conceal = math.map_range(reference.stats.concealment, min_conceal, max_conceal, 1, 0) + local ref_range = math.map_range(reference.stats.range, min_range, max_range, 1, 0) + local ref_expire = (reference.expire_t + reference.repeat_expire_t) * 0.5 + + local x_min = ref_dmg_min / (1 + ref_expire * 3 + ref_conceal + ref_range) + local x_max = ((ref_dmg_max / ref_dmg_min) - 1) / ref_charge_t + + local function get_damage(expire, range, conceal, charge_t) + local min = (1 + expire * 3 + conceal + range) * x_min + local max = min + min * charge_t * x_max + return min, max + end + + for _, data in pairs(self.melee_weapons) do + local expire = (data.expire_t + data.repeat_expire_t) * 0.5 + local range = math.map_range(data.stats.range, min_range, max_range, 1, 0) + local conceal = math.map_range(data.stats.concealment or 30, min_conceal, max_conceal, 1, 0) + local charge_t = data.stats.charge_time or 0 + local damage_mul = (data.tase_data or data.dot_data) and 0.4 or 1 + local effect_mul = (data.tase_data or data.dot_data) and 0.2 or 1 + local min, max = get_damage(expire, range, conceal, charge_t) + data.stats.min_damage = math.round(min * damage_mul, 0.5) + data.stats.max_damage = math.round(max * damage_mul, 0.5) + data.stats.min_damage_effect = math.round((math.map_range(expire, min_expire, max_expire, 30, 350) + (data.melee_damage_delay or 0) * 350) * effect_mul, 10) + data.stats.max_damage_effect = data.stats.min_damage_effect + data.stats.remove_weapon_movement_penalty = nil + data.stats.charge_time = data.stats.charge_time and data.stats.charge_time * 0.5 + end +end) diff --git a/lua/menumanager.lua b/lua/menumanager.lua index 37ab943..9fa16b7 100644 --- a/lua/menumanager.lua +++ b/lua/menumanager.lua @@ -1,59 +1,337 @@ --- Max progression taken from Classic Heisting, thanks gorg yet again +function MenuCallbackHandler:is_contract_difficulty_allowed(item) + if not managers.menu:active_menu() then + return false + end + if not managers.menu:active_menu().logic then + return false + end + if not managers.menu:active_menu().logic:selected_node() then + return false + end + if not managers.menu:active_menu().logic:selected_node():parameters().menu_component_data then + return false + end + local job_data = managers.menu:active_menu().logic:selected_node():parameters().menu_component_data + if not job_data.job_id then + return false + end + if job_data.professional and item:value() < 3 then + return false + end + local job_jc = tweak_data.narrative:job_data(job_data.job_id).jc + local difficulty_jc = (item:value() - 2) * 10 + local plvl = managers.experience:current_level() + local level_lock = tweak_data.difficulty_level_locks[item:value()] or 0 + local is_not_level_locked = plvl >= level_lock + return is_not_level_locked and managers.job:get_max_jc_for_player() >= math.clamp(job_jc + difficulty_jc, 0, 100) +end + +function MenuCrimeNetFiltersInitiator:update_node(node) + if MenuCallbackHandler:is_win32() then + local not_friends_only = not Global.game_settings.search_friends_only + + node:item("toggle_new_servers_only"):set_enabled(not_friends_only) + node:item("toggle_server_state_lobby"):set_enabled(not_friends_only) + node:item("toggle_job_appropriate_lobby"):set_enabled(not_friends_only) + node:item("toggle_mutated_lobby"):set_enabled(not_friends_only) + node:item("max_lobbies_filter"):set_enabled(not_friends_only) + node:item("server_filter"):set_enabled(not_friends_only) + node:item("kick_option_filter"):set_enabled(not_friends_only) + node:item("job_id_filter"):set_enabled(not_friends_only) + node:item("job_plan_filter"):set_enabled(not_friends_only) + node:item("toggle_job_appropriate_lobby"):set_visible(self:is_standard()) + node:item("toggle_allow_safehouses"):set_visible(self:is_standard()) + node:item("toggle_mutated_lobby"):set_visible(self:is_standard()) + node:item("toggle_one_down_lobby"):set_visible(self:is_standard()) + node:item("difficulty_filter"):set_visible(false) + node:item("difficulty"):set_visible(self:is_standard()) + node:item("job_id_filter"):set_visible(self:is_standard()) + node:item("max_spree_difference_filter"):set_visible(self:is_crime_spree()) + node:item("skirmish_wave_filter"):set_visible(self:is_skirmish()) + node:item("job_plan_filter"):set_visible(not self:is_skirmish()) + elseif MenuCallbackHandler:is_xb1() then + if Global.game_settings.search_crimespree_lobbies then + print("GN: CS lobby set to true") + node:item("difficulty_filter"):set_enabled(false) + node:item("max_spree_difference_filter"):set_enabled(true) + else + print("GN: CS lobby set to false") + node:item("difficulty_filter"):set_enabled(true) + node:item("max_spree_difference_filter"):set_enabled(false) + end + + if Global.game_settings.search_crimespree_lobbies then + node:item("toggle_mutated_lobby"):set_enabled(false) + elseif Global.game_settings.search_mutated_lobbies then + node:item("toggle_crimespree_lobby"):set_enabled(false) + else + node:item("toggle_mutated_lobby"):set_enabled(true) + node:item("toggle_crimespree_lobby"):set_enabled(true) + end + end +end + +function MenuCrimeNetFiltersInitiator:add_filters(node) + if node:item("divider_end") then + return + end + + local params = { + callback = "choice_difficulty_filter", + name = "difficulty", + text_id = "menu_diff_filter", + help_id = "menu_diff_help", + filter = true, + } + local data_node = { + { + value = -1, + text_id = "menu_any", + _meta = "option", + }, + { + value = 2, + text_id = "menu_difficulty_normal", + _meta = "option", + }, + { + value = 3, + text_id = "menu_difficulty_hard", + _meta = "option", + }, + { + value = 4, + text_id = "menu_difficulty_very_hard", + _meta = "option", + }, + { + value = 5, + text_id = "menu_difficulty_overkill", + _meta = "option", + }, + { + value = 6, + text_id = "menu_difficulty_easy_wish", + _meta = "option", + }, + type = "MenuItemMultiChoice", + } + local new_item = node:create_item(data_node, params) + + new_item:set_value(managers.network.matchmake:difficulty_filter()) + node:add_item(new_item) + + local item_params = { + visible_callback = "is_multiplayer is_win32", + name = "job_id_filter", + callback = "choice_job_id_filter", + text_id = "menu_job_id_filter", + filter = true, + } + local data_node = { + { + value = -1, + text_id = "menu_any", + _meta = "option", + }, + type = "MenuItemMultiChoice", + } + + for index, job_id in ipairs(tweak_data.narrative:get_jobs_index()) do + local job_tweak = tweak_data.narrative.jobs[job_id] + local contact = job_tweak.contact + local contact_tweak = tweak_data.narrative.contacts[contact] + local is_hidden = (job_tweak.hidden or contact_tweak and contact_tweak.hidden) and not job_tweak.show_in_filters + local allow = not job_tweak.wrapped_to_job and not is_hidden + + if allow then + local text_id, color_data = tweak_data.narrative:create_job_name(job_id) + local params = { + localize = false, + _meta = "option", + text_id = text_id, + value = index, + } + + for count, color in ipairs(color_data) do + params["color" .. count] = color.color + params["color_start" .. count] = color.start + params["color_stop" .. count] = color.stop + end + + table.insert(data_node, params) + end + end + + local new_item = node:create_item(data_node, item_params) + + new_item:set_value(managers.network.matchmake:get_lobby_filter("job_id") or -1) + node:add_item(new_item) + + local kick_params = { + visible_callback = "is_multiplayer is_win32", + name = "kick_option_filter", + callback = "choice_kick_option", + text_id = "menu_kicking_allowed_filter", + filter = true, + } + local data_node = { + { + value = -1, + text_id = "menu_any", + _meta = "option", + }, + type = "MenuItemMultiChoice", + } + local kick_filters = { + { + value = 1, + text_id = "menu_kick_server", + }, + { + value = 2, + text_id = "menu_kick_vote", + }, + { + value = 0, + text_id = "menu_kick_disabled", + }, + } + + for index, filter in ipairs(kick_filters) do + table.insert(data_node, { + _meta = "option", + text_id = filter.text_id, + value = filter.value, + }) + end + + local new_item = node:create_item(data_node, kick_params) + + new_item:set_value(managers.network.matchmake:get_lobby_filter("kick_option") or -1) + node:add_item(new_item) + + local divider_params = { + size = 8, + name = "divider_end", + no_text = true, + } + local data_node = { + type = "MenuItemDivider", + } + local new_item = node:create_item(data_node, divider_params) + + node:add_item(new_item) + + local reset_params = { + callback = "_reset_filters", + name = "reset_filters", + align = "right", + text_id = "dialog_reset_filters", + } + local data_node = {} + local new_item = node:create_item(data_node, reset_params) + + node:add_item(new_item) + self:modify_node(node, {}) +end + +-- Max progression and max mask customization, thanks gorg Hooks:Add("MenuManagerBuildCustomMenus", "restoreBtnsMainMenu", function(menu_manager, nodes) - local adv_options = nodes.adv_options - if not adv_options then - return - end - - params = { - name = "select_max_progress_btn", - text_id = "menu_max_progress", - help_id = "menu_max_progress_help", - callback = "max_progress_msg" - } - - new_item = adv_options:create_item(data, params) - new_item.dirty_callback = callback(adv_options, adv_options, "item_dirty") - if adv_options.callback_handler then - new_item:set_callback_handler(adv_options.callback_handler) - end - - position = 10 - table.insert(adv_options._items, position, new_item) + local adv_options = nodes.adv_options + if not adv_options then + return + end + + params = { + name = "select_max_progress_btn", + text_id = "menu_max_progress", + help_id = "menu_max_progress_help", + callback = "max_progress_msg", + } + + new_item = adv_options:create_item(data, params) + new_item.dirty_callback = callback(adv_options, adv_options, "item_dirty") + if adv_options.callback_handler then + new_item:set_callback_handler(adv_options.callback_handler) + end + + position = 10 + table.insert(adv_options._items, position, new_item) + + params = { + name = "select_max_masks_btn", + text_id = "menu_max_masks", + help_id = "menu_max_masks_help", + callback = "max_masks_msg", + } + + new_item = adv_options:create_item(data, params) + new_item.dirty_callback = callback(adv_options, adv_options, "item_dirty") + if adv_options.callback_handler then + new_item:set_callback_handler(adv_options.callback_handler) + end + + position = 10 + table.insert(adv_options._items, position, new_item) end) function MenuCallbackHandler:max_progress_msg() local dialog_data = { title = managers.localization:text("dialog_warning_title"), - text = managers.localization:text("menu_progress_msg", { - }), - focus_button = 1 + text = managers.localization:text("menu_progress_msg", {}), + focus_button = 1, } local yes_button = { text = managers.localization:text("dialog_yes"), - callback_func = callback(self, self, "max_progress", index) + callback_func = callback(self, self, "max_progress", index), } local no_button = { text = managers.localization:text("dialog_no"), callback_func = idk, - cancel_button = true + cancel_button = true, } dialog_data.button_list = { yes_button, - no_button + no_button, + } + + managers.system_menu:show(dialog_data) +end + +function MenuCallbackHandler:max_masks_msg() + local dialog_data = { + title = managers.localization:text("dialog_warning_title"), + text = managers.localization:text("menu_masks_msg", {}), + focus_button = 1, + } + local yes_button = { + text = managers.localization:text("dialog_yes"), + callback_func = callback(self, self, "max_masks", index), + } + local no_button = { + text = managers.localization:text("dialog_no"), + callback_func = idk, + cancel_button = true, + } + dialog_data.button_list = { + yes_button, + no_button, } managers.system_menu:show(dialog_data) end function MenuCallbackHandler:max_progress() - for i=managers.experience:current_level(), 99 do managers.experience:_level_up() end - managers.experience:set_current_rank(25) -- change this value if you want a different infamy + for i = managers.experience:current_level(), 99 do + managers.experience:_level_up() + end + -- managers.experience:set_current_rank(25) -- change this value if you want a different infamy managers.infamy:_set_points(managers.experience:current_rank()) - managers.money:_set_offshore(99999999999999^20) - managers.money:_set_total(99999999999999^20) - managers.custom_safehouse:add_coins(99999999999999^20) - managers.skilltree:give_specialization_points(99999999999999^20) + managers.money:_set_offshore(99999999999999 ^ 20) + managers.money:_set_total(99999999999999 ^ 20) + managers.custom_safehouse:add_coins(99999999999999 ^ 20) + managers.skilltree:give_specialization_points(99999999999999 ^ 20) for name, item in pairs(tweak_data.blackmarket.weapon_mods) do if not item.dlc or managers.dlc:is_dlc_unlocked(item.dlc) then @@ -66,12 +344,72 @@ function MenuCallbackHandler:max_progress() if name ~= "character_locked" then if item.dlc and managers.dlc:is_dlc_unlocked(item.dlc) then for i = 1, 10 do - managers.blackmarket:add_to_inventory(item.dlc, "masks", name) + managers.blackmarket:add_to_inventory(item.dlc, "masks", name) + end + else + local global_value = item.infamous and "infamous" or item.global_value or "normal" + for i = 1, 10 do + managers.blackmarket:add_to_inventory(global_value, "masks", name) + end + end + end + end + for name, item in pairs(tweak_data.blackmarket.materials) do + if name ~= "plastic" then + if item.dlc and managers.dlc:is_dlc_unlocked(item.dlc) then + local global_value = item.infamous and "infamous" or item.global_value or item.dlc + for i = 1, 10 do + managers.blackmarket:add_to_inventory(global_value, "materials", name) + end + else + local global_value = item.infamous and "infamous" or item.global_value or "normal" + for i = 1, 10 do + managers.blackmarket:add_to_inventory(global_value, "materials", name) + end + end + end + end + for name, item in pairs(tweak_data.blackmarket.textures) do + if name ~= "no_color_no_material" and name ~= "no_color_full_material" then + if item.dlc and managers.dlc:is_dlc_unlocked(item.dlc) then + local global_value = item.infamous and "infamous" or item.global_value or item.dlc + for i = 1, 10 do + managers.blackmarket:add_to_inventory(global_value, "textures", name) + end + else + local global_value = item.infamous and "infamous" or item.global_value or "normal" + for i = 1, 10 do + managers.blackmarket:add_to_inventory(global_value, "textures", name) + end + end + end + end + for name, item in pairs(tweak_data.blackmarket.colors) do + if item.dlc and managers.dlc:is_dlc_unlocked(item.dlc) then + local global_value = item.infamous and "infamous" or item.global_value or item.dlc + for i = 1, 10 do + managers.blackmarket:add_to_inventory(global_value, "colors", name) + end + else + local global_value = item.infamous and "infamous" or item.global_value or "normal" + for i = 1, 10 do + managers.blackmarket:add_to_inventory(global_value, "colors", name) + end + end + end +end + +function MenuCallbackHandler:max_masks() + for name, item in pairs(tweak_data.blackmarket.masks) do + if name ~= "character_locked" then + if item.dlc and managers.dlc:is_dlc_unlocked(item.dlc) then + for i = 1, 10 do + managers.blackmarket:add_to_inventory(item.dlc, "masks", name) end else local global_value = item.infamous and "infamous" or item.global_value or "normal" for i = 1, 10 do - managers.blackmarket:add_to_inventory(global_value, "masks", name) + managers.blackmarket:add_to_inventory(global_value, "masks", name) end end end @@ -81,12 +419,12 @@ function MenuCallbackHandler:max_progress() if item.dlc and managers.dlc:is_dlc_unlocked(item.dlc) then local global_value = item.infamous and "infamous" or item.global_value or item.dlc for i = 1, 10 do - managers.blackmarket:add_to_inventory(global_value, "materials", name) + managers.blackmarket:add_to_inventory(global_value, "materials", name) end else local global_value = item.infamous and "infamous" or item.global_value or "normal" for i = 1, 10 do - managers.blackmarket:add_to_inventory(global_value, "materials", name) + managers.blackmarket:add_to_inventory(global_value, "materials", name) end end end @@ -96,12 +434,12 @@ function MenuCallbackHandler:max_progress() if item.dlc and managers.dlc:is_dlc_unlocked(item.dlc) then local global_value = item.infamous and "infamous" or item.global_value or item.dlc for i = 1, 10 do - managers.blackmarket:add_to_inventory(global_value, "textures", name) + managers.blackmarket:add_to_inventory(global_value, "textures", name) end else local global_value = item.infamous and "infamous" or item.global_value or "normal" for i = 1, 10 do - managers.blackmarket:add_to_inventory(global_value, "textures", name) + managers.blackmarket:add_to_inventory(global_value, "textures", name) end end end @@ -110,12 +448,12 @@ function MenuCallbackHandler:max_progress() if item.dlc and managers.dlc:is_dlc_unlocked(item.dlc) then local global_value = item.infamous and "infamous" or item.global_value or item.dlc for i = 1, 10 do - managers.blackmarket:add_to_inventory(global_value, "colors", name) + managers.blackmarket:add_to_inventory(global_value, "colors", name) end else local global_value = item.infamous and "infamous" or item.global_value or "normal" for i = 1, 10 do - managers.blackmarket:add_to_inventory(global_value, "colors", name) + managers.blackmarket:add_to_inventory(global_value, "colors", name) end end end diff --git a/lua/missionmanager.lua b/lua/missionmanager.lua index 543097a..ec6616e 100644 --- a/lua/missionmanager.lua +++ b/lua/missionmanager.lua @@ -3,24 +3,24 @@ if Global.editor_mode then return end - -- Add custom mission script changes and triggers for specific levels -- Execution of mission scripts can trigger reinforce locations (trigger that has just a name disables previously enabled reinforcement with that id) -- Mission script elements can be disabled or enabled -local mission_script_elements = StreamHeist:mission_script_patches() -if not mission_script_elements then - return -end -Hooks:PreHook(MissionManager, "_activate_mission", "sh__activate_mission", function (self) +Hooks:PreHook(MissionManager, "_activate_mission", "sh__activate_mission", function(self) + local mission_script_elements = StreamHeist:mission_script_patches() + if not mission_script_elements then + return + end + for element_id, data in pairs(mission_script_elements) do local element = self:get_element_by_id(element_id) if not element then - StreamHeist:error("Mission script element", element_id, "could not be found") + StreamHeist:error(string.format("Mission script element %u could not be found", element_id)) else -- Check if this element is supposed to trigger reinforce points if data.reinforce then - Hooks:PostHook(element, "on_executed", "sh_on_executed_reinforce_" .. element_id, function () + Hooks:PostHook(element, "on_executed", "sh_on_executed_reinforce_" .. element_id, function() StreamHeist:log(string.format("%s executed, toggled %u reinforce point(s)", element:editor_name(), #data.reinforce)) for _, v in pairs(data.reinforce) do managers.groupai:state():set_area_min_police_force(v.name, v.force, v.position) @@ -31,20 +31,40 @@ Hooks:PreHook(MissionManager, "_activate_mission", "sh__activate_mission", funct -- Check if this element is supposed to trigger a point of no return -- thanks redflame - if data.ponr then - local function set_ponr() - local ponr_timer_balance_mul = data.ponr_player_mul and managers.groupai:state():_get_balancing_multiplier(data.ponr_player_mul) or 1 - managers.groupai:state():set_point_of_no_return_timer(data.ponr * ponr_timer_balance_mul, 0) + local is_pro_job = Global.game_settings and Global.game_settings.one_down + if is_pro_job then + if data.ponr then + local function set_ponr() + local ponr_timer_balance_mul = data.ponr_player_mul and managers.groupai:state():_get_balancing_multiplier(data.ponr_player_mul) or 1 + managers.groupai:state():set_point_of_no_return_timer(data.ponr * ponr_timer_balance_mul, -1) + end + + Hooks:PostHook(element, "on_executed", "eclipse_on_executed_ponr_" .. element_id, set_ponr) + Hooks:PostHook(element, "client_on_executed", "eclipse_client_on_executed_ponr_" .. element_id, set_ponr) end - Hooks:PostHook(element, "on_executed", "sh_on_executed_ponr_" .. element_id, set_ponr) - Hooks:PostHook(element, "client_on_executed", "sh_client_on_executed_ponr_" .. element_id, set_ponr) + if data.ponr_end then + Hooks:PostHook(element, "on_executed", "eclipse_on_executed_ponr_end_" .. element_id, function() + managers.groupai:state():remove_point_of_no_return_timer(0) + end) + Hooks:PostHook(element, "client_on_executed", "eclipse_client_on_executed_ponr_end_" .. element_id, function() + managers.groupai:state():remove_point_of_no_return_timer(0) + end) + end + + -- instantly force into post ponr state + if data.set_ponr_state then + if Network:is_server() then + managers.groupai:set_state("ponr") + managers.groupai:state():set_difficulty(1) + end + end end -- Check if this element is supposed to trigger a difficulty change if data.difficulty then - Hooks:PostHook(element, "on_executed", "sh_on_executed_difficulty_" .. element_id, function () - StreamHeist:log(string.format("%s executed, set difficulty to %.2f", element:editor_name(), data.difficulty)) + Hooks:PostHook(element, "on_executed", "sh_on_executed_difficulty_" .. element_id, function() + StreamHeist:log(string.format("%s executed, set difficulty to %.2g", element:editor_name(), data.difficulty)) managers.groupai:state():set_difficulty(data.difficulty) end) StreamHeist:log(string.format("%s hooked as difficulty change trigger", element:editor_name())) @@ -54,22 +74,48 @@ Hooks:PreHook(MissionManager, "_activate_mission", "sh__activate_mission", funct if data.values then for k, v in pairs(data.values) do element._values[k] = v - StreamHeist:log(string.format("%s value \"%s\" has been set to \"%s\"", element:editor_name(), k, tostring(v))) + StreamHeist:log(string.format('%s value "%s" has been set to "%s"', element:editor_name(), k, tostring(v))) end end if data.flashlight ~= nil then - Hooks:PostHook(element, "on_executed", "sh_on_executed_func_" .. element_id, function () + Hooks:PostHook(element, "on_executed", "sh_on_executed_func_" .. element_id, function() StreamHeist:log(string.format("%s executed, changing flashlight state to %s", element:editor_name(), data.flashlight and "true" or "false")) managers.game_play_central:set_flashlights_on(data.flashlight) end) StreamHeist:log(string.format("%s hooked as flashlight state trigger", element:editor_name())) end + if data.on_executed then + for _, v in pairs(data.on_executed) do + local new_element = self:get_element_by_id(v.id) + if new_element then + local val, i = table.find_value(element._values.on_executed, function(val) + return val.id == v.id + end) + if v.remove then + if val then + table.remove(element._values.on_executed, i) + StreamHeist:log(string.format("Removed element %s from on_executed of %s", new_element:editor_name(), element:editor_name())) + end + elseif val then + val.delay = v.delay or 0 + val.delay_rand = v.delay_rand or 0 + StreamHeist:log(string.format("Modified element %s in on_executed of %s", new_element:editor_name(), element:editor_name())) + else + table.insert(element._values.on_executed, v) + StreamHeist:log(string.format("Added element %s to on_executed of %s", new_element:editor_name(), element:editor_name())) + end + else + StreamHeist:error(string.format("Mission script element %u could not be found", v.id)) + end + end + end + if data.func then Hooks:PostHook(element, "on_executed", "sh_on_executed_func_" .. element_id, data.func) StreamHeist:log(string.format("%s hooked as function call trigger", element:editor_name())) end end end -end) \ No newline at end of file +end) diff --git a/lua/molotovgrenade.lua b/lua/molotovgrenade.lua index a733525..dc50719 100644 --- a/lua/molotovgrenade.lua +++ b/lua/molotovgrenade.lua @@ -1,2 +1 @@ -function MolotovGrenade:bullet_hit() -end \ No newline at end of file +function MolotovGrenade:bullet_hit() end diff --git a/lua/networkmanager.lua b/lua/networkmanager.lua new file mode 100644 index 0000000..071a0c7 --- /dev/null +++ b/lua/networkmanager.lua @@ -0,0 +1,2 @@ +NetworkMatchMakingSTEAM._BUILD_SEARCH_INTEREST_KEY = NetworkMatchMakingSTEAM._BUILD_SEARCH_INTEREST_KEY .. "_eclipse_dev" +NetworkMatchMakingEPIC._BUILD_SEARCH_INTEREST_KEY = NetworkMatchMakingEPIC._BUILD_SEARCH_INTEREST_KEY .. "_eclipse_dev" diff --git a/lua/networkmatchmakingsteam.lua b/lua/networkmatchmakingsteam.lua deleted file mode 100644 index f574e3b..0000000 --- a/lua/networkmatchmakingsteam.lua +++ /dev/null @@ -1 +0,0 @@ -NetworkMatchMakingSTEAM._BUILD_SEARCH_INTEREST_KEY = NetworkMatchMakingSTEAM._BUILD_SEARCH_INTEREST_KEY .. "eclipse_main" \ No newline at end of file diff --git a/lua/newflamethrowerbase.lua b/lua/newflamethrowerbase.lua index 0147f53..eab8afb 100644 --- a/lua/newflamethrowerbase.lua +++ b/lua/newflamethrowerbase.lua @@ -1,5 +1,7 @@ Hooks:PostHook(NewFlamethrowerBase, "setup_default", "eclipse_setup_default", function(self) - if Global.game_settings and Global.game_settings.one_down then self._bullet_slotmask = self._bullet_slotmask + 3 end + if Global.game_settings and Global.game_settings.one_down then + self._bullet_slotmask = self._bullet_slotmask + 3 + end end) local mvec_to = Vector3() @@ -45,7 +47,7 @@ function NewFlamethrowerBase:_fire_raycast(user_unit, from_pos, direction, dmg_m unit = body:unit(), ray = direction, normal = direction, - position = from_pos + position = from_pos, } self._bullet_class:on_collision(fake_ray, self._unit, user_unit, damage) @@ -66,21 +68,21 @@ function NewFlamethrowerBase:_fire_raycast(user_unit, from_pos, direction, dmg_m if self._alert_events then result.rays = { { - position = from_pos - } + position = from_pos, + }, } end managers.statistics:shot_fired({ hit = false, - weapon_unit = self._unit + weapon_unit = self._unit, }) for i = 1, hit_enemies, 1 do managers.statistics:shot_fired({ skip_bullet_count = true, hit = true, - weapon_unit = self._unit + weapon_unit = self._unit, }) end diff --git a/lua/newnpcraycastweaponbase.lua b/lua/newnpcraycastweaponbase.lua new file mode 100644 index 0000000..4c55dd6 --- /dev/null +++ b/lua/newnpcraycastweaponbase.lua @@ -0,0 +1,38 @@ +if not Network:is_server() then + return +end + +-- Make team AI weapons alert enemies (oversight from when bots got the ability to use player weapons) +Hooks:PostHook(NewNPCRaycastWeaponBase, "set_user_is_team_ai", "sh_set_user_is_team_ai", function(self) + if not self._setup or not alive(self._setup.user_unit) then + return + end + + self._setup.alert_AI = true + self._setup.alert_filter = self._setup.user_unit:brain():SO_access() + self._alert_events = {} +end) + +-- This should not set ammo data for NPCs +function NewNPCRaycastWeaponBase:_update_stats_values(...) + local can_shoot_through_shield = self._can_shoot_through_shield + local can_shoot_through_enemy = self._can_shoot_through_enemy + local can_shoot_through_wall = self._can_shoot_through_wall + local bullet_class = self._bullet_class + local bullet_slotmask = self._bullet_slotmask + local blank_slotmask = self._blank_slotmask + + NewRaycastWeaponBase._update_stats_values(self, ...) + + self._can_shoot_through_shield = can_shoot_through_shield + self._can_shoot_through_enemy = can_shoot_through_enemy + self._can_shoot_through_wall = can_shoot_through_wall + self._bullet_class = bullet_class + self._bullet_slotmask = bullet_slotmask + self._blank_slotmask = blank_slotmask +end + +-- Disable player skills affecting NPC weapons +function NewNPCRaycastWeaponBase:get_add_head_shot_mul() end + +function NewNPCRaycastWeaponBase:is_stagger() end diff --git a/lua/newraycastweaponbase.lua b/lua/newraycastweaponbase.lua index b7e3ce7..e71a2b4 100644 --- a/lua/newraycastweaponbase.lua +++ b/lua/newraycastweaponbase.lua @@ -1,10 +1,68 @@ -Hooks:PostHook(NewRaycastWeaponBase, "_update_stats_values", "eclipse__update_stats_values", function(self) - self._optimal_distance = 0 - self._optimal_range = 0 -end) - function NewRaycastWeaponBase:movement_penalty() - if managers.player:has_category_upgrade("player", "no_movement_penalty") then return 1 - else return self._movement_penalty or 1 + if managers.player:has_category_upgrade("player", "no_movement_penalty") then + return 1 + else + return self._movement_penalty or 1 + end +end + +-- remove ARs from BE +function NewRaycastWeaponBase:get_add_head_shot_mul() + if self:is_category("smg", "lmg", "minigun") and self._fire_mode == ids_auto or self:is_category("bow", "saw") then + return managers.player:upgrade_value("weapon", "automatic_head_shot_add", nil) + end + + return nil +end + +function NewRaycastWeaponBase:reload_speed_multiplier() + if self._current_reload_speed_multiplier then + return self._current_reload_speed_multiplier end -end \ No newline at end of file + + local multiplier = 1 + + for _, category in ipairs(self:weapon_tweak_data().categories) do + multiplier = multiplier + 1 - managers.player:upgrade_value(category, "reload_speed_multiplier", 1) + + if category == "shotgun" then -- shotgun reload speed stuff + if self._use_shotgun_reload then + multiplier = multiplier + 1 - managers.player:upgrade_value("shotgun", "pump_reload_speed", 1) + else + multiplier = multiplier + 1 - managers.player:upgrade_value("shotgun", "mag_reload_speed", 1) + end + end + end + + multiplier = multiplier + 1 - managers.player:upgrade_value("weapon", "passive_reload_speed_multiplier", 1) + multiplier = multiplier + 1 - managers.player:upgrade_value(self._name_id, "reload_speed_multiplier", 1) + + if self._setup and alive(self._setup.user_unit) and self._setup.user_unit:movement() then + local morale_boost_bonus = self._setup.user_unit:movement():morale_boost() + + if morale_boost_bonus then + multiplier = multiplier + 1 - morale_boost_bonus.reload_speed_bonus + end + + if self._setup.user_unit:movement():next_reload_speed_multiplier() then + multiplier = multiplier + 1 - self._setup.user_unit:movement():next_reload_speed_multiplier() + end + end + + if managers.player:has_activate_temporary_upgrade("temporary", "reload_weapon_faster") then + multiplier = multiplier + 1 - managers.player:temporary_upgrade_value("temporary", "reload_weapon_faster", 1) + end + + if managers.player:has_activate_temporary_upgrade("temporary", "single_shot_fast_reload") then + multiplier = multiplier + 1 - managers.player:temporary_upgrade_value("temporary", "single_shot_fast_reload", 1) + end + + multiplier = multiplier + 1 - managers.player:get_property("shock_and_awe_reload_multiplier", 1) + multiplier = multiplier + 1 - managers.player:get_temporary_property("bloodthirst_reload_speed", 1) + multiplier = multiplier + 1 - managers.player:upgrade_value("team", "crew_faster_reload", 1) + multiplier = self:_convert_add_to_mul(multiplier) + multiplier = multiplier * self:reload_speed_stat() + multiplier = managers.modifiers:modify_value("WeaponBase:GetReloadSpeedMultiplier", multiplier) + + return multiplier +end diff --git a/lua/npcraycastweaponbase.lua b/lua/npcraycastweaponbase.lua index a16d3a0..1a96877 100644 --- a/lua/npcraycastweaponbase.lua +++ b/lua/npcraycastweaponbase.lua @@ -1,9 +1,11 @@ Hooks:PreHook(NPCRaycastWeaponBase, "_fire_raycast", "_eclipse_fire_raycast", function(self, shoot_player, ...) - local __hostages = managers.groupai:state():all_hostages() - local __enemies = managers.enemy:all_enemies() - if shoot_player then - for k,v_key in ipairs(__hostages) do - if __enemies[v_key] then table.insert(self._setup.ignore_units, __enemies[v_key].unit) end - end - end -end) \ No newline at end of file + local __hostages = managers.groupai:state():all_hostages() + local __enemies = managers.enemy:all_enemies() + if shoot_player then + for k, v_key in ipairs(__hostages) do + if __enemies[v_key] then + table.insert(self._setup.ignore_units, __enemies[v_key].unit) + end + end + end +end) diff --git a/lua/playeractionfullyloaded.lua b/lua/playeractionfullyloaded.lua deleted file mode 100644 index 2585739..0000000 --- a/lua/playeractionfullyloaded.lua +++ /dev/null @@ -1,40 +0,0 @@ -local function on_ammo_pickup(unit, pickup_chance, increase) - local gained_throwable = false - local chance = pickup_chance - - if unit == managers.player:player_unit() then - local random = math.random() - - if random < chance then - gained_throwable = true - - managers.player:add_grenade_amount(1, true) - else - chance = chance + increase - end - end - - return gained_throwable, chance -end - -PlayerAction.FullyLoaded = { - Priority = 1, - Function = function (player_manager, pickup_chance, increase) - local co = coroutine.running() - local gained_throwable = false - local chance = pickup_chance - - local function on_ammo_pickup_message(unit) - gained_throwable, chance = on_ammo_pickup(unit, chance, increase) - end - - player_manager:register_message(Message.OnAmmoPickup, co, on_ammo_pickup_message) - player_manager:register_message(Message.OnAmmoPickup, co, on_ammo_pickup) - - while not gained_throwable do - coroutine.yield(co) - end - - player_manager:unregister_message(Message.OnAmmoPickup, co) - end -} \ No newline at end of file diff --git a/lua/playercamera.lua b/lua/playercamera.lua new file mode 100644 index 0000000..260d647 --- /dev/null +++ b/lua/playercamera.lua @@ -0,0 +1,13 @@ +-- aimpunch +local play_shaker_original = PlayerCamera.play_shaker +function PlayerCamera:play_shaker(effect, amplitude, ...) + return play_shaker_original( + self, + effect, + effect == "player_bullet_damage" and self._damage_bullet_shake_multiplier + or effect == "player_bullet_damage_strong" and self._damage_bullet_shake_multiplier + or effect == "player_bullet_damage_knock_out" and self._damage_bullet_shake_multiplier + or amplitude, + ... + ) +end diff --git a/lua/playerdamage.lua b/lua/playerdamage.lua index 8011c7b..20fc344 100644 --- a/lua/playerdamage.lua +++ b/lua/playerdamage.lua @@ -1,17 +1,199 @@ -- uppers cooldown -PlayerDamage._UPPERS_COOLDOWN = 60 +PlayerDamage._UPPERS_COOLDOWN = 90 -- Pro-Job adds bleedout time and revive health scaling (as well as friendly fire) Hooks:PreHook(PlayerDamage, "replenish", "eclipse_replenish", function(self) - if Global.game_settings.one_down then - self._lives_init = 4 - tweak_data.player.damage.DOWNED_TIME = 25 + if Global.game_settings.one_down then + self._lives_init = 4 + tweak_data.player.damage.DOWNED_TIME = 25 tweak_data.player.damage.DOWNED_TIME_DEC = 10 tweak_data.player.damage.DOWNED_TIME_MIN = 1 - tweak_data.player.damage.REVIVE_HEALTH_STEPS = {0.6, 0.35, 0.1} - end + tweak_data.player.damage.REVIVE_HEALTH_STEPS = { 0.4, 0.2, 0.1 } + end end) +-- Remove suppression +function PlayerDamage:_upd_suppression(t, dt) end + +-- Aimpunch stuffs +function PlayerDamage:damage_bullet(attack_data) + if not self:_chk_can_take_dmg() then + return + end + + local damage_info = { + result = { + variant = "bullet", + type = "hurt", + }, + attacker_unit = attack_data.attacker_unit, + attack_dir = attack_data.attacker_unit and attack_data.attacker_unit:movement():m_pos() - self._unit:movement():m_pos() or Vector3(1, 0, 0), + pos = mvector3.copy(self._unit:movement():m_head_pos()), + } + local pm = managers.player + local dmg_mul = pm:damage_reduction_skill_multiplier("bullet") + attack_data.damage = attack_data.damage * dmg_mul + attack_data.damage = managers.mutators:modify_value("PlayerDamage:TakeDamageBullet", attack_data.damage) + attack_data.damage = managers.modifiers:modify_value("PlayerDamage:TakeDamageBullet", attack_data.damage, attack_data.attacker_unit:base()._tweak_table) + + if _G.IS_VR then + local distance = mvector3.distance(self._unit:position(), attack_data.attacker_unit:position()) + + if tweak_data.vr.long_range_damage_reduction_distance[1] < distance then + local step = math.clamp(distance / tweak_data.vr.long_range_damage_reduction_distance[2], 0, 1) + local mul = 1 - math.step(tweak_data.vr.long_range_damage_reduction[1], tweak_data.vr.long_range_damage_reduction[2], step) + attack_data.damage = attack_data.damage * mul + end + end + + local damage_absorption = pm:damage_absorption() + + if damage_absorption > 0 then + attack_data.damage = math.max(0, attack_data.damage - damage_absorption) + end + + self:copr_update_attack_data(attack_data) + + if self._god_mode then + if attack_data.damage > 0 then + self:_send_damage_drama(attack_data, attack_data.damage) + end + + self:_call_listeners(damage_info) + + return + elseif self._invulnerable or self._mission_damage_blockers.invulnerable then + self:_call_listeners(damage_info) + + return + elseif self:incapacitated() then + return + elseif self:is_friendly_fire(attack_data.attacker_unit) then + return + elseif self:_chk_dmg_too_soon(attack_data.damage) then + return + elseif self._unit:movement():current_state().immortal then + return + elseif self._revive_miss and math.random() < self._revive_miss then + self:play_whizby(attack_data.col_ray.position) + + return + end + + self._last_received_dmg = attack_data.damage + self._next_allowed_dmg_t = Application:digest_value(pm:player_timer():time() + self._dmg_interval, true) + local dodge_roll = math.random() + local dodge_value = tweak_data.player.damage.DODGE_INIT or 0 + local armor_dodge_chance = pm:body_armor_value("dodge") + local skill_dodge_chance = pm:skill_dodge_chance(self._unit:movement():running(), self._unit:movement():crouching(), self._unit:movement():zipline_unit()) + dodge_value = dodge_value + armor_dodge_chance + skill_dodge_chance + + if self._temporary_dodge_t and TimerManager:game():time() < self._temporary_dodge_t then + dodge_value = dodge_value + self._temporary_dodge + end + + local smoke_dodge = 0 + + for _, smoke_screen in ipairs(managers.player._smoke_screen_effects or {}) do + if smoke_screen:is_in_smoke(self._unit) then + smoke_dodge = tweak_data.projectiles.smoke_screen_grenade.dodge_chance + + break + end + end + + dodge_value = 1 - (1 - dodge_value) * (1 - smoke_dodge) + + if dodge_roll < dodge_value then + if attack_data.damage > 0 then + self:_send_damage_drama(attack_data, 0) + end + + self:_call_listeners(damage_info) + self:play_whizby(attack_data.col_ray.position) + self:_hit_direction(attack_data.attacker_unit:position(), attack_data.col_ray and attack_data.col_ray.ray or damage_info.attacK_dir) + + self._next_allowed_dmg_t = Application:digest_value(pm:player_timer():time() + self._dmg_interval, true) + self._last_received_dmg = attack_data.damage + + managers.player:send_message(Message.OnPlayerDodge, nil, attack_data) + + return + end + + if attack_data.attacker_unit:base()._tweak_table == "tank" then + managers.achievment:set_script_data("dodge_this_fail", true) + end + + if self:get_real_armor() > 0 then + self._unit:sound():play("player_hit") + else + self._unit:sound():play("player_hit_permadamage") + end + + local shake_armor_multiplier = managers.player:body_armor_value("damage_shake") * (self:get_real_armor() > 0 and 1 or 2) + self._unit:camera()._damage_bullet_shake_multiplier = math.clamp(attack_data.damage, 0, 20) * shake_armor_multiplier + local gui_shake_number = tweak_data.gui.armor_damage_shake_base / shake_armor_multiplier + gui_shake_number = gui_shake_number + pm:upgrade_value("player", "damage_shake_addend", 0) + shake_armor_multiplier = tweak_data.gui.armor_damage_shake_base / gui_shake_number + local shake_multiplier = math.clamp(attack_data.damage, 0.2, 2) * shake_armor_multiplier + + -- The stronger the damage - the more lengthy and powerful the aimpunch + if attack_data.damage < 12.5 then + self._unit:camera():play_shaker("player_bullet_damage", 1 * shake_multiplier) + elseif attack_data.damage < 23 then + self._unit:camera():play_shaker("player_bullet_damage_strong", 1 * shake_multiplier) + else + self._unit:camera():play_shaker("player_bullet_damage_knock_out", 1 * shake_multiplier) + end + + if not _G.IS_VR then + managers.rumble:play("damage_bullet") + end + + self:_hit_direction(attack_data.attacker_unit:position(), attack_data.col_ray and attack_data.col_ray.ray or damage_info.attacK_dir) + pm:check_damage_carry(attack_data) + + attack_data.damage = managers.player:modify_value("damage_taken", attack_data.damage, attack_data) + + if self._bleed_out then + self:_bleed_out_damage(attack_data) + + return + end + + if not attack_data.ignore_suppression and not self:is_suppressed() then + return + end + + self:_check_chico_heal(attack_data) + + local armor_reduction_multiplier = 0 + + if self:get_real_armor() <= 0 then + armor_reduction_multiplier = 1 + end + + local health_subtracted = self:_calc_armor_damage(attack_data) + + if attack_data.armor_piercing then + attack_data.damage = attack_data.damage - health_subtracted + else + attack_data.damage = attack_data.damage * armor_reduction_multiplier + end + + health_subtracted = health_subtracted + self:_calc_health_damage(attack_data) + + if not self._bleed_out and health_subtracted > 0 then + self:_send_damage_drama(attack_data, health_subtracted) + elseif self._bleed_out then + self:chk_queue_taunt_line(attack_data) + end + + pm:send_message(Message.OnPlayerDamage, nil, attack_data) + self:_call_listeners(damage_info) +end + -- Friendly Fire function PlayerDamage:is_friendly_fire(unit) local attacker_mov_ext = alive(unit) and unit:movement() @@ -37,31 +219,141 @@ function PlayerDamage:is_friendly_fire(unit) return friendly_fire end --- The entirety of the code here is done by Hoppip, thanks again if you're reading this --- Grace period protects no matter the new potential damage but is shorter in general +-- Armor Breaking GP / Panic +function PlayerDamage:_calc_armor_damage(attack_data) + local health_subtracted = 0 + + if self:get_real_armor() > 0 then + health_subtracted = self:get_real_armor() + + self:change_armor(-attack_data.damage) + + health_subtracted = health_subtracted - self:get_real_armor() + + self:_damage_screen() + SoundDevice:set_rtpc("shield_status", self:armor_ratio() * 100) + self:_send_set_armor() + + local has_armor_panic = managers.player:has_enabled_cooldown_upgrade("cooldown", "panic_on_armor_break") + + if self:get_real_armor() <= 0 then + -- Armor breaking causes enemies to panic + if has_armor_panic then + local pos = managers.player:player_unit():position() + local skill = tweak_data.upgrades.values.player.armor_panic[1] + + if skill then + local area = skill.area + local chance = skill.chance + local amount = skill.amount + local enemies = World:find_units_quick("sphere", pos, area, managers.slot:get_mask("enemies")) + + for i, unit in ipairs(enemies) do + if unit:character_damage() then + unit:character_damage():build_suppression(amount, chance) + end + end + end + + managers.player:disable_cooldown_upgrade("cooldown", "panic_on_armor_break") + end + + self._unit:sound():play("player_armor_gone_stinger") + + if attack_data.armor_piercing then + self._unit:sound():play("player_sniper_hit_armor_gone") + end + + -- Add significantly longer grace period on armor break (repurposing Anarchist/Armorer damage timer) (sh) + self._can_take_dmg_timer = self._dmg_interval + 0.2 + + local pm = managers.player + + self:_start_regen_on_the_side(pm:upgrade_value("player", "passive_always_regen_armor", 0)) + + if pm:has_inactivate_temporary_upgrade("temporary", "armor_break_invulnerable") then + pm:activate_temporary_upgrade("temporary", "armor_break_invulnerable") + + self._can_take_dmg_timer = pm:temporary_upgrade_value("temporary", "armor_break_invulnerable", 0) + end + end + end + + managers.hud:damage_taken() + + return health_subtracted +end + +-- Grace period protects no matter the new potential damage but is shorter in general (sh) function PlayerDamage:_chk_dmg_too_soon() local next_allowed_dmg_t = type(self._next_allowed_dmg_t) == "number" and self._next_allowed_dmg_t or Application:digest_value(self._next_allowed_dmg_t, false) return managers.player:player_timer():time() < next_allowed_dmg_t end --- Add slightly longer grace period on dodge (repurposing Anarchist/Armorer damage timer) -Hooks:PostHook(PlayerDamage, "_send_damage_drama", "sh__send_damage_drama", function (self, attack_data, health_subtracted) - if health_subtracted == 0 and self._can_take_dmg_timer and self._can_take_dmg_timer <= 0 then - self._can_take_dmg_timer = self._dmg_interval + 0.2 +-- Make <50%hp invuln upgrade not proc on armor hits +function PlayerDamage:_calc_health_damage(attack_data) + if attack_data.weapon_unit then + local weap_base = alive(attack_data.weapon_unit) and attack_data.weapon_unit:base() + local weap_tweak_data = weap_base and weap_base.weapon_tweak_data and weap_base:weapon_tweak_data() + + if weap_tweak_data and weap_tweak_data.slowdown_data then + self:apply_slowdown(weap_tweak_data.slowdown_data) + end end -end) --- Add significantly longer grace period on armor break (repurposing Anarchist/Armorer damage timer) -local _calc_armor_damage_original = PlayerDamage._calc_armor_damage -function PlayerDamage:_calc_armor_damage(...) - local had_armor = self:get_real_armor() > 0 + if managers.player:has_activate_temporary_upgrade("temporary", "mrwi_health_invulnerable") then + return 0 + end + + local health_subtracted = 0 + health_subtracted = self:get_real_health() + + self:change_health(-attack_data.damage) - local health_subtracted = _calc_armor_damage_original(self, ...) + health_subtracted = health_subtracted - self:get_real_health() - if health_subtracted > 0 and had_armor and self:get_real_armor() <= 0 and self._can_take_dmg_timer <= 0 then - self._can_take_dmg_timer = self._dmg_interval + 0.3 + if managers.player:has_activate_temporary_upgrade("temporary", "copr_ability") and health_subtracted > 0 then + local teammate_heal_level = managers.player:upgrade_level_nil("player", "copr_teammate_heal") + + if teammate_heal_level and self:get_real_health() > 0 then + self._unit:network():send("copr_teammate_heal", teammate_heal_level) + end end + if self._has_mrwi_health_invulnerable then + local health_threshold = self._mrwi_health_invulnerable_threshold or 0.5 + local is_cooling_down = managers.player:get_temporary_property("mrwi_health_invulnerable", false) + + if self:health_ratio() <= health_threshold and health_subtracted > 0 and not is_cooling_down then -- was it so hard to just add one more check, overkill? + local cooldown_time = self._mrwi_health_invulnerable_cooldown or 10 + + managers.player:activate_temporary_upgrade("temporary", "mrwi_health_invulnerable") + managers.player:activate_temporary_property("mrwi_health_invulnerable", cooldown_time, true) + end + end + + local trigger_skills = table.contains({ + "bullet", + "explosion", + "melee", + "delayed_tick", + }, attack_data.variant) + + if self:get_real_health() == 0 and trigger_skills then + self:_chk_cheat_death() + end + + self:_damage_screen() + self:_check_bleed_out(trigger_skills) + managers.hud:set_player_health({ + current = self:get_real_health(), + total = self:_max_health(), + revives = Application:digest_value(self._revives, false), + }) + self:_send_set_health() + self:_set_health_effect() + managers.statistics:health_subtracted(health_subtracted) + return health_subtracted end @@ -78,21 +370,20 @@ function PlayerDamage:restore_health(health_restored, is_static, chk_health_rati end end --- add an upgrade that gives increased bleedout timer -Hooks:PostHook(PlayerDamage, "_regenerated", "eclipse__regenerated", function(self) - self._down_time = tweak_data.player.damage.DOWNED_TIME + managers.player:upgrade_value("player", "increased_bleedout_timer", 0) -end) - -- lower the on-kill godmode length for leech function PlayerDamage:on_copr_killshot() self._next_allowed_dmg_t = Application:digest_value(managers.player:player_timer():time() + 0.45, true) self._last_received_dmg = self:_max_health() end +-- add an upgrade that gives increased bleedout timer +Hooks:PostHook(PlayerDamage, "_regenerated", "eclipse__regenerated", function(self) + self._down_time = tweak_data.player.damage.DOWNED_TIME + managers.player:upgrade_value("player", "increased_bleedout_timer", 0) +end) + -- bring back decreasing bleedout timer based on the amount of downs Hooks:PreHook(PlayerDamage, "revive", "eclipse_revive", function(self) if not self:arrested() then - self._down_time = math.max(tweak_data.player.damage.DOWNED_TIME_MIN, self._down_time - tweak_data.player.damage.DOWNED_TIME_DEC) + self._down_time = math.max(tweak_data.player.damage.DOWNED_TIME_MIN, self._down_time - tweak_data.player.damage.DOWNED_TIME_DEC) end end) - diff --git a/lua/playerdriving.lua b/lua/playerdriving.lua new file mode 100644 index 0000000..eea7e98 --- /dev/null +++ b/lua/playerdriving.lua @@ -0,0 +1,34 @@ +-- Update nav tracker if the player is driving a vehicle +Hooks:OverrideFunction(PlayerDriving, "update", function(self, t, dt) + if self._vehicle == nil or not self._vehicle:is_active() or self._controller == nil then + return + end + + self:_update_input(dt) + + local input = self:_get_input(t, dt) + + self:_calculate_standard_variables(t, dt) + self:_update_ground_ray() + self:_update_fwd_ray() + self:_check_action_change_camera(t, input) + self:_check_action_rear_cam(t, input) + self:_update_hud(t, input) + self:_update_action_timers(t, input) + self:_check_action_exit_vehicle(t, input) + + if self._menu_closed_fire_cooldown > 0 then + self._menu_closed_fire_cooldown = self._menu_closed_fire_cooldown - dt + end + + if self._seat.driving then + self:_update_check_actions_driver(t, dt, input) + elseif self._seat.allow_shooting or self._stance == PlayerDriving.STANCE_SHOOTING then + self:_update_check_actions_passenger(t, dt, input) + else + self:_update_check_actions_passenger_no_shoot(t, dt, input) + end + + self:_upd_nav_data() + self:_upd_stance_switch_delay(t, dt) +end) diff --git a/lua/playerinventorygui.lua b/lua/playerinventorygui.lua new file mode 100644 index 0000000..90628b2 --- /dev/null +++ b/lua/playerinventorygui.lua @@ -0,0 +1,9 @@ +-- Fix melee weapon knockdown stat display +Hooks:PreHook(PlayerInventoryGui, "set_melee_stats", "shc_set_melee_stats", function(self, panel, data) + for _, v in pairs(data) do + if v.name == "damage_effect" then + v.multiple_of = nil + return + end + end +end) diff --git a/lua/playermanager.lua b/lua/playermanager.lua index fc7eaa9..73c10b2 100644 --- a/lua/playermanager.lua +++ b/lua/playermanager.lua @@ -1,13 +1,3 @@ -local old_upgrade_value = PlayerManager.upgrade_value - -function PlayerManager:upgrade_value(category, upgrade, ...) - local _upgrade_value = old_upgrade_value(self, category, upgrade, ...) - if category == "player" and upgrade == "pick_up_ammo_multiplier" and self:has_category_upgrade("player", "addition_ammo_eclipse") then - _upgrade_value = _upgrade_value + self:upgrade_value("player", "addition_ammo_eclipse", 0) - end - return _upgrade_value -end - -- hostage taker min hostages count Hooks:OverrideFunction(PlayerManager, "get_hostage_bonus_addend", function(self, category) local hostages = managers.groupai and managers.groupai:state():hostage_count() or 0 @@ -26,7 +16,7 @@ Hooks:OverrideFunction(PlayerManager, "get_hostage_bonus_addend", function(self, end if self:has_category_upgrade("player", "joker_counts_for_hostage_boost") then - hostages = hostages + minions + hostages = hostages + minions end hostage_min_sum = hostage_min_sum + self:upgrade_value("player", "hostage_min_sum_taker", 0) @@ -40,3 +30,181 @@ Hooks:OverrideFunction(PlayerManager, "get_hostage_bonus_addend", function(self, return addend * hostages end) + +function PlayerManager:on_headshot_dealt() + local t = Application:time() + local player_unit = self:player_unit() + local damage_ext = player_unit:character_damage() + local has_hitman_ammo_refund = managers.player:has_enabled_cooldown_upgrade("cooldown", "hitman_ammo_refund") + + if not player_unit then + return + end + + -- hitman refunds ammo on headshots + if has_hitman_ammo_refund and variant ~= "melee" then + managers.player:on_ammo_increase(1) + managers.player:disable_cooldown_upgrade("cooldown", "hitman_ammo_refund") + end + + -- make headshot regen check for maxed out armor + if damage_ext and damage_ext:armor_ratio() == 1 then + self._on_headshot_dealt_t = 0 + else + if self._on_headshot_dealt_t and t < self._on_headshot_dealt_t then + return + end + self._on_headshot_dealt_t = t + (tweak_data.upgrades.on_headshot_dealt_cooldown or 0) + end + + self._message_system:notify(Message.OnHeadShot, nil, nil) + + local regen_armor_bonus = managers.player:upgrade_value("player", "headshot_regen_armor_bonus", 0) + + if damage_ext and regen_armor_bonus > 0 then + damage_ext:restore_armor(regen_armor_bonus) + end +end + +-- sleight of hand check for weapon category +function PlayerManager:_on_enter_shock_and_awe_event() + local equipped_unit = self:get_current_state()._equipped_unit + if + not ( + equipped_unit:base():is_category("smg") + or equipped_unit:base():is_category("lmg") + or equipped_unit:base():is_category("minigun") + or equipped_unit:base():is_category("flamethrower") + or equipped_unit:base():is_category("bow") + ) + then + return + end + + if not self._coroutine_mgr:is_running("automatic_faster_reload") then + local data = self:upgrade_value("player", "automatic_faster_reload", nil) + local is_grenade_launcher = equipped_unit:base():is_category("grenade_launcher") + + if data and equipped_unit and not is_grenade_launcher and (equipped_unit:base():fire_mode() == "auto" or equipped_unit:base():is_category("bow", "flamethrower")) then + self._coroutine_mgr:add_and_run_coroutine( + "automatic_faster_reload", + PlayerAction.ShockAndAwe, + self, + data.target_enemies, + data.max_reload_increase, + data.min_reload_increase, + data.penalty, + data.min_bullets, + equipped_unit + ) + end + end +end + +-- shotgun panic stuff +local on_killshot_old = PlayerManager.on_killshot +function PlayerManager:on_killshot(killed_unit, variant, headshot, weapon_id) + on_killshot_old(self, killed_unit, variant, headshot, weapon_id) + + local has_shotgun_panic = managers.player:has_enabled_cooldown_upgrade("cooldown", "shotgun_panic_on_kill") + if has_shotgun_panic and variant ~= "melee" then + local equipped_unit = self:get_current_state()._equipped_unit:base() + + if equipped_unit:is_category("shotgun") then + local pos = managers.player:player_unit():position() + local skill = tweak_data.upgrades.values.shotgun.panic[1] + + if skill then + local area = skill.area + local chance = skill.chance + local amount = skill.amount + local enemies = World:find_units_quick("sphere", pos, area, managers.slot:get_mask("enemies")) + + for i, unit in ipairs(enemies) do + if unit:character_damage() then + unit:character_damage():build_suppression(amount, chance) + end + end + end + + managers.player:disable_cooldown_upgrade("cooldown", "shotgun_panic_on_kill") + end + end +end + +-- Shotgun CQB +PlayerAction.ShotgunCQB = { + Priority = 1, + Function = function(player_manager, speed_bonus, max_stacks, max_time) + local co = coroutine.running() + local current_time = Application:time() + local current_stacks = 1 + + local function on_hit(unit, attack_data) + local attacker_unit = attack_data.attacker_unit + local variant = attack_data.variant + + if attacker_unit == player_manager:player_unit() and variant == "bullet" then + current_stacks = current_stacks + 1 + + if current_stacks <= max_stacks then + player_manager:mul_to_property("shotguncqb", speed_bonus) + end + end + end + + player_manager:mul_to_property("shotguncqb", speed_bonus) + player_manager:register_message(Message.OnEnemyShot, co, on_hit) + + while current_time < max_time do + current_time = Application:time() + coroutine.yield(co) + end + + player_manager:remove_property("shotguncqb") + player_manager:unregister_message(Message.OnEnemyShot, co) + end, +} + +Hooks:PostHook(PlayerManager, "check_skills", "eclipse_check_skills", function(self) + if self:has_category_upgrade("shotgun", "speed_stack_on_kill") then + self._message_system:register(Message.OnEnemyShot, "shotguncqb", callback(self, self, "_on_enter_shotguncqb_event")) + else + self._message_system:unregister(Message.OnEnemyShot, "shotguncqb") + end +end) + +function PlayerManager:_on_enter_shotguncqb_event(unit, attack_data) + local attacker_unit = attack_data.attacker_unit + local variant = attack_data.variant + + if attacker_unit == self:player_unit() and variant == "bullet" and not self._coroutine_mgr:is_running("shotguncqb") and self:is_current_weapon_of_category("shotgun") then + local data = self:upgrade_value("shotgun", "speed_stack_on_kill", 0) + + if data ~= 0 then + self._coroutine_mgr:add_coroutine("shotguncqb", PlayerAction.ShotgunCQB, self, data.speed_bonus, data.max_stacks, Application:time() + data.max_time) + end + end +end + +local old_speed_multiplier = PlayerManager.movement_speed_multiplier +function PlayerManager:movement_speed_multiplier(...) + local multi = old_speed_multiplier(self, ...) + multi = multi * managers.player:get_property("shotguncqb", 1) + return multi +end + +local old_skill_dodge = PlayerManager.skill_dodge_chance + +function PlayerManager:skill_dodge_chance(...) + local dodge = old_skill_dodge(self, ...) + + if self:player_unit() and self:has_category_upgrade("player", "dodge_health_ratio_multiplier") then + local health_ratio = self:player_unit():character_damage():health_ratio() / 2 + local damage_health_ratio = self:get_damage_health_ratio(health_ratio, "dodge") + + dodge = dodge + self:upgrade_value("player", "dodge_health_ratio_multiplier", 0) * damage_health_ratio + end + + return dodge +end diff --git a/lua/playermovement.lua b/lua/playermovement.lua new file mode 100644 index 0000000..16c98fa --- /dev/null +++ b/lua/playermovement.lua @@ -0,0 +1,5 @@ +Hooks:PostHook(PlayerMovement, "init", "eclipse_init", function(self) + if managers.player:has_category_upgrade("player", "morale_boost") or managers.player:has_category_upgrade("cooldown", "long_dis_revive") then + self._rally_skill_data.range_sq = 490000 + end +end) diff --git a/lua/playerstandard.lua b/lua/playerstandard.lua index 88e92e0..9792c59 100644 --- a/lua/playerstandard.lua +++ b/lua/playerstandard.lua @@ -1,61 +1,311 @@ -- Friendly Fire local original_init = PlayerStandard.init function PlayerStandard:init(unit) - original_init(self, unit) + original_init(self, unit) - if Global.game_settings and Global.game_settings.one_down then - self._slotmask_bullet_impact_targets = self._slotmask_bullet_impact_targets + 3 - else - self._slotmask_bullet_impact_targets = managers.mutators:modify_value("PlayerStandard:init:melee_slot_mask", self._slotmask_bullet_impact_targets) - end + if Global.game_settings and Global.game_settings.one_down then + self._slotmask_bullet_impact_targets = self._slotmask_bullet_impact_targets + 3 + else + self._slotmask_bullet_impact_targets = managers.mutators:modify_value("PlayerStandard:init:melee_slot_mask", self._slotmask_bullet_impact_targets) + end end --- separate lock n' load from ovka -function PlayerStandard:_get_swap_speed_multiplier() - local multiplier = 1 - local weapon_tweak_data = self._equipped_unit:base():weapon_tweak_data() - multiplier = multiplier * managers.player:upgrade_value("weapon", "swap_speed_multiplier", 1) - multiplier = multiplier * managers.player:upgrade_value("weapon", "passive_swap_speed_multiplier", 1) - multiplier = multiplier * managers.player:upgrade_value("weapon", "generic_swap_speed_multiplier", 1) +-- Spray pattern implementation +-- Oh man! This is just like Counter-Strike! +function PlayerStandard:_check_action_primary_attack(t, input) + local new_action = nil + local action_wanted = input.btn_primary_attack_state or input.btn_primary_attack_release + action_wanted = action_wanted or self:is_shooting_count() + action_wanted = action_wanted or self:_is_charging_weapon() + + if action_wanted then + local action_forbidden = self:_is_reloading() + or self:_changing_weapon() + or self:_is_meleeing() + or self._use_item_expire_t + or self:_interacting() + or self:_is_throwing_projectile() + or self:_is_deploying_bipod() + or self._menu_closed_fire_cooldown > 0 + or self:is_switching_stances() + + if not action_forbidden then + self._queue_reload_interupt = nil + local start_shooting = false + + self._ext_inventory:equip_selected_primary(false) + + if self._equipped_unit then + local weap_base = self._equipped_unit:base() + local fire_mode = weap_base:fire_mode() + local fire_on_release = weap_base:fire_on_release() + + if weap_base:out_of_ammo() then + if input.btn_primary_attack_press then + weap_base:dryfire() + end + elseif weap_base.clip_empty and weap_base:clip_empty() then + if self:_is_using_bipod() then + if input.btn_primary_attack_press then + weap_base:dryfire() + end + + self._equipped_unit:base():tweak_data_anim_stop("fire") + elseif fire_mode == "single" then + if input.btn_primary_attack_press or self._equipped_unit:base().should_reload_immediately and self._equipped_unit:base():should_reload_immediately() then + self:_start_action_reload_enter(t) + end + else + new_action = true + + self:_start_action_reload_enter(t) + end + elseif self._running and not self._equipped_unit:base():run_and_shoot_allowed() then + self:_interupt_action_running(t) + else + if not self._shooting then + if weap_base:start_shooting_allowed() then + local start = fire_mode == "single" and input.btn_primary_attack_press + start = start or fire_mode == "auto" and input.btn_primary_attack_state + start = start or fire_mode == "burst" and input.btn_primary_attack_press + start = start or fire_mode == "volley" and input.btn_primary_attack_press + start = start and not fire_on_release + start = start or fire_on_release and input.btn_primary_attack_release + + if start then + weap_base:start_shooting() + self._camera_unit:base():start_shooting() + + self._shooting = true + self._shooting_t = t + start_shooting = true + + if fire_mode == "auto" then + self._unit:camera():play_redirect(self:get_animation("recoil_enter")) + + if + (not weap_base.akimbo or weap_base:weapon_tweak_data().allow_akimbo_autofire) + and (not weap_base.third_person_important or weap_base.third_person_important and not weap_base:third_person_important()) + then + self._ext_network:send("sync_start_auto_fire_sound", 0) + end + end + end + else + self:_check_stop_shooting() + + return false + end + end - for _, category in ipairs(weapon_tweak_data.categories) do - multiplier = multiplier * managers.player:upgrade_value(category, "swap_speed_multiplier", 1) - end + local suppression_ratio = self._unit:character_damage():effective_suppression_ratio() + local spread_mul = math.lerp(1, tweak_data.player.suppression.spread_mul, suppression_ratio) + local autohit_mul = math.lerp(1, tweak_data.player.suppression.autohit_chance_mul, suppression_ratio) + local suppression_mul = managers.blackmarket:threat_multiplier() + local dmg_mul = 1 + local weapon_tweak_data = weap_base:weapon_tweak_data() + local primary_category = weapon_tweak_data.categories[1] + + if not weapon_tweak_data.ignore_damage_multipliers then + dmg_mul = dmg_mul * managers.player:temporary_upgrade_value("temporary", "dmg_multiplier_outnumbered", 1) - multiplier = multiplier * managers.player:upgrade_value("team", "crew_faster_swap", 1) + if managers.player:has_category_upgrade("player", "overkill_all_weapons") or weap_base:is_category("shotgun", "saw") then + dmg_mul = dmg_mul * managers.player:temporary_upgrade_value("temporary", "overkill_damage_multiplier", 1) + end - if managers.player:has_activate_temporary_upgrade("temporary", "swap_weapon_faster") then - multiplier = multiplier * managers.player:temporary_upgrade_value("temporary", "swap_weapon_faster", 1) + local health_ratio = self._ext_damage:health_ratio() + local damage_health_ratio = managers.player:get_damage_health_ratio(health_ratio, primary_category) + + if damage_health_ratio > 0 then + local upgrade_name = weap_base:is_category("saw") and "melee_damage_health_ratio_multiplier" or "damage_health_ratio_multiplier" + local damage_ratio = damage_health_ratio + dmg_mul = dmg_mul * (1 + managers.player:upgrade_value("player", upgrade_name, 0) * damage_ratio) + end + + dmg_mul = dmg_mul * managers.player:temporary_upgrade_value("temporary", "berserker_damage_multiplier", 1) + dmg_mul = dmg_mul * managers.player:get_property("trigger_happy", 1) + end + + local fired = nil + + if fire_mode == "single" then + if input.btn_primary_attack_press and start_shooting then + fired = weap_base:trigger_pressed(self:get_fire_weapon_position(), self:get_fire_weapon_direction(), dmg_mul, nil, spread_mul, autohit_mul, suppression_mul) + elseif fire_on_release then + if input.btn_primary_attack_release then + fired = weap_base:trigger_released(self:get_fire_weapon_position(), self:get_fire_weapon_direction(), dmg_mul, nil, spread_mul, autohit_mul, suppression_mul) + elseif input.btn_primary_attack_state then + weap_base:trigger_held(self:get_fire_weapon_position(), self:get_fire_weapon_direction(), dmg_mul, nil, spread_mul, autohit_mul, suppression_mul) + end + end + elseif fire_mode == "burst" then + fired = weap_base:trigger_held(self:get_fire_weapon_position(), self:get_fire_weapon_direction(), dmg_mul, nil, spread_mul, autohit_mul, suppression_mul) + elseif fire_mode == "volley" then + if self._shooting then + fired = weap_base:trigger_held(self:get_fire_weapon_position(), self:get_fire_weapon_direction(), dmg_mul, nil, spread_mul, autohit_mul, suppression_mul) + end + elseif input.btn_primary_attack_state then + fired = weap_base:trigger_held(self:get_fire_weapon_position(), self:get_fire_weapon_direction(), dmg_mul, nil, spread_mul, autohit_mul, suppression_mul) + end + + if weap_base.manages_steelsight and weap_base:manages_steelsight() then + if weap_base:wants_steelsight() and not self._state_data.in_steelsight then + self:_start_action_steelsight(t) + elseif not weap_base:wants_steelsight() and self._state_data.in_steelsight then + self:_end_action_steelsight(t) + end + end + + local charging_weapon = weap_base:charging() + + if not self._state_data.charging_weapon and charging_weapon then + self:_start_action_charging_weapon(t) + elseif self._state_data.charging_weapon and not charging_weapon then + self:_end_action_charging_weapon(t) + end + + new_action = true + + if fired then + managers.rumble:play("weapon_fire") + + local weap_tweak_data = tweak_data.weapon[weap_base:get_name_id()] + local shake_tweak_data = weap_tweak_data.shake[fire_mode] or weap_tweak_data.shake + local shake_multiplier = shake_tweak_data[self._state_data.in_steelsight and "fire_steelsight_multiplier" or "fire_multiplier"] + + self._ext_camera:play_shaker("fire_weapon_rot", 1 * shake_multiplier) + self._ext_camera:play_shaker("fire_weapon_kick", 1 * shake_multiplier, 1, 0.15) + self._equipped_unit:base():tweak_data_anim_stop("unequip") + self._equipped_unit:base():tweak_data_anim_stop("equip") + + if not self._state_data.in_steelsight or not weap_base:tweak_data_anim_play("fire_steelsight", weap_base:fire_rate_multiplier()) then + weap_base:tweak_data_anim_play("fire", weap_base:fire_rate_multiplier()) + end + + if fire_mode ~= "auto" and weap_base:get_name_id() ~= "saw" then + local state = nil + + if not self._state_data.in_steelsight then + state = self._ext_camera:play_redirect(self:get_animation("recoil"), weap_base:fire_rate_multiplier()) + elseif weap_tweak_data.animations.recoil_steelsight then + state = self._ext_camera:play_redirect(weap_base:is_second_sight_on() and self:get_animation("recoil") or self:get_animation("recoil_steelsight"), 1) + end + end + + local recoil_multiplier = (weap_base:recoil() + weap_base:recoil_addend()) * weap_base:recoil_multiplier() + + cat_print("jansve", "[PlayerStandard] Weapon Recoil Multiplier: " .. tostring(recoil_multiplier)) + + -- Modify starting here + local kick_tweak_data = weap_tweak_data.kick[fire_mode] or weap_tweak_data.kick + local up, down, left, right = unpack(kick_tweak_data[self._state_data.in_steelsight and "steelsight" or self._state_data.ducking and "crouching" or "standing"]) + + local apply_spray = false + if fire_mode == "auto" and weap_tweak_data.spray then -- temporary spray check before we add it to all weapons + pattern_tweak_data = weap_tweak_data.spray.pattern -- first part of spray pattern + persist_pattern_tweak_data = weap_tweak_data.spray.persist_pattern -- second part of spray pattern (persist pattern) + recoil_recovery = weap_tweak_data.recoil_recovery_timer + apply_spray = true + end + + if apply_spray then + self._camera_unit:base():pattern_recoil_kick(pattern_tweak_data, persist_pattern_tweak_data, recoil_multiplier, recoil_recovery) + else + self._camera_unit:base():recoil_kick(up * recoil_multiplier, down * recoil_multiplier, left * recoil_multiplier, right * recoil_multiplier) + end + -- End modification + + if self._shooting_t then + local time_shooting = t - self._shooting_t + local achievement_data = tweak_data.achievement.never_let_you_go + + if achievement_data and weap_base:get_name_id() == achievement_data.weapon_id and achievement_data.timer <= time_shooting then + managers.achievment:award(achievement_data.award) + + self._shooting_t = nil + end + end + + if managers.player:has_category_upgrade(primary_category, "stacking_hit_damage_multiplier") then + self._state_data.stacking_dmg_mul = self._state_data.stacking_dmg_mul or {} + self._state_data.stacking_dmg_mul[primary_category] = self._state_data.stacking_dmg_mul[primary_category] or { + nil, + 0, + } + local stack = self._state_data.stacking_dmg_mul[primary_category] + + if fired.hit_enemy then + stack[1] = t + managers.player:upgrade_value(primary_category, "stacking_hit_expire_t", 1) + stack[2] = math.min(stack[2] + 1, tweak_data.upgrades.max_weapon_dmg_mul_stacks or 5) + else + stack[1] = nil + stack[2] = 0 + end + end + + if weap_base.set_recharge_clbk then + weap_base:set_recharge_clbk(callback(self, self, "weapon_recharge_clbk_listener")) + end + + managers.hud:set_ammo_amount(weap_base:selection_index(), weap_base:ammo_info()) + + local impact = not fired.hit_enemy + + if weap_base.third_person_important and weap_base:third_person_important() then + self._ext_network:send("shot_blank_reliable", impact, 0) + elseif fire_mode ~= "auto" or weap_base.akimbo and not weap_base:weapon_tweak_data().allow_akimbo_autofire then + self._ext_network:send("shot_blank", impact, 0) + end + + if fire_mode == "volley" then + self:_check_stop_shooting() + end + elseif fire_mode == "single" then + new_action = false + elseif fire_mode == "burst" then + if weap_base:shooting_count() == 0 then + new_action = false + end + elseif fire_mode == "volley" then + new_action = self:_is_charging_weapon() + end + end + end + elseif self:_is_reloading() and self._equipped_unit:base():reload_interuptable() and input.btn_primary_attack_press then + self._queue_reload_interupt = true + end end - multiplier = managers.modifiers:modify_value("PlayerStandard:GetSwapSpeedMultiplier", multiplier) + if not new_action then + self:_check_stop_shooting() + end - return multiplier + return new_action end -- No more sixth sense -Hooks:OverrideFunction(PlayerStandard, "_update_omniscience", -function(self, ...) - return +Hooks:OverrideFunction(PlayerStandard, "_update_omniscience", function(self, ...) + return end) --- Don't update sixth sense anymore -Hooks:OverrideFunction(PlayerStandard, "update", -function(self, t, dt) - PlayerMovementState.update(self, t, dt) - self:_calculate_standard_variables(t, dt) - self:_update_ground_ray() - self:_update_fwd_ray() - self:_update_check_actions(t, dt) - - if self._menu_closed_fire_cooldown > 0 then - self._menu_closed_fire_cooldown = self._menu_closed_fire_cooldown - dt - end - - self:_update_movement(t, dt) - self:_upd_nav_data() - managers.hud:_update_crosshair_offset(t, dt) - self:_upd_stance_switch_delay(t, dt) +-- Don't update sixth sense anymore and add sprint reload upgrade to shotguns +Hooks:OverrideFunction(PlayerStandard, "update", function(self, t, dt) + PlayerMovementState.update(self, t, dt) + self:_calculate_standard_variables(t, dt) + self:_update_ground_ray() + self:_update_fwd_ray() + self:_update_check_actions(t, dt) + + if self._menu_closed_fire_cooldown > 0 then + self._menu_closed_fire_cooldown = self._menu_closed_fire_cooldown - dt + end + + self:_update_movement(t, dt) + self:_upd_nav_data() + managers.hud:_update_crosshair_offset(t, dt) + self:_upd_stance_switch_delay(t, dt) + self.RUN_AND_RELOAD = managers.player:has_category_upgrade("player", "run_and_reload") + or self._equipped_unit and self._equipped_unit:base():is_category("shotgun") and managers.player:has_category_upgrade("shotgun", "run_and_reload") end) -- Melee while running @@ -71,7 +321,14 @@ Hooks:PostHook(PlayerStandard, "_start_action_running", "eclipse_start_action_ru return end - if self._shooting and not managers.player.RUN_AND_SHOOT or self:_changing_weapon() or self._use_item_expire_t or self._state_data.in_air or self:_is_throwing_projectile() or self:_is_charging_weapon() then + if + self._shooting and not managers.player.RUN_AND_SHOOT + or self:_changing_weapon() + or self._use_item_expire_t + or self._state_data.in_air + or self:_is_throwing_projectile() + or self:_is_charging_weapon() + then self._running_wanted = true return end @@ -196,10 +453,11 @@ function PlayerStandard:_check_use_item(t, input) managers.player:drop_carry() self._throw_time = nil return true - else return old_check_use(self, t, input) + else + return old_check_use(self, t, input) end end function PlayerManager.carry_blocked_by_cooldown() return false -end \ No newline at end of file +end diff --git a/lua/playertased.lua b/lua/playertased.lua new file mode 100644 index 0000000..816cc16 --- /dev/null +++ b/lua/playertased.lua @@ -0,0 +1,101 @@ +function PlayerTased:enter(state_data, enter_data) + PlayerTased.super.enter(self, state_data, enter_data) + self:_start_action_tased(managers.player:player_timer():time(), state_data.non_lethal_electrocution) + + if state_data.non_lethal_electrocution then + state_data.non_lethal_electrocution = nil + local recover_time = Application:time() + + tweak_data.player.damage.TASED_TIME * managers.player:upgrade_value("player", "electrocution_resistance_multiplier", 1) * (state_data.electrocution_duration_multiplier or 1) + state_data.electrocution_duration_multiplier = nil + self._recover_delayed_clbk = "PlayerTased_recover_delayed_clbk" + + managers.enemy:add_delayed_clbk(self._recover_delayed_clbk, callback(self, self, "clbk_exit_to_std"), recover_time) + else + self._fatal_delayed_clbk = "PlayerTased_fatal_delayed_clbk" + local tased_time = tweak_data.player.damage.TASED_TIME + tased_time = managers.modifiers:modify_value("PlayerTased:TasedTime", tased_time) + + managers.enemy:add_delayed_clbk(self._fatal_delayed_clbk, callback(self, self, "clbk_exit_to_fatal"), TimerManager:game():time() + tased_time) + + if Network:is_server() then + self:_register_revive_SO() + end + end + + self._next_shock = 0.5 + self._taser_value = 1 + self._num_shocks = 0 + + managers.groupai:state():on_criminal_disabled(self._unit, "electrified") + --remove the on_reload call to get rid of autoreloading when you get tased + + local projectile_entry = managers.blackmarket:equipped_projectile() + + if tweak_data.blackmarket.projectiles[projectile_entry].is_a_grenade then + self:_interupt_action_throw_grenade() + else + self:_interupt_action_throw_projectile() + end + + self:_interupt_action_reload() + self:_interupt_action_steelsight() + self:_interupt_action_melee(managers.player:player_timer():time()) + self:_interupt_action_ladder(managers.player:player_timer():time()) + self:_interupt_action_charging_weapon(managers.player:player_timer():time()) + + self._rumble_electrified = managers.rumble:play("electrified") + self.tased = true + self._state_data = state_data + + CopDamage.register_listener("on_criminal_tased", { + "on_criminal_tased", + }, callback(self, self, "_on_tased_event")) +end + +function PlayerTased:_check_action_shock(t, input) + self._next_shock = self._next_shock or 0.5 + local difficulty_index = tweak_data:difficulty_to_index(Global.game_settings.difficulty) + local weaker_tase = managers.player:upgrade_value("player", "weaker_tase_effect", 0) + + if self._next_shock < t then + self._num_shocks = self._num_shocks or 0 + self._num_shocks = self._num_shocks + 1 + if difficulty_index == 6 then + self._next_shock = t + (0.15 + math.rand(0.375)) * (1 + weaker_tase) + else + self._next_shock = t + (0.25 + math.rand(0.75)) * (1 + weaker_tase) + end + self._unit:camera():play_shaker("player_taser_shock", 1, 10) + self._unit:camera():camera_unit():base():set_target_tilt((math.random(2) == 1 and -1 or 1) * math.random(15) * (1 - weaker_tase)) + + self._taser_value = self._taser_value or 1 + self._taser_value = math.max(self._taser_value - 0.25, 0) + + self._unit:sound():play("tasered_shock") + managers.rumble:play("electric_shock") + + if not alive(self._counter_taser_unit) then + self._camera_unit:base():start_shooting() + + self._recoil_t = t + 0.5 + + if not managers.player:has_category_upgrade("player", "resist_firing_tased") then + input.btn_primary_attack_state = true + input.btn_primary_attack_press = true + end + + self._camera_unit:base():recoil_kick(-5, 5, -5, 5) + self._unit:camera():play_redirect(self:get_animation("tased_boost")) + end + elseif self._recoil_t then + if not managers.player:has_category_upgrade("player", "resist_firing_tased") then + input.btn_primary_attack_state = true + end + + if self._recoil_t < t then + self._recoil_t = nil + + self._camera_unit:base():stop_shooting() + end + end +end diff --git a/lua/playertweakdata.lua b/lua/playertweakdata.lua index dcba895..4fe7546 100644 --- a/lua/playertweakdata.lua +++ b/lua/playertweakdata.lua @@ -1,27 +1,40 @@ -Hooks:PostHook(PlayerTweakData, "_set_overkill_290", "eclipse__set_overkill_290", function (self) - self.damage.MIN_DAMAGE_INTERVAL = 0.1 - self.damage.BLEED_OUT_HEALTH_INIT = 23 - self.damage.REVIVE_HEALTH_STEPS = {0.6} - self.damage.respawn_time_penalty = 0 - self.damage.DOWNED_TIME = 30 - self.damage.DOWNED_TIME_DEC = 0 - self.damage.DOWNED_TIME_MIN = 30 -end) +function PlayerTweakData:_set_normal() + self.damage.REVIVE_HEALTH_STEPS = { 0.7 } + self.damage.MIN_DAMAGE_INTERVAL = 0.4 +end -local _set_overkill_290_orig = PlayerTweakData._set_overkill_290 -function PlayerTweakData:_set_sm_wish() - _set_overkill_290_orig(self) - self.damage.MIN_DAMAGE_INTERVAL = 0.05 +function PlayerTweakData:_set_hard() + self.damage.REVIVE_HEALTH_STEPS = { 0.6 } + self.damage.MIN_DAMAGE_INTERVAL = 0.3 end +function PlayerTweakData:_set_overkill() + self.damage.REVIVE_HEALTH_STEPS = { 0.5 } + self.damage.MIN_DAMAGE_INTERVAL = 0.25 +end -Hooks:PostHook(PlayerTweakData, "init", "eclipse__init", function (self) - self.suppression.decay_start_delay = 0.35 +function PlayerTweakData:_set_overkill_145() + self.damage.REVIVE_HEALTH_STEPS = { 0.4 } + self.damage.MIN_DAMAGE_INTERVAL = 0.2 +end + +function PlayerTweakData:_set_easy_wish() + self.damage.REVIVE_HEALTH_STEPS = { 0.4 } + self.damage.MIN_DAMAGE_INTERVAL = 0.15 +end + +Hooks:PostHook(PlayerTweakData, "init", "eclipse__init", function(self) + self.damage.respawn_time_penalty = 0 + self.damage.BLEED_OUT_HEALTH_INIT = 23 self.omniscience.start_t = 3 self.omniscience.interval_t = 1.5 + self.omniscience.target_resense_t = 0 + self.damage.REGENERATE_TIME = 4.5 + self.damage.DOWNED_TIME = 30 + self.damage.DOWNED_TIME_DEC = 0 + self.damage.DOWNED_TIME_MIN = 30 + self.damage.automatic_respawn_time = nil end) -- Game too hard for single player appparently???? -function PlayerTweakData:_set_singleplayer() - self.damage.REGENERATE_TIME = 3 -end \ No newline at end of file +function PlayerTweakData:_set_singleplayer() end diff --git a/lua/preplanningtweakdata.lua b/lua/preplanningtweakdata.lua new file mode 100644 index 0000000..901aec6 --- /dev/null +++ b/lua/preplanningtweakdata.lua @@ -0,0 +1,7 @@ +Hooks:PostHook(PrePlanningTweakData, "init", "eclipse_init", function(self) + -- less trivial big bank preplan + self.types.vault_thermite.budget_cost = 6 + self.types.escape_c4_loud.budget_cost = 5 + self.types.escape_elevator_loud.budget_cost = 6 + self.types.escape_bus_loud.budget_cost = 10 +end) diff --git a/lua/projectilebase.lua b/lua/projectilebase.lua index 3ebfabf..dfe13b1 100644 --- a/lua/projectilebase.lua +++ b/lua/projectilebase.lua @@ -4,81 +4,81 @@ local mvec2 = Vector3() local mrot1 = Rotation() function ProjectileBase:update(unit, t, dt) - if not self._simulated and not self._collided then - self._unit:m_position(mvec1) - mvector3.set(mvec2, self._velocity * dt) - mvector3.add(mvec1, mvec2) - self._unit:set_position(mvec1) - - if self._orient_to_vel then - mrotation.set_look_at(mrot1, mvec2, math.UP) - self._unit:set_rotation(mrot1) - end - - self._velocity = Vector3(self._velocity.x, self._velocity.y, self._velocity.z - 980 * dt) - end - - if self._sweep_data and not self._collided then - self._unit:m_position(self._sweep_data.current_pos) - - local col_ray = nil - local __ignore_units = {} - - -- Cannot shoot yourself - if alive(self._thrower_unit) then - table.insert(__ignore_units, self._thrower_unit) - end - - -- Cannot shoot peers - local _peers = _peers or managers.network:session():peers() - for _, _peer in pairs(_peers) do - if alive(_peer:unit()) then - table.insert(__ignore_units, _peer:unit()) - end - end - - if #__ignore_units > 0 then - col_ray = World:raycast("ray", self._sweep_data.last_pos, self._sweep_data.current_pos, "slot_mask", self._sweep_data.slot_mask, "ignore_unit", __ignore_units) - else - col_ray = World:raycast("ray", self._sweep_data.last_pos, self._sweep_data.current_pos, "slot_mask", self._sweep_data.slot_mask) - end - - if self._draw_debug_trail then - Draw:brush(Color(1, 0, 0, 1), nil, 3):line(self._sweep_data.last_pos, self._sweep_data.current_pos) - end - - if col_ray and col_ray.unit then - mvector3.direction(mvec1, self._sweep_data.last_pos, self._sweep_data.current_pos) - mvector3.add(mvec1, col_ray.position) - self._unit:set_position(mvec1) - self._unit:set_position(mvec1) - - if self._draw_debug_impact then - Draw:brush(Color(0.5, 0, 0, 1), nil, 10):sphere(col_ray.position, 4) - Draw:brush(Color(0.5, 1, 0, 0), nil, 10):sphere(self._unit:position(), 3) - end - - col_ray.velocity = self._unit:velocity() - self._collided = true - - self:_on_collision(col_ray) - end - - self._unit:m_position(self._sweep_data.last_pos) - end + if not self._simulated and not self._collided then + self._unit:m_position(mvec1) + mvector3.set(mvec2, self._velocity * dt) + mvector3.add(mvec1, mvec2) + self._unit:set_position(mvec1) + + if self._orient_to_vel then + mrotation.set_look_at(mrot1, mvec2, math.UP) + self._unit:set_rotation(mrot1) + end + + self._velocity = Vector3(self._velocity.x, self._velocity.y, self._velocity.z - 980 * dt) + end + + if self._sweep_data and not self._collided then + self._unit:m_position(self._sweep_data.current_pos) + + local col_ray = nil + local __ignore_units = {} + + -- Cannot shoot yourself + if alive(self._thrower_unit) then + table.insert(__ignore_units, self._thrower_unit) + end + + -- Cannot shoot peers + local _peers = _peers or managers.network:session():peers() + for _, _peer in pairs(_peers) do + if alive(_peer:unit()) then + table.insert(__ignore_units, _peer:unit()) + end + end + + if #__ignore_units > 0 then + col_ray = World:raycast("ray", self._sweep_data.last_pos, self._sweep_data.current_pos, "slot_mask", self._sweep_data.slot_mask, "ignore_unit", __ignore_units) + else + col_ray = World:raycast("ray", self._sweep_data.last_pos, self._sweep_data.current_pos, "slot_mask", self._sweep_data.slot_mask) + end + + if self._draw_debug_trail then + Draw:brush(Color(1, 0, 0, 1), nil, 3):line(self._sweep_data.last_pos, self._sweep_data.current_pos) + end + + if col_ray and col_ray.unit then + mvector3.direction(mvec1, self._sweep_data.last_pos, self._sweep_data.current_pos) + mvector3.add(mvec1, col_ray.position) + self._unit:set_position(mvec1) + self._unit:set_position(mvec1) + + if self._draw_debug_impact then + Draw:brush(Color(0.5, 0, 0, 1), nil, 10):sphere(col_ray.position, 4) + Draw:brush(Color(0.5, 1, 0, 0), nil, 10):sphere(self._unit:position(), 3) + end + + col_ray.velocity = self._unit:velocity() + self._collided = true + + self:_on_collision(col_ray) + end + + self._unit:m_position(self._sweep_data.last_pos) + end end function ProjectileBase:create_sweep_data() - self._sweep_data = { - slot_mask = self._slot_mask - } - - if Global.game_settings and Global.game_settings.one_down then - self._sweep_data.slot_mask = self._sweep_data.slot_mask + 3 - else - self._sweep_data.slot_mask = managers.mutators:modify_value("ProjectileBase:create_sweep_data:slot_mask", self._sweep_data.slot_mask) - end - - self._sweep_data.current_pos = self._unit:position() - self._sweep_data.last_pos = mvector3.copy(self._sweep_data.current_pos) + self._sweep_data = { + slot_mask = self._slot_mask, + } + + if Global.game_settings and Global.game_settings.one_down then + self._sweep_data.slot_mask = self._sweep_data.slot_mask + 3 + else + self._sweep_data.slot_mask = managers.mutators:modify_value("ProjectileBase:create_sweep_data:slot_mask", self._sweep_data.slot_mask) + end + + self._sweep_data.current_pos = self._unit:position() + self._sweep_data.last_pos = mvector3.copy(self._sweep_data.current_pos) end diff --git a/lua/projectilestweakdata.lua b/lua/projectilestweakdata.lua index 7d33988..f1ccbc9 100644 --- a/lua/projectilestweakdata.lua +++ b/lua/projectilestweakdata.lua @@ -1,24 +1,24 @@ local initproj_orig = BlackMarketTweakData._init_projectiles function BlackMarketTweakData:_init_projectiles(tweak_data) - initproj_orig(self, tweak_data) + initproj_orig(self, tweak_data) - -- 45s injector cooldown - self.projectiles.chico_injector.base_cooldown = 45 - -- 16s flask cooldown - self.projectiles.damage_control.base_cooldown = 16 + -- 45s injector cooldown + self.projectiles.chico_injector.base_cooldown = 45 + -- 16s flask cooldown + self.projectiles.damage_control.base_cooldown = 16 - -- remove retarded anticheat - self.projectiles.rocket_ray_frag.time_cheat = nil - self.projectiles.launcher_frag_m32.time_cheat = nil + -- remove retarded anticheat + self.projectiles.rocket_ray_frag.time_cheat = nil + self.projectiles.launcher_frag_m32.time_cheat = nil - -- grenade amounts - self.projectiles.frag.max_amount = 5 - self.projectiles.frag_com.max_amount = 5 - self.projectiles.dada_com.max_amount = 5 - self.projectiles.dynamite.max_amount = 5 - self.projectiles.concussion.max_amount = 5 - self.projectiles.fir_com.max_amount = 5 - self.projectiles.molotov.max_amount = 5 - self.projectiles.poison_gas_grenade.max_amount = 5 - self.projectiles.wpn_gre_electric.max_amount = 5 + -- grenade amounts + self.projectiles.frag.max_amount = 3 + self.projectiles.frag_com.max_amount = 3 + self.projectiles.dada_com.max_amount = 3 + self.projectiles.dynamite.max_amount = 3 + self.projectiles.concussion.max_amount = 3 + self.projectiles.fir_com.max_amount = 3 + self.projectiles.molotov.max_amount = 3 + self.projectiles.poison_gas_grenade.max_amount = 3 + self.projectiles.wpn_gre_electric.max_amount = 3 end diff --git a/lua/quickcsgrenade.lua b/lua/quickcsgrenade.lua index c1c284d..7519961 100644 --- a/lua/quickcsgrenade.lua +++ b/lua/quickcsgrenade.lua @@ -25,12 +25,12 @@ function QuickCsGrenade:_play_sound_and_effects(...) World:effect_manager():spawn({ effect = Idstring("effects/particles/explosions/explosion_smoke_grenade"), position = self._unit:position(), - normal = self._unit:rotation():y() + normal = self._unit:rotation():y(), }) self._smoke_effect = World:effect_manager():spawn({ effect = Idstring("effects/particles/explosions/cs_grenade_smoke"), - parent = self._unit:orientation_object() + parent = self._unit:orientation_object(), }) managers.environment_controller:set_blurzone(self._unit:key(), 1, self._unit:position(), self._radius * self._radius_blurzone_multiplier, 0, true) @@ -39,4 +39,4 @@ function QuickCsGrenade:_play_sound_and_effects(...) self._unit:sound_source():post_event("grenade_gas_explode") end -end \ No newline at end of file +end diff --git a/lua/quickflashgrenade.lua b/lua/quickflashgrenade.lua index f657f62..a3d7489 100644 --- a/lua/quickflashgrenade.lua +++ b/lua/quickflashgrenade.lua @@ -1,6 +1,6 @@ -- Allow flashes through corpses and debris to make them more consistent -- Also removes inconsistent flashes through random light bounces -Hooks:PostHook(QuickFlashGrenade, "init", "sh_init", function (self) +Hooks:PostHook(QuickFlashGrenade, "init", "sh_init", function(self) self._slotmask = managers.slot:get_mask("bullet_impact_targets") - World:make_slot_mask(17) end) diff --git a/lua/quicksmokegreande.lua b/lua/quicksmokegrenade.lua similarity index 94% rename from lua/quicksmokegreande.lua rename to lua/quicksmokegrenade.lua index f8af51a..0507743 100644 --- a/lua/quicksmokegreande.lua +++ b/lua/quicksmokegrenade.lua @@ -25,16 +25,16 @@ function QuickSmokeGrenade:_play_sound_and_effects(...) World:effect_manager():spawn({ effect = Idstring("effects/particles/explosions/explosion_smoke_grenade"), position = self._unit:position(), - normal = self._unit:rotation():y() + normal = self._unit:rotation():y(), }) self._smoke_effect = World:effect_manager():spawn({ effect = Idstring("effects/particles/explosions/smoke_grenade_smoke"), - parent = self._unit:orientation_object() + parent = self._unit:orientation_object(), }) body:push_at(body:mass(), math.UP * 100, self._unit:position() + Vector3(math.rand(-10, 10), math.rand(-10, 10), math.rand(-10, 10))) self._unit:sound_source():post_event("grenade_gas_explode") end -end \ No newline at end of file +end diff --git a/lua/raycastweaponbase.lua b/lua/raycastweaponbase.lua index 9b6dcac..7a3d577 100644 --- a/lua/raycastweaponbase.lua +++ b/lua/raycastweaponbase.lua @@ -1,14 +1,18 @@ --- Friendly Fire local init_original = RaycastWeaponBase.init function RaycastWeaponBase:init(...) init_original(self, ...) + -- Shock and Awe shotgun interaction restoration + if table.contains(tweak_data.weapon[self._name_id].categories, "shotgun") then + self.SHIELD_KNOCK_BACK_CHANCE = tweak_data.upgrades.values.player.shield_knock_bullet.chance / tweak_data.weapon[self._name_id].rays + end + + -- Friendly Fire if Global.game_settings and Global.game_settings.one_down then self._bullet_slotmask = self._bullet_slotmask + 3 else self._bullet_slotmask = managers.mutators:modify_value("RaycastWeaponBase:setup:weapon_slot_mask", self._bullet_slotmask) end - end -- Fix inverted suppression - in vanilla, the closer your shots are to an enemy, the less they suppress them @@ -25,3 +29,281 @@ function RaycastWeaponBase:check_autoaim(...) return closest_ray, suppression_enemies end +-- No aim assist (shc) +Hooks:PostHook(RaycastWeaponBase, "init", "eclipse_init", function(self) + if self._autohit_data then + self._autohit_current = 0 + self._autohit_data.INIT_RATIO = 0 + self._autohit_data.MIN_RATIO = 0 + self._autohit_data.MAX_RATIO = 0 + end +end) + +local mvec_to = Vector3() +local mvec_right_ax = Vector3() +local mvec_up_ay = Vector3() +local mvec_spread_direction = Vector3() + +-- lower damage on shield pen +function RaycastWeaponBase:_fire_raycast(user_unit, from_pos, direction, dmg_mul, shoot_player, spread_mul, autohit_mul, suppr_mul) + if self:gadget_overrides_weapon_functions() then + return self:gadget_function_override("_fire_raycast", self, user_unit, from_pos, direction, dmg_mul, shoot_player, spread_mul, autohit_mul, suppr_mul) + end + + local result = {} + local ray_distance = self:weapon_range() + local spread_x, spread_y = self:_get_spread(user_unit) + spread_y = spread_y or spread_x + spread_mul = spread_mul or 1 + + mvector3.cross(mvec_right_ax, direction, math.UP) + mvector3.normalize(mvec_right_ax) + mvector3.cross(mvec_up_ay, direction, mvec_right_ax) + mvector3.normalize(mvec_up_ay) + mvector3.set(mvec_spread_direction, direction) + + local theta = math.random() * 360 + + mvector3.multiply(mvec_right_ax, math.rad(math.sin(theta) * math.random() * spread_x * spread_mul)) + mvector3.multiply(mvec_up_ay, math.rad(math.cos(theta) * math.random() * spread_y * spread_mul)) + mvector3.add(mvec_spread_direction, mvec_right_ax) + mvector3.add(mvec_spread_direction, mvec_up_ay) + mvector3.set(mvec_to, mvec_spread_direction) + mvector3.multiply(mvec_to, ray_distance) + mvector3.add(mvec_to, from_pos) + + local ray_hits, hit_enemy = self:_collect_hits(from_pos, mvec_to) + local auto_hit_candidate, suppression_enemies = self:check_autoaim(from_pos, direction) + + if suppression_enemies and self._suppression then + result.enemies_in_cone = suppression_enemies + end + + if self._autoaim then + local weight = 0.1 + + if auto_hit_candidate and not hit_enemy then + local autohit_chance = 1 - math.clamp((self._autohit_current - self._autohit_data.MIN_RATIO) / (self._autohit_data.MAX_RATIO - self._autohit_data.MIN_RATIO), 0, 1) + + if autohit_mul then + autohit_chance = autohit_chance * autohit_mul + end + + if math.random() < autohit_chance then + self._autohit_current = (self._autohit_current + weight) / (1 + weight) + + mvector3.set(mvec_spread_direction, auto_hit_candidate.ray) + mvector3.set(mvec_to, mvec_spread_direction) + mvector3.multiply(mvec_to, ray_distance) + mvector3.add(mvec_to, from_pos) + + ray_hits, hit_enemy = self:_collect_hits(from_pos, mvec_to) + end + end + + if hit_enemy then + self._autohit_current = (self._autohit_current + weight) / (1 + weight) + elseif auto_hit_candidate then + self._autohit_current = self._autohit_current / (1 + weight) + end + end + + local hit_count = 0 + local hit_anyone = false + local cop_kill_count = 0 + local hit_through_wall = false + local hit_through_shield = false + local is_civ_f = CopDamage.is_civilian + local damage = self:_get_current_damage(dmg_mul) + + for _, hit in ipairs(ray_hits) do + local dmg = self:get_damage_falloff(damage, hit, user_unit) + + -- penetrating a surface reduces the damage you deal to an enemy + if hit.unit:in_slot(managers.slot:get_mask("world_geometry")) then + hit_through_wall = true + damage = damage * 0.5 + elseif hit.unit:in_slot(managers.slot:get_mask("enemy_shield_check")) then + hit_through_shield = hit_through_shield or alive(hit.unit:parent()) + damage = damage * 0.4 + end + + if dmg > 0 then + local hit_result = self._bullet_class:on_collision(hit, self._unit, user_unit, dmg) + + if hit_result then + hit.damage_result = hit_result + hit_anyone = true + hit_count = hit_count + 1 + + if hit_result.type == "death" then + local unit_type = hit.unit:base() and hit.unit:base()._tweak_table + local is_civilian = unit_type and is_civ_f(unit_type) + + if not is_civilian then + cop_kill_count = cop_kill_count + 1 + end + + hit_through_wall = hit_through_wall or hit.unit:in_slot(self.wall_mask) + hit_through_shield = hit_through_shield or hit.unit:in_slot(self.shield_mask) and alive(hit.unit:parent()) + + self:_check_kill_achievements(cop_kill_count, unit_type, is_civilian, hit_through_wall, hit_through_shield) + end + end + end + end + + self:_check_tango_achievements(cop_kill_count) + + result.hit_enemy = hit_anyone + + if self._autoaim then + self._shot_fired_stats_table.hit = hit_anyone + self._shot_fired_stats_table.hit_count = hit_count + + if (not self._ammo_data or not self._ammo_data.ignore_statistic) and not self._rays then + managers.statistics:shot_fired(self._shot_fired_stats_table) + end + end + + local furthest_hit = ray_hits[#ray_hits] + + if (not furthest_hit or furthest_hit.distance > 600) and alive(self._obj_fire) then + self._obj_fire:m_position(self._trail_effect_table.position) + mvector3.set(self._trail_effect_table.normal, mvec_spread_direction) + + local trail = World:effect_manager():spawn(self._trail_effect_table) + + if furthest_hit then + World:effect_manager():set_remaining_lifetime(trail, math.clamp((furthest_hit.distance - 600) / 10000, 0, furthest_hit.distance)) + end + end + + if self._alert_events then + result.rays = ray_hits + end + + return result +end + +-- no elite shield pen +function RaycastWeaponBase.collect_hits(from, to, setup_data) + setup_data = setup_data or {} + local ray_hits = nil + local hit_enemy = false + local ignore_unit = setup_data.ignore_units or {} + local enemy_mask = setup_data.enemy_mask + local bullet_slotmask = setup_data.bullet_slotmask or managers.slot:get_mask("bullet_impact_targets") + + if setup_data.stop_on_impact then + ray_hits = {} + local hit = World:raycast("ray", from, to, "slot_mask", bullet_slotmask, "ignore_unit", ignore_unit) + + if hit then + table.insert(ray_hits, hit) + + hit_enemy = hit.unit:in_slot(enemy_mask) + end + + return ray_hits, hit_enemy + end + + local can_shoot_through_wall = setup_data.can_shoot_through_wall + local can_shoot_through_shield = setup_data.can_shoot_through_shield + local can_shoot_through_enemy = setup_data.can_shoot_through_enemy + local wall_mask = setup_data.wall_mask + local shield_mask = setup_data.shield_mask + local ai_vision_ids = Idstring("ai_vision") + local bulletproof_ids = Idstring("bulletproof") + + if can_shoot_through_wall then + ray_hits = World:raycast_wall("ray", from, to, "slot_mask", bullet_slotmask, "ignore_unit", ignore_unit, "thickness", 40, "thickness_mask", wall_mask) + else + ray_hits = World:raycast_all("ray", from, to, "slot_mask", bullet_slotmask, "ignore_unit", ignore_unit) + end + + local units_hit = {} + local unique_hits = {} + + for i, hit in ipairs(ray_hits) do + if not units_hit[hit.unit:key()] then + units_hit[hit.unit:key()] = true + unique_hits[#unique_hits + 1] = hit + hit.hit_position = hit.position + hit_enemy = hit_enemy or hit.unit:in_slot(enemy_mask) + local weak_body = hit.body:has_ray_type(ai_vision_ids) + weak_body = weak_body or hit.body:has_ray_type(bulletproof_ids) + + if not can_shoot_through_enemy and hit_enemy then + break + elseif not can_shoot_through_wall and hit.unit:in_slot(wall_mask) and weak_body then + break + elseif not can_shoot_through_shield and hit.unit:in_slot(shield_mask) then + break + elseif hit.unit:in_slot(shield_mask) and (hit.unit:name():key() == "af254947f0288a6c" or hit.unit:name():key() == "15cbabccf0841ff8") and not can_shoot_through_titan_shield then -- hi thanks resmod if you're reading this :) + break + end + end + end + + return unique_hits, hit_enemy +end + +-- Auto Fire Sound Fix +-- Thanks offyerrocker + +_G.AutoFireSoundFixBlacklist = { + ["saw"] = true, + ["saw_secondary"] = true, + ["flamethrower_mk2"] = true, + ["m134"] = true, + ["mg42"] = true, + ["shuno"] = true, + ["system"] = true, + ["par"] = true, +} + +Hooks:Register("AFSF2_OnWriteBlacklist") +Hooks:Add("BaseNetworkSessionOnLoadComplete", "AFSF2_OnLoadComplete", function() + Hooks:Call("AFSF2_OnWriteBlacklist", AutoFireSoundFixBlacklist) +end) + +--Check for if AFSF's fix code should apply to this particular weapon +function RaycastWeaponBase:_soundfix_should_play_normal() + local name_id = self:get_name_id() or "xX69dank420blazermachineXx" + if not self._setup.user_unit == managers.player:player_unit() then + return true + elseif tweak_data.weapon[name_id].use_fix ~= nil then + return tweak_data.weapon[name_id].use_fix + elseif AutoFireSoundFixBlacklist[name_id] then + return true + elseif not self:weapon_tweak_data().sounds.fire_single then + return true + end + return false +end + +--Prevent playing sounds except for blacklisted weapons +local orig_fire_sound = RaycastWeaponBase._fire_sound +function RaycastWeaponBase:_fire_sound(...) + if self:_soundfix_should_play_normal() then + return orig_fire_sound(self, ...) + end +end + +--Play sounds here instead for fix-applicable weapons; or else if blacklisted, use original function and don't play the fixed single-fire sound +--U200: there goes AFSF2's compatibility with other mods +Hooks:PreHook(RaycastWeaponBase, "fire", "autofiresoundfix2_raycastweaponbase_fire", function(self, ...) + if not self:_soundfix_should_play_normal() then + self._bullets_fired = 0 + self:play_tweak_data_sound(self:weapon_tweak_data().sounds.fire_single, "fire_single") + end +end) + +--stop_shooting is only used for fire sound loops, so playing individual single-fire sounds means it doesn't need to be called +local orig_stop_shooting = RaycastWeaponBase.stop_shooting +function RaycastWeaponBase:stop_shooting(...) + if self:_soundfix_should_play_normal() then + return orig_stop_shooting(self, ...) + end +end diff --git a/lua/savefilemanager.lua b/lua/savefilemanager.lua index 5ccefd5..74ffcb1 100644 --- a/lua/savefilemanager.lua +++ b/lua/savefilemanager.lua @@ -1,2 +1,2 @@ SavefileManager.PROGRESS_SLOT = 021 -SavefileManager.BACKUP_SLOT = 021 \ No newline at end of file +SavefileManager.BACKUP_SLOT = 021 diff --git a/lua/sentrygunbase.lua b/lua/sentrygunbase.lua index e02e6f1..35842d7 100644 --- a/lua/sentrygunbase.lua +++ b/lua/sentrygunbase.lua @@ -1,25 +1,42 @@ -SentryGunBase.DEPLOYEMENT_COST = {0.5, 0.75, 0.75} -SentryGunBase.AMMO_MUL = {2, 3} +SentryGunBase.DEPLOYEMENT_COST = { 0.65, 0.9, 0.9 } +SentryGunBase.AMMO_MUL = { 2, 3 } -- Unregister sentry guns to prevent enemies from getting stuck/cheesed -- Enemies will still shoot sentries but they won't actively path towards them Hooks:PostHook(SentryGunBase, "setup", "sh_setup", SentryGunBase.unregister) -- Create table for sixth sense timing data -Hooks:PostHook(SentryGunBase, "init", "eclipse_init", -function(self, unit) - self._state_data = self._state_data or {} +Hooks:PostHook(SentryGunBase, "init", "eclipse_init", function(self, unit) + self._state_data = self._state_data or {} end) --- Check for sixth sense every tick -Hooks:PostHook(SentryGunBase, "update", "eclipse_update", -function(self, unit, t, dt) - self:_update_omniscience(t, dt) +-- Check if sentries can mark +function SentryGunBase:has_marking() + return self._sentry_marking or false +end + +-- Workaround to the normal sentry unregistration +Hooks:PostHook(SentryGunBase, "on_death", "eclipse_sentry_on_death", function(self) + managers.groupai:state():unregister_marking_sentry(self._unit) +end) + +Hooks:PostHook(SentryGunBase, "pre_destroy", "eclipse_sentry_pre_destroy", function(self) + managers.groupai:state():unregister_marking_sentry(self._unit) +end) + +-- Register the sentry as marking to the group ai state +Hooks:PostHook(SentryGunBase, "setup", "eclipse_sentry_setup", function(self, owner) + local sentry_owner = nil + if owner and owner:base().upgrade_value then + sentry_owner = owner + end + self._sentry_marking = PlayerSkill.has_skill("sentry_gun", "standstill_omniscience", sentry_owner) + managers.groupai:state():register_marking_sentry(self._unit) end) -- Create new sixth sense function function SentryGunBase:_update_omniscience(t, dt) - if not managers.player:has_category_upgrade("player", "standstill_omniscience") or not tweak_data.player.omniscience then + if not self:has_marking() then if self._state_data.omniscience_t then self._state_data.omniscience_t = nil end @@ -34,15 +51,15 @@ function SentryGunBase:_update_omniscience(t, dt) for _, unit in ipairs(sensed_targets) do if alive(unit) and not unit:base():char_tweak().is_escort and not unit:base():has_tag("spooc") then - self._state_data.omniscience_units_detected = self._state_data.omniscience_units_detected or {} + self._state_data.omniscience_units_detected = self._state_data.omniscience_units_detected or {} - if not self._state_data.omniscience_units_detected[unit:key()] or self._state_data.omniscience_units_detected[unit:key()] <= t then - self._state_data.omniscience_units_detected[unit:key()] = t + tweak_data.player.omniscience.target_resense_t + if not self._state_data.omniscience_units_detected[unit:key()] or self._state_data.omniscience_units_detected[unit:key()] <= t then + self._state_data.omniscience_units_detected[unit:key()] = t + tweak_data.player.omniscience.target_resense_t - managers.game_play_central:auto_highlight_enemy(unit, true) + managers.game_play_central:auto_highlight_enemy(unit, true) - break - end + break + end end end self._state_data.omniscience_t = t + tweak_data.player.omniscience.interval_t diff --git a/lua/sentrygunweapon.lua b/lua/sentrygunweapon.lua index 47c0a03..c2ba757 100644 --- a/lua/sentrygunweapon.lua +++ b/lua/sentrygunweapon.lua @@ -1,2 +1,2 @@ SentryGunWeapon = SentryGunWeapon or class() -SentryGunWeapon._AP_ROUNDS_DAMAGE_MULTIPLIER = 1.75 \ No newline at end of file +SentryGunWeapon._AP_ROUNDS_DAMAGE_MULTIPLIER = 1.75 diff --git a/lua/shotgunbase.lua b/lua/shotgunbase.lua index 182b4d5..6052107 100644 --- a/lua/shotgunbase.lua +++ b/lua/shotgunbase.lua @@ -1,9 +1,8 @@ -function ShotgunBase:get_damage_falloff(damage, col_ray, user_unit) - local distance = col_ray.distance or mvector3.distance(col_ray.unit:position(), user_unit:position()) - local inc_range_mul = 1 - local current_state = user_unit:movement()._current_state - if current_state and current_state:in_steelsight() then - inc_range_mul = managers.player:upgrade_value("shotgun", "steelsight_range_inc", 1) +Hooks:PostHook(ShotgunBase, "_update_stats_values", "eclipse__update_stats_values", function(self) + local extra_pellets = managers.player:upgrade_value("shotgun", "extra_pellets", 0) + if self._ammo_data then + if self._ammo_data.rays ~= 1 then + self._rays = self._rays + extra_pellets + end end - return (1 - math.min(1, math.max(0, distance - self._damage_near * inc_range_mul) / (self._damage_far * inc_range_mul))) * damage -end \ No newline at end of file +end) diff --git a/lua/sidejobgenericdlcmanager.lua b/lua/sidejobgenericdlcmanager.lua index 76cc8f4..6e6ea12 100644 --- a/lua/sidejobgenericdlcmanager.lua +++ b/lua/sidejobgenericdlcmanager.lua @@ -1,11 +1,11 @@ -- unlock ww2 weapons (hydrogen) local original_function = original_function or SideJobGenericDLCManager.load - function SideJobGenericDLCManager:load(cache, version) - local state = cache[self.save_table_name] - if state and state.version == self.save_version then - for _, saved_challenge in ipairs(state.challenges or {}) do - saved_challenge.completed = true - end - end - return original_function(self, cache, version) -end \ No newline at end of file +function SideJobGenericDLCManager:load(cache, version) + local state = cache[self.save_table_name] + if state and state.version == self.save_version then + for _, saved_challenge in ipairs(state.challenges or {}) do + saved_challenge.completed = true + end + end + return original_function(self, cache, version) +end diff --git a/lua/skilltreetweakdata.lua b/lua/skilltreetweakdata.lua index b705e51..e586b1b 100644 --- a/lua/skilltreetweakdata.lua +++ b/lua/skilltreetweakdata.lua @@ -1,220 +1,347 @@ local data = SkillTreeTweakData.init function SkillTreeTweakData:init(tweak_data) - data(self, tweak_data) + data(self, tweak_data) + + local function digest(value) + return Application:digest_value(value, true) + end + + -- reduce t4 cost + self.tier_unlocks = { + digest(0), + digest(1), + digest(3), + digest(16), + } + + -- MASTERMIND -- + + -- Bandage + self.skills.combat_medic[1].upgrades = { "player_revive_damage_reduction_level_1" } + self.skills.combat_medic.name_id = "menu_bandage" + self.skills.combat_medic.desc_id = "menu_bandage_desc" + + -- Painkillers + self.skills.tea_time[1].upgrades = { "player_revive_damage_reduction_level_2" } + self.skills.tea_time[2].upgrades = { "player_revived_damage_resist_1" } + self.skills.tea_time.name_id = "menu_fast_learner_beta" + self.skills.tea_time.desc_id = "menu_field_surgery_desc" + self.skills.tea_time.icon_xy = { 0, 10 } + + -- Company Soul + self.skills.fast_learner[1].upgrades = { "team_weapon_recoil_index_addend", "team_weapon_suppression_recoil_index_addend" } + self.skills.fast_learner[2].upgrades = { "team_stamina_multiplier" } + self.skills.fast_learner.name_id = "menu_company_soul" + self.skills.fast_learner.desc_id = "menu_company_soul_desc" + self.skills.fast_learner.icon_xy = { 5, 2 } + + -- Combat Doctor + self.skills.tea_cookies[1].upgrades = { "temporary_revive_damage_reduction_1", "player_revive_damage_reduction_1" } + self.skills.tea_cookies[2].upgrades = { "player_revive_interaction_speed_multiplier", "temporary_combat_medic_damage_multiplier1" } + self.skills.tea_cookies.name_id = "menu_medic_2x_beta" + self.skills.tea_cookies.desc_id = "menu_combat_doctor_desc" + self.skills.tea_cookies.icon_xy = { 4, 9 } + + -- Keepers + self.skills.medic_2x.name_id = "menu_keepers" - -- Overkill - self.skills.overkill[1].upgrades = {"player_overkill_damage_multiplier"} - self.skills.overkill[2].upgrades = {"player_overkill_damage_multiplier_2", "player_overkill_all_weapons", "weapon_swap_speed_multiplier"} - - -- Mag Plus - self.skills.fast_fire[1].upgrades = {"player_automatic_mag_increase_1"} - self.skills.fast_fire[2].upgrades = {"player_automatic_mag_increase_2"} - self.skills.fast_fire.icon_xy = {2, 0} - - -- Fire Control - self.skills.fire_control[2].upgrades = {"player_ap_bullets_1"} - - -- Oppressor - self.skills.heavy_impact[1].upgrades = {"player_suppression_bonus"} - self.skills.heavy_impact[2].upgrades = {"player_suppression_bonus_2"} - self.skills.heavy_impact.name_id = "menu_oppressor" - self.skills.heavy_impact.desc_id = "menu_oppressor_desc" - self.skills.heavy_impact.icon_xy = {7, 0} - - -- Body Expertise - self.skills.body_expertise[1].upgrades = {"player_no_movement_penalty"} - self.skills.body_expertise[2].upgrades = {"weapon_automatic_head_shot_add_1"} - - -- Bulletstorm - self.skills.bandoliers[1].upgrades = {"temporary_no_ammo_cost_1"} - self.skills.bandoliers[2].upgrades = {"temporary_no_ammo_cost_2"} - self.skills.bandoliers.name_id = "menu_ammo_reservoir_beta" - self.skills.bandoliers.desc_id = "menu_ammo_reservoir_beta_desc" - self.skills.bandoliers.icon_xy = {4, 5} - - -- Fully Loaded - self.skills.carbon_blade[1].upgrades = {"player_regain_throwable_from_ammo_1"} - self.skills.carbon_blade[2].upgrades = {"extra_ammo_multiplier1", "player_pick_up_ammo_multiplier", "player_pick_up_ammo_multiplier_2"} - self.skills.carbon_blade.name_id = "menu_bandoliers_beta" - self.skills.carbon_blade.desc_id = "menu_bandoliers_beta_desc" - self.skills.carbon_blade.icon_xy = {3, 0} - - -- Saw Massacre - self.skills.ammo_reservoir[1].upgrades = {"saw_enemy_slicer"} - self.skills.ammo_reservoir[2].upgrades = {"saw_ignore_shields_1", "saw_panic_when_kill_1"} - self.skills.ammo_reservoir.name_id = "menu_carbon_blade_beta" - self.skills.ammo_reservoir.desc_id = "menu_carbon_blade_beta_desc" - self.skills.ammo_reservoir.icon_xy = {0, 2} + -- Inspire + self.skills.inspire[1].upgrades = { "player_morale_boost" } + self.skills.inspire[2].upgrades = { "cooldown_long_dis_revive" } + self.skills.inspire.icon_xy = { 11, 5 } - -- Marksman - self.skills.sharpshooter[1].upgrades = {"weapon_single_spread_index_addend"} - self.skills.sharpshooter[2].upgrades = {"single_shot_accuracy_inc_1"} + -- Forced Friendship + self.skills.triathlete[1].upgrades = { "cable_tie_quantity" } + self.skills.triathlete[2].upgrades = { "cable_tie_interact_speed_multiplier" } - -- Confident - self.skills.cable_guy[1].upgrades = {"player_intimidate_range_mul", "player_intimidate_aura", "player_intimidation_multiplier"} - self.skills.cable_guy[2].upgrades = {"team_damage_hostage_absorption"} + -- Stockholm Syndrome + self.skills.cable_guy[1].upgrades = { "player_intimidate_range_mul", "player_intimidate_aura", "player_intimidation_multiplier" } + self.skills.cable_guy[2].upgrades = { "player_civ_intimidation_mul", "player_civ_calming_alerts", "player_civilian_reviver" } + self.skills.cable_guy.icon_xy = { 3, 8 } -- Hostage Situation - table.insert(self.skills.stockholm_syndrome[1].upgrades, "player_civ_intimidation_mul") - self.skills.stockholm_syndrome[2].upgrades = {"team_hostage_situation"} - self.skills.stockholm_syndrome.icon_xy = {6, 7} + self.skills.stockholm_syndrome[1].upgrades = { "team_damage_hostage_absorption" } + self.skills.stockholm_syndrome[2].upgrades = { "team_hostage_situation" } + self.skills.stockholm_syndrome.icon_xy = { 6, 7 } - -- Forced Friendship - self.skills.triathlete[1].upgrades = {"cable_tie_quantity"} - self.skills.triathlete[2].upgrades = {"cable_tie_interact_speed_multiplier"} + -- Partners in Crime + self.skills.control_freak[1].upgrades = { "player_passive_convert_enemies_health_multiplier_1" } + self.skills.control_freak[2].upgrades = { "player_passive_convert_enemies_health_multiplier_2" } -- Hostage Taker - self.skills.black_marketeer[1].upgrades = {"player_hostage_health_regen_addend_1", "player_hostage_min_sum_taker_1"} - self.skills.black_marketeer[2].upgrades = {"player_hostage_health_regen_addend_2", "player_hostage_min_sum_taker_2", "player_joker_counts_for_hostage_boost"} - - -- Berserker - self.skills.wolverine[1].upgrades = {"player_melee_damage_health_ratio_multiplier"} - self.skills.wolverine[2].upgrades = {"player_movement_speed_damage_health_ratio_multiplier", "player_movement_speed_damage_health_ratio_threshold_multiplier"} - - -- Nine Lives (Tough Guy) - self.skills.nine_lives[1].upgrades = {"player_bleed_out_health_multiplier"} - self.skills.nine_lives[2].upgrades = {"player_primary_weapon_when_downed"} - - -- Iron Man - self.skills.juggernaut[1].upgrades = {"body_armor6"} - self.skills.juggernaut[2].upgrades = {"player_armor_multiplier"} + self.skills.black_marketeer[1].upgrades = { "player_hostage_health_regen_addend_1", "player_hostage_min_sum_taker_1" } + self.skills.black_marketeer[2].upgrades = { "player_hostage_health_regen_addend_2", "player_hostage_min_sum_taker_2", "player_joker_counts_for_hostage_boost" } - -- Inspire - self.skills.inspire[1].upgrades = {"player_morale_boost"} - self.skills.inspire[2].upgrades = {"player_revive_interaction_speed_multiplier"} + -- Marksman + self.skills.sharpshooter[1].upgrades = { "weapon_single_spread_index_addend" } + self.skills.sharpshooter[2].upgrades = { "single_shot_accuracy_inc_1" } -- Lock N' Load - self.skills.rifleman[1].upgrades = {"weapon_generic_swap_speed_multiplier", "weapon_enter_steelsight_speed_multiplier"} - self.skills.rifleman[2].upgrades = {"player_run_and_shoot_1"} - self.skills.rifleman.icon_xy = {7, 10} + self.skills.rifleman[1].upgrades = { "weapon_swap_speed_multiplier", "weapon_enter_steelsight_speed_multiplier" } + self.skills.rifleman[2].upgrades = { "player_run_and_shoot_1" } + self.skills.rifleman.icon_xy = { 7, 10 } self.skills.rifleman.name_id = "menu_rifleman" self.skills.rifleman.desc_id = "menu_rifleman_desc" -- Kilmer table.delete(self.skills.speedy_reload[1].upgrades, "smg_reload_speed_multiplier") - self.skills.speedy_reload.icon_xy = {1, 9} self.skills.speedy_reload.name_id = "menu_kilmer" + self.skills.speedy_reload.icon_xy = { 1, 9 } + + -- ENFORCER -- + + -- Hard Boiled + self.skills.underdog[1].upgrades = { "shotgun_damage_multiplier_1" } + self.skills.underdog[2].upgrades = { "shotgun_swap_speed_multiplier" } + self.skills.underdog.name_id = "menu_from_the_hip" + self.skills.underdog.desc_id = "menu_from_the_hip_desc" + self.skills.underdog.icon_xy = { 5, 0 } + + -- Fast Hands + self.skills.shotgun_cqb[1].upgrades = { "shotgun_pump_reload_speed_1" } + self.skills.shotgun_cqb[2].upgrades = { "shotgun_pump_reload_speed_2", "enter_steelsight_speed_multiplier" } + self.skills.shotgun_cqb.icon_xy = { 5, 1 } + + -- Point Blank + self.skills.shotgun_impact[1].upgrades = { "shotgun_extra_pellets" } + self.skills.shotgun_impact[2].upgrades = { "shotgun_hip_fire_spread_multiplier" } + + -- Shotgun CQB + self.skills.far_away[1].upgrades = { "shotgun_hip_rate_of_fire_1" } + self.skills.far_away[2].upgrades = { "shotgun_speed_stack_on_kill" } + self.skills.far_away.icon_xy = { 8, 6 } + + -- Mag-Fed Specialist + self.skills.close_by[1].upgrades = { "shotgun_mag_reload_speed" } + self.skills.close_by[2].upgrades = { "shotgun_magazine_capacity_inc_1" } + self.skills.close_by.icon_xy = { 8, 7 } + + -- Shotgun Hell + self.skills.overkill[1].upgrades = { "shotgun_consume_no_ammo_chance_1" } + self.skills.overkill[2].upgrades = { "cooldown_shotgun_panic_on_kill" } -- Resilience - self.skills.oppressor.icon_xy = {6, 1} + self.skills.oppressor.icon_xy = { 6, 1 } - -- Tough Guy - self.skills.show_of_force[1].upgrades = {"player_damage_shake_addend"} - self.skills.show_of_force.icon_xy = {2, 12} + -- Thick skin + self.skills.show_of_force[1].upgrades = { "player_damage_shake_addend" } + self.skills.show_of_force.icon_xy = { 2, 12 } + + -- Underdog + self.skills.pack_mule[1].upgrades = { "player_damage_multiplier_outnumbered" } + self.skills.pack_mule[2].upgrades = { "player_damage_dampener_outnumbered" } + self.skills.pack_mule.icon_xy = { 2, 1 } + self.skills.pack_mule.name_id = "menu_underdog_beta" + self.skills.pack_mule.desc_id = "menu_underdog_beta_desc" + + -- Iron Man + self.skills.juggernaut[1].upgrades = { "body_armor6" } + self.skills.juggernaut[2].upgrades = { "player_armor_multiplier" } + + -- Fully Loaded + self.skills.ammo_reservoir[1].upgrades = { "player_add_armor_stat_skill_ammo_mul" } + self.skills.ammo_reservoir[2].upgrades = { "extra_ammo_multiplier1" } + self.skills.ammo_reservoir.name_id = "menu_bandoliers_beta" + self.skills.ammo_reservoir.desc_id = "menu_bandoliers_beta_desc" + self.skills.ammo_reservoir.icon_xy = { 3, 0 } + + -- Bulletstorm + self.skills.bandoliers[1].upgrades = { "temporary_no_ammo_cost_1" } + self.skills.bandoliers[2].upgrades = { "temporary_no_ammo_cost_2" } + self.skills.bandoliers.name_id = "menu_ammo_reservoir_beta" + self.skills.bandoliers.desc_id = "menu_ammo_reservoir_beta_desc" + self.skills.bandoliers.icon_xy = { 4, 5 } + + -- TECHNICIAN -- - -- Rifleman - self.skills.defense_up[1].upgrades = {"player_steelsight_when_downed"} - self.skills.defense_up[2].upgrades = {"player_steelsight_normal_movement_speed", "assault_rifle_move_spread_index_addend", "snp_move_spread_index_addend", "smg_move_spread_index_addend"} - self.skills.defense_up.icon_xy = {6, 5} + -- Transporter + self.skills.defense_up[1].upgrades = { "carry_movement_speed_multiplier" } + self.skills.defense_up[2].upgrades = { "carry_throw_distance_multiplier" } + self.skills.defense_up.icon_xy = { 6, 0 } -- Daredevil - self.skills.sentry_targeting_package[1].upgrades = {"player_interacting_damage_multiplier"} - self.skills.sentry_targeting_package[2].upgrades = {"player_run_and_reload"} - self.skills.sentry_targeting_package.icon_xy = {10, 6} + self.skills.sentry_targeting_package[1].upgrades = { "player_interacting_damage_multiplier" } + self.skills.sentry_targeting_package[2].upgrades = { "player_run_and_reload" } + self.skills.sentry_targeting_package.icon_xy = { 10, 6 } -- Defense Package - self.skills.engineering[1].upgrades = {"sentry_gun_armor_multiplier"} - self.skills.engineering[2].upgrades = {"sentry_gun_shield"} - self.skills.engineering.icon_xy = {7, 5} + self.skills.engineering[1].upgrades = { "sentry_gun_armor_multiplier" } + self.skills.engineering[2].upgrades = { "sentry_gun_shield" } + self.skills.engineering.icon_xy = { 7, 5 } -- Sentry Nest - self.skills.tower_defense[2].upgrades = {"sentry_gun_cost_reduction_1", "sentry_gun_cost_reduction_2", "sentry_gun_extra_ammo_multiplier_1"} - self.skills.tower_defense.icon_xy = {7, 6} + self.skills.tower_defense[2].upgrades = { "sentry_gun_cost_reduction_1", "sentry_gun_cost_reduction_2", "sentry_gun_extra_ammo_multiplier_1" } + self.skills.tower_defense.icon_xy = { 7, 6 } -- PhD in Engineering - self.skills.eco_sentry[1].upgrades = {"sentry_gun_standstill_omniscience", "sentry_gun_spread_multiplier"} - self.skills.eco_sentry[2].upgrades = {"sentry_gun_ap_bullets", "sentry_gun_fire_rate_reduction_1"} - self.skills.eco_sentry.icon_xy = {7, 8} + self.skills.eco_sentry[1].upgrades = { "sentry_gun_standstill_omniscience", "sentry_gun_spread_multiplier" } + self.skills.eco_sentry[2].upgrades = { "sentry_gun_ap_bullets", "sentry_gun_fire_rate_reduction_1" } + self.skills.eco_sentry.icon_xy = { 7, 8 } - -- Fast Hands - self.skills.shock_and_awe[1].upgrades = {"smg_reload_speed_multiplier", "lmg_reload_speed_multiplier"} - self.skills.shock_and_awe.icon_xy = {3, 3} + -- Oppressor + self.skills.heavy_impact[1].upgrades = { "player_suppression_bonus" } + self.skills.heavy_impact[2].upgrades = { "player_suppression_bonus_2" } + self.skills.heavy_impact.name_id = "menu_oppressor" + self.skills.heavy_impact.desc_id = "menu_oppressor_desc" + self.skills.heavy_impact.icon_xy = { 7, 0 } + + -- Fire Control + self.skills.fire_control[2].upgrades = { "player_ap_bullets_1" } + + -- Sleight of Hand + self.skills.shock_and_awe[1].upgrades = { "smg_reload_speed_multiplier", "lmg_reload_speed_multiplier" } + self.skills.shock_and_awe.icon_xy = { 3, 3 } self.skills.shock_and_awe.name_id = "menu_fast_hands" self.skills.shock_and_awe.desc_id = "menu_fast_hands_desc" + -- Mag Plus + self.skills.fast_fire[1].upgrades = { "player_automatic_mag_increase_1" } + self.skills.fast_fire[2].upgrades = { "player_automatic_mag_increase_2" } + self.skills.fast_fire.icon_xy = { 2, 0 } + + -- Body Expertise + self.skills.body_expertise[1].upgrades = { "player_no_movement_penalty" } + self.skills.body_expertise[2].upgrades = { "weapon_automatic_head_shot_add_1" } + + -- GHOST -- + -- Inner Pockets - self.skills.cleaner[1].upgrades = {"player_melee_concealment_modifier"} - self.skills.cleaner[2].upgrades = {"player_ballistic_vest_concealment_1"} - self.skills.cleaner.icon_xy = {10, 7} + self.skills.cleaner[1].upgrades = { "player_melee_concealment_modifier" } + self.skills.cleaner[2].upgrades = { "player_ballistic_vest_concealment_1" } + self.skills.cleaner.icon_xy = { 10, 7 } self.skills.cleaner.name_id = "menu_thick_skin_beta" self.skills.cleaner.desc_id = "menu_thick_skin_beta_desc" - -- Quick Grab - self.skills.second_chances[1].upgrades = {"carry_interact_speed_multiplier_2"} - - -- Chameleon - self.skills.jail_workout[1].upgrades = {"player_suspicion_bonus", "player_camouflage_bonus_1", "player_camouflage_bonus_2"} - self.skills.jail_workout[2].upgrades = {"player_concealment_bonus_1"} + -- Logistician + self.skills.second_chances[1].upgrades = { "carry_interact_speed_multiplier_2" } + self.skills.second_chances[2].upgrades = { "player_pick_lock_easy_speed_multiplier", "player_pick_lock_hard" } + self.skills.second_chances.icon_xy = { 5, 4 } -- ECM feedback - self.skills.ecm_booster[1].upgrades = {"ecm_jammer_can_activate_feedback"} - self.skills.ecm_booster[2].upgrades = {"ecm_jammer_can_retrigger"} - self.skills.ecm_booster.icon_xy = {6, 2} + self.skills.ecm_booster[1].upgrades = { "ecm_jammer_can_activate_feedback" } + self.skills.ecm_booster[2].upgrades = { "ecm_jammer_can_retrigger" } + self.skills.ecm_booster.icon_xy = { 6, 2 } + + -- Chameleon + self.skills.jail_workout[1].upgrades = { "player_suspicion_bonus", "player_camouflage_bonus_1", "player_camouflage_bonus_2" } + self.skills.jail_workout[2].upgrades = { "player_concealment_bonus_1" } -- ECM Specialist - self.skills.ecm_2x[2].upgrades = {"ecm_jammer_duration_multiplier_1", "ecm_jammer_duration_multiplier_2", "ecm_jammer_feedback_duration_boost_1", "ecm_jammer_feedback_duration_boost_2", "player_buy_bodybags_asset", "player_additional_assets", "player_buy_spotter_asset"} + self.skills.ecm_2x[2].upgrades = { "ecm_jammer_duration_multiplier_1", "ecm_jammer_duration_multiplier_2", "ecm_jammer_feedback_duration_boost_1", "ecm_jammer_feedback_duration_boost_2" } - -- Sixth Sense - self.skills.chameleon[1].upgrades = {"player_tape_loop_duration_1", "player_tape_loop_duration_2"} - self.skills.chameleon[2].upgrades = {"ecm_jammer_affects_pagers", "ecm_jammer_can_open_sec_doors"} - self.skills.chameleon.icon_xy = {6, 3} + -- Blackout + self.skills.chameleon[1].upgrades = { "player_tape_loop_duration_1", "player_tape_loop_duration_2" } + self.skills.chameleon[2].upgrades = { "ecm_jammer_affects_pagers", "ecm_jammer_can_open_sec_doors" } + self.skills.chameleon.icon_xy = { 6, 3 } -- Athlete - self.skills.sprinter[1].upgrades = {"player_walk_speed_multiplier", "player_movement_speed_multiplier"} - self.skills.sprinter[2].upgrades = {"player_stamina_regen_timer_multiplier", "player_stamina_regen_multiplier"} - self.skills.sprinter.icon_xy = {1, 8} + self.skills.sprinter[1].upgrades = { "player_walk_speed_multiplier", "player_movement_speed_multiplier" } + self.skills.sprinter[2].upgrades = { "player_stamina_regen_timer_multiplier", "player_stamina_regen_multiplier" } + self.skills.sprinter.icon_xy = { 1, 8 } self.skills.sprinter.name_id = "menu_sprinter" self.skills.sprinter.desc_id = "menu_sprinter_desc" -- Duck and Cover - self.skills.awareness[1].upgrades = {"player_crouch_speed_multiplier"} - self.skills.awareness[2].upgrades = {"player_crouch_dodge_chance_1"} - self.skills.awareness.icon_xy = {0, 11} + self.skills.awareness[1].upgrades = { "player_crouch_speed_multiplier" } + self.skills.awareness[2].upgrades = { "player_crouch_dodge_chance_1" } + self.skills.awareness.icon_xy = { 0, 11 } self.skills.awareness.name_id = "menu_awareness" self.skills.awareness.desc_id = "menu_awareness_desc" -- Sprinter - self.skills.optic_illusions[1].upgrades = {"player_can_strafe_run", "player_run_speed_multiplier"} - self.skills.optic_illusions[2].upgrades = {"player_run_dodge_chance", "player_on_zipline_dodge_chance"} - self.skills.optic_illusions.icon_xy = {7, 3} + self.skills.optic_illusions[1].upgrades = { "player_can_strafe_run", "player_run_speed_multiplier" } + self.skills.optic_illusions[2].upgrades = { "player_run_dodge_chance", "player_on_zipline_dodge_chance" } + self.skills.optic_illusions.icon_xy = { 7, 3 } - -- The Professional - self.skills.silence_expert[1].upgrades = {"weapon_silencer_recoil_index_addend",} - self.skills.silence_expert[2].upgrades = {"weapon_silencer_enter_steelsight_speed_multiplier", "weapon_silencer_spread_index_addend"} + -- Second Wind + self.skills.dire_need[1].upgrades = { "temporary_damage_speed_multiplier" } + self.skills.dire_need[2].upgrades = { "cooldown_panic_on_armor_break" } + self.skills.dire_need.icon_xy = { 10, 9 } + + -- Shockproof + self.skills.insulation[1].upgrades = { "player_resist_firing_tased", "player_weaker_tase_effect" } + + -- Resilient Assault + self.skills.scavenger[1].upgrades = { "player_critical_hit_chance_1" } + self.skills.scavenger[2].upgrades = { "player_armor_depleted_stagger_shot_1", "player_armor_depleted_stagger_shot_2" } + self.skills.scavenger.icon_xy = { 10, 8 } -- Eagle Eye - self.skills.thick_skin[1].upgrades = {"weapon_special_damage_taken_multiplier"} - self.skills.thick_skin[2].upgrades = {"player_mark_enemy_time_multiplier", "player_marked_distance_mul"} - self.skills.thick_skin.icon_xy = {3, 7} + self.skills.thick_skin[1].upgrades = { "weapon_special_damage_taken_multiplier" } + self.skills.thick_skin[2].upgrades = { "weapon_steelsight_highlight_specials", "player_marked_distance_mul" } + self.skills.thick_skin.icon_xy = { 3, 7 } self.skills.thick_skin.name_id = "menu_cleaner_beta" self.skills.thick_skin.desc_id = "menu_cleaner_beta_desc" - -- Low Blow - self.skills.unseen_strike[1].upgrades = {"player_detection_risk_add_crit_chance_1"} - self.skills.unseen_strike[2].upgrades = {"player_detection_risk_add_crit_chance_2"} - self.skills.unseen_strike.icon_xy = {0, 12} - self.skills.unseen_strike.name_id = "menu_backstab_beta" - self.skills.unseen_strike.desc_id = "menu_backstab_beta_desc" + -- The Professional + self.skills.silence_expert[1].upgrades = { "weapon_silencer_recoil_index_addend" } + self.skills.silence_expert[2].upgrades = { "weapon_silencer_enter_steelsight_speed_multiplier", "weapon_silencer_spread_index_addend" } -- HVT - self.skills.hitman[2].upgrades = {"player_marked_inc_dmg_distance_1", "weapon_steelsight_highlight_specials"} + self.skills.hitman[1].upgrades = { "player_marked_inc_dmg_distance_1" } + self.skills.hitman[2].upgrades = { "player_marked_enemy_extra_damage", "player_mark_enemy_time_multiplier" } -- Silencer Expert - self.skills.backstab[1].upgrades = {"player_silencer_concealment_penalty_decrease_1", "player_silencer_concealment_increase_1"} - self.skills.backstab[2].upgrades = {"weapon_silencer_damage_multiplier", "weapon_armor_piercing_chance_silencer"} - self.skills.backstab.icon_xy = {5, 9} + self.skills.backstab[1].upgrades = { "player_silencer_concealment_penalty_decrease_1", "player_silencer_concealment_increase_1" } + self.skills.backstab[2].upgrades = { "weapon_silencer_damage_multiplier", "weapon_armor_piercing_chance_silencer" } + self.skills.backstab.icon_xy = { 5, 9 } self.skills.backstab.name_id = "menu_silenced_damage" self.skills.backstab.desc_id = "menu_silenced_damage_desc" + -- Low Blow + self.skills.unseen_strike[1].upgrades = { "player_detection_risk_add_crit_chance_1" } + self.skills.unseen_strike[2].upgrades = { "player_detection_risk_add_crit_chance_2", "weapon_extra_crit_damage_mul" } + self.skills.unseen_strike.icon_xy = { 0, 12 } + self.skills.unseen_strike.name_id = "menu_backstab_beta" + self.skills.unseen_strike.desc_id = "menu_backstab_beta_desc" + + -- FUGITIVE -- + + -- Tough Guy + self.skills.nine_lives[1].upgrades = { "player_steelsight_when_downed" } + self.skills.nine_lives[2].upgrades = { "player_primary_weapon_when_downed" } + self.skills.nine_lives.icon_xy = { 1, 2 } + + -- Quick Fix + self.skills.running_from_death[1].upgrades = { "first_aid_kit_deploy_time_multiplier" } + self.skills.running_from_death[2].upgrades = { "first_aid_kit_damage_reduction_upgrade" } + self.skills.running_from_death.icon_xy = { 1, 11 } + self.skills.running_from_death.name_id = "menu_tea_time_beta" + self.skills.running_from_death.desc_id = "menu_tea_time_beta_desc" + + -- Running from Death + self.skills.up_you_go[1].upgrades = { "player_temp_swap_weapon_faster_1", "player_temp_reload_weapon_faster_1" } + self.skills.up_you_go[2].upgrades = { "player_temp_increased_movement_speed_1" } + self.skills.up_you_go.name_id = "menu_running_from_death_beta" + self.skills.up_you_go.desc_id = "menu_running_from_death_beta_desc" + self.skills.up_you_go.icon_xy = { 11, 3 } + + -- Uppers + self.skills.feign_death[1].upgrades = { "first_aid_kit_quantity_increase_1" } + self.skills.feign_death[2].upgrades = { "first_aid_kit_quantity_increase_2", "first_aid_kit_auto_recovery_1" } + self.skills.feign_death.icon_xy = { 2, 11 } + self.skills.feign_death.name_id = "menu_tea_cookies_beta" + self.skills.feign_death.desc_id = "menu_tea_cookies_beta_desc" + + -- Swan Song + table.delete(self.skills.perseverance[2].upgrades, "player_berserker_no_ammo_cost") + -- Messiah - self.skills.messiah[1].upgrades = {"player_messiah_revive_from_bleed_out_1", "player_increased_bleedout_timer"} - self.skills.messiah[2].upgrades = {"player_messiah_revive_from_bleed_out_2", "player_super_syndrome_1"} + self.skills.messiah[1].upgrades = { "player_messiah_revive_from_bleed_out_1", "player_increased_bleedout_timer" } + self.skills.messiah[2].upgrades = { "player_messiah_revive_from_bleed_out_2", "player_super_syndrome_1" } - -- Counter Strike - self.skills.drop_soap[1].upgrades = {"player_run_and_melee_eclipse"} - self.skills.drop_soap[2].upgrades = {"cooldown_counter_strike_eclipse"} + -- Berserker + self.skills.wolverine[1].upgrades = { "player_movement_speed_damage_health_ratio_multiplier", "player_movement_speed_damage_health_ratio_threshold_multiplier" } + self.skills.wolverine[2].upgrades = { "player_melee_damage_health_ratio_multiplier" } + -- Counter Strike + self.skills.drop_soap[1].upgrades = { "player_run_and_melee_eclipse" } + self.skills.drop_soap[2].upgrades = { "cooldown_counter_strike_eclipse" } + -- MISC STUFF -- + -- Medic Tree + self.trees[1].tiers[2][1] = "fast_learner" + self.trees[1].tiers[2][2] = "tea_time" -- Sentry tree self.trees[7].tiers[2][2] = "engineering" self.trees[7].tiers[3][2] = "tower_defense" @@ -233,44 +360,150 @@ function SkillTreeTweakData:init(tweak_data) -- Swap Inner Pockets and Deft Hands self.trees[11].tiers[2][2] = "optic_illusions" self.trees[12].tiers[2][1] = "thick_skin" + -- Swap Uppers and Swan Song + self.trees[14].tiers[3][1] = "feign_death" + self.trees[14].tiers[3][2] = "perseverance" + + -- PERK DECKS -- + + -- categories + self.specialization_category = { + { + name_id = "menu_st_category_all", + category = "all", + }, + { + name_id = "menu_st_category_health", + category = "health", + }, + { + name_id = "menu_st_category_healing", + category = "healing", + }, + { + name_id = "menu_st_category_armor", + category = "armor", + }, + { + name_id = "menu_st_category_armor_gating", + category = "armor_gating", + }, + { + name_id = "menu_st_category_resistance", + category = "resistance", + }, + { + name_id = "menu_st_category_support", + category = "support", + }, + { + name_id = "menu_st_category_dodge", + category = "dodge", + }, + { + name_id = "menu_st_category_favorites", + category = "favorites", + }, + } + + -- ccf + self.specializations[1].category = { "health", "support" } + -- mus + self.specializations[2].category = { "health", "healing" } + -- arm + self.specializations[3].category = "armor" + -- rog + self.specializations[4].category = "dodge" + -- hit + self.specializations[5].category = "armor_gating" + -- crk + self.specializations[6].category = { "armor", "dodge" } + -- brg + self.specializations[7].category = "dodge" + -- inf + self.specializations[8].category = "resistance" + -- soc + self.specializations[9].category = { "healing", "armor_gating" } + -- gmb + self.specializations[10].category = { "healing", "support" } + -- grd + self.specializations[11].category = "healing" + -- yak + self.specializations[12].category = "dodge" + -- exp + self.specializations[13].category = "healing" + -- man + self.specializations[14].category = { "support", "resistance" } + -- anr + self.specializations[15].category = { "armor", "armor_gating" } + -- bik + self.specializations[16].category = { "healing", "armor_gating" } + -- kpn + self.specializations[17].category = { "health", "resistance" } + -- sic + self.specializations[18].category = { "dodge", "armor_gating" } + -- stc + self.specializations[19].category = { "health", "resistance" } + -- tgt + self.specializations[20].category = { "healing", "support" } + -- hck + self.specializations[21].category = { "support", "healing" } + -- lch + self.specializations[22].category = { "health", "resistance" } + + -- crew chief + table.delete(self.specializations[1][3].upgrades, "player_damage_dampener_close_contact_1") + table.delete(self.specializations[1][9].upgrades, "team_hostage_damage_dampener_multiplier") - -- Old Swan Song - table.delete(self.skills.perseverance[2].upgrades, "player_berserker_no_ammo_cost") - - -- hacker - table.delete(self.specializations[21][9].upgrades, "player_passive_dodge_chance_2") + -- muscle + table.delete(self.specializations[2][9].upgrades, "player_passive_health_multiplier_5") + self.specializations[2][7].upgrades = { "temporary_mrwi_health_invulnerable_1" } + self.specializations[2][7].texture_bundle_folder = "mrwi" + self.specializations[2][7].icon_xy = { 3, 0 } -- armorer table.delete(self.specializations[3][1].upgrades, "player_tier_armor_multiplier_2") table.delete(self.specializations[3][9].upgrades, "player_tier_armor_multiplier_6") - self.specializations[3][3].upgrades ={"player_tier_armor_multiplier_2"} - self.specializations[3][5].upgrades ={"player_tier_armor_multiplier_3", "player_tier_armor_multiplier_4", "player_tier_armor_multiplier_5"} - - -- muscle - table.delete(self.specializations[2][9].upgrades, "player_passive_health_multiplier_5") + self.specializations[3][3].upgrades = { "player_tier_armor_multiplier_2" } + self.specializations[3][5].upgrades = { "player_tier_armor_multiplier_3", "player_tier_armor_multiplier_4", "player_tier_armor_multiplier_5" } + + -- hitman + self.specializations[5][1].upgrades = { "player_primary_reload_secondary_1", "player_secondary_reload_primary_1" } + self.specializations[5][1].texture_bundle_folder = "mrwi" + self.specializations[5][1].icon_xy = { 0, 0 } + self.specializations[5][3].upgrades = { "weapon_passive_swap_speed_multiplier_1" } + self.specializations[5][5].upgrades = { "player_unseen_temp_increased_crit_chance_1", "player_unseen_increased_crit_chance_1" } + self.specializations[5][5].icon_xy = { 2, 8 } + self.specializations[5][7].upgrades = { "cooldown_hitman_ammo_refund" } + self.specializations[5][7].icon_xy = { 3, 3 } + self.specializations[5][9].upgrades = { "player_killshot_regen_armor_bonus", "player_passive_loot_drop_multiplier" } + self.specializations[5][9].icon_xy = { 0, 5 } + + -- burglar + table.delete(self.specializations[7][7].upgrades, "player_tier_dodge_chance_3") + self.specializations[7][9].upgrades = { "player_dodge_ricochet_bullets", "player_passive_loot_drop_multiplier" } + self.specializations[7][9].texture_bundle_folder = "mrwi" + self.specializations[7][9].icon_xy = { 2, 0 } -- socio table.delete(self.specializations[9][7].upgrades, "player_tier_armor_multiplier_3") - -- yakuza - self.specializations[12][3].upgrades ={"weapon_passive_swap_speed_multiplier_1"} - table.insert(self.specializations[12][9].upgrades, "player_damage_health_ratio_multiplier") - table.delete(self.specializations[12][9].upgrades, "player_movement_speed_damage_health_ratio_threshold_multiplier") - - -- leech - table.delete(self.specializations[22][9].upgrades, "player_activate_ability_downed") - table.delete(self.specializations[22][9].upgrades, "player_passive_health_multiplier_4") - - -- anarchist - self.specializations[15][7].upgrades = {"player_passive_dodge_chance_1", "player_armor_increase_3"} - table.delete(self.specializations[15][1].upgrades, "temporary_armor_break_invulnerable_1") - - -- kingpin - table.delete(self.specializations[17][9].upgrades, "player_passive_health_multiplier_4") + -- Gambler + table.insert(self.specializations[10][9].upgrades, "player_increased_pickup_area_1") + table.insert(self.specializations[10][9].upgrades, "player_increased_pickup_area_2") + table.insert(self.specializations[10][3].upgrades, "player_pick_up_ammo_multiplier") + table.delete(self.specializations[10][3].upgrades, "temporary_loose_ammo_give_team") - -- Crew Chief - table.delete(self.specializations[1][3].upgrades, "player_damage_dampener_close_contact_1") - table.delete(self.specializations[1][9].upgrades, "team_hostage_damage_dampener_multiplier") + -- yakuza + self.specializations[12][1].upgrades = { "player_armor_regen_damage_health_ratio_multiplier_1", "player_armor_regen_damage_health_ratio_threshold_multiplier" } + self.specializations[12][3].upgrades = { "weapon_passive_swap_speed_multiplier_1" } + self.specializations[12][3].icon_xy = { 7, 2 } + self.specializations[12][5].upgrades = { "player_camouflage_multiplier" } + self.specializations[12][5].name_id = "menu_deck4_3" + self.specializations[12][5].icon_xy = { 4, 2 } + self.specializations[12][7].upgrades = { "player_dodge_health_ratio_multiplier" } + self.specializations[12][7].icon_xy = { 1, 8 } + self.specializations[12][9].upgrades = { "player_damage_health_ratio_multiplier", "player_damage_damage_health_ratio_threshold_multiplier" } -- Grinder and Ex-President table.delete(self.specializations[11][3].upgrades, "player_passive_health_multiplier_1") @@ -280,18 +513,41 @@ function SkillTreeTweakData:init(tweak_data) table.delete(self.specializations[13][5].upgrades, "player_passive_health_multiplier_2") table.delete(self.specializations[13][7].upgrades, "player_passive_health_multiplier_3") - -- Get rid of burglars crouch move speed boost to avoid bugging upgrades out (no one plays the deck anyway so who cares) - table.delete(self.specializations[7][9].upgrades, "player_crouch_speed_multiplier_2") + -- Maniac + table.insert(self.specializations[14][3].upgrades, "player_panic_suppression") - -- Gambler - table.insert(self.specializations[10][9].upgrades, "player_increased_pickup_area_1") - table.insert(self.specializations[10][9].upgrades, "player_increased_pickup_area_2") - table.insert(self.specializations[10][3].upgrades, "player_addition_ammo_eclipse") - table.delete(self.specializations[10][3].upgrades, "temporary_loose_ammo_give_team") + -- anarchist + self.specializations[15][5].upgrades = { "player_chico_armor_multiplier_1", "player_armor_increase_2" } + self.specializations[15][7].upgrades = { "player_passive_dodge_chance_1", "player_armor_increase_3" } + table.delete(self.specializations[15][1].upgrades, "temporary_armor_break_invulnerable_1") + + -- kingpin + table.delete(self.specializations[17][9].upgrades, "player_passive_health_multiplier_4") + + -- hacker + table.delete(self.specializations[21][3].upgrades, "player_passive_health_multiplier_2") + table.insert(self.specializations[21][7].upgrades, "player_passive_health_multiplier_2") + table.delete(self.specializations[21][7].upgrades, "player_pocket_ecm_kill_dodge_1") + table.delete(self.specializations[21][9].upgrades, "player_passive_dodge_chance_2") + + -- leech + table.delete(self.specializations[22][9].upgrades, "player_activate_ability_downed") + table.delete(self.specializations[22][9].upgrades, "player_passive_health_multiplier_4") + + -- wild cards + for _, perkdeck in pairs(self.specializations) do + perkdeck[2].upgrades = { "player_regain_throwable_from_ammo_1" } + perkdeck[2].icon_xy = { 0, 8 } + perkdeck[4].upgrades = { "player_passive_suspicion_bonus", "player_buy_bodybags_asset", "player_additional_assets", "player_buy_spotter_asset" } + perkdeck[6].upgrades = { "armor_kit", "player_passive_armor_movement_penalty_multiplier" } + end - -- Remove some default upgrades + -- Buncha default upgrade fuckery + table.insert(self.default_upgrades, "player_bleed_out_health_multiplier") table.insert(self.default_upgrades, "sentry_gun_rot_speed_multiplier") + table.insert(self.default_upgrades, "passive_player_xp_multiplier") table.delete(self.default_upgrades, "player_steelsight_when_downed") + table.delete(self.default_upgrades, "carry_movement_speed_multiplier") table.delete(self.default_upgrades, "carry_interact_speed_multiplier_2") table.delete(self.default_upgrades, "ecm_jammer_can_activate_feedback") table.delete(self.default_upgrades, "ecm_jammer_can_retrigger") diff --git a/lua/snipergrazedamage.lua b/lua/snipergrazedamage.lua index ded7240..9eed336 100644 --- a/lua/snipergrazedamage.lua +++ b/lua/snipergrazedamage.lua @@ -1,109 +1,109 @@ - local TRAIL_EFFECT = Idstring("effects/particles/weapons/sniper_trail") - local idstr_trail = Idstring("trail") - local idstr_simulator_length = Idstring("simulator_length") - local idstr_size = Idstring("size") - local trail_length - - local civ_dmg_class = Network:is_server() and CivilianDamage or HuskCivilianDamage - - -- honestly mind blown is so much better as a skill design than graze, thank you Hoppip - function SniperGrazeDamage:on_weapon_fired(weapon_unit, result) - -- disable mind blown with contractor, grom, kang arms, lebensauger and non-sniper weapons - if not alive(weapon_unit) or not weapon_unit:base():is_category("snp") or weapon_unit:base():is_category("ng") or weapon_unit ~= managers.player:equipped_weapon_unit() or not result.hit_enemy then - return - end - - local player_unit = managers.player:player_unit() - if not player_unit then - return - end - - local upgrade_value = managers.player:upgrade_value("snp", "graze_damage") - local sentry_mask = managers.slot:get_mask("sentry_gun") - local ally_mask = managers.slot:get_mask("all_criminals") - local enemy_mask = managers.slot:get_mask("enemies") - local geometry_mask = managers.slot:get_mask("bullet_blank_impact_targets") - local hit_enemies = {} - local ignored_enemies = {} - - for _, hit in ipairs(result.rays) do - local is_turret = hit.unit:in_slot(sentry_mask) - local is_ally = hit.unit:in_slot(ally_mask) +local TRAIL_EFFECT = Idstring("effects/particles/weapons/sniper_trail") +local idstr_trail = Idstring("trail") +local idstr_simulator_length = Idstring("simulator_length") +local idstr_size = Idstring("size") +local trail_length + +local civ_dmg_class = Network:is_server() and CivilianDamage or HuskCivilianDamage + +-- honestly mind blown is so much better as a skill design than graze, thank you Hoppip +function SniperGrazeDamage:on_weapon_fired(weapon_unit, result) + -- disable mind blown with contractor, grom, kang arms, lebensauger and non-sniper weapons + if not alive(weapon_unit) or not weapon_unit:base():is_category("snp") or weapon_unit:base():is_category("ng") or weapon_unit ~= managers.player:equipped_weapon_unit() or not result.hit_enemy then + return + end - local res = hit.damage_result - local attack_data = res and res.attack_data - if attack_data and attack_data.headshot and not is_turret and not is_ally then - local multiplier = (res.type == "death" or res.type == "healed") and upgrade_value.damage_factor_kill or upgrade_value.damage_factor - local key = hit.unit:key() - hit_enemies[key] = { - position = hit.position, - damage = attack_data.damage * multiplier - } - ignored_enemies[key] = true - end - end + local player_unit = managers.player:player_unit() + if not player_unit then + return + end - for _, hit in pairs(hit_enemies) do - self:find_closest_hit(hit, ignored_enemies, upgrade_value, enemy_mask, geometry_mask, player_unit, upgrade_value.times) + local upgrade_value = managers.player:upgrade_value("snp", "graze_damage") + local sentry_mask = managers.slot:get_mask("sentry_gun") + local ally_mask = managers.slot:get_mask("all_criminals") + local enemy_mask = managers.slot:get_mask("enemies") + local geometry_mask = managers.slot:get_mask("bullet_blank_impact_targets") + local hit_enemies = {} + local ignored_enemies = {} + + for _, hit in ipairs(result.rays) do + local is_turret = hit.unit:in_slot(sentry_mask) + local is_ally = hit.unit:in_slot(ally_mask) + + local res = hit.damage_result + local attack_data = res and res.attack_data + if attack_data and attack_data.headshot and not is_turret and not is_ally then + local multiplier = (res.type == "death" or res.type == "healed") and upgrade_value.damage_factor_kill or upgrade_value.damage_factor + local key = hit.unit:key() + hit_enemies[key] = { + position = hit.position, + damage = attack_data.damage * multiplier, + } + ignored_enemies[key] = true end + end + for _, hit in pairs(hit_enemies) do + self:find_closest_hit(hit, ignored_enemies, upgrade_value, enemy_mask, geometry_mask, player_unit, weapon_unit, upgrade_value.times) end +end - function SniperGrazeDamage:find_closest_hit(hit, ignored_enemies, upgrade_value, enemy_mask, geometry_mask, player_unit, times) - if times <= 0 then - return - end - DelayedCalls:Add("grazehit" .. tostring(hit), 0.05, function () - local hit_units = World:find_units_quick("sphere", hit.position, upgrade_value.radius, enemy_mask) - local closest - local closest_d_sq = math.huge - for _, unit in ipairs(hit_units) do - if not ignored_enemies[unit:key()] then - local anim = unit:anim_data() - local is_hostage = anim.hands_back or anim.surrender or anim.hands_tied or getmetatable(unit:character_damage()) == civ_dmg_class - if not is_hostage then - local d_s = mvector3.distance_sq(hit.position, unit:movement():m_head_pos()) - if d_s < closest_d_sq and not World:raycast("ray", hit.position, unit:movement():m_head_pos(), "slot_mask", geometry_mask) then - closest = unit - closest_d_sq = d_s - end +function SniperGrazeDamage:find_closest_hit(hit, ignored_enemies, upgrade_value, enemy_mask, geometry_mask, player_unit, weapon_unit, times) + if times <= 0 then + return + end + DelayedCalls:Add("grazehit" .. tostring(hit), 0.05, function() + local hit_units = World:find_units_quick("sphere", hit.position, upgrade_value.radius, enemy_mask) + local closest + local closest_d_sq = math.huge + for _, unit in ipairs(hit_units) do + if not ignored_enemies[unit:key()] then + local anim = unit:anim_data() + local is_hostage = anim.hands_back or anim.surrender or anim.hands_tied or getmetatable(unit:character_damage()) == civ_dmg_class + if not is_hostage then + local d_s = mvector3.distance_sq(hit.position, unit:movement():m_head_pos()) + if d_s < closest_d_sq and not World:raycast("ray", hit.position, unit:movement():m_head_pos(), "slot_mask", geometry_mask) then + closest = unit + closest_d_sq = d_s end end end + end - if not closest then - return - end - - ignored_enemies[closest:key()] = true - - local hit_pos = Vector3() - mvector3.set(hit_pos, closest:movement():m_head_pos()) - - if not trail_length then - trail_length = World:effect_manager():get_initial_simulator_var_vector2(TRAIL_EFFECT, idstr_trail, idstr_simulator_length, idstr_size) - end - local trail = World:effect_manager():spawn({ - effect = TRAIL_EFFECT, - position = hit.position, - normal = hit_pos - hit.position - }) - mvector3.set_y(trail_length, math.sqrt(closest_d_sq)) - World:effect_manager():set_simulator_var_vector2(trail, idstr_trail, idstr_simulator_length, idstr_size, trail_length) + if not closest then + return + end - closest:character_damage():damage_simple({ - variant = "graze", - damage = hit.damage, - attacker_unit = player_unit, - pos = hit_pos, - attack_dir = hit_pos - hit.position - }) + ignored_enemies[closest:key()] = true - hit = { - position = hit_pos, - damage = hit.damage - } + local hit_pos = Vector3() + mvector3.set(hit_pos, closest:movement():m_head_pos()) - self:find_closest_hit(hit, ignored_enemies, upgrade_value, enemy_mask, geometry_mask, player_unit, times - 1) - end) - end + if not trail_length then + trail_length = World:effect_manager():get_initial_simulator_var_vector2(TRAIL_EFFECT, idstr_trail, idstr_simulator_length, idstr_size) + end + local trail = World:effect_manager():spawn({ + effect = TRAIL_EFFECT, + position = hit.position, + normal = hit_pos - hit.position, + }) + mvector3.set_y(trail_length, math.sqrt(closest_d_sq)) + World:effect_manager():set_simulator_var_vector2(trail, idstr_trail, idstr_simulator_length, idstr_size, trail_length) + + closest:character_damage():damage_simple({ + variant = "graze", + damage = hit.damage, + attacker_unit = player_unit, + weapon_unit = weapon_unit, + pos = hit_pos, + attack_dir = hit_pos - hit.position, + }) + + hit = { + position = hit_pos, + damage = hit.damage, + } + + self:find_closest_hit(hit, ignored_enemies, upgrade_value, enemy_mask, geometry_mask, player_unit, weapon_unit, times - 1) + end) +end diff --git a/lua/spooclogicattack.lua b/lua/spooclogicattack.lua index dbfb480..cbb830f 100644 --- a/lua/spooclogicattack.lua +++ b/lua/spooclogicattack.lua @@ -1,4 +1,5 @@ --- More consistent Cloaker attacks. Why would Cloakers not be allowed to start a sprint attack when another cop is in the way? +-- More consistent Cloaker attacks, why wouldn't they be allowed to start a sprint attack when another cop is in the way? +-- Also force Cloakers to stand up before starting a charge function SpoocLogicAttack._upd_spooc_attack(data, my_data) if my_data.spooc_attack or data.t <= data.spooc_attack_timeout_t or data.unit:movement():chk_action_forbidden("walk") then return @@ -21,10 +22,10 @@ function SpoocLogicAttack._upd_spooc_attack(data, my_data) return end - if not data.spooc_attack_delay_t then - data.spooc_attack_delay_t = focus_enemy.verified_t + 0.35 + if not my_data.spooc_attack_delay_t then + my_data.spooc_attack_delay_t = focus_enemy.verified_t + 0.35 return - elseif data.spooc_attack_delay_t > data.t then + elseif my_data.spooc_attack_delay_t > data.t then return end @@ -42,29 +43,19 @@ function SpoocLogicAttack._upd_spooc_attack(data, my_data) local action = SpoocLogicAttack._chk_request_action_spooc_attack(data, my_data, flying_strike) if action then - data.spooc_attack_delay_t = nil + my_data.spooc_attack_delay_t = nil my_data.spooc_attack = { start_t = data.t, target_u_data = focus_enemy, - action = action + action = action, } return true end end - --- Force Cloakers to stand up before starting an attack -Hooks:PreHook(SpoocLogicAttack, "_chk_request_action_spooc_attack", "sh___chk_request_action_spooc_attack", function (data) - data.unit:movement():action_request({ - body_part = 4, - type = "stand" - }) -end) - - -- Update logic every frame -Hooks:PostHook(SpoocLogicAttack, "enter", "sh_enter", function (data) +Hooks:PostHook(SpoocLogicAttack, "enter", "sh_enter", function(data) data.brain:set_update_enabled_state(true) end) @@ -78,7 +69,13 @@ function SpoocLogicAttack.update(data) local focus_enemy = data.attention_obj if my_data.spooc_attack then - if my_data.spooc_attack.action:complete() and focus_enemy and focus_enemy.verified and (not focus_enemy.criminal_record or not focus_enemy.criminal_record.status) and focus_enemy.dis < my_data.weapon_range.close then + if + my_data.spooc_attack.action:complete() + and focus_enemy + and focus_enemy.verified + and (not focus_enemy.criminal_record or not focus_enemy.criminal_record.status) + and focus_enemy.dis < my_data.weapon_range.close + then SpoocLogicAttack._cancel_spooc_attempt(data, my_data) end @@ -94,7 +91,7 @@ function SpoocLogicAttack.update(data) if not data.unit:anim_data().to_idle and not data.unit:movement():chk_action_forbidden("walk") then data.unit:movement():action_request({ body_part = 2, - type = "idle" + type = "idle", }) my_data.wants_stop_old_walk_action = nil diff --git a/lua/tangomanager.lua b/lua/tangomanager.lua index df971b0..3ee7108 100644 --- a/lua/tangomanager.lua +++ b/lua/tangomanager.lua @@ -1,11 +1,11 @@ -- unlock arbiter (hydrogen) local original_function = original_function or TangoManager.load - function TangoManager:load(cache, version) - local state = cache.Tango - if state and state.version == TangoManager.SAVE_DATA_VERSION then - for _, saved_challenge in ipairs(state.challenges or {}) do - saved_challenge.completed = true - end - end - return original_function(self, cache, version) -end \ No newline at end of file +function TangoManager:load(cache, version) + local state = cache.Tango + if state and state.version == TangoManager.SAVE_DATA_VERSION then + for _, saved_challenge in ipairs(state.challenges or {}) do + saved_challenge.completed = true + end + end + return original_function(self, cache, version) +end diff --git a/lua/tankcoplogicattack.lua b/lua/tankcoplogicattack.lua index 6cf27a1..2db1a4d 100644 --- a/lua/tankcoplogicattack.lua +++ b/lua/tankcoplogicattack.lua @@ -1,13 +1,14 @@ -- Don't exit attack logic while chasing function TankCopLogicAttack._chk_exit_attack_logic(data, ...) - if not data.attention_obj or data.attention_obj.dis > 2000 or data.attention_obj.reaction < AIAttentionObject.REACT_COMBAT then - CopLogicAttack._chk_exit_attack_logic(data, ...) + if not data.internal_data.walking_to_chase_pos then + if not data.attention_obj or data.attention_obj.dis > 2000 or data.attention_obj.reaction < AIAttentionObject.REACT_COMBAT then + CopLogicAttack._chk_exit_attack_logic(data, ...) + end end end - -- Update logic every frame and fix Dozers sprinting when they shouldn't -Hooks:PostHook(TankCopLogicAttack, "enter", "sh_enter", function (data) +Hooks:PostHook(TankCopLogicAttack, "enter", "sh_enter", function(data) data.brain:set_update_enabled_state(true) end) @@ -38,23 +39,22 @@ function TankCopLogicAttack.update(data) local enemy_visible = focus_enemy.verified if focus_enemy.reaction >= AIAttentionObject.REACT_COMBAT then - -- Stop running if we're close enough - if enemy_visible and focus_enemy.verified_dis < 600 and unit:anim_data().run then + --[[ Stop running if we're close enough + if enemy_visible and focus_enemy.dis < 600 and unit:anim_data().run then unit:brain():action_request({ body_part = 2, type = "idle" }) - end + end]] if my_data.walking_to_chase_pos then -- Check if the current chase pos is too far from our focus enemy and if so, cancel chase to get a better pos - if mvector3.distance_sq(my_data.walking_to_chase_pos:get_destination_pos(), focus_enemy.m_pos) > 1440000 then + if my_data.chase_enemy and mvector3.distance_sq(my_data.walking_to_chase_pos:get_destination_pos(), my_data.chase_enemy.m_pos) > 1440000 then TankCopLogicAttack._cancel_chase_attempt(data, my_data) end elseif my_data.pathing_to_chase_pos then elseif my_data.chase_path then - local walk = enemy_visible and focus_enemy.verified_dis < 800 - TankCopLogicAttack._chk_request_action_walk_to_chase_pos(data, my_data, walk and "walk" or "run") + TankCopLogicAttack._chk_request_action_walk_to_chase_pos(data, my_data, enemy_visible and focus_enemy.dis < 800 and "walk" or "run") elseif my_data.chase_pos then local from_pos = unit:movement():nav_tracker():field_position() local to_pos = my_data.chase_pos @@ -62,22 +62,23 @@ function TankCopLogicAttack.update(data) my_data.chase_pos = nil -- Check if direct path is possible - if math.abs(from_pos.z - to_pos.z) < 100 and not managers.navigation:raycast({allow_entry = false, pos_from = from_pos, pos_to = to_pos}) then + if math.abs(from_pos.z - to_pos.z) < 100 and not managers.navigation:raycast({ allow_entry = false, pos_from = from_pos, pos_to = to_pos }) then my_data.chase_path = { mvector3.copy(from_pos), - to_pos + to_pos, } else my_data.chase_path_search_id = tostring(unit:key()) .. "chase" my_data.pathing_to_chase_pos = true data.brain:add_pos_rsrv("path", { radius = 60, - position = mvector3.copy(to_pos) + position = mvector3.copy(to_pos), }) unit:brain():search_for_path(my_data.chase_path_search_id, to_pos) end elseif focus_enemy.nav_tracker then my_data.chase_pos = CopLogicAttack._find_flank_pos(data, my_data, focus_enemy.nav_tracker, 1000) + my_data.chase_enemy = focus_enemy end else TankCopLogicAttack._cancel_chase_attempt(data, my_data) @@ -85,4 +86,4 @@ function TankCopLogicAttack.update(data) end function TankCopLogicAttack.queued_update() end -function TankCopLogicAttack.queue_update() end \ No newline at end of file +function TankCopLogicAttack.queue_update() end diff --git a/lua/taserlogicattack.lua b/lua/taserlogicattack.lua index e6a4b6e..4a007f0 100644 --- a/lua/taserlogicattack.lua +++ b/lua/taserlogicattack.lua @@ -28,37 +28,40 @@ function TaserLogicAttack._upd_aim(data, my_data, reaction) if tase and (not my_data.tasing or my_data.tasing.target_u_key ~= focus_enemy.u_key) and not focus_enemy.unit:movement():zipline_unit() then local tase_action = { body_part = 3, - type = "tase" + type = "tase", + blocks = { + walk = -1, + }, } - if data.unit:brain():action_request(tase_action) then + if data.brain:action_request(tase_action) then my_data.tasing = { target_u_data = focus_enemy, target_u_key = focus_enemy.u_key, - start_t = data.t + start_t = data.t, } -- Stop moving when we tase CopLogicAttack._cancel_charge(data, my_data) - data.unit:brain():action_request({ + data.brain:action_request({ body_part = 2, - type = "idle" + type = "idle", }) managers.groupai:state():on_tase_start(data.key, focus_enemy.u_key) end elseif not my_data.shooting and not my_data.tasing then - my_data.shooting = data.unit:brain():action_request({ + my_data.shooting = data.brain:action_request({ body_part = 3, - type = "shoot" + type = "shoot", }) end end else if my_data.shooting or my_data.tasing then - data.unit:brain():action_request({ + data.brain:action_request({ body_part = 3, - type = "idle" + type = "idle", }) end @@ -71,7 +74,6 @@ function TaserLogicAttack._upd_aim(data, my_data, reaction) CopLogicAttack.aim_allow_fire(shoot, aim, data, my_data) end - -- Save taser charge sound cooldown to data to persist over logic changes function TaserLogicAttack._chk_play_charge_weapon_sound(data, my_data, focus_enemy) if my_data.tasing or data.last_charge_snd_play_t and data.t - data.last_charge_snd_play_t < 10 then @@ -84,12 +86,13 @@ function TaserLogicAttack._chk_play_charge_weapon_sound(data, my_data, focus_ene end end - -- Update logic every frame -Hooks:PostHook(TaserLogicAttack, "enter", "sh_enter", function (data) +Hooks:PostHook(TaserLogicAttack, "enter", "sh_enter", function(data) data.brain:set_update_enabled_state(true) local my_data = data.internal_data + my_data.tase_sphere_cast_radius = data.char_tweak.weapon[data.unit:inventory():equipped_unit():base():weapon_tweak_data().usage].tase_sphere_cast_radius + my_data.tase_slot_mask = managers.slot:get_mask("bullet_blank_impact_targets") my_data.detection_task_key = "TaserLogicAttack._upd_enemy_detection" .. tostring(data.key) CopLogicBase.queue_task(my_data, my_data.detection_task_key, TaserLogicAttack._upd_enemy_detection, data, data.t + 0.2) end) @@ -141,7 +144,6 @@ end function TaserLogicAttack.queued_update() end function TaserLogicAttack.queue_update() end - -- Add tase delay whenever tase action ends, not just when the tased person is downed local action_complete_clbk_original = TaserLogicAttack.action_complete_clbk function TaserLogicAttack.action_complete_clbk(data, action, ...) @@ -171,4 +173,21 @@ function TaserLogicAttack.action_complete_clbk(data, action, ...) else return action_complete_clbk_original(data, action, ...) end -end \ No newline at end of file +end + +-- Check line of sight for tase reaction +local to_vec = Vector3() +local _chk_reaction_to_attention_object_original = TaserLogicAttack._chk_reaction_to_attention_object +function TaserLogicAttack._chk_reaction_to_attention_object(data, attention_data, ...) + local reaction = _chk_reaction_to_attention_object_original(data, attention_data, ...) + + if reaction == AIAttentionObject.REACT_SPECIAL_ATTACK then + local my_data = data.internal_data + attention_data.unit:character_damage():shoot_pos_mid(to_vec) + if CopActionTase.is_obstructed(data.unit:movement():m_head_pos(), to_vec, my_data.tase_slot_mask, my_data.tase_sphere_cast_radius) then + return AIAttentionObject.REACT_COMBAT + end + end + + return reaction +end diff --git a/lua/teamaidamage.lua b/lua/teamaidamage.lua new file mode 100644 index 0000000..8ef5427 --- /dev/null +++ b/lua/teamaidamage.lua @@ -0,0 +1,2 @@ +-- Add missing friendly fire check +TeamAIDamage.is_friendly_fire = PlayerDamage.is_friendly_fire diff --git a/lua/teamaimovement.lua b/lua/teamaimovement.lua index 3826c74..7609c74 100644 --- a/lua/teamaimovement.lua +++ b/lua/teamaimovement.lua @@ -1,2 +1,4 @@ -- Fix endless Cloaker beatdown on team AI -Hooks:PostHook(TeamAIMovement, "on_SPOOCed", "sh_on_SPOOCed", function () return true end) +Hooks:PostHook(TeamAIMovement, "on_SPOOCed", "sh_on_SPOOCed", function() + return true +end) diff --git a/lua/tweakdata.lua b/lua/tweakdata.lua index 4fff4c7..4f79b4d 100644 --- a/lua/tweakdata.lua +++ b/lua/tweakdata.lua @@ -1,7 +1,36 @@ -if not tweak_data then return end +-- lock dw / ds +tweak_data.difficulty_level_locks = { + 0, + 0, + 0, + 0, + 0, + 80, + 69420, + 69420, +} + +-- lower difficulty xp muls +tweak_data.experience_manager.difficulty_multiplier = { + 1.5, + 3, + 6, + 12, + 12, + 12, +} + +-- remove alive player multipliers +tweak_data.experience_manager.alive_humans_multiplier = { + [0] = 1, + 1, + 1, + 1, + 1, +} -- medics don't heal cloakers and other medics -tweak_data.medic.disabled_units = {"spooc", "medic"} +tweak_data.medic.disabled_units = { "spooc", "medic" } -- Arrows tweak_data.projectiles.frankish_arrow.damage = 50 @@ -9,50 +38,64 @@ tweak_data.projectiles.frankish_arrow_exp.damage = 50 tweak_data.projectiles.crossbow_arrow_exp.damage = 35 tweak_data.projectiles.ecp_arrow.damage = 25 tweak_data.projectiles.ecp_arrow_exp.damage = 25 -tweak_data.projectiles.west_arrow.damage = 65 -tweak_data.projectiles.west_arrow_exp.damage = 65 +tweak_data.projectiles.west_arrow.damage = 80 +tweak_data.projectiles.west_arrow_exp.damage = 80 tweak_data.projectiles.bow_poison_arrow.damage = 10 +tweak_data.projectiles.arblast_arrow.damage = 100 +tweak_data.projectiles.arblast_arrow_exp.damage = 100 +tweak_data.projectiles.arblast_poison_arrow.damage = 25 -- grenade launchers and rocket launchers -- 101 tweak_data.projectiles.rocket_ray_frag.damage = 128 tweak_data.projectiles.rocket_ray_frag.player_damage = 16 +tweak_data.projectiles.rocket_ray_frag.range = 700 + +-- rpg +tweak_data.projectiles.launcher_rocket.damage = 24000 -- the extra 0 isn't a typo :trolline: +tweak_data.projectiles.launcher_rocket.player_damage = 24 +tweak_data.projectiles.launcher_rocket.range = 700 -- piglet -tweak_data.projectiles.launcher_frag_m32.damage = 42 +tweak_data.projectiles.launcher_frag_m32.damage = 82 tweak_data.projectiles.launcher_frag_m32.player_damage = 16 -- china -tweak_data.weapon_disable_crit_for_damage.launcher_frag_china = {explosion = false, fire = false} -- why is china puff allowed to crit lmao -tweak_data.projectiles.launcher_frag_china.damage = 40 +tweak_data.weapon_disable_crit_for_damage.launcher_frag_china = { explosion = false, fire = false } -- why is china puff allowed to crit lmao +tweak_data.projectiles.launcher_frag_china.damage = 82 tweak_data.projectiles.launcher_frag_china.player_damage = 16 -- arbiter -tweak_data.projectiles.launcher_frag_arbiter.damage = 30 -tweak_data.projectiles.launcher_frag_china.player_damage = 16 --- viper -tweak_data.projectiles.launcher_frag_ms3gl.damage = 26 +tweak_data.projectiles.launcher_frag_arbiter.damage = 70 +tweak_data.projectiles.launcher_frag_arbiter.player_damage = 16 +-- basilisk +tweak_data.projectiles.launcher_frag_ms3gl.damage = 56 tweak_data.projectiles.launcher_frag_ms3gl.player_damage = 16 -- gl40 -tweak_data.projectiles.launcher_frag.damage = 64 +tweak_data.projectiles.launcher_frag.damage = 82 tweak_data.projectiles.launcher_frag.player_damage = 16 -- compact 40 -tweak_data.projectiles.launcher_frag_slap.damage = 64 +tweak_data.projectiles.launcher_frag_slap.damage = 82 tweak_data.projectiles.launcher_frag_slap.player_damage = 16 -- underbarrel lf -tweak_data.projectiles.launcher_m203.damage = 48 +tweak_data.projectiles.launcher_m203.damage = 60 tweak_data.projectiles.launcher_m203.player_damage = 16 +-- underbarrel groza +tweak_data.projectiles.underbarrel_m203_groza.damage = 60 +tweak_data.projectiles.underbarrel_m203_groza.player_damage = 16 -- electric -tweak_data.projectiles.launcher_electric.damage = 14 -tweak_data.projectiles.launcher_electric_m32.damage = 14 -tweak_data.projectiles.launcher_electric_china.damage = 12 -tweak_data.projectiles.launcher_electric_slap.damage = 14 +tweak_data.projectiles.launcher_electric.damage = 10 +tweak_data.projectiles.launcher_electric_m32.damage = 10 +tweak_data.projectiles.launcher_electric_china.damage = 10 +tweak_data.projectiles.launcher_electric_slap.damage = 10 tweak_data.projectiles.launcher_electric_arbiter.damage = 10 -tweak_data.projectiles.underbarrel_electric.damage = 14 +tweak_data.projectiles.underbarrel_electric.damage = 10 +tweak_data.projectiles.underbarrel_electric_groza.damage = 10 +tweak_data.projectiles.launcher_electric_ms3gl.damage = 10 -- incendiary buff tweak_data.projectiles.fir_com.damage = 10 tweak_data.projectiles.fir_com.fire_dot_data.dot_damage = 30 -tweak_data.projectiles.fir_com.range = 600 +tweak_data.projectiles.fir_com.range = 800 -- frags buff tweak_data.projectiles.frag.damage = 200 tweak_data.projectiles.frag_com.damage = 200 @@ -66,15 +109,11 @@ tweak_data.projectiles.poison_gas_grenade.poison_gas_dot_data.dot_tick_period = tweak_data.projectiles.poison_gas_grenade.poison_gas_dot_data.dot_length = 10 tweak_data.projectiles.poison_gas_grenade.poison_gas_range = 400 tweak_data.projectiles.poison_gas_grenade.poison_gas_duration = 10 + tweak_data.projectiles.launcher_poison.damage = 9 tweak_data.projectiles.launcher_poison.poison_gas_range = 300 tweak_data.projectiles.launcher_poison.poison_gas_duration = 10 -tweak_data.projectiles.launcher_poison.poison_gas_dot_data = {hurt_animation_chance = 0.3, dot_damage = 4, dot_length = 10, dot_tick_period = 2} - -tweak_data.projectiles.launcher_poison_ms3gl_conversion = deep_clone(tweak_data.projectiles.launcher_poison) -tweak_data.projectiles.launcher_poison_ms3gl_conversion.damage = 7 -tweak_data.projectiles.launcher_poison_ms3gl_conversion.poison_gas_duration = 3 -tweak_data.projectiles.launcher_poison_ms3gl_conversion.poison_gas_range = 250 +tweak_data.projectiles.launcher_poison.poison_gas_dot_data = { hurt_animation_chance = 0.3, dot_damage = 4, dot_length = 10, dot_tick_period = 2 } tweak_data.projectiles.launcher_poison_gre_m79 = deep_clone(tweak_data.projectiles.launcher_poison) tweak_data.projectiles.launcher_poison_gre_m79.damage = 9 tweak_data.projectiles.launcher_poison_gre_m79.poison_gas_range = 300 diff --git a/lua/tweakdatavr.lua b/lua/tweakdatavr.lua new file mode 100644 index 0000000..fddd93b --- /dev/null +++ b/lua/tweakdatavr.lua @@ -0,0 +1,4 @@ +-- remove vr dmg resistance +Hooks:PostHook(TweakDataVR, "init", "eclipse_init", function(self) + self.long_range_damage_reduction = { 0, 0 } +end) diff --git a/lua/unitnetworkhandler.lua b/lua/unitnetworkhandler.lua index e19d74b..18364b9 100644 --- a/lua/unitnetworkhandler.lua +++ b/lua/unitnetworkhandler.lua @@ -16,9 +16,9 @@ function UnitNetworkHandler:sync_friendly_fire_damage(peer_id, unit, damage, var armor_piercing = true, variant = variant, col_ray = { - position = unit:position() + position = unit:position(), }, - push_vel = Vector3() + push_vel = Vector3(), } if variant == "bullet" or variant == "projectile" then @@ -31,45 +31,10 @@ function UnitNetworkHandler:sync_friendly_fire_damage(peer_id, unit, damage, var end end - if not Global.game_settings and Global.game_settings.one_down then + if not Global.game_settings and Global.game_settings.one_down then managers.job:set_memory("trophy_flawless", true, false) end end --- Force Cloakers to stand up before starting an attack on clients -Hooks:PreHook(UnitNetworkHandler, "action_spooc_start", "sh_action_spooc_start", function (self, unit) - if not self._verify_character(unit) or not self._verify_gamestate(self._gamestate_filter.any_ingame) then - return - end - - unit:movement():action_request({ - body_part = 4, - type = "stand" - }) - - unit:movement():action_request({ - body_part = 3, - type = "idle" - }) -end) - - --- Fix function ignoring the actual aim state parameter and always starting a shoot action (thanks to RedFlame) -function UnitNetworkHandler:action_aim_state(cop, start) - if not self._verify_gamestate(self._gamestate_filter.any_ingame) or not self._verify_character(cop) then - return - end - - if start then - cop:movement():action_request({ - block_type = "action", - body_part = 3, - type = "shoot" - }) - else - cop:movement():sync_action_aim_end() - end -end - -- Properly sync reload function UnitNetworkHandler:reload_weapon_cop(cop, sender) @@ -89,12 +54,11 @@ function UnitNetworkHandler:reload_weapon_cop(cop, sender) -- Otherwise request an actual reload action cop:movement():action_request({ body_part = 3, - type = "reload" + type = "reload", }) end end - -- Ignore duplicate grenade sync function UnitNetworkHandler:sync_smoke_grenade(detonate_pos, shooter_pos, duration, flashbang) if not self._verify_gamestate(self._gamestate_filter.any_ingame) then @@ -132,4 +96,4 @@ function UnitNetworkHandler:sync_cs_grenade(detonate_pos, shooter_pos, duration) self._last_grenade_pos = mvector3.copy(detonate_pos) managers.groupai:state():sync_cs_grenade(detonate_pos, shooter_pos, duration) -end \ No newline at end of file +end diff --git a/lua/upgradestweakdata.lua b/lua/upgradestweakdata.lua index f42b748..3a5ccdf 100644 --- a/lua/upgradestweakdata.lua +++ b/lua/upgradestweakdata.lua @@ -3,21 +3,58 @@ function UpgradesTweakData:_init_pd2_values(tweak_data) old_pd2_values_init(self, tweak_data) -- 100 skill points - self.values.rep_upgrades.values = {0} + self.values.rep_upgrades.values = { 0 } -- Movement speed nerfs self.values.player.body_armor.movement = { - 0.925, - 0.9, 0.85, + 0.825, 0.8, - 0.725, - 0.625, - 0.525 + 0.75, + 0.7, + 0.6, + 0.5, + } + self.values.player.body_armor.stamina = { + 0.85, + 0.825, + 0.8, + 0.75, + 0.7, + 0.6, + 0.5, + } + + -- steadiness + self.values.player.body_armor.damage_shake = { + 0.5, + 0.48, + 0.46, + 0.44, + 0.4, + 0.4, + 0.4, + } + + -- dodge + self.values.player.body_armor.dodge = { + 0.1, + 0.05, + 0, + -0.05, + -0.2, + -0.25, + -0.55, } -- ictv nerf self.values.player.body_armor.armor[7] = 13 + + -- bullseye nerf + self.on_headshot_dealt_cooldown = 5 + + -- make sna less cancer + self.values.player.shield_knock_bullet.chance = 0.7 end local old_init = UpgradesTweakData.init @@ -32,114 +69,125 @@ function UpgradesTweakData:init(tweak_data) self.weapon_movement_penalty.minigun = 0.75 self.weapon_movement_penalty.heavy = 0.75 - -- Skills ------------- -- Mastermind -- - -- Uppers - self.skill_descs.tea_cookies.multipro4 = "60" + -- Bandage + self.values.temporary.passive_revive_damage_reduction[1] = { 0.9, 5 } + self.skill_descs.combat_medic.multibasic = "10%" + self.skill_descs.combat_medic.multibasic2 = "5" + + -- Painkillers + self.values.temporary.passive_revive_damage_reduction[2] = { 0.5, 5 } + self.values.temporary.revived_damage_resist[1] = { 0.5, 5 } + self.skill_descs.tea_time.multibasic = "40%" + self.skill_descs.tea_time.multipro = "50%" + self.skill_descs.tea_time.multibasic2 = "5" + + -- Company Soul + self.skill_descs.fast_learner.multibasic = "8" + self.skill_descs.fast_learner.multipro = "50%" + + -- Combat Medic + self.values.player.revive_damage_reduction[1] = 0.6 + self.values.temporary.revive_damage_reduction[1][1] = 0.6 + self.values.player.revive_interaction_speed_multiplier = { 2 / 3 } + self.skill_descs.tea_cookies.multibasic = "40%" + self.skill_descs.tea_cookies.multibasic2 = "5" + self.skill_descs.tea_cookies.multipro = "33%" + self.skill_descs.tea_cookies.multipro2 = "25%" + self.skill_descs.tea_cookies.multipro3 = "10" -- Inspire self.morale_boost_speed_bonus = 1.3 self.morale_boost_reload_speed_bonus = 1.3 - self.morale_boost_time = 6 - self.values.player.revive_interaction_speed_multiplier = {1 / 3} - self.skill_descs.inspire.multibasic = "30%" - self.skill_descs.inspire.multibasic2 = "6" - self.skill_descs.inspire.multipro = "200%" + self.morale_boost_time = 7 + self.values.cooldown.long_dis_revive[1][2] = 120 + self.skill_descs.inspire.multibasic = "7m" + self.skill_descs.inspire.multibasic2 = "30%" + self.skill_descs.inspire.multibasic3 = "7" + self.skill_descs.inspire.multipro = "120" -- FFriendship self.skill_descs.triathlete.multibasic = "4" self.skill_descs.triathlete.multipro = "75%" -- Confident - self.values.team.damage = { - hostage_absorption = {0.25}, - hostage_absorption_limit = 3 - } - self.skill_descs.cable_guy.multipro = "2.5" - self.skill_descs.cable_guy.multipro2 = "3" + self.skill_descs.cable_guy.multipro = "50%" -- PiC - self.values.player.passive_convert_enemies_health_multiplier = {0.54, 0.02} - self.values.player.minion_master_health_multiplier = {1.1} - self.skill_descs.control_freak.multipro3 = "10%" - self.skill_descs.control_freak.multipro4 = "53%" + self.values.player.passive_convert_enemies_health_multiplier = { 0.10, 0.01 } + self.skill_descs.control_freak.multibasic3 = "90%" + self.skill_descs.control_freak.multipro4 = "9%" -- Hostage Situation - self.values.team.hostage_situation = {15} self.definitions.team_hostage_situation = { - category = "feature", + category = "feature", name_id = "hostage_situation", upgrade = { - category = "team", + category = "team", upgrade = "hostage_situation", - value = 1 - } + value = 1, + }, } - self.skill_descs.stockholm_syndrome.multipro = "15" - self.skill_descs.stockholm_syndrome.multipro2 = "25" + self.values.team.hostage_situation = { 4 } + self.values.team.damage = { + hostage_absorption = { 0.2 }, + hostage_absorption_limit = 4, + } + self.skill_descs.stockholm_syndrome.multibasic = "2" + self.skill_descs.stockholm_syndrome.multipro = "4" -- Hostage Taker - self.values.player.hostage_min_sum_taker = {2, 1} - self.values.player.joker_counts_for_hostage_boost = {true} - self.values.player.hostage_health_regen_addend = {1, 1.2} + self.values.player.hostage_min_sum_taker = { 1, 1 } + self.values.player.joker_counts_for_hostage_boost = { true } + self.values.player.hostage_health_regen_addend = { 1, 1.2 } self.definitions.player_hostage_min_sum_taker_1 = { - category = "feature", + category = "feature", name_id = "hostage_min_sum_taker", upgrade = { - category = "player", + category = "player", upgrade = "hostage_min_sum_taker", - value = 1 - } + value = 1, + }, } self.definitions.player_hostage_min_sum_taker_2 = { - category = "feature", + category = "feature", name_id = "hostage_min_sum_taker", upgrade = { - category = "player", + category = "player", upgrade = "hostage_min_sum_taker", - value = 2 - } + value = 2, + }, } self.definitions.player_joker_counts_for_hostage_boost = { - category = "feature", + category = "feature", name_id = "joker_counts_for_hostage_boost", upgrade = { - category = "player", + category = "player", upgrade = "joker_counts_for_hostage_boost", - value = 1 - } + value = 1, + }, } - self.skill_descs.black_marketeer.multibasic = "2" + self.skill_descs.black_marketeer.multibasic = "1" self.skill_descs.black_marketeer.multibasic2 = "10" self.skill_descs.black_marketeer.multibasic3 = "5" - self.skill_descs.black_marketeer.multipro = "1" - self.skill_descs.black_marketeer.multipro2 = "12" + self.skill_descs.black_marketeer.multipro = "12" -- Lock N' Load - self.definitions.weapon_generic_swap_speed_multiplier = { - name_id = "menu_generic_swap_speed_multiplier", - category = "feature", - upgrade = { - value = 1, - upgrade = "generic_swap_speed_multiplier", - category = "weapon" - } - } - self.values.weapon.generic_swap_speed_multiplier = {1.25} + self.values.weapon.swap_speed_multiplier = { 1.25 } self.skill_descs.rifleman.multibasic2 = "25%" -- Kilmer - self.values.snp.reload_speed_multiplier = {1.25} - self.values.assault_rifle.reload_speed_multiplier = {1.25} - self.values.temporary.single_shot_fast_reload[1][1] = 1.6 - self.values.temporary.single_shot_fast_reload[1][2] = 3 + self.values.snp.reload_speed_multiplier = { 1.25 } + self.values.assault_rifle.reload_speed_multiplier = { 1.25 } + self.values.temporary.single_shot_fast_reload[1][1] = 1.4 + self.values.temporary.single_shot_fast_reload[1][2] = 6 self.skill_descs.speedy_reload.multibasic = "25%" - self.skill_descs.speedy_reload.multipro = "60%" - self.skill_descs.speedy_reload.multipro2 = "3" + self.skill_descs.speedy_reload.multipro = "40%" + self.skill_descs.speedy_reload.multipro2 = "6" -- Mind Blown self.values.snp.graze_damage = { @@ -147,14 +195,14 @@ function UpgradesTweakData:init(tweak_data) radius = 500, times = 1, damage_factor = 0.35, - damage_factor_kill = 0.35 + damage_factor_kill = 0.35, }, { radius = 500, times = 1, damage_factor = 0.35, - damage_factor_kill = 1 - } + damage_factor_kill = 1, + }, } self.skill_descs.single_shot_ammo_return.multibasic = "35%" self.skill_descs.single_shot_ammo_return.multibasic2 = "5m" @@ -162,68 +210,164 @@ function UpgradesTweakData:init(tweak_data) -- Enforcer -- - -- Underdog - self.values.temporary.dmg_multiplier_outnumbered = {{1.1, 3}} - self.skill_descs.underdog.multibasic2 = "10%" - self.skill_descs.underdog.multibasic3 = "3" - - -- Overkill - self.definitions.player_overkill_damage_multiplier_2 = { - name_id = "menu_player_overkill_damage_multiplier", - category = "temporary", + -- Hard Boiled + self.values.shotgun.swap_speed_multiplier = { 1.25 } + self.definitions.shotgun_swap_speed_multiplier = { + name_id = "menu_shotgun_swap_speed_multiplier", + category = "feature", upgrade = { - value = 2, - upgrade = "overkill_damage_multiplier", - category = "temporary" - } + value = 1, + upgrade = "swap_speed_multiplier", + category = "shotgun", + }, } + self.skill_descs.underdog.multibasic = "5%" + self.skill_descs.underdog.multipro = "25%" - self.values.temporary.overkill_damage_multiplier = { - {1.3, 5}, - {1.45, 12} + -- Fast Hands + self.values.shotgun.pump_reload_speed = { 1.25, 1.5 } + self.definitions.shotgun_pump_reload_speed_1 = { + name_id = "menu_shotgun_pump_reload_speed", + category = "feature", + upgrade = { + value = 1, + upgrade = "pump_reload_speed", + category = "shotgun", + }, } + self.definitions.shotgun_pump_reload_speed_2 = { + name_id = "menu_shotgun_pump_reload_speed", + category = "feature", + upgrade = { + value = 2, + upgrade = "pump_reload_speed", + category = "shotgun", + }, + } + self.skill_descs.shotgun_cqb.multibasic = "25%" + self.skill_descs.shotgun_cqb.multipro = "25%" + + -- Point Blank + self.values.shotgun.extra_pellets = { 4 } + self.values.shotgun.hip_fire_spread_multiplier[1] = 1.25 + self.definitions.shotgun_extra_pellets = { + name_id = "menu_shotgun_extra_pellets", + category = "feature", + upgrade = { + value = 1, + upgrade = "extra_pellets", + category = "shotgun", + }, + } + self.skill_descs.shotgun_impact.multibasic = "4" + self.skill_descs.shotgun_impact.multipro = "25%" - self.skill_descs.overkill.multibasic = "30%" - self.skill_descs.overkill.multibasic2 = "5" - self.skill_descs.overkill.multipro = "45%" - self.skill_descs.overkill.multipro2 = "12" - self.skill_descs.overkill.multipro3 = "80%" - - -- Close By + -- Shotgun CQB + self.definitions.shotgun_speed_stack_on_kill = { + name_id = "menu_shotgun_speed_stack_on_kill", + category = "feature", + upgrade = { + value = 1, + upgrade = "speed_stack_on_kill", + category = "shotgun", + }, + } + self.values.shotgun.hip_rate_of_fire[1] = 1.15 + self.values.shotgun.speed_stack_on_kill = { { + max_stacks = 5, + max_time = 12, + speed_bonus = 1.08, + } } + self.skill_descs.far_away.multibasic = "15%" + self.skill_descs.far_away.multipro = "8%" + self.skill_descs.far_away.multipro2 = "12" + self.skill_descs.far_away.multipro3 = "5" + + -- Mag-fed Specialist + self.values.shotgun.mag_reload_speed = { 1.25 } self.values.shotgun.magazine_capacity_inc[1] = 5 - self.skill_descs.close_by.multipro2 = "5" + self.definitions.shotgun_mag_reload_speed = { + name_id = "menu_shotgun_mag_reload_speed", + category = "feature", + upgrade = { + value = 1, + upgrade = "mag_reload_speed", + category = "shotgun", + }, + } + self.skill_descs.close_by.multibasic = "25%" + self.skill_descs.close_by.multipro = "5" + + -- Shotgun Hell + self.definitions.cooldown_shotgun_panic_on_kill = { + name_id = "menu_cooldown_shotgun_panic_on_kill", + category = "cooldown", + upgrade = { + value = 1, + upgrade = "shotgun_panic_on_kill", + category = "cooldown", + }, + } + self.values.shotgun.consume_no_ammo_chance[1] = 0.125 + self.values.cooldown.shotgun_panic_on_kill = { { 1, 5 } } + self.values.shotgun.panic = { { chance = 0.75, area = 800, amount = "panic" } } + self.skill_descs.overkill.multibasic = "12.5%" + self.skill_descs.overkill.multipro = "75%" + self.skill_descs.overkill.multipro2 = "5" -- Resilience - self.values.player.flashbang_multiplier = {0.75, 0.75} - self.skill_descs.oppressor.multipro2 = "25%" + self.values.player.armor_regen_timer_multiplier[1] = 0.95 + self.values.player.flashbang_multiplier = { 0.5, 0.5 } + self.skill_descs.oppressor.multibasic2 = "5%" + self.skill_descs.oppressor.multipro2 = "50%" + + -- Underdog + self.skill_descs.pack_mule.multibasic = "10" + self.skill_descs.pack_mule.multibasic2 = "15%" + self.skill_descs.pack_mule.multibasic3 = "7" + self.skill_descs.pack_mule.multipro = "18" + self.skill_descs.pack_mule.multipro2 = "10%" + self.skill_descs.pack_mule.multipro3 = "7" -- Thick Skin self.values.player.damage_shake_addend[1] = 1 self.skill_descs.show_of_force.multibasic = "10" + -- Shock and Awe + self.values.team.armor.regen_time_multiplier[1] = 0.9 + self.skill_descs.iron_man.multibasic2 = "10%" + + -- Bullseye + self.values.player.headshot_regen_armor_bonus[2] = 4.5 + self.skill_descs.prison_wife.multibasic2 = "5" + self.skill_descs.prison_wife.multipro3 = "40" + + -- Scavenger + self.values.player.increased_pickup_area[1] = 1.3 + self.skill_descs.scavenging.multibasic = "30%" + -- Bulletstorm self.skill_descs.bandoliers.multibasic = "12" self.skill_descs.bandoliers.multipro2 = "30" - -- Saw Massacre - self.skill_descs.ammo_reservoir.multibasic2 = "50%" - self.skill_descs.ammo_reservoir.multipro = "50%" - self.skill_descs.ammo_reservoir.multipro3 = "10" - -- Fully Loaded - self.values.player.regain_throwable_from_ammo[1].chance = -0.06 - self.values.player.regain_throwable_from_ammo[1].chance_inc = 0.003 - self.values.player.extra_ammo_multiplier[1] = 1.25 - self.values.player.pick_up_ammo_multiplier = {1.35, 1.45} - self.skill_descs.carbon_blade.multibasic = "-6%" - self.skill_descs.carbon_blade.multibasic2 = "0.3%" - self.skill_descs.carbon_blade.multipro2 = "25%" - self.skill_descs.carbon_blade.multipro3 = "45%" + self.values.player.body_armor.skill_ammo_mul = { + 1.04, + 1.05, + 1.06, + 1.07, + 1.08, + 1.1, + 1.12, + } + self.skill_descs.ammo_reservoir.multibasic = "12%" + self.skill_descs.ammo_reservoir.multipro = "25%" -- Technician -- -- Rifleman - self.skill_descs.defense_up.multipro = "16" + self.skill_descs.defense_up.multibasic = "50%" + self.skill_descs.defense_up.multipro = "50%" -- Die Hard self.values.player.interacting_damage_multiplier[1] = 0.25 @@ -237,14 +381,16 @@ function UpgradesTweakData:init(tweak_data) self.skill_descs.tower_defense.multipro2 = "50%" -- PhD in Engineering + self.values.sentry_gun.standstill_omniscience = { true } self.definitions.sentry_gun_standstill_omniscience = { name_id = "menu_sentry_gun_standstill_omniscience", category = "feature", upgrade = { value = 1, upgrade = "standstill_omniscience", - category = "player" - } + synced = true, + category = "sentry_gun", + }, } self.skill_descs.eco_sentry.multibasic = "100%" self.skill_descs.eco_sentry.multipro = "75%" @@ -259,12 +405,12 @@ function UpgradesTweakData:init(tweak_data) self.skill_descs.kick_starter.multibasic = "30%" -- Combat Engineering - self.values.trip_mine.explosion_size_multiplier_1 = {1.5} + self.values.trip_mine.explosion_size_multiplier_1 = { 1.5 } self.skill_descs.combat_engineering.multibasic = "50%" -- More Firepower - self.values.shape_charge.quantity = {2, 5} - self.values.trip_mine.quantity = {5, 15} + self.values.shape_charge.quantity = { 2, 5 } + self.values.trip_mine.quantity = { 5, 15 } self.skill_descs.more_fire_power.multibasic = "2" self.skill_descs.more_fire_power.multibasic2 = "5" self.skill_descs.more_fire_power.multipro = "3" @@ -284,16 +430,16 @@ function UpgradesTweakData:init(tweak_data) upgrade = { value = 2, upgrade = "suppression_multiplier", - category = "player" - } + category = "player", + }, } - self.values.player.suppression_multiplier = {1.15, 1.45} + self.values.player.suppression_multiplier = { 1.15, 1.45 } self.skill_descs.heavy_impact.multibasic = "15%" self.skill_descs.heavy_impact.multipro = "30%" - -- Fast Hands - self.values.lmg.reload_speed_multiplier = {1.2} - self.values.smg.reload_speed_multiplier = {1.2} + -- Sleight of Hand + self.values.lmg.reload_speed_multiplier = { 1.2 } + self.values.smg.reload_speed_multiplier = { 1.2 } self.skill_descs.shock_and_awe.multibasic = "20%" -- Mag Plus @@ -303,26 +449,26 @@ function UpgradesTweakData:init(tweak_data) upgrade = { value = 2, upgrade = "automatic_mag_increase", - category = "player" - } + category = "player", + }, } - self.values.player.automatic_mag_increase = {5, 15} + self.values.player.automatic_mag_increase = { 5, 15 } self.skill_descs.fast_fire.multibasic = "5" self.skill_descs.fast_fire.multipro = "10" - -- LMG Specialist - self.values.player.no_movement_penalty = {true} + -- Heavy Gun Expert + self.values.player.no_movement_penalty = { true } self.definitions.player_no_movement_penalty = { name_id = "menu_player_no_movement_penalty", category = "feature", upgrade = { value = 1, upgrade = "no_movement_penalty", - category = "player" - } + category = "player", + }, } - self.values.weapon.automatic_head_shot_add[1] = 0.6 - self.skill_descs.body_expertise.multipro = "60%" + self.values.weapon.automatic_head_shot_add[1] = 0.8 + self.skill_descs.body_expertise.multipro = "80%" -- Ghost -- @@ -330,13 +476,14 @@ function UpgradesTweakData:init(tweak_data) self.values.player.melee_concealment_modifier[1] = 1 self.values.player.ballistic_vest_concealment[1] = 3 self.skill_descs.cleaner.multibasic2 = "1" - self.skill_descs.cleaner.multipro = "3" + self.skill_descs.cleaner.multipro2 = "3" - -- Quick Grab - self.values.carry.interact_speed_multiplier = {0.5, 0.25} - self.values.player.pick_lock_easy_speed_multiplier[1] = 0.75 + -- Logistician + self.values.carry.interact_speed_multiplier = { 0.5, 0.25 } + self.values.player.pick_lock_easy_speed_multiplier[1] = 0.5 self.skill_descs.second_chances.multibasic = "50%" - self.skill_descs.second_chances.multipro = "25%" + self.skill_descs.second_chances.multipro = "50%" + self.skill_descs.second_chances.multipro2 = "50%" -- ECM Feedback self.ecm_feedback_retrigger_interval = 120 @@ -356,8 +503,9 @@ function UpgradesTweakData:init(tweak_data) self.skill_descs.chameleon.multibasic2 = "3" -- Athlete - self.skill_descs.sprinter.multibasic2 = "10%" + self.skill_descs.sprinter.multibasic = "25%" self.skill_descs.sprinter.multipro = "25%" + self.skill_descs.sprinter.multipro2 = "10%" -- Duck and Cover self.values.player.crouch_speed_multiplier[1] = 1.15 @@ -366,50 +514,97 @@ function UpgradesTweakData:init(tweak_data) self.skill_descs.awareness.multipro = "10%" -- Sprinter - self.values.player.run_speed_multiplier[1] = 1.2 + self.values.player.run_speed_multiplier[1] = 1.15 self.values.player.on_zipline_dodge_chance[1] = 0.1 - self.skill_descs.optic_illusions.multibasic = "20%" + self.skill_descs.optic_illusions.multibasic = "15%" self.skill_descs.optic_illusions.multipro = "10%" + -- Second Wind + self.definitions.cooldown_panic_on_armor_break = { + name_id = "menu_cooldown_panic_on_armor_break", + category = "cooldown", + upgrade = { + value = 1, + upgrade = "panic_on_armor_break", + category = "cooldown", + }, + } + self.values.cooldown.panic_on_armor_break = { { 1, 12 } } + self.values.player.armor_panic = { { chance = 0.75, area = 500, amount = "panic" } } + self.values.temporary.damage_speed_multiplier = { { 1.25, 5 } } + self.skill_descs.dire_need.multibasic = "25%" + self.skill_descs.dire_need.multibasic2 = "5" + self.skill_descs.dire_need.multipro = "75%" + self.skill_descs.dire_need.multipro2 = "12" + + -- Shockproof + self.values.player.weaker_tase_effect = { 0.33 } + self.definitions.player_weaker_tase_effect = { + name_id = "menu_weaker_tase_effect", + category = "feature", + upgrade = { + value = 1, + upgrade = "weaker_tase_effect", + category = "player", + }, + } + self.skill_descs.insulation.multibasic = "33%" + -- Sneaky Bastard self.values.player.detection_risk_add_dodge_chance = { - {0.01, 2, "below", 35, 0.1}, - {0.015, 1, "below", 35, 0.15} + { 0.01, 2, "below", 35, 0.1 }, + { 0.015, 1, "below", 35, 0.15 }, } self.skill_descs.jail_diet.multibasic2 = "2" self.skill_descs.jail_diet.multipro = "1.5%" self.skill_descs.jail_diet.multipro4 = "15%" + -- Resilient Assault + self.values.player.critical_hit_chance[1] = 0.05 + self.values.player.armor_depleted_stagger_shot = { + 0, + 3, + } + self.skill_descs.scavenger.multibasic = "5%" + self.skill_descs.scavenger.multipro = "3" + -- Eagle Eye self.values.weapon.special_damage_taken_multiplier[1] = 1.1 self.values.player.marked_distance_mul[1] = 4 self.skill_descs.thick_skin.multibasic = "10%" self.skill_descs.thick_skin.multipro = "4" - self.skill_descs.thick_skin.multipro2 = "100%" -- The Professional self.skill_descs.silence_expert.multibasic = "8" self.skill_descs.silence_expert.multipro = "12" self.skill_descs.silence_expert.multipro2 = "100%" + -- HVT + self.values.player.marked_inc_dmg_distance[1][2] = 1.2 + self.values.player.marked_enemy_damage_mul = 1.3 + self.skill_descs.hitman.multibasic = "20%" + self.skill_descs.hitman.multibasic2 = "10" + self.skill_descs.hitman.multipro = "30%" + self.skill_descs.hitman.multipro2 = "100%" + -- Silencer Expert self.definitions.weapon_silencer_damage_multiplier = { - category = "feature", + category = "feature", name_id = "silencer_damage_multiplier", upgrade = { - category = "weapon", + category = "weapon", upgrade = "silencer_damage_multiplier", - value = 1 - } + value = 1, + }, } self.definitions.weapon_armor_piercing_chance_silencer = { - category = "feature", + category = "feature", name_id = "armor_piercing_chance_silencer", upgrade = { - category = "weapon", + category = "weapon", upgrade = "armor_piercing_chance_silencer", - value = 1 - } + value = 1, + }, } self.values.weapon.armor_piercing_chance_silencer[1] = 0.5 self.skill_descs.backstab.multibasic = "1" @@ -418,58 +613,79 @@ function UpgradesTweakData:init(tweak_data) self.skill_descs.backstab.multipro2 = "50%" -- Low Blow + self.definitions.weapon_extra_crit_damage_mul = { + category = "feature", + name_id = "extra_crit_damage_mul", + upgrade = { + category = "weapon", + upgrade = "extra_crit_damage_mul", + value = 1, + }, + } self.values.player.detection_risk_add_crit_chance = { - {0.04, 2, "below", 35, 0.4}, - {0.06, 1, "below", 35, 0.6} + { 0.03, 2, "below", 35, 0.3 }, + { 0.03, 1, "below", 35, 0.3 }, } - self.skill_descs.unseen_strike.multibasic = "4%" + self.values.weapon.extra_crit_damage_mul = { 1 } + self.skill_descs.unseen_strike.multibasic = "3%" self.skill_descs.unseen_strike.multibasic2 = "2" self.skill_descs.unseen_strike.multibasic3 = "35" - self.skill_descs.unseen_strike.multibasic4 = "40%" - self.skill_descs.unseen_strike.multipro = "6%" + self.skill_descs.unseen_strike.multibasic4 = "30%" + self.skill_descs.unseen_strike.multibasic5 = "200%" + self.skill_descs.unseen_strike.multipro = "3%" self.skill_descs.unseen_strike.multipro2 = "1" - self.skill_descs.unseen_strike.multipro3 = "35" - self.skill_descs.unseen_strike.multipro4 = "60%" - - -- HVT basic buff and aced nerf - self.values.player.marked_enemy_damage_mul = 1.25 - self.values.player.marked_inc_dmg_distance[1][2] = 1.3 - self.skill_descs.hitman.multibasic = "25%" - self.skill_descs.hitman.multipro = "30%" - self.skill_descs.hitman.multipro2 = "10" - self.skill_descs.hitman.multipro3 = "100%" + self.skill_descs.unseen_strike.multipro3 = "100%" -- Fugitive -- -- Equilbrium nerf - self.values.pistol.swap_speed_multiplier = {1.25} -- funny how this is actually 50% in vanilla and not 30% - self.skill_descs.equilibrium.multibasic3 = "25%" + self.values.pistol.swap_speed_multiplier = { 1.10 } -- funny how this is actually 50% in vanilla and not 30% + self.skill_descs.equilibrium.multibasic3 = "10%" - -- Trigger Happy rework + -- Trigger Happy self.values.pistol.stacking_hit_damage_multiplier = { - {max_stacks = 5, max_time = 3, damage_bonus = 1.15}, - {max_stacks = 5, max_time = 6, damage_bonus = 1.15} + { max_stacks = 5, max_time = 3, damage_bonus = 1.15 }, + { max_stacks = 2, max_time = 6, damage_bonus = 1.375 }, } - self.skill_descs.trigger_happy.multibasic4 = "15%" + self.skill_descs.trigger_happy.multibasic = "15%" self.skill_descs.trigger_happy.multibasic2 = "3" self.skill_descs.trigger_happy.multibasic3 = "5" + self.skill_descs.trigger_happy.multipro = "37.5%" self.skill_descs.trigger_happy.multipro2 = "6" + self.skill_descs.trigger_happy.multipro3 = "2" + + -- Quick Fix + self.skill_descs.running_from_death.multipro = "10%" + self.skill_descs.running_from_death.multipro2 = "120" + + -- Running from Death + self.skill_descs.up_you_go.multibasic = "100%" + self.skill_descs.up_you_go.multipro = "30%" + self.skill_descs.up_you_go.multipro2 = "10" + + -- Uppers + self.values.first_aid_kit.quantity = { 3, 7 } + self.values.first_aid_kit.first_aid_kit_auto_recovery[1] = 300 + self.skill_descs.feign_death.multibasic = "3" + self.skill_descs.feign_death.multipro = "4" + self.skill_descs.feign_death.multipro2 = "3" + self.skill_descs.feign_death.multipro3 = "90" -- Swan Song - self.values.temporary.berserker_damage_multiplier[2] = {1, 9} + self.values.temporary.berserker_damage_multiplier[2] = { 1, 9 } self.skill_descs.perseverance.multipro = "6" -- Messiah - self.values.player.messiah_revive_from_bleed_out = {1, 3} - self.values.player.increased_bleedout_timer = {10} + self.values.player.messiah_revive_from_bleed_out = { 1, 3 } + self.values.player.increased_bleedout_timer = { 10 } self.definitions.player_messiah_revive_from_bleed_out_2 = { name_id = "menu_player_pistol_revive_from_bleed_out", category = "feature", upgrade = { value = 2, upgrade = "messiah_revive_from_bleed_out", - category = "player" - } + category = "player", + }, } self.definitions.player_increased_bleedout_timer = { name_id = "menu_player_increased_bleedout_timer", @@ -477,8 +693,8 @@ function UpgradesTweakData:init(tweak_data) upgrade = { value = 1, upgrade = "increased_bleedout_timer", - category = "player" - } + category = "player", + }, } self.skill_descs.messiah.multibasic = "1" self.skill_descs.messiah.multibasic2 = "10" @@ -489,52 +705,51 @@ function UpgradesTweakData:init(tweak_data) self.skill_descs.steroids.multibasic = "200%" -- Bloodthirst - self.values.player.melee_damage_stacking = {{max_multiplier = 5, melee_multiplier = 0.5}} + self.values.player.melee_damage_stacking = { { max_multiplier = 5, melee_multiplier = 0.5 } } self.skill_descs.bloodthirst.multibasic2 = "500%" -- Zerker - self.values.player.melee_damage_health_ratio_multiplier = {1.5} - self.values.player.movement_speed_damage_health_ratio_multiplier = {0.25} + self.values.player.movement_speed_damage_health_ratio_multiplier = { 0.2 } self.skill_descs.wolverine.multibasic = "50%" - self.skill_descs.wolverine.multibasic2 = "150%" + self.skill_descs.wolverine.multibasic2 = "20%" self.skill_descs.wolverine.multipro = "50%" - self.skill_descs.wolverine.multipro2 = "25%" + self.skill_descs.wolverine.multipro2 = "250%" -- Counterstrike - self.values.player.run_and_melee_eclipse = {true} - self.values.cooldown.counter_strike_eclipse = {{1, 10}} + self.values.player.run_and_melee_eclipse = { true } + self.values.cooldown.counter_strike_eclipse = { { 1, 10 } } self.definitions.player_run_and_melee_eclipse = { - name_id = "menu_player_run_and_melee_eclipse", - category = "feature", - upgrade = { - value = 1, - upgrade = "run_and_melee_eclipse", - category = "player" - } - } - self.definitions.player_counter_strike_eclipse_2 = { - name_id = "menu_player_counter_strike_eclipse", - category = "feature", - upgrade = { - value = 2, - upgrade = "counter_strike_eclipse", - category = "player" - } - } - self.definitions.cooldown_counter_strike_eclipse = { - name_id = "menu_cooldown_counter_strike_eclipse", + name_id = "menu_player_run_and_melee_eclipse", + category = "feature", + upgrade = { + value = 1, + upgrade = "run_and_melee_eclipse", + category = "player", + }, + } + self.definitions.player_counter_strike_eclipse_2 = { + name_id = "menu_player_counter_strike_eclipse", + category = "feature", + upgrade = { + value = 2, + upgrade = "counter_strike_eclipse", + category = "player", + }, + } + self.definitions.cooldown_counter_strike_eclipse = { + name_id = "menu_cooldown_counter_strike_eclipse", category = "cooldown", upgrade = { value = 1, upgrade = "counter_strike_eclipse", - category = "cooldown" - } - } + category = "cooldown", + }, + } self.skill_descs.drop_soap.multipro = "10" -- Frenzy - self.values.player.health_damage_reduction = {0.85, 0.65} - self.values.player.max_health_reduction = {0.1} + self.values.player.health_damage_reduction = { 0.85, 0.65 } + self.values.player.max_health_reduction = { 0.1 } self.skill_descs.frenzy.multibasic = "10%" self.skill_descs.frenzy.multibasic2 = "15%" self.skill_descs.frenzy.multipro = "35%" @@ -542,166 +757,248 @@ function UpgradesTweakData:init(tweak_data) -- Perk Decks ------------- - -- Infiltrator / Socio healing - self.values.player.melee_kill_life_leech = {1} - self.specialization_descs[9][5].multiperk = "10" - self.values.temporary.melee_life_leech[1][1] = 3 - self.specialization_descs[8][9].multiperk = "30" - -- Crew Chief - self.values.team.health.hostage_multiplier = {1.04} + self.values.team.health.hostage_multiplier = { 1.04 } self.specialization_descs[1][9].multiperk = "4%" -- Muscle - self.values.player.passive_health_regen = {0.8} + self.values.player.passive_health_regen = { 0.8 } + self.values.temporary.mrwi_health_invulnerable[1][3] = 30 self.specialization_descs[2][9].multiperk = "40%" self.specialization_descs[2][9].multiperk2 = "8" - - -- Hitman - self.values.player.perk_armor_regen_timer_multiplier[5] = 0.4 - self.specialization_descs[5][9].multiperk = "25%" + self.specialization_descs[2][7].multiperk = "50%" + self.specialization_descs[2][7].multiperk2 = "2" + self.specialization_descs[2][7].multiperk3 = "30" -- Armorer - self.values.temporary.armor_break_invulnerable = {{2, 24}} - self.specialization_descs[3][7].multiperk3 = "24" + self.values.temporary.armor_break_invulnerable = { { 2, 30 } } + self.specialization_descs[3][7].multiperk3 = "30" self.specialization_descs[3][1].multiperk = "5%" self.specialization_descs[3][3].multiperk = "5%" self.specialization_descs[3][5].multiperk = "5%" - -- Subtle card gives lower dodge - self.values.player.passive_dodge_chance = {0.05, 0.2, 0.3} + -- Rogue (and other dodge decks) + self.values.player.passive_dodge_chance = { 0.05, 0.15, 0.25 } + self.values.player.tier_dodge_chance = { 0.1, 0.15, 0.2 } self.specialization_descs[4][1].multiperk = "5%" + self.specialization_descs[4][5].multiperk = "10%" self.specialization_descs[4][7].multiperk = "10%" self.specialization_descs[6][1].multiperk = "5%" + self.specialization_descs[7][1].multiperk = "10%" self.specialization_descs[13][5].multiperk3 = "5%" self.specialization_descs[18][5].multiperk = "5%" self.specialization_descs[21][5].multiperk2 = "5%" - -- Anarchist - self.values.player.armor_grinding = {{ - {1, 1.5}, - {1.5, 2.25}, - {2, 3}, - {2.5, 3.75}, - {3.5, 4.5}, - {5, 6}, - {7, 9} - }} - self.values.player.damage_to_armor = {{ - {1, 1.5}, - {1, 1.5}, - {1, 1.5}, - {1, 1.5}, - {1, 1.5}, - {1, 1.5}, - {1, 1.5} - }} - self.values.player.armor_increase = {0.5, 0.75, 1} - self.values.player.tier_dodge_chance[1] = 0.15 - self.specialization_descs[15][3].multiperk2 = "50%" - self.specialization_descs[15][5].multiperk2 = "75%" - self.specialization_descs[15][7].multiperk2 = "100%" - self.specialization_descs[15][7].multiperk3 = "5%" + -- Hitman + self.values.player.primary_reload_secondary[1] = 5 + self.values.player.secondary_reload_primary[1] = 5 + self.values.temporary.unseen_strike[1] = { 1.2, 5 } + self.values.cooldown.hitman_ammo_refund = { { 1, 2 } } + self.specialization_descs[5][1].multiperk = "5" + self.specialization_descs[5][3].multiperk = "80%" + self.specialization_descs[5][5].multiperk = "20%" + self.specialization_descs[5][5].multiperk2 = "5" + self.specialization_descs[5][5].multiperk3 = "4" + self.specialization_descs[5][7].multiperk = "1" + self.specialization_descs[5][7].multiperk2 = "3" + self.specialization_descs[5][9].multiperk = "30" + self.specialization_descs[5][9].multiperk2 = "1" + self.definitions.cooldown_hitman_ammo_refund = { + name_id = "menu_cooldown_hitman_ammo_refund", + category = "cooldown", + upgrade = { + value = 1, + upgrade = "hitman_ammo_refund", + category = "cooldown", + }, + } -- Crook - self.values.player.level_2_armor_multiplier[3] = 1.8 - self.values.player.level_3_armor_multiplier[3] = 1.7 - self.values.player.level_4_armor_multiplier[3] = 1.65 - self.values.player.level_2_dodge_addend[3] = 0.2 - self.values.player.level_3_dodge_addend[3] = 0.2 - self.values.player.level_4_dodge_addend[3] = 0.2 - self.specialization_descs[6][5].multiperk = "5%" + self.values.player.level_2_dodge_addend = { + 0.05, + 0.15, + 0.20, + } + self.values.player.level_3_dodge_addend = { + 0.05, + 0.15, + 0.225, + } + self.values.player.level_2_armor_multiplier = { + 1.1, + 1.2, + 1.35, + } + self.values.player.level_3_armor_multiplier = { + 1.2, + 1.3, + 1.5, + } + + -- Infiltrator / Socio healing + self.values.player.melee_kill_life_leech = { 1.5 } + self.specialization_descs[9][5].multiperk = "15" + self.values.temporary.melee_life_leech[1][1] = 4 + self.specialization_descs[8][9].multiperk = "40" -- Gambler - for _, v in pairs(self.values.temporary.loose_ammo_restore_health) do v[2] = 10 end + self.values.player.pick_up_ammo_multiplier[1] = 1.15 + for _, v in pairs(self.values.temporary.loose_ammo_restore_health) do + v[2] = 10 + end self.values.temporary.loose_ammo_give_team[1][2] = 5 - self.values.player.increased_pickup_area = {1.5, 2.25} + self.values.player.increased_pickup_area[2] = 2.25 self.definitions.player_increased_pickup_area_2 = { name_id = "menu_player_increased_pickup_area", category = "feature", upgrade = { value = 2, upgrade = "increased_pickup_area", - category = "player" - } - } - self.values.player.addition_ammo_eclipse = {0.15} - self.definitions.player_addition_ammo_eclipse = { - name_id = "menu_player_addition_ammo_eclipse", - category = "feature", - upgrade = { - value = 1, - upgrade = "addition_ammo_eclipse", - category = "player" - } + category = "player", + }, } self.specialization_descs[10][1].multiperk3 = "10" self.specialization_descs[10][3].multiperk = "15%" self.specialization_descs[10][9].multiperk4 = "125%" -- Grinder - self.damage_to_hot_data.tick_time = 0.8 - self.damage_to_hot_data.armors_allowed = {"level_1"} - self.specialization_descs[11][1].multiperk2 = "0.8" - self.specialization_descs[11][3].multiperk2 = "0.8" - self.specialization_descs[11][5].multiperk2 = "0.8" - self.specialization_descs[11][7].multiperk2 = "0.8" - self.specialization_descs[11][9].multiperk2 = "0.8" - - -- Biker - self.values.player.wild_health_amount = {0.25} - self.specialization_descs[16][1].multiperk = "2.5" - - -- Ex-President - self.values.player.body_armor.skill_max_health_store = {4, 3.5, 3, 2.5, 2, 1.5, 1} + self.damage_to_hot_data.tick_time = 0.5 + self.damage_to_hot_data.total_ticks = 3 + self.damage_to_hot_data.stacking_cooldown = 1.25 + self.values.player.damage_to_hot_extra_ticks[1] = 2 + self.damage_to_hot_data.armors_allowed = { "level_1" } + self.specialization_descs[11][1].multiperk2 = "0.5" + self.specialization_descs[11][3].multiperk2 = "0.5" + self.specialization_descs[11][5].multiperk2 = "0.5" + self.specialization_descs[11][7].multiperk2 = "0.5" + self.specialization_descs[11][9].multiperk2 = "0.5" + self.specialization_descs[11][1].multiperk3 = "1.5" + self.specialization_descs[11][3].multiperk3 = "1.5" + self.specialization_descs[11][5].multiperk3 = "1.5" + self.specialization_descs[11][7].multiperk3 = "1.5" + self.specialization_descs[11][9].multiperk3 = "2.5" + self.specialization_descs[11][1].multiperk4 = "1.25" -- Yakuza - self.values.player.damage_health_ratio_multiplier = {0.35} + self.values.player.damage_health_ratio_multiplier = { 0.30 } + self.values.player.armor_regen_damage_health_ratio_multiplier[1] = 0.3 + self.values.player.armor_regen_damage_health_ratio_threshold_multiplier[1] = 4 + self.values.player.damage_damage_health_ratio_threshold_multiplier = { 2 } + self.values.player.dodge_health_ratio_multiplier = { 0.4 } + self.definitions.player_dodge_health_ratio_multiplier = { + name_id = "menu_player_dodge_health_ratio_multiplier", + category = "feature", + upgrade = { + value = 1, + upgrade = "dodge_health_ratio_multiplier", + category = "player", + }, + } + self.definitions.player_damage_damage_health_ratio_threshold_multiplier = { + name_id = "menu_player_damage_damage_health_ratio_threshold_multiplier", + category = "feature", + upgrade = { + value = 1, + upgrade = "damage_damage_health_ratio_threshold_multiplier", + category = "player", + }, + } + self.specialization_descs[12][1].multiperk = "30%" self.specialization_descs[12][3].multiperk = "80%" - self.specialization_descs[12][9].multiperk = "25%" - self.specialization_descs[12][9].multiperk2 = "35%" - self.specialization_descs[12][9].multiperk3 = "50%" - self.specialization_descs[12][9].multiperk4 = "25%" + self.specialization_descs[12][5].multiperk = "15%" + self.specialization_descs[12][7].multiperk = "40%" + self.specialization_descs[12][9].multiperk = "30%" + + -- Ex-President + self.values.player.body_armor.skill_max_health_store = { 6, 5, 4, 3, 2.5, 2, 1 } + self.values.player.armor_health_store_amount = { 0.1, 0.3, 0.5 } + self.specialization_descs[13][1].multiperk = "1" + self.specialization_descs[13][3].multiperk = "2" + self.specialization_descs[13][7].multiperk = "2" -- Maniac - self.cocaine_stacks_convert_levels = {600 / 8, 60} - self.values.player.cocaine_stack_absorption_multiplier = {1.5} + self.cocaine_stacks_convert_levels = { 600 / 8, 60 } + self.values.player.cocaine_stack_absorption_multiplier = { 1.5 } self.specialization_descs[14][1].multiperk6 = "75" self.specialization_descs[14][7].multiperk2 = "60" self.specialization_descs[14][9].multiperk = "50%" + -- Anarchist + self.values.player.chico_armor_multiplier[1] = 1.3 + self.values.player.armor_grinding = { { + { 1, 1.5 }, + { 1.5, 2.25 }, + { 2, 3 }, + { 2.5, 3.75 }, + { 3.5, 4.5 }, + { 5, 6 }, + { 7, 9 }, + } } + self.values.player.damage_to_armor = { { + { 1, 1.5 }, + { 1, 1.5 }, + { 1, 1.5 }, + { 1, 1.5 }, + { 1, 1.5 }, + { 1, 1.5 }, + { 1, 1.5 }, + } } + self.values.player.armor_increase = { 0.1, 0.15, 0.2 } + self.specialization_descs[15][3].multiperk2 = "10%" + self.specialization_descs[15][5].multiperk2 = "15%" + self.specialization_descs[15][7].multiperk2 = "20%" + self.specialization_descs[15][7].multiperk3 = "5%" + self.specialization_descs[15][9].multiperk3 = "30%" + + -- Biker + self.wild_trigger_time = 8 + self.wild_max_triggers_per_time = 2 + self.values.player.wild_health_amount = { 0.75 } + self.specialization_descs[16][1].multiperk = "7.5" + self.specialization_descs[16][1].multiperk3 = "2" + self.specialization_descs[16][1].multiperk4 = "8" + -- KP self.specialization_descs[17][1].multiperk3 = "45" self.specialization_descs[17][9].multiperk3 = "5 points" self.specialization_descs[17][9].multiperk3 = "1" -- Sicario - self.values.player.dodge_shot_gain = {{0.05, 1}} - self.specialization_descs[18][3].multiperk = "5%" + self.values.player.dodge_shot_gain = { { 0.1, 1 } } + self.specialization_descs[18][3].multiperk = "10%" self.specialization_descs[18][3].multiperk2 = "1" -- Stoic self.specialization_descs[19][1].multiperk3 = "16" -- Tag Team - self.values.player.tag_team_damage_absorption = {{kill_gain = 0.15, max = 0.6}} + self.values.player.tag_team_base.kill_health_gain = 0.5 + self.values.player.tag_team_base.tagged_health_gain_ratio = 1 + self.values.player.tag_team_damage_absorption = { { kill_gain = 0.15, max = 0.6 } } + self.specialization_descs[20][1].multiperk2 = "5" + self.specialization_descs[20][1].multiperk3 = "5" self.specialization_descs[20][5].multiperk = "1.5" self.specialization_descs[20][5].multiperk2 = "6" -- Hacker - self.values.temporary.pocket_ecm_kill_dodge[1] = {0.15, 25, 3} - self.values.player.pocket_ecm_heal_on_kill = {1} - self.specialization_descs[21][5].multiperk = "10" - self.specialization_descs[21][7].multiperk = "3" - self.specialization_descs[21][7].multiperk2 = "15%" - self.specialization_descs[21][7].multiperk3 = "25" + self.values.player.pocket_ecm_heal_on_kill = { + 1.5, + } + self.values.team.pocket_ecm_heal_on_kill = { + 0.5, + } + self.specialization_descs[21][3].multiperk = "10%" + self.specialization_descs[21][5].multiperk = "15" + self.specialization_descs[21][7].multiperk = "10%" + self.specialization_descs[21][9].multiperk = "5" -- Leech + self.values.player.copr_kill_life_leech[2] = 1 self.copr_ability_cooldown = 60 self.values.temporary.copr_ability[1][2] = 4 self.values.temporary.copr_ability[2][2] = 8 self.values.player.copr_static_damage_ratio[2] = 0.0625 - self.values.player.copr_teammate_heal = {0.15, 0.3} + self.values.player.copr_teammate_heal = { 0.15, 0.3 } self.values.player.copr_activate_bonus_health_ratio[1] = 50 self.specialization_descs[22][1].multiperk3 = "0.45" self.specialization_descs[22][1].multiperk4 = "1.5" @@ -711,4 +1008,9 @@ function UpgradesTweakData:init(tweak_data) self.specialization_descs[22][5].multiperk3 = "3" self.specialization_descs[22][9].multiperk = "6.25%" self.specialization_descs[22][9].multiperk2 = "40%" + + -- misc + self.values.player.passive_xp_multiplier[1] = 1.2 + self.values.player.regain_throwable_from_ammo[1].chance = 0.02 + self.values.player.regain_throwable_from_ammo[1].chance_inc = 0.001 end diff --git a/lua/weaponfactorytweakdata.lua b/lua/weaponfactorytweakdata.lua index 2a115ab..d31bff2 100644 --- a/lua/weaponfactorytweakdata.lua +++ b/lua/weaponfactorytweakdata.lua @@ -1,251 +1,491 @@ Hooks:PostHook(WeaponFactoryTweakData, "init", "eclipse__init", function(self) - -- Shotgun Ammo Rework - -- DB - self.parts.wpn_fps_upg_a_dragons_breath.custom_stats.fire_dot_data = {dot_trigger_chance = "100", dot_damage = "20", dot_length = "1.5", dot_trigger_max_distance = "1500", dot_tick_period = "0.5"} - -- HE - self.parts.wpn_fps_upg_a_explosive.stats = {total_ammo_mod = -10, damage = -15, spread = 2} - self.parts.wpn_fps_upg_a_explosive.custom_stats = {ignore_statistic = true, ammo_pickup_max_mul = 0.9, ammo_pickup_min_mul = 0.9, bullet_class = "InstantExplosiveBulletBase", rays = 1} + -- Shotguns -- 000 - self.parts.wpn_fps_upg_a_custom.custom_stats = {rays = 8, ammo_pickup_max_mul = 1, ammo_pickup_min_mul = 1} -- overwrites walk in closet so technically you still lose pickup - self.parts.wpn_fps_upg_a_custom_free.custom_stats = {rays = 8, ammo_pickup_max_mul = 1, ammo_pickup_min_mul = 1} - -- Flechette - self.parts.wpn_fps_upg_a_piercing.stats.damage = 0 - -- bitchass - self.wpn_fps_shot_huntsman.override.wpn_fps_upg_a_explosive = nil - self.wpn_fps_pis_judge.override.wpn_fps_upg_a_explosive = nil - self.wpn_fps_shot_b682.override.wpn_fps_upg_a_explosive = nil - self.wpn_fps_pis_x_judge.override.wpn_fps_upg_a_explosive = nil - self.wpn_fps_sho_coach.override.wpn_fps_upg_a_explosive = nil - self.wpn_fps_shot_serbu.override.wpn_fps_upg_a_custom = nil - self.wpn_fps_shot_serbu.override.wpn_fps_upg_a_custom_free = nil - - -- Shell Rack for loco and r880 - self.parts.wpn_fps_shot_r870_body_rack.stats.reload = 2 - self.parts.wpn_fps_shot_r870_body_rack.stats.total_ammo_mod = 0 - self.parts.wpn_fps_shot_r870_body_rack.stats.recoil = -2 - self.parts.wpn_fps_shot_r870_body_rack.stats.concealment = -1 - -- Extended Mag for loco and r880 - self.parts.wpn_fps_shot_shorty_m_extended_short.stats.concealment = -2 - self.parts.wpn_fps_shot_shorty_m_extended_short.stats.recoil = -2 - self.parts.wpn_fps_shot_r870_m_extended.stats.concealment = -2 - self.parts.wpn_fps_shot_r870_m_extended.stats.recoil = -2 - - -- Flamethrower Tanks - -- Rare - self.parts.wpn_fps_fla_mk2_mag_rare.stats.damage = -10 - self.parts.wpn_fps_fla_mk2_mag_rare.stats.concealment = 3 - self.parts.wpn_fps_fla_mk2_mag_rare.custom_stats = {ammo_pickup_max_mul = 1.65, ammo_pickup_min_mul = 1.65} - self.parts.wpn_fps_fla_mk2_mag_rare.desc_id = "bm_wp_upg_mk2_rare_desc" - self.parts.wpn_fps_fla_mk2_mag_rare.has_description = true - -- Well Done - self.parts.wpn_fps_fla_mk2_mag_welldone.stats.damage = 10 - self.parts.wpn_fps_fla_mk2_mag_welldone.stats.concealment = -3 - self.parts.wpn_fps_fla_mk2_mag_welldone.custom_stats = {ammo_pickup_max_mul = 1, ammo_pickup_min_mul = 1} - self.parts.wpn_fps_fla_mk2_mag_welldone.desc_id = "bm_wp_upg_mk2_welldone_desc" - self.parts.wpn_fps_fla_mk2_mag_welldone.has_description = true - - -- Arrows - self.parts.wpn_fps_bow_frankish_m_explosive.stats.damage = 0 - self.parts.wpn_fps_bow_frankish_m_poison.stats = {damage = -40} - self.parts.wpn_fps_upg_a_crossbow_explosion.stats.damage = 0 - self.parts.wpn_fps_upg_a_crossbow_poison.stats = {damage = -25} - self.parts.wpn_fps_bow_ecp_m_arrows_explosive.stats.damage = 0 - self.parts.wpn_fps_bow_ecp_m_arrows_poison.stats = {damage = -15} - self.parts.wpn_fps_upg_a_bow_explosion.stats.damage = 0 - self.parts.wpn_fps_upg_a_bow_poison.stats = {damage = -55} - - -- Union Short Barrel buff - self.parts.wpn_fps_ass_corgi_b_short.stats.spread = -1 - - -- Gadgets - -- Military Laser module - self.parts.wpn_fps_upg_fl_ass_peq15.stats.recoil = 0 - self.parts.wpn_fps_upg_fl_ass_peq15.stats.spread = 2 - -- Tactical Laser module - self.parts.wpn_fps_upg_fl_ass_smg_sho_peqbox.stats.concealment = 0 - -- Assault Light - self.parts.wpn_fps_upg_fl_ass_smg_sho_surefire.stats.concealment = 0 - - -- bipod nerf - self.parts.wpn_fps_upg_bp_lmg_lionbipod.stats.recoil = -1 - - - -- DMR Kit fixes and concealment nerfs - -- ak family - self.parts.wpn_fps_upg_ass_ak_b_zastava.custom_stats = {ammo_pickup_max_mul = 0.8, ammo_pickup_min_mul = 0.9} - self.parts.wpn_fps_upg_ass_ak_b_zastava.stats.concealment = -6 - self.parts.wpn_fps_upg_ass_ak_b_zastava.stats.recoil = -8 - self.parts.wpn_fps_upg_ass_ak_b_zastava.stats.damage = 75 - self.wpn_fps_ass_74.override.wpn_fps_upg_ass_ak_b_zastava.custom_stats.ammo_pickup_max_mul = 0.45 - self.wpn_fps_ass_74.override.wpn_fps_upg_ass_ak_b_zastava.stats.concealment = -7 - self.wpn_fps_ass_74.override.wpn_fps_upg_ass_ak_b_zastava.stats.recoil = -11 - self.wpn_fps_ass_74.override.wpn_fps_upg_ass_ak_b_zastava.stats.damage = 95 - -- car family - self.parts.wpn_fps_upg_ass_m4_b_beowulf.custom_stats.ammo_pickup_min_mul = 0.4 - self.parts.wpn_fps_upg_ass_m4_b_beowulf.stats.concealment = -7 - self.parts.wpn_fps_upg_ass_m4_b_beowulf.stats.recoil = -11 - self.parts.wpn_fps_upg_ass_m4_b_beowulf.stats.damage = 110 - self.wpn_fps_ass_m16.override.wpn_fps_upg_ass_m4_b_beowulf.custom_stats = {ammo_pickup_max_mul = 0.8, ammo_pickup_min_mul = 0.9} - self.wpn_fps_ass_m16.override.wpn_fps_upg_ass_m4_b_beowulf.stats.concealment = -6 - self.wpn_fps_ass_m16.override.wpn_fps_upg_ass_m4_b_beowulf.stats.recoil = -8 - self.wpn_fps_ass_m16.override.wpn_fps_upg_ass_m4_b_beowulf.stats.damage = 75 - -- m308 b-stock - self.parts.wpn_fps_ass_m14_body_ruger.stats.concealment = 8 - -- gewehr - self.parts.wpn_fps_ass_g3_b_sniper.custom_stats = {ammo_pickup_max_mul = 1.1, ammo_pickup_min_mul = 1.1} - self.parts.wpn_fps_ass_g3_b_sniper.stats.concealment = -5 - self.parts.wpn_fps_ass_g3_b_sniper.stats.recoil = -11 - self.parts.wpn_fps_ass_g3_b_sniper.stats.damage = 73 - -- broomstick - self.parts.wpn_fps_pis_c96_b_long.custom_stats = {ammo_pickup_max_mul = 0.8, ammo_pickup_min_mul = 0.8} - - - -- Commando 553 modifications - self.parts.wpn_fps_ass_s552_body_standard_black.stats = {spread = 0, recoil = -2, concealment = 2} - self.parts.wpn_fps_ass_s552_b_long.stats = {spread = 2, concealment = -2} - self.parts.wpn_fps_ass_s552_fg_standard_green.stats = {concealment = 2, spread = -1, recoil = -1} - self.parts.wpn_fps_ass_s552_fg_railed.stats = {concealment = -3, recoil = 2, spread = 2} - self.parts.wpn_fps_ass_s552_g_standard_green.stats = {spread = 1, recoil = 1} - - -- Falcon modifications - self.parts.wpn_fps_ass_fal_s_01.stats = {recoil = -2, concealment = 2} - self.parts.wpn_fps_ass_fal_s_wood.stats = {recoil = 3, concealment = -2} - self.parts.wpn_fps_ass_fal_g_01.stats = {recoil = 1, spread = -2, concealment = 2} - - -- Speedpull nerfs - self.parts.wpn_fps_m4_upg_m_quick.stats = {recoil = -2, reload = 3} - self.parts.wpn_fps_upg_ak_m_quick.stats = {recoil = -2, reload = 3} - self.parts.wpn_fps_ass_g36_m_quick.stats = {recoil = -2, reload = 3} - self.parts.wpn_fps_ass_aug_m_quick.stats = {recoil = -2, reload = 3} - self.parts.wpn_fps_smg_sr2_m_quick.stats = {recoil = -2, reload = 3} - self.parts.wpn_fps_smg_mac10_m_quick.stats = {recoil = -2, reload = 3} - self.parts.wpn_fps_smg_p90_m_strap.stats = {recoil = -2, reload = 3} - self.parts.wpn_fps_smg_fmg9_m_speed.stats = {spread = 1, reload = 3, recoil = -1, concealment = -2} - - -- Barrel Extentions - - -- Suppressors - - -- PBS - self.parts.wpn_fps_upg_ns_ass_pbs1.stats.spread = 2 - self.parts.wpn_fps_upg_ns_ass_pbs1.stats.concealment = -3 - -- Tooth Fairy (cavity) - self.parts.wpn_fps_ass_sub2000_fg_suppressed.stats.damage = -5 - -- KS12-S Long Silencer - self.parts.wpn_fps_ass_shak12_ns_suppressor.stats.damage = -3 - -- K-B100 Suppressor (ketchnov) - self.parts.wpn_fps_ass_groza_b_supressor.stats.damage = -1 - -- Silent Killer - self.parts.wpn_fps_upg_ns_shot_thick.stats.damage = -2 - -- Shh - self.parts.wpn_fps_upg_ns_sho_salvo_large.stats.damage = -2 - -- Silenced Barrel (lion's roar) - self.parts.wpn_fps_ass_vhs_b_silenced.stats.damage = -2 - -- Stealth Barrel (car-4) - self.parts.wpn_fps_m4_uupg_b_sd.stats.damage = -3 - -- Suppressed Barrel (steakout) - self.parts.wpn_fps_sho_aa12_barrel_silenced.stats.damage = -2 - -- CE Muffler (mosconi12g) - self.parts.wpn_fps_sho_m590_b_suppressor.stats.damage = -3 - -- Sniper Suppressor (rattlesnake) - self.parts.wpn_fps_snp_msr_ns_suppressor.stats.damage = -5 - -- Medium Barrel (r700 supp) - self.parts.wpn_fps_snp_r700_b_medium.stats.damage = -5 - -- Wind Whistler Barrel (rangehitter) - self.parts.wpn_fps_snp_sbl_b_short.stats.damage = -3 - -- Beak Suppressor (platypus) - self.parts.wpn_fps_snp_model70_ns_suppressor.stats.damage = -3 - -- Gedämpfter Barrel (lebensauger) - self.parts.wpn_fps_snp_wa2000_b_suppressed.stats.damage = -3 - -- Silenced Barrel (desert fox) - self.parts.wpn_fps_snp_desertfox_b_silencer.stats.damage = -3 - -- Contractor Silencer - self.parts.wpn_fps_snp_tti_ns_hex.stats.damage = -3 - -- Compensated Suppressor (r93) - self.parts.wpn_fps_snp_r93_b_suppressed.stats.damage = -3 - -- Outlaw's Silened Barrel (repeater) - self.parts.wpn_fps_snp_winchester_b_suppressed.stats.damage = -5 - -- Tikho Barrel (grom) - self.parts.wpn_fps_snp_siltstone_b_silenced.stats.damage = -5 - -- Silenced Barrel (nagant) - self.parts.wpn_fps_snp_mosin_b_sniper.stats.damage = -3 - -- Suppressed Barrel (thanatos) - self.parts.wpn_fps_snp_m95_barrel_suppressed.stats.damage = -20 - -- Roctec - self.parts.wpn_fps_upg_ns_pis_medium_gem.stats.damage = -2 - -- Champion's - self.parts.wpn_fps_upg_ns_pis_large_kac.stats.damage = -2 - -- Standard issue - self.parts.wpn_fps_upg_ns_pis_medium.stats.damage = -2 - -- Size doesn't matter - self.parts.wpn_fps_upg_ns_pis_small.stats.damage = -3 - -- Monolith - self.parts.wpn_fps_upg_ns_pis_large.stats.damage = -2 - -- Asepsis - self.parts.wpn_fps_upg_ns_pis_medium_slim.stats.damage = -2 - -- Budget - self.parts.wpn_fps_upg_ns_ass_filter.stats.damage = -2 - -- Jungle Ninja - self.parts.wpn_fps_upg_ns_pis_jungle.stats.damage = -3 - -- Ninja Barrel (mp5) - self.parts.wpn_fps_smg_mp5_fg_mp5sd.stats.damage = -2 - -- Silentgear (jackal) - self.parts.wpn_fps_smg_schakal_ns_silencer.stats.damage = -2 - -- BY90 Wide (akgen) - self.parts.wpn_fps_smg_vityaz_b_supressed.stats.damage = -2 - -- Silenced Barrel (streetsweeper) - self.parts.wpn_fps_sho_striker_b_suppressed.stats.damage = -2 - -- Silenced Barrel (goliath) - self.parts.wpn_fps_sho_rota_b_silencer.stats.damage = -3 - - -- Compensators / Nozzles / Muzzles - - -- Stubby - self.parts.wpn_fps_upg_ns_ass_smg_stubby.stats.damage = 3 - self.parts.wpn_fps_upg_ns_ass_smg_stubby.stats.spread = 3 - self.parts.wpn_fps_upg_ns_ass_smg_stubby.stats.concealment = -1 - -- Tank - self.parts.wpn_fps_upg_ns_ass_smg_tank.stats.damage = 2 - self.parts.wpn_fps_upg_ns_ass_smg_tank.stats.recoil = 3 - self.parts.wpn_fps_upg_ns_ass_smg_tank.stats.spread = 3 - -- Fire Breather - self.parts.wpn_fps_upg_ns_ass_smg_firepig.stats.spread = 2 - self.parts.wpn_fps_upg_ns_ass_smg_firepig.stats.recoil = 3 - self.parts.wpn_fps_upg_ns_ass_smg_firepig.stats.suppression = -9 - -- Competitors Compensator - self.parts.wpn_fps_upg_ass_ns_jprifles.stats.spread = 2 - self.parts.wpn_fps_upg_ass_ns_jprifles.stats.recoil = 4 - -- Funnel of Fun - self.parts.wpn_fps_upg_ass_ns_linear.stats.recoil = 2 - self.parts.wpn_fps_upg_ass_ns_linear.stats.spread = 2 - self.parts.wpn_fps_upg_ass_ns_linear.stats.suppression = -9 - -- Tactical - self.parts.wpn_fps_upg_ass_ns_surefire.stats.recoil = 2 - self.parts.wpn_fps_upg_ass_ns_surefire.stats.spread = 4 - self.parts.wpn_fps_upg_ass_ns_surefire.stats.suppression = -1 - -- Ported - self.parts.wpn_fps_upg_ass_ns_battle.stats.concealment = 0 - self.parts.wpn_fps_upg_ass_ns_battle.stats.recoil = 2 - self.parts.wpn_fps_upg_ass_ns_battle.stats.spread = 2 - self.parts.wpn_fps_upg_ass_ns_battle.stats.damage = -3 - -- Marmon - self.parts.wpn_fps_upg_ns_ass_smg_v6.stats.concealment = -2 - self.parts.wpn_fps_upg_ns_ass_smg_v6.stats.recoil = 3 - self.parts.wpn_fps_upg_ns_ass_smg_v6.stats.spread = 3 - -- KS12-A Burst Muzzle - self.parts.wpn_fps_ass_shak12_ns_muzzle.stats.concealment = 0 - self.parts.wpn_fps_ass_shak12_ns_muzzle.stats.suppression = -7 - -- DHL - self.parts.wpn_fps_upg_ns_duck.stats.recoil = -3 - self.parts.wpn_fps_upg_ns_duck.stats.suppression = -5 - self.parts.wpn_fps_upg_ns_duck.stats.concealment = -4 - -- hailstorm loud ext - self.parts.wpn_fps_hailstorm_b_extended.stats = {value = 1, concealment = -2, damage = 1, spread = 3, recoil = 2} - - - -- misc - table.delete(self.wpn_fps_sho_sko12.uses_parts, "wpn_fps_upg_i_singlefire") - table.delete(self.wpn_fps_sho_sko12.uses_parts, "wpn_fps_upg_i_autofire") - table.delete(self.wpn_fps_gre_ms3gl.uses_parts, "wpn_fps_gre_ms3gl_conversion") - table.insert(self.parts.wpn_fps_smg_fmg9_conversion.forbids, "wpn_fps_lmg_hk51b_ns_jcomp") + self.parts.wpn_fps_upg_a_custom.custom_stats = { rays = 6, optimal_distance_addend = 310, near_falloff_addend = 110, damage_near_mul = 0.5, damage_far_mul = 0.85 } + self.parts.wpn_fps_upg_a_custom_free.custom_stats = self.parts.wpn_fps_upg_a_custom.custom_stats + self.parts.wpn_fps_upg_a_custom.stats.damage = 5 + self.parts.wpn_fps_upg_a_custom.stats.total_ammo_mod = -6 + self.parts.wpn_fps_upg_a_custom.stats.recoil = -2 + self.parts.wpn_fps_upg_a_custom_free.stats = self.parts.wpn_fps_upg_a_custom.stats + -- Flechette + self.parts.wpn_fps_upg_a_piercing.stats.damage = -8 + self.parts.wpn_fps_upg_a_piercing.stats.suppression = 11 + self.parts.wpn_fps_upg_a_piercing.custom_stats = { rays = 12, armor_piercing_add = 1, damage_near_mul = 1.5, damage_far_mul = 1.5 } + -- AP Slug + self.parts.wpn_fps_upg_a_slug.stats.total_ammo_mod = -4 + self.parts.wpn_fps_upg_a_slug.stats.spread = 6 + self.parts.wpn_fps_upg_a_slug.stats.recoil = -2 + -- HE Slug + self.parts.wpn_fps_upg_a_explosive.stats.total_ammo_mod = -8 + self.parts.wpn_fps_upg_a_explosive.stats.damage = 30 + self.parts.wpn_fps_upg_a_explosive.stats.spread = 4 + self.parts.wpn_fps_upg_a_explosive.custom_stats = + { ignore_statistic = true, ammo_pickup_max_mul = 0.8, ammo_pickup_min_mul = 0.8, bullet_class = "InstantExplosiveBulletBase", rays = 1, damage_near_mul = 10 } + -- DB + self.parts.wpn_fps_upg_a_dragons_breath.stats.total_ammo_mod = -8 + self.parts.wpn_fps_upg_a_dragons_breath.custom_stats.ammo_pickup_max_mul = 0.85 + self.parts.wpn_fps_upg_a_dragons_breath.custom_stats.ammo_pickup_min_mul = 0.85 + self.parts.wpn_fps_upg_a_dragons_breath.custom_stats.fire_dot_data = { dot_trigger_chance = "100", dot_damage = "3", dot_length = "4", dot_trigger_max_distance = "1500", dot_tick_period = "0.25" } + -- Shell Rack for loco and r880 + self.parts.wpn_fps_shot_r870_body_rack.stats.reload = 2 + self.parts.wpn_fps_shot_r870_body_rack.stats.total_ammo_mod = 0 + self.parts.wpn_fps_shot_r870_body_rack.stats.recoil = -2 + self.parts.wpn_fps_shot_r870_body_rack.stats.concealment = -1 + -- Extended Mag for loco and r880 + self.parts.wpn_fps_shot_shorty_m_extended_short.stats.concealment = -2 + self.parts.wpn_fps_shot_shorty_m_extended_short.stats.recoil = -2 + self.parts.wpn_fps_shot_r870_m_extended.stats.concealment = -2 + self.parts.wpn_fps_shot_r870_m_extended.stats.recoil = -2 + -- extended barrel for raven + self.parts.wpn_fps_sho_ksg_b_long.stats.extra_ammo = 1 + -- remove stat overrides + self.wpn_fps_shot_huntsman.override.wpn_fps_upg_a_explosive = nil + self.wpn_fps_shot_huntsman.override.wpn_fps_upg_a_slug = nil + self.wpn_fps_shot_b682.override.wpn_fps_upg_a_explosive = nil + self.wpn_fps_sho_coach.override.wpn_fps_upg_a_explosive = nil + self.wpn_fps_pis_judge.override.wpn_fps_upg_a_explosive = nil + self.wpn_fps_pis_judge.override.wpn_fps_upg_a_piercing = nil + self.wpn_fps_pis_x_judge.override.wpn_fps_upg_a_explosive = nil + self.wpn_fps_pis_x_judge.override.wpn_fps_upg_a_piercing = nil + self.wpn_fps_sho_striker.override.wpn_fps_upg_a_slug = nil + self.wpn_fps_sho_striker.override.wpn_fps_upg_a_custom = nil + self.wpn_fps_sho_striker.override.wpn_fps_upg_a_custom_free = nil + + -- Secondary Sights + self.parts.wpn_fps_upg_o_sig.stats.recoil = 0 + self.parts.wpn_fps_upg_o_45rds.stats.recoil = 0 + self.parts.wpn_fps_upg_o_45rds_v2.stats.recoil = 0 + self.parts.wpn_fps_upg_o_45steel.stats.concealment = 0 + self.parts.wpn_fps_upg_o_xpsg33_magnifier.stats.recoil = 0 + + -- Weapon-Specific Stuff + -- Saiga stuff + self.parts.wpn_fps_sho_basset_m_extended.stats.reload = -2 + self.parts.wpn_fps_sho_saiga_b_short.stats.recoil = -2 + self.parts.wpn_fps_sho_saiga_b_short.stats.concealment = 2 + self.parts.wpn_fps_sho_saiga_fg_holy.stats.recoil = -2 + self.parts.wpn_fps_sho_saiga_fg_holy.stats.concealment = 2 + -- mp5 straight mag + self.parts.wpn_fps_smg_mp5_m_straight.stats.total_ammo_mod = -5 + self.parts.wpn_fps_smg_mp5_m_straight.custom_stats = { ammo_pickup_max_mul = 0.8 } + -- union short barrel + self.parts.wpn_fps_ass_corgi_b_short.stats.spread = -1 + -- bipod nerf + self.parts.wpn_fps_upg_bp_lmg_lionbipod.stats.recoil = -1 + -- Commando 553 modifications + self.parts.wpn_fps_ass_s552_body_standard_black.stats = { spread = 0, recoil = -2, concealment = 2 } + self.parts.wpn_fps_ass_s552_b_long.stats = { spread = 2, concealment = -2 } + self.parts.wpn_fps_ass_s552_fg_standard_green.stats = { concealment = 2, spread = -1, recoil = -1 } + self.parts.wpn_fps_ass_s552_fg_railed.stats = { concealment = -3, recoil = 2, spread = 2 } + self.parts.wpn_fps_ass_s552_g_standard_green.stats = { spread = 1, recoil = 1 } + -- Falcon modifications + self.parts.wpn_fps_ass_fal_s_01.stats = { recoil = -2, concealment = 2 } + self.parts.wpn_fps_ass_fal_s_wood.stats = { recoil = 3, concealment = -2 } + self.parts.wpn_fps_ass_fal_g_01.stats = { recoil = 1, spread = -2, concealment = 2 } + -- Akron stuff + self.parts.wpn_fps_lmg_hcar_barrel_dmr.stats = { extra_ammo = -5, total_ammo_mod = -10, damage = 20, value = 3 } + self.parts.wpn_fps_lmg_hcar_barrel_dmr.custom_stats = { ammo_pickup_max_mul = 0.8, ammo_pickup_min_mul = 0.8 } + self.parts.wpn_fps_lmg_hcar_body_conversionkit.stats = { extra_ammo = 40, total_ammo_mod = 20, damage = -40, value = 1, spread = 1, recoil = 1, fire_rate = 1.667 } + self.parts.wpn_fps_lmg_hcar_body_conversionkit.custom_stats = { ammo_pickup_max_mul = 2.25, ammo_pickup_min_mul = 2.25, fire_rate_multiplier = 1.667 } + -- campbell stuff + self.parts.wpn_fps_lmg_kacchainsaw_b_long.stats = { spread = 1, recoil = -1 } + self.parts.wpn_fps_lmg_kacchainsaw_b_short.stats = { spread = -1, recoil = 1 } + self.parts.wpn_fps_lmg_kacchainsaw_conversionkit.stats = { damage = 40, recoil = -2, spread = 2 } + self.parts.wpn_fps_lmg_kacchainsaw_conversionkit.custom_stats = { fire_rate_multiplier = 0.6, ammo_pickup_max_mul = 0.83 } + + -- Weapon Magazines + + -- Speedpull + self.parts.wpn_fps_m4_upg_m_quick.stats = { concealment = -1, reload = 1 } + self.parts.wpn_fps_upg_ak_m_quick.stats = { concealment = -1, reload = 1 } + self.parts.wpn_fps_ass_g36_m_quick.stats = { concealment = -1, reload = 1 } + self.parts.wpn_fps_ass_aug_m_quick.stats = { concealment = -1, reload = 1 } + self.parts.wpn_fps_smg_sr2_m_quick.stats = { concealment = -1, reload = 1 } + self.parts.wpn_fps_smg_mac10_m_quick.stats = { concealment = -1, reload = 1 } + self.parts.wpn_fps_smg_p90_m_strap.stats = { concealment = -1, reload = 1 } + self.parts.wpn_fps_smg_fmg9_m_speed.stats = { concealment = -1, reload = 1 } + self.parts.wpn_fps_snp_awp_m_speed.stats = { concealment = -1, reload = 1 } + -- L5 + self.parts.wpn_fps_upg_m4_m_l5.stats = { concealment = -1, recoil = 1 } + -- Expert + self.parts.wpn_fps_ass_l85a2_m_emag.stats = { concealment = -1, spread = 1 } + -- Quad + self.parts.wpn_fps_upg_m4_m_quad.stats = { extra_ammo = 15, reload = -2, concealment = -3 } + self.parts.wpn_fps_upg_ak_m_quad.stats = { extra_ammo = 15, reload = -2, concealment = -3 } + -- Tactical + self.parts.wpn_fps_upg_m4_m_pmag.stats = { value = 2, concealment = -2, recoil = 1, spread = 1 } + -- Vintage + self.parts.wpn_fps_upg_m4_m_straight.stats = { concealment = 1, extra_ammo = -3 } + -- Milspec + self.parts.wpn_fps_m4_uupg_m_std.stats = { concealment = -1, extra_ammo = 3 } + -- Low Drag + self.parts.wpn_fps_upg_ak_m_uspalm.stats = { concealment = -2, recoil = 1, spread = 1 } + -- Extended Falcon + self.parts.wpn_fps_ass_fal_m_01.stats = { extra_ammo = 10, concealment = -2 } + -- Extended Kang Arms + self.parts.wpn_fps_snp_qbu88_m_extended.stats = { concealment = -2, extra_ammo = 5 } + -- CE Extender + self.parts.wpn_fps_sho_m590_b_long.stats = { extra_ammo = 1, concealment = -2 } + -- Signature SMG extended + self.parts.wpn_fps_smg_shepheard_mag_extended.stats = { extra_ammo = 5, concealment = -1 } + + -- Gadgets + -- Military Laser module + self.parts.wpn_fps_upg_fl_ass_peq15.stats.recoil = 0 + self.parts.wpn_fps_upg_fl_ass_peq15.stats.spread = 2 + -- Tactical Laser module + self.parts.wpn_fps_upg_fl_ass_smg_sho_peqbox.stats.concealment = 0 + -- Assault Light + self.parts.wpn_fps_upg_fl_ass_smg_sho_surefire.stats.concealment = 0 + -- extra mag for stryk + self.parts.wpn_fps_pis_g18c_m_mag_33rnd.stats.reload = -1 + self.wpn_fps_pis_x_g18c.override.wpn_fps_pis_g18c_m_mag_33rnd.stats.reload = -1 + self.parts.wpn_fps_pis_czech_m_extended.stats.reload = -1 + self.wpn_fps_pis_x_czech.override.wpn_fps_pis_czech_m_extended.stats.reload = -1 + + -- Conversion kits + -- ak family + self.parts.wpn_fps_upg_ass_ak_b_zastava.custom_stats = { ammo_pickup_min_mul = 0.5, ammo_pickup_max_mul = 0.375 } + self.parts.wpn_fps_upg_ass_ak_b_zastava.stats.total_ammo_mod = -7 + self.parts.wpn_fps_upg_ass_ak_b_zastava.stats.concealment = -6 + self.parts.wpn_fps_upg_ass_ak_b_zastava.stats.recoil = -8 + self.parts.wpn_fps_upg_ass_ak_b_zastava.stats.damage = 87 + self.wpn_fps_ass_74.override.wpn_fps_upg_ass_ak_b_zastava.custom_stats = { ammo_pickup_min_mul = 0.5, ammo_pickup_max_mul = 0.25 } + self.wpn_fps_ass_74.override.wpn_fps_upg_ass_ak_b_zastava.stats.total_ammo_mod = -10 + self.wpn_fps_ass_74.override.wpn_fps_upg_ass_ak_b_zastava.stats.concealment = -7 + self.wpn_fps_ass_74.override.wpn_fps_upg_ass_ak_b_zastava.stats.recoil = -11 + self.wpn_fps_ass_74.override.wpn_fps_upg_ass_ak_b_zastava.stats.damage = 105 + -- car family + self.parts.wpn_fps_upg_ass_m4_b_beowulf.custom_stats = { ammo_pickup_min_mul = 0.5, ammo_pickup_max_mul = 0.1875 } + self.parts.wpn_fps_upg_ass_m4_b_beowulf.stats.total_ammo_mod = -12 + self.parts.wpn_fps_upg_ass_m4_b_beowulf.stats.concealment = -7 + self.parts.wpn_fps_upg_ass_m4_b_beowulf.stats.recoil = -11 + self.parts.wpn_fps_upg_ass_m4_b_beowulf.stats.damage = 117 + self.wpn_fps_ass_m16.override.wpn_fps_upg_ass_m4_b_beowulf.custom_stats = { ammo_pickup_min_mul = 0.5, ammo_pickup_max_mul = 0.375 } + self.wpn_fps_ass_m16.override.wpn_fps_upg_ass_m4_b_beowulf.stats.total_ammo_mod = -7 + self.wpn_fps_ass_m16.override.wpn_fps_upg_ass_m4_b_beowulf.stats.concealment = -6 + self.wpn_fps_ass_m16.override.wpn_fps_upg_ass_m4_b_beowulf.stats.recoil = -8 + self.wpn_fps_ass_m16.override.wpn_fps_upg_ass_m4_b_beowulf.stats.damage = 87 + -- m308 b-stock + self.parts.wpn_fps_ass_m14_body_ruger.stats.concealment = 8 + -- gewehr + self.parts.wpn_fps_ass_g3_b_sniper.custom_stats = { ammo_pickup_min_mul = 1.25, ammo_pickup_max_mul = 0.5 } + self.parts.wpn_fps_ass_g3_b_sniper.stats.total_ammo_mod = -8 + self.parts.wpn_fps_ass_g3_b_sniper.stats.concealment = -5 + self.parts.wpn_fps_ass_g3_b_sniper.stats.recoil = -11 + self.parts.wpn_fps_ass_g3_b_sniper.stats.damage = 85 + self.parts.wpn_fps_ass_g3_b_short.stats.total_ammo_mod = 11 + self.parts.wpn_fps_ass_g3_b_short.custom_stats = { ammo_pickup_max_mul = 2 } + -- broomstick + self.parts.wpn_fps_pis_c96_b_long.custom_stats = { ammo_pickup_min_mul = 0.33, ammo_pickup_max_mul = 0.5 } + -- ks12 + self.parts.wpn_fps_ass_shak12_body_vks.stats.damage = 64 + self.parts.wpn_fps_ass_shak12_body_vks.stats.concealment = -6 + self.parts.wpn_fps_ass_shak12_body_vks.stats.recoil = -8 + self.parts.wpn_fps_ass_shak12_body_vks.stats.total_ammo_mod = -10 + self.parts.wpn_fps_ass_shak12_body_vks.stats.fire_rate = -3 + self.parts.wpn_fps_ass_shak12_body_vks.custom_stats = { ammo_pickup_min_mul = 0.5, ammo_pickup_max_mul = 0.25 } + self.parts.wpn_fps_ass_shak12_body_vks.custom_stats.fire_rate_multiplier = 0.7 + -- wasp exclusive kit + self.parts.wpn_fps_smg_fmg9_conversion.stats.recoil = 1 + self.parts.wpn_fps_smg_fmg9_conversion.stats.spread = 1 + -- awp exclusive kits + self.parts.wpn_fps_snp_awp_conversion_dragonlore.stats = {} + self.parts.wpn_fps_snp_awp_conversion_dragonlore.custom_stats = {} + self.parts.wpn_fps_snp_awp_conversion_wildlands.stats = {} + + -- mcshay pack + self.parts.wpn_fps_upg_ak_body_upperreceiver_zenitco.stats = { value = 3, concealment = -1, damage = 3, spread = -2 } + self.parts.wpn_fps_m4_uupg_m_strike.stats = { value = 1, extra_ammo = 4, concealment = -2 } + self.parts.wpn_fps_m4_uupg_upper_radian.stats = { value = 3, spread = 1, recoil = 2 } + self.parts.wpn_fps_m4_uupg_lower_radian.stats = { value = 1, recoil = 1 } + self.parts.wpn_fps_uupg_fg_radian.stats = { value = 1, recoil = 2, spread = 1, damage = 2, concealment = -1 } + self.parts.wpn_fps_m4_uupg_g_billet.stats = { value = 6, spread = 1, recoil = 1 } + self.parts.wpn_fps_upg_ak_ns_zenitco.stats = { value = 1, concealment = -1, damage = 3, spread = 2, recoil = 1 } + + -- Specials + + -- Flamethrower Tanks + -- Rare + self.parts.wpn_fps_fla_mk2_mag_rare.stats.damage = -10 + self.parts.wpn_fps_fla_mk2_mag_rare.stats.concealment = 3 + self.parts.wpn_fps_fla_mk2_mag_rare.custom_stats = { ammo_pickup_max_mul = 1.65, ammo_pickup_min_mul = 1.25 } + self.parts.wpn_fps_fla_mk2_mag_rare.desc_id = "bm_wp_upg_mk2_rare_desc" + self.parts.wpn_fps_fla_mk2_mag_rare.has_description = true + -- Well Done + self.parts.wpn_fps_fla_mk2_mag_welldone.stats.damage = 10 + self.parts.wpn_fps_fla_mk2_mag_welldone.stats.concealment = -3 + self.parts.wpn_fps_fla_mk2_mag_welldone.custom_stats = { ammo_pickup_max_mul = 1, ammo_pickup_min_mul = 0.75 } + self.parts.wpn_fps_fla_mk2_mag_welldone.desc_id = "bm_wp_upg_mk2_welldone_desc" + self.parts.wpn_fps_fla_mk2_mag_welldone.has_description = true + + -- Arrows + self.parts.wpn_fps_bow_frankish_m_explosive.stats.damage = 0 + self.parts.wpn_fps_bow_frankish_m_poison.stats = { damage = -40 } + self.parts.wpn_fps_upg_a_crossbow_explosion.stats.damage = 0 + self.parts.wpn_fps_upg_a_crossbow_poison.stats = { damage = -25 } + self.parts.wpn_fps_bow_ecp_m_arrows_explosive.stats.damage = 0 + self.parts.wpn_fps_bow_ecp_m_arrows_poison.stats = { damage = -15 } + self.parts.wpn_fps_upg_a_bow_explosion.stats.damage = 0 + self.parts.wpn_fps_upg_a_bow_poison.stats = { damage = -55 } + self.parts.wpn_fps_bow_arblast_m_explosive.stats.damage = 0 -- apparently none of this matters cause of some vanilla bs + self.parts.wpn_fps_bow_arblast_m_poison.stats = { damage = -20 } -- same thing + + -- Barrel Extentions + + -- Suppressors + -- Medium & Rami + self.parts.wpn_fps_upg_ns_ass_smg_medium.stats = { damage = -3, recoil = 1, spread = 1, concealment = -1 } + self.parts.wpn_fps_lmg_kacchainsaw_ns_suppressor.stats = { damage = -3, recoil = 1, spread = 1, concealment = -1 } + -- PBS + self.parts.wpn_fps_upg_ns_ass_pbs1.stats.spread = 2 + self.parts.wpn_fps_upg_ns_ass_pbs1.stats.concealment = -3 + -- Tooth Fairy (cavity) + self.parts.wpn_fps_ass_sub2000_fg_suppressed.stats.damage = -5 + -- KS12-S Long Silencer + self.parts.wpn_fps_ass_shak12_ns_suppressor.stats.damage = -3 + -- K-B100 Suppressor (ketchnov) + self.parts.wpn_fps_ass_groza_b_supressor.stats.damage = -1 + -- Silent Killer + self.parts.wpn_fps_upg_ns_shot_thick.stats.damage = -2 + -- Shh + self.parts.wpn_fps_upg_ns_sho_salvo_large.stats.damage = -2 + -- Silenced Barrel (lion's roar) + self.parts.wpn_fps_ass_vhs_b_silenced.stats.damage = -2 + -- Stealth Barrel (car-4) + self.parts.wpn_fps_m4_uupg_b_sd.stats.damage = -3 + -- Suppressed Barrel (steakout) + self.parts.wpn_fps_sho_aa12_barrel_silenced.stats.damage = -2 + -- CE Muffler (mosconi12g) + self.parts.wpn_fps_sho_m590_b_suppressor.stats.damage = -3 + -- Sniper Suppressor (rattlesnake) + self.parts.wpn_fps_snp_msr_ns_suppressor.stats.damage = -5 + -- Medium Barrel (r700 supp) + self.parts.wpn_fps_snp_r700_b_medium.stats.damage = -5 + -- Wind Whistler Barrel (rangehitter) + self.parts.wpn_fps_snp_sbl_b_short.stats.damage = -3 + -- Beak Suppressor (platypus) + self.parts.wpn_fps_snp_model70_ns_suppressor.stats.damage = -3 + -- Gedämpfter Barrel (lebensauger) + self.parts.wpn_fps_snp_wa2000_b_suppressed.stats.damage = -3 + -- Silenced Barrel (desert fox) + self.parts.wpn_fps_snp_desertfox_b_silencer.stats.damage = -3 + -- Contractor Silencer + self.parts.wpn_fps_snp_tti_ns_hex.stats.damage = -3 + -- Compensated Suppressor (r93) + self.parts.wpn_fps_snp_r93_b_suppressed.stats.damage = -3 + -- Outlaw's Silened Barrel (repeater) + self.parts.wpn_fps_snp_winchester_b_suppressed.stats.damage = -5 + -- Tikho Barrel (grom) + self.parts.wpn_fps_snp_siltstone_b_silenced.stats.damage = -5 + -- Silenced Barrel (nagant) + self.parts.wpn_fps_snp_mosin_b_sniper.stats.damage = -3 + -- Suppressed Barrel (thanatos) + self.parts.wpn_fps_snp_m95_barrel_suppressed.stats.damage = -10 + -- Roctec + self.parts.wpn_fps_upg_ns_pis_medium_gem.stats.damage = -2 + -- Champion's + self.parts.wpn_fps_upg_ns_pis_large_kac.stats.damage = -2 + -- Standard issue + self.parts.wpn_fps_upg_ns_pis_medium.stats.damage = -2 + -- Size doesn't matter + self.parts.wpn_fps_upg_ns_pis_small.stats.damage = -3 + -- Monolith + self.parts.wpn_fps_upg_ns_pis_large.stats.damage = -2 + -- Asepsis + self.parts.wpn_fps_upg_ns_pis_medium_slim.stats.damage = -2 + -- Budget + self.parts.wpn_fps_upg_ns_ass_filter.stats.damage = -2 + -- Jungle Ninja + self.parts.wpn_fps_upg_ns_pis_jungle.stats.damage = -3 + -- Ninja Barrel (mp5) + self.parts.wpn_fps_smg_mp5_fg_mp5sd.stats.damage = -2 + -- Silentgear (jackal) + self.parts.wpn_fps_smg_schakal_ns_silencer.stats.damage = -2 + -- BY90 Wide (akgen) + self.parts.wpn_fps_smg_vityaz_b_supressed.stats.damage = -2 + -- Silenced Barrel (streetsweeper) + self.parts.wpn_fps_sho_striker_b_suppressed.stats.damage = -2 + -- Silenced Barrel (goliath) + self.parts.wpn_fps_sho_rota_b_silencer.stats.damage = -3 + + -- Compensators / Nozzles / Muzzles + -- Stubby + self.parts.wpn_fps_upg_ns_ass_smg_stubby.stats.damage = 3 + self.parts.wpn_fps_upg_ns_ass_smg_stubby.stats.spread = 3 + self.parts.wpn_fps_upg_ns_ass_smg_stubby.stats.concealment = -1 + -- Tank + self.parts.wpn_fps_upg_ns_ass_smg_tank.stats.damage = 2 + self.parts.wpn_fps_upg_ns_ass_smg_tank.stats.recoil = 3 + self.parts.wpn_fps_upg_ns_ass_smg_tank.stats.spread = 3 + -- Fire Breather + self.parts.wpn_fps_upg_ns_ass_smg_firepig.stats.spread = 2 + self.parts.wpn_fps_upg_ns_ass_smg_firepig.stats.recoil = 3 + self.parts.wpn_fps_upg_ns_ass_smg_firepig.stats.suppression = -9 + -- Competitors Compensator + self.parts.wpn_fps_upg_ass_ns_jprifles.stats.spread = 2 + self.parts.wpn_fps_upg_ass_ns_jprifles.stats.recoil = 4 + -- Funnel of Fun + self.parts.wpn_fps_upg_ass_ns_linear.stats.recoil = 2 + self.parts.wpn_fps_upg_ass_ns_linear.stats.spread = 2 + self.parts.wpn_fps_upg_ass_ns_linear.stats.suppression = -9 + -- Tactical + self.parts.wpn_fps_upg_ass_ns_surefire.stats.recoil = 2 + self.parts.wpn_fps_upg_ass_ns_surefire.stats.spread = 4 + self.parts.wpn_fps_upg_ass_ns_surefire.stats.suppression = -1 + -- Ported + self.parts.wpn_fps_upg_ass_ns_battle.stats.concealment = 0 + self.parts.wpn_fps_upg_ass_ns_battle.stats.recoil = 2 + self.parts.wpn_fps_upg_ass_ns_battle.stats.spread = 2 + self.parts.wpn_fps_upg_ass_ns_battle.stats.damage = -3 + -- Marmon + self.parts.wpn_fps_upg_ns_ass_smg_v6.stats.concealment = -2 + self.parts.wpn_fps_upg_ns_ass_smg_v6.stats.recoil = 3 + self.parts.wpn_fps_upg_ns_ass_smg_v6.stats.spread = 3 + -- KS12-A Burst Muzzle + self.parts.wpn_fps_ass_shak12_ns_muzzle.stats.concealment = 0 + self.parts.wpn_fps_ass_shak12_ns_muzzle.stats.suppression = -7 + -- DHL + self.parts.wpn_fps_upg_ns_duck.stats.recoil = -3 + self.parts.wpn_fps_upg_ns_duck.stats.suppression = -5 + self.parts.wpn_fps_upg_ns_duck.stats.concealment = -4 + -- hailstorm loud ext + self.parts.wpn_fps_hailstorm_b_extended.stats = { value = 1, concealment = -2, damage = 1, spread = 3, recoil = 2 } + + -- misc + table.delete(self.wpn_fps_sho_sko12.uses_parts, "wpn_fps_upg_i_singlefire") + table.delete(self.wpn_fps_sho_sko12.uses_parts, "wpn_fps_upg_i_autofire") + table.delete(self.wpn_fps_gre_ms3gl.uses_parts, "wpn_fps_gre_ms3gl_conversion") + table.insert(self.parts.wpn_fps_smg_fmg9_conversion.forbids, "wpn_fps_lmg_hk51b_ns_jcomp") + local weapons = { + "wpn_fps_shot_saiga", + "wpn_fps_shot_r870", + "wpn_fps_shot_huntsman", + "wpn_fps_shot_serbu", + "wpn_fps_sho_ben", + "wpn_fps_sho_striker", + "wpn_fps_sho_ksg", + "wpn_fps_pis_judge", + "wpn_fps_sho_spas12", + "wpn_fps_shot_b682", + "wpn_fps_sho_aa12", + "wpn_fps_sho_boot", + "wpn_fps_shot_m37", + "wpn_fps_shot_m1897", + "wpn_fps_sho_m590", + "wpn_fps_sho_rota", + "wpn_fps_sho_basset", + "wpn_fps_sho_x_basset", + "wpn_fps_pis_x_judge", + "wpn_fps_sho_x_rota", + "wpn_fps_sho_coach", + "wpn_fps_sho_ultima", + "wpn_fps_sho_sko12", + "wpn_fps_sho_x_sko12", + } + for _, factory_id in ipairs(weapons) do + if self[factory_id] and self[factory_id].uses_parts then + table.delete(self[factory_id].uses_parts, "wpn_fps_upg_a_rip") -- fuck tombstone + end + end end) + +-- Gun Perks replace stat boosts +function WeaponFactoryTweakData:create_bonuses(tweak_data, weapon_skins) + self.parts.wpn_fps_upg_perk_template = { + custom = true, + exclude_from_challenge = true, + texture_bundle_folder = "gunperk", + third_unit = "units/payday2/weapons/wpn_upg_dummy/wpn_upg_dummy", + has_description = true, + a_obj = "a_body", + type = "bonus", + name_id = nil, + desc_id = nil, + sub_type = "bonus_stats", + internal_part = true, + unit = "units/payday2/weapons/wpn_upg_dummy/wpn_upg_dummy", + pcs = { + 10, + 20, + 30, + 40, + }, + stats = {}, + custom_stats = {}, + perks = { + "bonus", + }, + } + + -- speedloader + self.parts.wpn_fps_upg_perk_speedloader = deep_clone(self.parts.wpn_fps_upg_perk_template) + self.parts.wpn_fps_upg_perk_speedloader.name_id = "bm_menu_perk_speedloader" + self.parts.wpn_fps_upg_perk_speedloader.desc_id = "bm_menu_perk_speedloader_desc" + self.parts.wpn_fps_upg_perk_speedloader.stats = { reload = 2, total_ammo_mod = -5 } + self.parts.wpn_fps_upg_perk_speedloader_lmg = deep_clone(self.parts.wpn_fps_upg_perk_speedloader) + self.parts.wpn_fps_upg_perk_speedloader_lmg.stats = { reload = 3 } -- make it more impactful on lmgs + + -- haste + self.parts.wpn_fps_upg_perk_haste = deep_clone(self.parts.wpn_fps_upg_perk_template) + self.parts.wpn_fps_upg_perk_haste.name_id = "bm_menu_perk_haste" + self.parts.wpn_fps_upg_perk_haste.desc_id = "bm_menu_perk_haste_desc" + self.parts.wpn_fps_upg_perk_haste.stats = { total_ammo_mod = -3 } + self.parts.wpn_fps_upg_perk_haste.custom_stats = { movement_speed = 1.1 } + + -- dead silence + self.parts.wpn_fps_upg_perk_deadsilence = deep_clone(self.parts.wpn_fps_upg_perk_template) + self.parts.wpn_fps_upg_perk_deadsilence.name_id = "bm_menu_perk_deadsilence" + self.parts.wpn_fps_upg_perk_deadsilence.desc_id = "bm_menu_perk_deadsilence_desc" + self.parts.wpn_fps_upg_perk_deadsilence.stats = { concealment = 3, total_ammo_mod = -3, recoil = -1, spread = -1 } + + -- jawbreaker + self.parts.wpn_fps_upg_perk_jawbreaker = deep_clone(self.parts.wpn_fps_upg_perk_template) + self.parts.wpn_fps_upg_perk_jawbreaker.name_id = "bm_menu_perk_jawbreaker" + self.parts.wpn_fps_upg_perk_jawbreaker.desc_id = "bm_menu_perk_jawbreaker_desc" + self.parts.wpn_fps_upg_perk_jawbreaker.stats = { damage = 15, fire_rate = 0.85 } + self.parts.wpn_fps_upg_perk_jawbreaker.custom_stats = { ammo_pickup_max_mul = 0.75, ammo_pickup_min_mul = 0.75, fire_rate_multiplier = 0.85 } + + -- whirlwind + self.parts.wpn_fps_upg_perk_whirlwind = deep_clone(self.parts.wpn_fps_upg_perk_template) + self.parts.wpn_fps_upg_perk_whirlwind.name_id = "bm_menu_perk_whirlwind" + self.parts.wpn_fps_upg_perk_whirlwind.desc_id = "bm_menu_perk_whirlwind_desc" + self.parts.wpn_fps_upg_perk_whirlwind.stats = { recoil = -3, spread = -1, fire_rate = 1.15 } + self.parts.wpn_fps_upg_perk_whirlwind.custom_stats = { fire_rate_multiplier = 1.15 } + + -- stockpile + self.parts.wpn_fps_upg_perk_stockpile = deep_clone(self.parts.wpn_fps_upg_perk_template) + self.parts.wpn_fps_upg_perk_stockpile.name_id = "bm_menu_perk_stockpile" + self.parts.wpn_fps_upg_perk_stockpile.desc_id = "bm_menu_perk_stockpile_desc" + self.parts.wpn_fps_upg_perk_stockpile.stats = { total_ammo_mod = 5, reload = -3 } + + local uses_parts = { + wpn_fps_upg_perk_speedloader = { category = { "assault_rifle", "smg", "snp", "shotgun", "crossbow", "flamethrower", "pistol", "minigun", "akimbo", "lmg", "bow" } }, + wpn_fps_upg_perk_haste = { category = { "assault_rifle", "smg", "snp", "shotgun", "flamethrower", "pistol", "minigun", "akimbo", "lmg", "bow" } }, + wpn_fps_upg_perk_deadsilence = { category = { "assault_rifle", "smg", "snp", "shotgun", "crossbow", "flamethrower", "pistol", "minigun", "akimbo", "lmg" } }, + wpn_fps_upg_perk_jawbreaker = { category = { "assault_rifle", "smg", "snp", "shotgun", "pistol", "minigun", "akimbo", "lmg" } }, + wpn_fps_upg_perk_whirlwind = { category = { "assault_rifle", "smg", "snp", "shotgun", "pistol", "minigun", "akimbo", "lmg" } }, + wpn_fps_upg_perk_stockpile = { category = { "assault_rifle", "smg", "snp", "shotgun", "crossbow", "flamethrower", "pistol", "minigun", "akimbo", "lmg", "bow" } }, + } + local all_pass, weapon_pass, exclude_weapon_pass, category_pass, exclude_category_pass = nil + + for id, data in pairs(tweak_data.upgrades.definitions) do + local weapon_tweak = tweak_data.weapon[data.weapon_id] + local primary_category = weapon_tweak and weapon_tweak.categories and weapon_tweak.categories[1] + + if data.weapon_id and weapon_tweak and data.factory_id and self[data.factory_id] then + for part_id, params in pairs(uses_parts) do + weapon_pass = not params.weapon or table.contains(params.weapon, data.weapon_id) + exclude_weapon_pass = not params.exclude_weapon or not table.contains(params.exclude_weapon, data.weapon_id) + category_pass = not params.category or table.contains(params.category, primary_category) + exclude_category_pass = not params.exclude_category or not table.contains(params.exclude_category, primary_category) + all_pass = weapon_pass and exclude_weapon_pass and category_pass and exclude_category_pass + + if all_pass then + table.insert(self[data.factory_id].uses_parts, part_id) + table.insert(self[data.factory_id .. "_npc"].uses_parts, part_id) + end + end + end + end +end diff --git a/lua/weaponfallofftweakdata.lua b/lua/weaponfallofftweakdata.lua new file mode 100644 index 0000000..c945d7c --- /dev/null +++ b/lua/weaponfallofftweakdata.lua @@ -0,0 +1,37 @@ +function WeaponFalloffTemplate.setup_weapon_falloff_templates() + local weapon_falloff_templates = {} + weapon_falloff_templates.SHOTGUN_FALL_NORMAL = { + optimal_distance = 000, + optimal_range = 1200, + near_falloff = 0, + far_falloff = 1500, + near_multiplier = 1.4, + far_multiplier = 0.3, + } + weapon_falloff_templates.SHOTGUN_FALL_MODERATE = { + optimal_distance = 000, + optimal_range = 1400, + near_falloff = 0, + far_falloff = 2000, + near_multiplier = 1.4, + far_multiplier = 0.3, + } + weapon_falloff_templates.SHOTGUN_FALL_HIGH = { + optimal_distance = 000, + optimal_range = 1600, + near_falloff = 0, + far_falloff = 2500, + near_multiplier = 1.4, + far_multiplier = 0.4, + } + weapon_falloff_templates.SHOTGUN_FALL_VHIGH = { + optimal_distance = 000, + optimal_range = 1800, + near_falloff = 0, + far_falloff = 3000, + near_multiplier = 1.4, + far_multiplier = 0.5, + } + + return weapon_falloff_templates +end diff --git a/lua/weapontweakdata.lua b/lua/weapontweakdata.lua index b98df81..3384581 100644 --- a/lua/weapontweakdata.lua +++ b/lua/weapontweakdata.lua @@ -16,395 +16,889 @@ self.trip_mines = { alert_radius = 5000 } + +-- ALL OF THE TABLE STUFF + + +-- falloff tables +local FALLOFF_TEMPLATE = WeaponFalloffTemplate.setup_weapon_falloff_templates() + +-- total ammo tables +local total_ammo_tables = { + sniper = 30, + secondary_sniper = 20, + + lmg_low = 300, + lmg_high = 400, + + shot_very_high = 70, + shot_high = 60, + shot_mid = 50, + shot_low = 40, + shot_very_low = 30, + + ar_high = 180, + ar_mid = 150, + ar_low = 120, + ar_very_low = 90, + + dmr = 70, + dmr_low = 50, + + smg_high = 150, + smg_mid = 120, + smg_low = 90, + smg_very_low = 90, + + pistol_high = 90, + pistol_mid = 75, + pistol_low = 60, + pistol_very_low = 45, + revolver = 36, + revolver_ap = 30, + + akimbo_pis_high = 180, + akimbo_pis_mid = 120, + akimbo_pis_low = 90, + akimbo_special = 60 +} + +-- ammo pickup tables +local pickup_tables = { + sniper_high = {1, 1.75}, + sniper_mid = {1, 1.375}, + sniper_low = {1, 1.2}, + secondary_sniper = {0.5, 1}, + + lmg = {4, 12}, + lmg_low = {4, 10}, + minigun = {4, 8}, + + shot_very_high = {2, 3}, + shot_high = {2, 2}, + shot_mid = {1, 2}, + shot_low = {0.8, 1.4}, + shot_very_low = {0.8, 1.25}, + shot_special = {0.25, 0.45}, + + ar_high = {2, 10}, + ar_mid = {2, 8}, + ar_low = {2, 6}, + ar_very_low = {2, 4}, + + dmr = {1, 1.5}, + dmr_low = {0.75, 1.1}, + + smg_high = {2, 9}, + smg_mid = {2, 7}, + smg_low = {2, 4.5}, + smg_very_low = {2, 3}, + + autopistol_high = {3, 5}, + autopistol_mid = {2, 4}, + pistol_high = {2.25, 3}, + pistol_mid = {2, 2.5}, + pistol_low = {1.5, 2}, + pistol_very_low = {1, 1.5}, + revolver_high = {1.25, 2}, + revolver = {1, 1.5}, + + revolver_ap = {0.35, 0.5}, + pistol_ap = {0.4, 0.6} +} + +-- normal kick tables +local kick_tables = { + sniper_auto = {standing = {3, 3.8, -0.3, 0.3}, crouching = {3, 3.8, -0.3, 0.3}, steelsight = {3, 3.8, -0.3, 0.3}}, + sniper_low = {standing = {4, 4.8, -0.3, 0.3}, crouching = {4, 4.8, -0.3, 0.3}, steelsight = {4, 4.8, -0.3, 0.3}}, + sniper_mid = {standing = {4.5, 5.5, -0.3, 0.3}, crouching = {4.5, 5.5, -0.3, 0.3}, steelsight = {4.5, 5.5, -0.3, 0.3}}, + + lmg = {standing = {0.8, 1.2, -1.1, 1.1}, crouching = {0.8, 1.2, -1.1, 1.1}, steelsight = {0.8, 1.2, -1.1, 1.1}}, + lmg_high = {standing = {0.9, 1.2, -1, 1}, crouching = {0.9, 1.2, -1, 1}, steelsight = {0.9, 1.2, -1, 1}}, + mini = {standing = {0.3, 0.4, -0.2, 0.5}, crouching = {0.3, 0.4, -0.2, 0.5}, steelsight = {0.3, 0.4, -0.2, 0.5}}, + micro = {standing = {0.5, 0.7, -0.6, 0.2}, crouching = {0.5, 0.7, -0.6, 0.2}, steelsight = {0.5, 0.7, -0.6, 0.2}}, + hailstorm = {standing = {0.75, 0.9, -0.8, 0.8}, crouching = {0.75, 0.9, -0.8, 0.8}, steelsight = {0.75, 0.9, -0.8, 0.8}}, + hailstorm_volley = {standing = {5, 6, -0.16, 0.16}, crouching = {5, 6, -0.16, 0.16}, steelsight ={0.75, 0.9, -0.8, 0.8}}, + + shot_auto = {standing = {2, 2.5, -0.5, 0.5}, crouching = {2, 2.5, -0.5, 0.5}, steelsight = {2, 2.5, -0.5, 0.5}}, + shot_low = {standing = {3, 4, -0.5, 0.5}, crouching = {3, 4, -0.5, 0.5}, steelsight = {3, 4, -0.5, 0.5}}, + shot_high = {standing = {4, 5, -0.5, 0.5}, crouching = {4, 5, -0.5, 0.5}, steelsight = {4, 5, -0.5, 0.5}}, + + ar_low = {standing = {1.3, 1.4, -0.95, 0.95}, crouching = {1.3, 1.4, -0.95, 0.95}, steelsight = {1.3, 1.4, -0.95, 0.95}}, + ar_mid = {standing = {1.4, 1.6, -1.15, 1.15}, crouching = {1.4, 1.6, -1.15, 1.15}, steelsight = {1.4, 1.6, -1.15, 1.15}}, + ar_high = {standing = {1.5, 1.75, -1.2, 1.2}, crouching = {1.5, 1.75, -1.2, 1.2}, steelsight = {1.5, 1.75, -1.2, 1.2}}, + ar_very_high = {standing = {1.6, 1.9, -1.25, 1.25}, crouching = {1.6, 1.9, -1.25, 1.25}, steelsight = {1.6, 1.9, -1.25, 1.25}}, + ks12 = {standing = {1.5, 1.8, -1.25, 1.25}, crouching = {1.5, 1.8, -1.25, 1.25}, steelsight = {1.5, 1.8, -1.25, 1.25}}, + + dmr_low = {standing = {1.6, 2, -0.45, 0.45}, crouching = {1.6, 2, -0.45, 0.45}, steelsight = {1.6, 2, -0.45, 0.45}}, + dmr_high = {standing = {3.5, 4.5, -0.6, 0.6}, crouching = {3.5, 4.5, -0.6, 0.6}, steelsight = {3.5, 4.5, -0.6, 0.6}}, + + revolver_ap = {standing = {2.9, 3, -0.5, 0.5}, crouching = {2.9, 3, -0.5, 0.5}, steelsight = {2.9, 3, -0.5, 0.5}}, + revolver = {standing = {1.8, 2.2, -0.45, 0.45}, crouching = {1.8, 2.2, -0.45, 0.45}, steelsight = {1.8, 2.2, -0.45, 0.45}}, + revolver_low = {standing = {1.3, 1.6, -0.45, 0.45}, crouching = {1.3, 1.6, -0.45, 0.45}, steelsight = {1.3, 1.6, -0.45, 0.45}}, + + pistol_auto = {standing = {1.1, 1.3, -0.95, 0.95}, crouching = {1.1, 1.3, -0.95, 0.95}, steelsight = {1.1, 1.3, -0.95, 0.95}}, + pistol_low = {standing = {1.55, 2.05, -0.75, 0.75}, crouching = {1.55, 2.05, -0.75, 0.75}, steelsight = {1.55, 2.05, -0.75, 0.75}}, + pistol_mid = {standing = {1.8, 2.3, -0.8, 0.8}, crouching = {1.8, 2.3, -0.8, 0.8}, steelsight = {1.8, 2.3, -0.8, 0.8}}, + pistol_high = {standing = {2, 2.5, -0.8, 0.8}, crouching = {2, 2.5, -0.8, 0.8}, steelsight = {2, 2.5, -0.8, 0.8}}, + + akimbo_pistol_auto = {standing = {0.95, 1.05, -1.1, 1.1}, crouching = {0.95, 1.05, -1.1, 1.1}, steelsight = {0.95, 1.05, -1.1, 1.1}} +} + +-- spray pattern tables +local spray_tables = { + ar_left_low = { + pattern = { + { up = 0.7, down = 0.7, left = -0.04, right = 0.05 }, + { up = 1.3, down = 1.3, left = -0.15, right = 0.14 }, + { up = 1.8, down = 1.8, left = 0.15, right = 0.3 }, + { up = 1.5, down = 1.5, left = -0.3, right = -0.15 }, + { up = 1.3, down = 1.3, left = 0.15, right = 0.5 }, + { up = 1.9, down = 1.9, left = 0.14, right = 0.15 }, + { up = 1.4, down = 1.4, left = -1, right = -1 }, + { up = 1.1, down = 1.1, left = -0.8, right = -0.8 }, + { up = 0.45, down = 0.45, left = -1, right = -1 }, + { up = 0.95, down = 0.95, left = 0.7, right = 0.7 }, + { up = 0.45, down = 0.45, left = 0.9, right = 1 }, + { up = 0.35, down = 0.55, left = 1, right = 1.1 }, + { up = 0.55, down = 0.55, left = 1.1, right = 1.2 }, + }, + persist_pattern = { + { up = 0.5, down = 0.75, left = -0.5, right = 1.5 } + } + }, + ar_right_low = { + pattern = { + { up = 0.7, down = 0.7, left = -0.05, right = 0.04 }, + { up = 1.3, down = 1.3, left = -0.15, right = -0.14 }, + { up = 1.8, down = 1.8, left = -0.15, right = -0.3 }, + { up = 1.5, down = 1.5, left = 0.3, right = 0.15 }, + { up = 1.3, down = 1.3, left = -0.15, right = -0.5 }, + { up = 1.9, down = 1.9, left = -0.14, right = -0.15 }, + { up = 1.4, down = 1.4, left = 1, right = 1 }, + { up = 1.1, down = 1.1, left = 0.8, right = 0.8 }, + { up = 0.45, down = 0.45, left = 1, right = 1 }, + { up = 0.95, down = 0.95, left = -0.7, right = -0.7 }, + { up = 0.45, down = 0.45, left = -0.9, right = -1 }, + { up = 0.35, down = 0.55, left = -1, right = -1.1 }, + { up = 0.55, down = 0.55, left = -1.1, right = -1.2 }, + }, + persist_pattern = { + { up = 0.5, down = 0.75, left = 0.5, right = -1.5 } + } + }, + ar_left_mid = { + pattern = { + { up = 0.8, down = 0.8, left = -0.04, right = 0.05 }, + { up = 1.5, down = 1.5, left = -0.2, right = 0.19 }, + { up = 2, down = 2, left = 0.25, right = 0.4 }, + { up = 1.6, down = 1.6, left = -0.4, right = -0.25 }, + { up = 1.4, down = 1.4, left = 0.3, right = 0.6 }, + { up = 2, down = 2.1, left = 0.2, right = 0.3 }, + { up = 1.55, down = 1.55, left = -1.2, right = -1.2 }, + { up = 1.25, down = 1.25, left = -1, right = -1 }, + { up = 0.45, down = 0.45, left = -1.3, right = -1.3 }, + { up = 0.95, down = 0.95, left = 0.9, right = 0.9 }, + { up = 0.45, down = 0.45, left = 1, right = 1.15 }, + { up = 0.35, down = 0.55, left = 1.1, right = 1.3 }, + { up = 0.55, down = 0.55, left = 1.3, right = 1.5 }, + }, + persist_pattern = { + { up = 0.5, down = 0.75, left = -0.75, right = 1.75 } + } + }, + ar_right_mid = { + pattern = { + { up = 0.8, down = 0.8, left = 0.04, right = -0.05 }, + { up = 1.5, down = 1.5, left = 0.2, right = -0.19 }, + { up = 2, down = 2, left = -0.25, right = -0.4 }, + { up = 1.6, down = 1.6, left = 0.4, right = 0.25 }, + { up = 1.4, down = 1.4, left = -0.3, right = -0.6 }, + { up = 2, down = 2.1, left = -0.2, right = -0.3 }, + { up = 1.55, down = 1.55, left = 1.2, right = 1.2 }, + { up = 1.25, down = 1.25, left = 1, right = 1 }, + { up = 0.45, down = 0.45, left = 1.3, right = 1.3 }, + { up = 0.95, down = 0.95, left = -0.9, right = -0.9 }, + { up = 0.45, down = 0.45, left = -1, right = -1.15 }, + { up = 0.35, down = 0.55, left = -1.1, right = -1.3 }, + { up = 0.55, down = 0.55, left = -1.3, right = -1.5 }, + }, + persist_pattern = { + { up = 0.5, down = 0.75, left = 0.75, right = -1.75 } + } + }, + ar_left_high = { + pattern = { + { up = 0.8, down = 0.9, left = -0.04, right = 0.05 }, + { up = 1.6, down = 1.65, left = -0.2, right = 0.19 }, + { up = 2.1, down = 2.1, left = 0.25, right = 0.4 }, + { up = 1.75, down = 1.75, left = -0.4, right = -0.25 }, + { up = 1.55, down = 1.55, left = 0.3, right = 0.6 }, + { up = 2.1, down = 2.2, left = 0.2, right = 0.3 }, + { up = 1.6, down = 1.65, left = -1.2, right = -1.2 }, + { up = 1.3, down = 1.4, left = -1, right = -1 }, + { up = 0.45, down = 0.45, left = -1.3, right = -1.3 }, + { up = 0.95, down = 0.95, left = 0.9, right = 0.9 }, + { up = 0.45, down = 0.45, left = 1, right = 1.15 }, + { up = 0.35, down = 0.55, left = 1.1, right = 1.3 }, + { up = 0.55, down = 0.55, left = 1.3, right = 1.5 }, + }, + persist_pattern = { + { up = 0.5, down = 0.75, left = -0.75, right = 1.75 } + } + }, + ar_right_high = { + pattern = { + { up = 0.8, down = 0.9, left = 0.04, right = -0.05 }, + { up = 1.6, down = 1.65, left = 0.2, right = -0.19 }, + { up = 2.1, down = 2.1, left = -0.25, right = -0.4 }, + { up = 1.75, down = 1.75, left = 0.4, right = 0.25 }, + { up = 1.55, down = 1.55, left = -0.3, right = -0.6 }, + { up = 2.1, down = 2.2, left = -0.2, right = -0.3 }, + { up = 1.6, down = 1.65, left = 1.2, right = 1.2 }, + { up = 1.3, down = 1.4, left = 1, right = 1 }, + { up = 0.45, down = 0.45, left = 1.3, right = 1.3 }, + { up = 0.95, down = 0.95, left = -0.9, right = -0.9 }, + { up = 0.45, down = 0.45, left = -1, right = -1.15 }, + { up = 0.35, down = 0.55, left = -1.1, right = -1.3 }, + { up = 0.55, down = 0.55, left = -1.3, right = -1.5 }, + }, + persist_pattern = { + { up = 0.5, down = 0.75, left = 0.75, right = -1.75 } + } + }, + sg_auto = { + pattern = { + { up = 3, down = 3, left = 0.5, right = 1 }, + { up = 3, down = 3, left = -1, right = -0.5 }, + { up = 2, down = 2.5, left = -1.8, right = -2 }, + { up = 2, down = 2, left = -2.3, right = -2.5 }, + { up = 1.25, down = 1.5, left = 1, right = 0.8 }, + { up = 1.5, down = 1.5, left = 2, right = 2.5 }, + { up = 1, down = 1.25, left = 2.5, right = 3 }, + { up = 1.5, down = 1.5, left = 3, right = 3 }, + }, + persist_pattern = { + { up = 2, down = 3, left = -3, right = 1 }, + } + }, + lmg_right = { + pattern = { + { up = 0.2, down = 0.2, left = 0.8, right = 0.8 }, + { up = 0.5, down = 0.8, left = 0.8, right = 0.8 }, + { up = 0.8, down = 0.8, left = 0.6, right = 0.6 }, + { up = 0.9, down = 1, left = 0.6, right = 0.6 }, + { up = 1, down = 1.1, left = 0.6, right = 0.6 }, + { up = 1.1, down = 1.2, left = 0.6, right = 0.6 }, + { up = 1.2, down = 1.3, left = 0.4, right = 0.4 }, + { up = 1.2, down = 1.4, left = 0.2, right = 0.3 }, + { up = 0.8, down = 0.8, left = -0.2, right = -0.3 }, + { up = 0.8, down = 0.8, left = -0.4, right = -0.8 }, + { up = 1, down = 1, left = -0.8, right = -1 }, + { up = 1, down = 1.1, left = -1, right = -1 }, + { up = 1.1, down = 1.3, left = -0.8, right = -1 }, + { up = 1.3, down = 1.3, left = -0.7, right = -0.7 }, + { up = 1.1, down = 1.1, left = -0.3, right = -0.2 }, + { up = 1, down = 1.2, left = 0.3, right = 0.4 }, + }, + persist_pattern = { + { up = 0.8, down = 1.1, left = -1, right = 2 } + } + }, + lmg_left = { + pattern = { + { up = 0.2, down = 0.2, left = -0.8, right = -0.8 }, + { up = 0.5, down = 0.8, left = -0.8, right = -0.8 }, + { up = 0.8, down = 0.8, left = -0.6, right = -0.6 }, + { up = 0.9, down = 1, left = -0.6, right = -0.6 }, + { up = 1, down = 1.1, left = -0.6, right = -0.6 }, + { up = 1.1, down = 1.2, left = -0.6, right = -0.6 }, + { up = 1.2, down = 1.3, left = -0.4, right = -0.4 }, + { up = 1.2, down = 1.4, left = 0.2, right = 0.3 }, + { up = 0.8, down = 0.8, left = 0.2, right = 0.3 }, + { up = 0.8, down = 0.8, left = 0.4, right = 0.8 }, + { up = 1, down = 1, left = 0.8, right = 1 }, + { up = 1, down = 1.1, left = 1, right = 1 }, + { up = 1.1, down = 1.3, left = 0.8, right = 1 }, + { up = 1.3, down = 1.3, left = 0.7, right = 0.7 }, + { up = 1.1, down = 1.1, left = 0.3, right = 0.2 }, + { up = 1, down = 1.2, left = -0.3, right = -0.4 }, + }, + persist_pattern = { + { up = 0.8, down = 1.1, left = -2, right = 1 } + } + }, + mini = { + pattern = { + { up = 0.4, down = 0.5, left = -0.1, right = -0.3 }, + { up = 0.5, down = 0.6, left = -0.1, right = -0.3 }, + { up = 0.6, down = 0.7, left = -0.1, right = -0.3 }, + { up = 0.6, down = 0.7, left = -0.1, right = -0.3 }, + { up = 0.6, down = 0.7, left = -0.1, right = -0.3 }, + { up = 0.6, down = 0.7, left = -0.1, right = -0.3 }, + { up = 0.6, down = 0.7, left = 0.1, right = 0.3 }, + { up = 0.6, down = 0.7, left = 0.1, right = 0.3 }, + { up = 0.6, down = 0.7, left = 0.1, right = 0.3 }, + { up = 0.6, down = 0.7, left = 0.1, right = 0.3 }, + { up = 0.6, down = 0.7, left = 0.1, right = 0.35 }, + { up = 0.6, down = 0.7, left = 0.1, right = 0.35 }, + { up = 0.6, down = 0.7, left = 0.1, right = 0.35 }, + { up = 0.6, down = 0.7, left = 0.1, right = 0.35 }, + { up = 0.6, down = 0.7, left = -0.15, right = 0.12 }, + { up = 0.6, down = 0.7, left = -0.15, right = 0.12 }, + { up = 0.6, down = 0.7, left = -0.15, right = 0.12 }, + { up = 0.6, down = 0.7, left = -0.15, right = 0.12 }, + { up = 0.6, down = 0.7, left = -0.15, right = 0.12 }, + { up = 0.6, down = 0.7, left = -0.15, right = 0.12 }, + { up = 0.6, down = 0.7, left = -0.15, right = 0.12 }, + { up = 0.6, down = 0.7, left = -0.15, right = 0.12 }, + { up = 0.5, down = 0.5, left = -0.15, right = 0.12 }, + { up = 0.5, down = 0.5, left = -0.15, right = 0.12 }, + { up = 0.5, down = 0.5, left = -0.15, right = 0.12 }, + { up = 0.5, down = 0.5, left = -0.15, right = 0.12 }, + { up = 0.5, down = 0.5, left = -0.15, right = 0.12 }, + { up = 0.4, down = 0.5, left = -0.15, right = 0.12 }, + { up = 0.4, down = 0.5, left = -0.15, right = 0.12 }, + { up = 0.4, down = 0.5, left = -0.15, right = 0.12 }, + { up = 0.4, down = 0.5, left = -0.15, right = 0.12 }, + { up = 0.4, down = 0.5, left = -0.15, right = 0.12 }, + { up = 0.3, down = 0.4, left = -0.15, right = 0.12 }, + { up = 0.3, down = 0.4, left = -0.15, right = 0.12 }, + { up = 0.3, down = 0.4, left = -0.15, right = 0.12 }, + { up = 0.3, down = 0.4, left = -0.15, right = 0.12 }, + { up = 0.3, down = 0.4, left = -0.15, right = 0.12 }, + { up = 0.3, down = 0.4, left = -0.15, right = 0.12 }, + { up = 0.2, down = 0.2, left = -0.15, right = 0.12 }, + { up = 0.2, down = 0.2, left = -0.15, right = 0.12 }, + { up = 0.2, down = 0.2, left = -0.15, right = 0.12 }, + { up = 0.2, down = 0.2, left = -0.15, right = 0.12 }, + { up = 0.2, down = 0.2, left = -0.15, right = 0.12 }, + { up = 0.1, down = 0.1, left = -0.15, right = 0.12 }, + { up = 0.1, down = 0.1, left = -0.15, right = 0.12 }, + { up = 0.1, down = 0.1, left = -0.15, right = 0.12 }, + }, + persist_pattern = { + { up = 0.05, down = 0.1, left = -0.3, right = 0.35 } + } + } +} + +-- recoil recovery timer tables +local recovery_tables = { + low = 0.175, + mid = 0.35, + high = 0.5 +} + + + +-- ACTUAL WEAPON STUFF + + -- Sniper Rifles -- Contractor -self.tti.AMMO_MAX = 30 +self.tti.AMMO_MAX = total_ammo_tables.sniper self.tti.CLIP_AMMO_MAX = 15 self.tti.stats.concealment = 13 -self.tti.stats.damage = 120 +self.tti.stats.damage = 145 self.tti.stats_modifiers = {damage = 2} -self.tti.AMMO_PICKUP = {1.5, 2} -self.tti.kick.standing = {3, 3.8, -0.3, 0.3} -self.tti.kick.crouching = self.tti.kick.standing -self.tti.kick.steelsight = self.tti.kick.standing +self.tti.AMMO_PICKUP = pickup_tables.sniper_high +self.tti.kick = kick_tables.sniper_auto self.tti.categories = {"snp", "ng"} -- Grom -self.siltstone.AMMO_MAX = 30 -self.siltstone.stats.damage = 120 +self.siltstone.AMMO_MAX = total_ammo_tables.sniper +self.siltstone.stats.damage = 145 self.siltstone.stats_modifiers = {damage = 2} self.siltstone.stats.concealment = 20 -self.siltstone.AMMO_PICKUP = {1.5, 2} -self.siltstone.kick.standing = {3, 3.8, -0.3, 0.3} -self.siltstone.kick.crouching = self.siltstone.kick.standing -self.siltstone.kick.steelsight = self.siltstone.kick.standing +self.siltstone.AMMO_PICKUP = pickup_tables.sniper_high +self.siltstone.kick = kick_tables.sniper_auto self.siltstone.categories = {"snp", "ng"} -- Kang Arms -self.qbu88.AMMO_MAX = 30 +self.qbu88.AMMO_MAX = total_ammo_tables.sniper self.qbu88.stats.recoil = 7 -self.qbu88.stats.damage = 120 +self.qbu88.stats.damage = 145 self.qbu88.stats_modifiers = {damage = 2} -self.qbu88.AMMO_PICKUP = {1.5, 2} -self.qbu88.kick.standing = {3.8, 4.5, -0.3, 0.3} -self.qbu88.kick.crouching = self.qbu88.kick.standing -self.qbu88.kick.steelsight = self.qbu88.kick.standing +self.qbu88.AMMO_PICKUP = pickup_tables.sniper_high +self.qbu88.kick = kick_tables.sniper_auto self.qbu88.categories = {"snp", "ng"} -- Lebensauger -self.wa2000.AMMO_MAX = 30 +self.wa2000.AMMO_MAX = total_ammo_tables.sniper self.wa2000.CLIP_AMMO_MAX = 10 self.wa2000.stats.reload = 13 -self.wa2000.stats.damage = 120 +self.wa2000.stats.damage = 145 self.wa2000.stats_modifiers = {damage = 2} -self.wa2000.AMMO_PICKUP = {1.5, 2} -self.wa2000.kick.standing = {3, 3.8, -0.3, 0.3} -self.wa2000.kick.crouching = self.wa2000.kick.standing -self.wa2000.kick.steelsight = self.wa2000.kick.standing +self.wa2000.AMMO_PICKUP = pickup_tables.sniper_high +self.wa2000.kick = kick_tables.sniper_auto self.wa2000.categories = {"snp", "ng"} --- Rangehitter -self.sbl.AMMO_MAX = 30 -self.sbl.CLIP_AMMO_MAX = 15 -self.sbl.stats.damage = 120 -self.sbl.stats.reload = 13 -self.sbl.AMMO_PICKUP = {1.5, 2} -self.sbl.fire_mode_data.fire_rate = 60 / 150 -self.sbl.kick.standing = {3, 3.8, -0.3, 0.3} -self.sbl.kick.crouching = self.sbl.kick.standing -self.sbl.kick.steelsight = self.sbl.kick.standing -self.sbl.categories = {"snp", "ng"} - -- Repeater -self.winchester1874.stats.damage = 147 +self.winchester1874.stats.damage = 155 self.winchester1874.stats_modifiers = {damage = 2} -self.winchester1874.fire_mode_data.fire_rate = 60 / 85 -self.winchester1874.AMMO_PICKUP = {1.1, 1.5} -self.winchester1874.AMMO_MAX = 30 +self.winchester1874.fire_mode_data.fire_rate = 60 / 75 +self.winchester1874.AMMO_PICKUP = pickup_tables.sniper_mid +self.winchester1874.AMMO_MAX = total_ammo_tables.sniper +self.winchester1874.kick = kick_tables.sniper_low -- Rattlesnake -self.msr.stats.damage = 147 +self.msr.stats.damage = 155 self.msr.stats_modifiers = {damage = 2} -self.msr.AMMO_PICKUP = {1.1, 1.5} -self.msr.AMMO_MAX = 30 +self.msr.AMMO_PICKUP = pickup_tables.sniper_mid +self.msr.AMMO_MAX = total_ammo_tables.sniper +self.msr.kick = kick_tables.sniper_low -- R700 -self.r700.stats.damage = 147 +self.r700.stats.damage = 155 self.r700.stats_modifiers = {damage = 2} -self.r700.AMMO_PICKUP = {1.1, 1.5} +self.r700.AMMO_PICKUP = pickup_tables.sniper_mid self.r700.fire_mode_data.fire_rate = 60 / 60 -self.r700.AMMO_MAX = 30 -self.r700.kick.standing = self.msr.kick.standing -self.r700.kick.crouching = self.msr.kick.crouching -self.r700.kick.steelsight = self.msr.kick.steelsight +self.r700.AMMO_MAX = total_ammo_tables.sniper +self.r700.kick = kick_tables.sniper_low -- Desert Fox -self.desertfox.AMMO_PICKUP = {0.9, 1.2} +self.desertfox.AMMO_MAX = total_ammo_tables.sniper +self.desertfox.AMMO_PICKUP = pickup_tables.sniper_low self.desertfox.fire_mode_data.fire_rate = 60 / 50 -self.desertfox.kick.standing = self.r93.kick.standing -self.desertfox.kick.crouching = self.r93.kick.crouching -self.desertfox.kick.steelsight = self.r93.kick.steelsight +self.desertfox.kick = kick_tables.sniper_mid -- Nagant self.mosin.AMMO_MAX = 30 self.mosin.fire_mode_data.fire_rate = 60 / 70 -self.mosin.AMMO_PICKUP = {0.9, 1.2} -self.mosin.kick.standing = self.r93.kick.standing -self.mosin.kick.crouching = self.r93.kick.crouching -self.mosin.kick.steelsight = self.r93.kick.steelsight +self.mosin.AMMO_PICKUP = pickup_tables.sniper_low +self.mosin.AMMO_MAX = total_ammo_tables.sniper +self.mosin.kick = kick_tables.sniper_mid -- R93 self.r93.fire_mode_data.fire_rate = 60 / 55 -self.r93.AMMO_PICKUP = {0.9, 1.2} +self.r93.AMMO_PICKUP = pickup_tables.sniper_low +self.r93.AMMO_MAX = total_ammo_tables.sniper +self.r93.kick = kick_tables.sniper_mid -- Platypus self.model70.CLIP_AMMO_MAX = 6 -self.model70.AMMO_PICKUP = {0.9, 1.2} -self.model70.kick.standing = self.r93.kick.standing -self.model70.kick.crouching = self.r93.kick.crouching -self.model70.kick.steelsight = self.r93.kick.steelsight +self.model70.AMMO_MAX = total_ammo_tables.sniper +self.model70.AMMO_PICKUP = pickup_tables.sniper_low +self.model70.kick = kick_tables.sniper_mid -- Thanatos -self.m95.stats.damage = 200 -self.m95.stats_modifiers = {damage = 25} -self.m95.kick.standing = {5, 6, -1, 1} +self.m95.stats.damage = 125 +self.m95.stats_modifiers = {damage = 8} +self.m95.AMMO_PICKUP = {0.55, 0.65} +self.m95.AMMO_MAX = total_ammo_tables.secondary_sniper +self.m95.fire_mode_data.fire_rate = 1.5 +self.m95.kick.standing = {4, 5, -1, 1} self.m95.kick.crouching = self.m95.kick.standing self.m95.kick.steelsight = self.m95.kick.standing +self.m95.timers = { + reload_not_empty = 3.8, + reload_empty = 5.1, + unequip = 0.9, + equip = 0.9 +} +-- AWP +self.awp.CLIP_AMMO_MAX = 5 +self.awp.stats.damage = 125 +self.awp.stats.reload = 9 +self.awp.stats_modifiers = {damage = 8} +self.awp.AMMO_PICKUP = {0.4, 0.5} +self.awp.AMMO_MAX = total_ammo_tables.secondary_sniper +self.awp.kick.standing = {4, 5, -1, 1} +self.awp.kick.crouching = self.m95.kick.standing +self.awp.kick.steelsight = self.m95.kick.standing --- LMGs and Miniguns +-- Rangehitter +self.sbl.use_data.selection_index = SELECTION.SECONDARY +self.sbl.AMMO_MAX = total_ammo_tables.secondary_sniper +self.sbl.CLIP_AMMO_MAX = 10 +self.sbl.stats.damage = 145 +self.sbl.AMMO_PICKUP = pickup_tables.secondary_sniper +self.sbl.fire_mode_data.fire_rate = 60 / 150 +self.sbl.kick = kick_tables.sniper_auto +self.sbl.categories = {"snp", "ng"} + +-- Scout +self.scout.stats.damage = 155 +self.scout.AMMO_MAX = total_ammo_tables.secondary_sniper +self.scout.AMMO_PICKUP = pickup_tables.secondary_sniper +self.scout.kick = kick_tables.sniper_low + +-- North Star +self.victor.AMMO_MAX = total_ammo_tables.secondary_sniper +self.victor.stats.damage = 145 +self.victor.stats_modifiers = {damage = 2} +self.victor.AMMO_PICKUP = pickup_tables.secondary_sniper +self.victor.kick = kick_tables.sniper_auto +self.victor.categories = {"snp", "ng"} + +-- Aran +self.contender.AMMO_MAX = total_ammo_tables.secondary_sniper +self.contender.ignore_damage_upgrades = false +self.contender.fire_mode_data.fire_rate = 60 / 70 +self.contender.AMMO_PICKUP = pickup_tables.secondary_sniper +self.contender.kick = kick_tables.sniper_mid + + + +-- LMGs and Miniguns -- KSP -self.m249.AMMO_PICKUP = {8, 10} -self.m249.kick.standing = {0.8, 1.2, -1, 1} -self.m249.kick.crouching = self.m249.kick.standing -self.m249.kick.steelsight = self.m249.kick.standing +self.m249.stats.damage = 60 +self.m249.stats.reload = 9 +self.m249.stats.recoil = 6 +self.m249.stats.concealment = 0 +self.m249.AMMO_MAX = total_ammo_tables.lmg_high +self.m249.AMMO_PICKUP = pickup_tables.lmg +self.m249.kick = kick_tables.lmg +self.m249.spray = spray_tables.lmg_right +self.m249.recoil_recovery_timer = recovery_tables.high -- KSP 58 -self.par.AMMO_PICKUP = {8, 10} -self.par.kick.standing = self.m249.kick.standing -self.par.kick.crouching = self.m249.kick.standing -self.par.kick.steelsight = self.m249.kick.standing +self.par.stats.damage = 60 +self.par.stats.reload = 9 +self.par.stats.recoil = 6 +self.par.stats.concealment = 0 +self.par.AMMO_MAX = total_ammo_tables.lmg_high +self.par.AMMO_PICKUP = pickup_tables.lmg +self.par.kick = kick_tables.lmg +self.par.spray = spray_tables.lmg_left +self.par.recoil_recovery_timer = recovery_tables.high -- Buzzsaw -self.mg42.AMMO_PICKUP = {8, 10} -self.mg42.kick.standing = {0.9, 1.3, -1, 1} -self.mg42.kick.crouching = self.mg42.kick.standing -self.mg42.kick.steelsight = self.m249.kick.standing +self.mg42.stats.damage = 60 +self.mg42.stats.reload = 9 +self.mg42.stats.recoil = 6 +self.mg42.stats.concealment = 0 +self.mg42.AMMO_MAX = total_ammo_tables.lmg_low +self.mg42.AMMO_PICKUP = pickup_tables.lmg +self.mg42.kick = kick_tables.lmg +self.mg42.spray = spray_tables.lmg_left +self.mg42.recoil_recovery_timer = recovery_tables.high -- RPK self.rpk.stats.spread = 10 -self.rpk.stats.damage = 110 +self.rpk.stats.damage = 80 self.rpk.stats.reload = 9 +self.rpk.stats.recoil = 2 self.rpk.stats.concealment = 1 -self.rpk.AMMO_PICKUP = {8, 10} -self.rpk.kick.standing = {0.9, 1.2, -0.9, 0.9} -self.rpk.kick.crouching = self.rpk.kick.standing -self.rpk.kick.steelsight = self.rpk.kick.standing +self.rpk.AMMO_MAX = total_ammo_tables.lmg_low +self.rpk.AMMO_PICKUP = pickup_tables.lmg_low +self.rpk.kick = kick_tables.lmg_high +self.rpk.spray = spray_tables.lmg_right +self.rpk.recoil_recovery_timer = recovery_tables.high -- Brenner self.hk21.stats.spread = 12 -self.hk21.stats.damage = 110 -self.hk21.stats.reload = 12 -self.hk21.AMMO_PICKUP = {8, 10} -self.hk21.kick.standing = self.rpk.kick.standing -self.hk21.kick.crouching = self.rpk.kick.standing -self.hk21.kick.steelsight = self.rpk.kick.standing +self.hk21.stats.damage = 80 +self.hk21.stats.reload = 9 +self.hk21.stats.recoil = 2 +self.hk21.stats.concealment = 0 +self.hk21.AMMO_MAX = total_ammo_tables.lmg_low +self.hk21.AMMO_PICKUP = pickup_tables.lmg_low +self.hk21.kick = kick_tables.lmg_high +self.hk21.spray = spray_tables.lmg_left +self.hk21.recoil_recovery_timer = recovery_tables.high -- M60 -self.m60.stats.damage = 110 -self.m60.AMMO_PICKUP = {8, 10} -self.m60.kick.standing = self.rpk.kick.standing -self.m60.kick.crouching = self.rpk.kick.standing -self.m60.kick.steelsight = self.rpk.kick.standing - --- new hk51b lmg idr the in-game name lol -self.hk51b.stats.damage = 100 -self.hk51b.AMMO_PICKUP = {7, 8} -self.hk51b.kick.standing = self.rpk.kick.standing -self.hk51b.kick.crouching = self.rpk.kick.standing -self.hk51b.kick.steelsight = self.rpk.kick.standing +self.m60.stats.damage = 80 +self.m60.stats.reload = 9 +self.m60.stats.recoil = 3 +self.m60.stats.concealment = 0 +self.m60.AMMO_MAX = total_ammo_tables.lmg_low +self.m60.AMMO_PICKUP = pickup_tables.lmg_low +self.m60.kick = kick_tables.lmg_high +self.m60.spray = spray_tables.lmg_right +self.m60.recoil_recovery_timer = recovery_tables.high + +-- Akron +self.hcar.stats.damage = 80 +self.hcar.stats.concealment = 0 +self.hcar.AMMO_MAX = total_ammo_tables.ar_low +self.hcar.AMMO_PICKUP = pickup_tables.ar_low +self.hcar.kick = kick_tables.lmg_high +self.hcar.spray = spray_tables.lmg_left +self.hcar.recoil_recovery_timer = recovery_tables.high + +-- verstchektshscxd +self.hk51b.stats.damage = 75 +self.hk51b.stats.reload = 9 +self.hk51b.stats.concealment = 10 +self.hk51b.AMMO_PICKUP = pickup_tables.ar_mid +self.hk51b.AMMO_MAX = total_ammo_tables.ar_low +self.hk51b.kick = kick_tables.lmg_high +self.hk51b.spray = spray_tables.lmg_left +self.hk51b.recoil_recovery_timer = recovery_tables.high + +-- campbell +self.kacchainsaw.stats.damage = 60 +self.kacchainsaw.stats.reload = 9 +self.kacchainsaw.stats.recoil = 6 +self.kacchainsaw.stats.concealment = 0 +self.kacchainsaw.AMMO_MAX = total_ammo_tables.lmg_low +self.kacchainsaw.AMMO_PICKUP = pickup_tables.lmg +self.kacchainsaw.kick = kick_tables.lmg +self.kacchainsaw.spray = spray_tables.lmg_left +self.kacchainsaw.recoil_recovery_timer = recovery_tables.high -- Minigun self.m134.stats.damage = 40 -self.m134.AMMO_PICKUP = {4.5, 6} -self.m134.kick.standing = {0.3, 0.4, -0.2, 0.5} -self.m134.kick.crouching = self.m134.kick.standing -self.m134.kick.steelsight = self.m134.kick.standing +self.m134.stats.concealment = 0 +self.m134.AMMO_PICKUP = pickup_tables.minigun +self.m134.kick = kick_tables.mini +self.m134.spray = spray_tables.mini +self.m134.recoil_recovery_timer = recovery_tables.high -- Microgun self.shuno.stats.damage = 60 -self.shuno.AMMO_PICKUP = {4.5, 6} -self.shuno.kick.standing = {0.5, 0.7, -0.6, 0.2} -self.shuno.kick.crouching = self.shuno.kick.standing -self.shuno.kick.steelsight = self.shuno.kick.standing +self.shuno.stats.concealment = 0 +self.shuno.stats.recoil = 0 +self.shuno.AMMO_PICKUP = pickup_tables.minigun +self.shuno.kick = kick_tables.micro +self.shuno.spray = spray_tables.mini +self.shuno.recoil_recovery_timer = recovery_tables.high -- Hailstorm -self.hailstorm.stats.concealment = 2 -self.hailstorm.AMMO_PICKUP = {4.5, 6} -self.hailstorm.kick.standing = {0.75, 0.9, -0.75, 0.75} -self.hailstorm.kick.crouching = self.hailstorm.kick.standing -self.hailstorm.kick.steelsight = self.hailstorm.kick.standing -self.hailstorm.kick.volley.standing = {5, 6, -0.16, 0.16} -self.hailstorm.kick.volley.crouching = self.hailstorm.kick.volley.standing -self.hailstorm.kick.volley.steelsight = self.hailstorm.kick.standing +self.hailstorm.stats.concealment = 3 +-- self.hailstorm.AMMO_MAX = this one's fine actually +self.hailstorm.AMMO_PICKUP = pickup_tables.minigun +self.hailstorm.kick = kick_tables.hailstorm +self.hailstorm.kick.volley = kick_tables.hailstorm_volley +self.hailstorm.spray = spray_tables.ar_left_mid +self.hailstorm.recoil_recovery_timer = recovery_tables.high self.hailstorm.fire_mode_data.volley.can_shoot_through_wall = true self.hailstorm.fire_mode_data.volley.spread_mul = 1 -self.hailstorm.fire_mode_data.volley.damage_mul = 15 -self.hailstorm.fire_mode_data.volley.rays = 10 +self.hailstorm.fire_mode_data.volley.damage_mul = 20 +self.hailstorm.fire_mode_data.volley.rays = 6 self.hailstorm.fire_mode_data.volley.ammo_usage = 120 + -- Shotguns -- Izhma -self.saiga.rays = 12 +self.saiga.rays = 8 +self.saiga.stats.damage = 80 self.saiga.stats.spread = 12 -self.saiga.AMMO_PICKUP = {3, 4} -self.saiga.kick.standing = {2.5, 3.2, -0.5, 0.5} -self.saiga.kick.crouching = self.saiga.kick.standing -self.saiga.kick.steelsight = self.saiga.kick.standing -self.saiga.spread.standing = self.new_m4.spread.crouching -self.saiga.spread.moving_standing = self.new_m4.spread.crouching -self.saiga.spread.moving_crouching = self.new_m4.spread.crouching +self.saiga.AMMO_MAX = total_ammo_tables.shot_very_high +self.saiga.AMMO_PICKUP = pickup_tables.shot_very_high +self.saiga.kick = kick_tables.shot_auto +self.saiga.spray = spray_tables.sg_auto +self.saiga.recoil_recovery_timer = recovery_tables.high +self.saiga.damage_falloff = FALLOFF_TEMPLATE.SHOTGUN_FALL_NORMAL -- Steakout -self.aa12.rays = 12 +self.aa12.rays = 8 +self.aa12.stats.damage = 80 self.aa12.stats.spread = 12 -self.aa12.AMMO_PICKUP = {3, 4} +self.aa12.AMMO_MAX = total_ammo_tables.shot_very_high +self.aa12.AMMO_PICKUP = pickup_tables.shot_very_high self.aa12.fire_mode_data.fire_rate = 60 / 333 -self.aa12.kick.standing = {2.5, 3.2, -0.5, 0.5} -self.aa12.kick.crouching = self.aa12.kick.standing -self.aa12.kick.steelsight = self.aa12.kick.standing -self.aa12.spread.standing = self.new_m4.spread.crouching -self.aa12.spread.moving_standing = self.new_m4.spread.crouching -self.aa12.spread.moving_crouching = self.new_m4.spread.crouching +self.aa12.kick = kick_tables.shot_auto +self.aa12.spray = spray_tables.sg_auto +self.aa12.recoil_recovery_timer = recovery_tables.high +self.aa12.damage_falloff = FALLOFF_TEMPLATE.SHOTGUN_FALL_NORMAL -- VD-12 -self.sko12.CLIP_AMMO_MAX = 20 -self.sko12.rays = 12 -self.sko12.stats.damage = 55 +self.sko12.rays = 8 +self.sko12.stats.damage = 110 self.sko12.stats.spread = 12 self.sko12.stats.recoil = 8 self.sko12.stats.reload = 9 self.sko12.stats.concealment = 2 -self.sko12.AMMO_PICKUP = {3, 4} +self.sko12.CLIP_AMMO_MAX = 13 +self.sko12.AMMO_MAX = total_ammo_tables.shot_high +self.sko12.AMMO_PICKUP = pickup_tables.shot_high self.sko12.FIRE_MODE = "single" self.sko12.CAN_TOGGLE_FIREMODE = false self.sko12.fire_mode_data.fire_rate = 60 / 333 -self.sko12.kick.standing = {3, 4, -0.5, 0.5} -self.sko12.kick.crouching = self.sko12.kick.standing -self.sko12.kick.steelsight = self.sko12.kick.standing -self.sko12.spread.standing = self.new_m4.spread.crouching -self.sko12.spread.moving_standing = self.new_m4.spread.crouching -self.sko12.spread.moving_crouching = self.new_m4.spread.crouching +self.sko12.kick = kick_tables.shot_low +self.sko12.damage_falloff = FALLOFF_TEMPLATE.SHOTGUN_FALL_NORMAL -- M1014 -self.benelli.rays = 12 +self.benelli.rays = 8 +self.benelli.stats.damage = 110 self.benelli.stats.spread = 12 -self.benelli.AMMO_PICKUP = {3, 4} -self.benelli.spread.standing = self.new_m4.spread.crouching -self.benelli.spread.moving_standing = self.new_m4.spread.crouching -self.benelli.spread.moving_crouching = self.new_m4.spread.crouching +self.benelli.AMMO_MAX = total_ammo_tables.shot_high +self.benelli.AMMO_PICKUP = pickup_tables.shot_high +self.benelli.fire_mode_data.fire_rate = 60 / 383 +self.benelli.kick = kick_tables.shot_low +self.benelli.damage_falloff = FALLOFF_TEMPLATE.SHOTGUN_FALL_NORMAL -- Predator -self.spas12.rays = 12 +self.spas12.rays = 8 +self.spas12.stats.damage = 110 self.spas12.stats.spread = 12 -self.spas12.AMMO_PICKUP = {3, 4} -self.spas12.fire_mode_data.fire_rate = 60 / 429 -self.spas12.spread.standing = self.new_m4.spread.crouching -self.spas12.spread.moving_standing = self.new_m4.spread.crouching -self.spas12.spread.moving_crouching = self.new_m4.spread.crouching +self.spas12.AMMO_MAX = total_ammo_tables.shot_high +self.spas12.AMMO_PICKUP = pickup_tables.shot_high +self.spas12.fire_mode_data.fire_rate = 60 / 383 +self.spas12.kick = kick_tables.shot_low +self.spas12.damage_falloff = FALLOFF_TEMPLATE.SHOTGUN_FALL_NORMAL -- Raven -self.ksg.CLIP_AMMO_MAX = 10 -self.ksg.rays = 12 -self.ksg.stats.damage = 90 +self.ksg.rays = 8 +self.ksg.stats.damage = 155 self.ksg.stats.concealment = 20 self.ksg.stats.reload = 12 -self.ksg.AMMO_PICKUP = {1, 1.8} -self.ksg.fire_mode_data.fire_rate = 0.5 -self.ksg.kick.standing = {3, 4, -0.2, 0.2} -self.ksg.kick.crouching = self.ksg.kick.standing -self.ksg.kick.steelsight = self.ksg.kick.standing +self.ksg.CLIP_AMMO_MAX = 8 +self.ksg.AMMO_MAX = total_ammo_tables.shot_mid +self.ksg.AMMO_PICKUP = pickup_tables.shot_mid +self.ksg.fire_mode_data.fire_rate = 0.6 +self.ksg.kick = kick_tables.shot_low +self.ksg.damage_falloff = FALLOFF_TEMPLATE.SHOTGUN_FALL_MODERATE + +-- Nova +self.supernova.rays = 8 +self.supernova.stats.damage = 155 +self.supernova.AMMO_MAX = total_ammo_tables.shot_mid +self.supernova.AMMO_PICKUP = pickup_tables.shot_mid +self.supernova.fire_mode_data.fire_rate = 0.6 +self.supernova.kick = kick_tables.shot_low +self.supernova.damage_falloff = FALLOFF_TEMPLATE.SHOTGUN_FALL_MODERATE + -- Reinfeld 880 +self.r870.rays = 8 +self.r870.stats.damage = 155 self.r870.CLIP_AMMO_MAX = 8 -self.r870.rays = 12 -self.r870.stats.damage = 90 -self.r870.stats.reload = 12 -self.r870.AMMO_PICKUP = {1, 1.8} -self.r870.fire_mode_data.fire_rate = 0.5 -self.r870.kick.standing = {3, 4, -0.2, 0.2} -self.r870.kick.crouching = self.r870.kick.standing -self.r870.kick.steelsight = self.r870.kick.standing - --- Reinfeld 88 (Trench Gun) -self.m1897.rays = 12 -self.m1897.stats.damage = 125 -self.m1897.stats.reload = 12 -self.m1897.AMMO_PICKUP = {0.6, 1.6} -self.m1897.fire_mode_data.fire_rate = 0.6 -self.m1897.kick.standing = {3, 4, -0.2, 0.2} -self.m1897.kick.crouching = self.m1897.kick.standing -self.m1897.kick.steelsight = self.m1897.kick.standing +self.r870.AMMO_MAX = total_ammo_tables.shot_mid +self.r870.AMMO_PICKUP = pickup_tables.shot_mid +self.r870.fire_mode_data.fire_rate = 0.6 +self.r870.kick = kick_tables.shot_low +self.r870.damage_falloff = FALLOFF_TEMPLATE.SHOTGUN_FALL_MODERATE -- Mosconi Tactical -self.m590.rays = 12 +self.m590.rays = 8 self.m590.stats.reload = 11 -self.m590.stats.damage = 90 +self.m590.stats.damage = 155 self.m590.stats.concealment = 8 -self.m590.AMMO_PICKUP = {1, 1.8} -self.m590.fire_mode_data.fire_rate = 0.5 -self.m590.kick.standing = {3, 4, -0.2, 0.2} -self.m590.kick.crouching = self.m590.kick.standing -self.m590.kick.steelsight = self.m590.kick.standing +self.m590.AMMO_MAX = total_ammo_tables.shot_mid +self.m590.AMMO_PICKUP = pickup_tables.shot_mid +self.m590.fire_mode_data.fire_rate = 0.6 +self.m590.kick = kick_tables.shot_low +self.m590.damage_falloff = FALLOFF_TEMPLATE.SHOTGUN_FALL_MODERATE + +-- Breaker +self.boot.rays = 8 +self.boot.stats.damage = 135 +self.boot.stats_modifiers = {damage = 2} +self.boot.AMMO_MAX = total_ammo_tables.shot_low +self.boot.AMMO_PICKUP = pickup_tables.shot_low +self.boot.fire_mode_data.fire_rate = 0.8 +self.boot.kick = kick_tables.shot_high +self.boot.damage_falloff = FALLOFF_TEMPLATE.SHOTGUN_FALL_HIGH + +-- Reinfeld 88 (Trench Gun) +self.m1897.rays = 8 +self.m1897.stats.damage = 135 +self.m1897.stats_modifiers = {damage = 2} +self.m1897.AMMO_MAX = total_ammo_tables.shot_low +self.m1897.AMMO_PICKUP = pickup_tables.shot_low +self.m1897.fire_mode_data.fire_rate = 0.8 +self.m1897.kick = kick_tables.shot_high +self.m1897.damage_falloff = FALLOFF_TEMPLATE.SHOTGUN_FALL_HIGH + +-- GSPS +self.m37.use_data.selection_index = SELECTION.PRIMARY +self.m37.rays = 8 +self.m37.stats.damage = 135 +self.m37.stats_modifiers = {damage = 2} +self.m37.AMMO_MAX = total_ammo_tables.shot_low +self.m37.AMMO_PICKUP = pickup_tables.shot_low +self.m37.fire_mode_data.fire_rate = 0.8 +self.m37.kick = kick_tables.shot_high +self.m37.damage_falloff = FALLOFF_TEMPLATE.SHOTGUN_FALL_HIGH -- Mosconi -self.huntsman.rays = 12 -self.huntsman.stats.damage = 130 +self.huntsman.rays = 8 +self.huntsman.stats.damage = 180 self.huntsman.stats_modifiers = {damage = 2} -self.huntsman.AMMO_PICKUP = {0.42, 1.47} -self.huntsman.kick.standing = {4, 5, -0.2, 0.2} -self.huntsman.kick.crouching = self.huntsman.kick.standing -self.huntsman.kick.steelsight = self.huntsman.kick.standing +self.huntsman.AMMO_MAX = total_ammo_tables.shot_very_low +self.huntsman.AMMO_PICKUP = pickup_tables.shot_very_low +self.huntsman.kick = kick_tables.shot_high +self.huntsman.damage_falloff = FALLOFF_TEMPLATE.SHOTGUN_FALL_VHIGH -- Joceline -self.b682.rays = 12 -self.b682.stats.damage = 130 +self.b682.rays = 8 +self.b682.stats.damage = 180 self.b682.stats_modifiers = {damage = 2} -self.b682.AMMO_PICKUP = {0.42, 1.47} -self.b682.kick.standing = {4, 5, -0.2, 0.2} -self.b682.kick.crouching = self.huntsman.kick.standing -self.b682.kick.steelsight = self.huntsman.kick.standing +self.b682.AMMO_MAX = total_ammo_tables.shot_very_low +self.b682.AMMO_PICKUP = pickup_tables.shot_very_low +self.b682.kick = kick_tables.shot_high +self.b682.damage_falloff = FALLOFF_TEMPLATE.SHOTGUN_FALL_VHIGH --- Breaker -self.boot.rays = 12 -self.boot.stats.damage = 180 -self.boot.AMMO_PICKUP = {0.42, 1.15} -self.boot.kick.standing = {2.5, 3, -0.2, 0.2} -self.boot.kick.crouching = self.boot.kick.standing -self.boot.kick.steelsight = self.boot.kick.standing +-- Claire +self.coach.rays = 8 +self.coach.stats.damage = 180 +self.coach.stats_modifiers = {damage = 2} +self.coach.AMMO_MAX = total_ammo_tables.shot_very_low +self.coach.AMMO_PICKUP = pickup_tables.shot_special +self.coach.kick = kick_tables.shot_high +self.coach.damage_falloff = FALLOFF_TEMPLATE.SHOTGUN_FALL_VHIGH -- Judge -self.judge.rays = 12 -self.judge.AMMO_PICKUP = {0.275, 0.65} +self.judge.rays = 8 +self.judge.stats.damage = 100 +self.judge.stats_modifiers = {damage = 2} +self.judge.AMMO_MAX = total_ammo_tables.shot_very_low +self.judge.AMMO_PICKUP = pickup_tables.shot_special self.judge.AMMO_MAX = 25 -self.judge.kick.standing = {2.5, 3, -0.2, 0.2} -self.judge.kick.crouching = self.judge.kick.standing -self.judge.kick.steelsight = self.judge.kick.standing - --- Claire -self.coach.rays = 12 -self.coach.AMMO_PICKUP = {0.25, 0.65} -self.coach.kick.standing = {4, 5, -0.2, 0.2} -self.coach.kick.crouching = self.coach.kick.standing -self.coach.kick.steelsight = self.coach.kick.standing - --- GSPS -self.m37.rays = 12 -self.m37.AMMO_PICKUP = {0.42, 1.15} -self.m37.kick.standing = {4, 5, -0.2, 0.2} -self.m37.kick.crouching = self.m37.kick.standing -self.m37.kick.steelsight = self.m37.kick.standing +self.judge.kick = kick_tables.shot_low +self.judge.damage_falloff = FALLOFF_TEMPLATE.SHOTGUN_FALL_HIGH -- Loco self.serbu.AMMO_MAX = 24 self.serbu.CLIP_AMMO_MAX = 4 -self.serbu.rays = 12 -self.serbu.AMMO_PICKUP = {0.6, 1.6} -self.serbu.fire_mode_data.fire_rate = 0.5 -self.serbu.kick.standing = {3, 4, -0.2, 0.2} -self.serbu.kick.crouching = self.serbu.kick.standing -self.serbu.kick.steelsight = self.serbu.kick.standing +self.serbu.rays = 8 +self.serbu.stats.damage = 155 +self.serbu.AMMO_MAX = total_ammo_tables.shot_mid +self.serbu.AMMO_PICKUP = pickup_tables.shot_mid +self.serbu.fire_mode_data.fire_rate = 0.6 +self.serbu.kick = kick_tables.shot_low +self.serbu.damage_falloff = FALLOFF_TEMPLATE.SHOTGUN_FALL_MODERATE -- Goliath -self.rota.rays = 12 -self.rota.AMMO_PICKUP = {1.75, 2.65} -self.rota.spread.standing = self.new_m4.spread.crouching -self.rota.spread.moving_standing = self.new_m4.spread.crouching -self.rota.spread.moving_crouching = self.new_m4.spread.crouching +self.rota.rays = 8 +self.rota.stats.damage = 110 +self.rota.AMMO_MAX = total_ammo_tables.shot_high +self.rota.AMMO_PICKUP = pickup_tables.shot_high +self.rota.kick = kick_tables.shot_low +self.rota.damage_falloff = FALLOFF_TEMPLATE.SHOTGUN_FALL_NORMAL -- Sweeper -self.striker.rays = 12 -self.striker.AMMO_PICKUP = {1.75, 2.65} -self.striker.spread.standing = self.new_m4.spread.crouching -self.striker.spread.moving_standing = self.new_m4.spread.crouching -self.striker.spread.moving_crouching = self.new_m4.spread.crouching - --- Ultima -self.ultima.rays = 12 -self.ultima.stats.damage = 70 -self.ultima.AMMO_PICKUP = {1.15, 1.8} -self.ultima.kick.standing = {3, 3.5, -0.2, 0.2} -self.ultima.spread.standing = self.new_m4.spread.crouching -self.ultima.spread.moving_standing = self.new_m4.spread.crouching -self.ultima.spread.moving_crouching = self.new_m4.spread.crouching +self.striker.rays = 8 +self.striker.stats.damage = 110 +self.striker.stats.concealment = 24 +self.striker.AMMO_MAX = total_ammo_tables.shot_high +self.striker.AMMO_PICKUP = pickup_tables.shot_high +self.striker.kick = kick_tables.shot_low +self.striker.damage_falloff = FALLOFF_TEMPLATE.SHOTGUN_FALL_NORMAL + +-- Grimm +self.basset.rays = 8 +self.basset.stats.damage = 75 +self.basset.AMMO_MAX = total_ammo_tables.shot_high +self.basset.AMMO_PICKUP = pickup_tables.shot_very_high +self.basset.kick = kick_tables.shot_auto +self.basset.spray = spray_tables.sg_auto +self.basset.recoil_recovery_timer = recovery_tables.high +self.basset.damage_falloff = FALLOFF_TEMPLATE.SHOTGUN_FALL_NORMAL + +-- Argos +self.ultima.rays = 8 +self.ultima.stats.damage = 155 +self.ultima.stats.reload = 10 +self.ultima.stats.concealment = 17 +self.ultima.AMMO_MAX = total_ammo_tables.shot_mid +self.ultima.AMMO_PICKUP = pickup_tables.shot_mid +self.ultima.kick = kick_tables.shot_low +self.ultima.damage_falloff = FALLOFF_TEMPLATE.SHOTGUN_FALL_MODERATE --- ARs +-- ARs -- AMCAR self.amcar.fire_mode_data = {fire_rate = 60 / 700} @@ -412,467 +906,569 @@ self.amcar.auto = {fire_rate = 60 / 700} self.amcar.stats.damage = 52 self.amcar.stats.spread = 14 self.amcar.stats.recoil = 16 -self.amcar.AMMO_PICKUP = {6, 8} -self.amcar.kick.standing = {0.9, 1.2, -0.65, 0.65} -self.amcar.kick.crouching = self.amcar.kick.standing -self.amcar.kick.steelsight = self.amcar.kick.standing +self.amcar.AMMO_MAX = total_ammo_tables.ar_high +self.amcar.AMMO_PICKUP = pickup_tables.ar_high +self.amcar.kick = kick_tables.ar_low -- Commando 553 self.s552.stats.spread = 14 self.s552.stats.concealment = 18 -self.s552.AMMO_PICKUP = {6, 8} -self.s552.kick.standing = {1.1, 1.3, -0.75, 0.75} -self.s552.kick.crouching = self.s552.kick.standing -self.s552.kick.steelsight = self.s552.kick.standing +self.s552.AMMO_MAX = total_ammo_tables.ar_high +self.s552.AMMO_PICKUP = pickup_tables.ar_high +self.s552.kick = kick_tables.ar_low -- Clarion self.famas.stats.damage = 43 self.famas.stats.recoil = 13 -self.famas.AMMO_PICKUP = {6, 8} -self.famas.kick.standing = {1.1, 1.3, -0.75, 0.75} -self.famas.kick.crouching = self.famas.kick.standing -self.famas.kick.steelsight = self.famas.kick.standing +self.famas.stats.spread = 11 +self.famas.AMMO_MAX = total_ammo_tables.ar_high +self.famas.AMMO_PICKUP = pickup_tables.ar_high +self.famas.kick = kick_tables.ar_low -- JP36 self.g36.stats.spread = 14 -self.g36.AMMO_PICKUP = {6, 8} -self.g36.kick.standing = {1.1, 1.3, -0.75, 0.75} -self.g36.kick.crouching = self.g36.kick.standing -self.g36.kick.steelsight = self.g36.kick.standing +self.g36.stats.reload = 12 +self.g36.AMMO_MAX = total_ammo_tables.ar_high +self.g36.AMMO_PICKUP = pickup_tables.ar_high +self.g36.kick = kick_tables.ar_low -- AS Val self.asval.stats.damage = 46 -self.asval.AMMO_PICKUP = {6, 8} -self.asval.kick.standing = {1.1, 1.3, -0.75, 0.75} -self.asval.kick.crouching = self.asval.kick.standing -self.asval.kick.steelsight = self.asval.kick.standing +self.asval.AMMO_MAX = total_ammo_tables.ar_high +self.asval.AMMO_PICKUP = pickup_tables.ar_high +self.asval.kick = kick_tables.ar_low -- CAR-4 self.new_m4.fire_mode_data.fire_rate = 60 / 780 -self.new_m4.AMMO_PICKUP = {5, 7} -self.new_m4.kick.standing = {1.3, 1.5, -0.75, 0.75} -self.new_m4.kick.crouching = self.new_m4.kick.standing -self.new_m4.kick.steelsight = self.new_m4.kick.standing +self.new_m4.AMMO_MAX = total_ammo_tables.ar_mid +self.new_m4.AMMO_PICKUP = pickup_tables.ar_mid +self.new_m4.kick = kick_tables.ar_mid -- AK5 -self.ak5.AMMO_PICKUP = {5, 7} -self.ak5.kick.standing = {1.3, 1.5, -0.75, 0.75} -self.ak5.kick.crouching = self.ak5.kick.standing -self.ak5.kick.steelsight = self.ak5.kick.standing +self.ak5.AMMO_MAX = total_ammo_tables.ar_mid +self.ak5.AMMO_PICKUP = pickup_tables.ar_mid +self.ak5.kick = kick_tables.ar_mid -- Union self.corgi.stats.damage = 62 -self.corgi.AMMO_PICKUP = {5, 7} -self.corgi.kick.standing = {1.3, 1.5, -0.75, 0.75} -self.corgi.kick.crouching = self.corgi.kick.standing -self.corgi.kick.steelsight = self.corgi.kick.standing +self.corgi.AMMO_MAX = total_ammo_tables.ar_mid +self.corgi.AMMO_PICKUP = pickup_tables.ar_mid +self.corgi.kick = kick_tables.ar_mid -- UAR -self.aug.AMMO_PICKUP = {5, 7} -self.aug.kick.standing = {1.3, 1.5, -0.75, 0.75} -self.aug.kick.crouching = self.aug.kick.standing -self.aug.kick.steelsight = self.aug.kick.standing +self.aug.AMMO_MAX = total_ammo_tables.ar_mid +self.aug.AMMO_PICKUP = pickup_tables.ar_mid +self.aug.kick = kick_tables.ar_mid -- Queen's Wrath -self.l85a2.AMMO_PICKUP = {5, 7} -self.l85a2.kick.standing = {1.3, 1.5, -0.75, 0.75} -self.l85a2.kick.crouching = self.l85a2.kick.standing -self.l85a2.kick.steelsight = self.l85a2.kick.standing +self.corgi.stats.reload = 12 +self.l85a2.AMMO_MAX = total_ammo_tables.ar_mid +self.l85a2.AMMO_PICKUP = pickup_tables.ar_mid +self.l85a2.kick = kick_tables.ar_mid + +-- Rodion +self.tkb.AMMO_MAX = total_ammo_tables.ar_mid +self.tkb.AMMO_PICKUP = pickup_tables.ar_mid +self.tkb.kick = kick_tables.ar_mid +self.tkb.fire_mode_data.volley = { + spread_mul = 1, + damage_mul = 1, + ammo_usage = 3, + rays = 3, + can_shoot_through_wall = false, + can_shoot_through_shield = true, + can_shoot_through_enemy = true, + muzzleflash = "effects/payday2/particles/weapons/tkb_muzzle", + muzzleflash_silenced = "effects/payday2/particles/weapons/tkb_suppressed" +} -- Tempest -self.komodo.AMMO_PICKUP = {5, 7} -self.komodo.kick.standing = {1.3, 1.5, -0.75, 0.75} -self.komodo.kick.crouching = self.komodo.kick.standing -self.komodo.kick.steelsight = self.komodo.kick.standing +self.komodo.AMMO_MAX = total_ammo_tables.ar_mid +self.komodo.AMMO_PICKUP = pickup_tables.ar_mid +self.komodo.kick = kick_tables.ar_mid -- AK Rifle self.ak74.stats.damage = 77 self.ak74.stats.concealment = 15 -self.ak74.AMMO_PICKUP = {4.5, 6} -self.ak74.kick.standing = {1.3, 1.5, -0.75, 0.75} -self.ak74.kick.crouching = self.ak74.kick.standing -self.ak74.kick.steelsight = self.ak74.kick.standing +self.ak74.AMMO_MAX = total_ammo_tables.ar_low +self.ak74.AMMO_PICKUP = pickup_tables.ar_low +self.ak74.kick = kick_tables.ar_high -- Lion's Roar self.vhs.stats.damage = 72 self.vhs.stats.concealment = 12 -self.vhs.AMMO_PICKUP = {4.5, 6} -self.vhs.AMMO_MAX = 150 -self.vhs.kick.standing = {1.3, 1.5, -0.85, 0.85} -self.vhs.kick.crouching = self.vhs.kick.standing -self.vhs.kick.steelsight = self.vhs.kick.standing +self.vhs.AMMO_MAX = total_ammo_tables.ar_low +self.vhs.AMMO_PICKUP = pickup_tables.ar_low +self.vhs.kick = kick_tables.ar_high -- Gecko self.galil.stats.damage = 72 self.galil.stats.concealment = 12 -self.galil.AMMO_PICKUP = {4.5, 6} -self.galil.AMMO_MAX = 140 -self.galil.kick.standing = {1.3, 1.5, -0.85, 0.85} -self.galil.kick.crouching = self.galil.kick.standing -self.galil.kick.steelsight = self.galil.kick.standing +self.galil.AMMO_MAX = total_ammo_tables.ar_low +self.galil.AMMO_PICKUP = pickup_tables.ar_low +self.galil.kick = kick_tables.ar_high + +-- CR805 +self.hajk.categories = { + "assault_rifle" +} +self.hajk.stats.damage = 80 +self.hajk.AMMO_MAX = total_ammo_tables.ar_low +self.hajk.AMMO_PICKUP = pickup_tables.ar_low +self.hajk.kick = kick_tables.ar_high +self.hajk.use_data.selection_index = SELECTION.PRIMARY -- Bootleg self.tecci.stats.spread = 11 self.tecci.stats.reload = 11 self.tecci.stats.damage = 60 self.tecci.stats.concealment = 5 -self.tecci.AMMO_PICKUP = {5, 7} -self.tecci.kick.standing = {1.3, 1.5, -0.85, 0.85} -self.tecci.kick.crouching = self.tecci.kick.standing -self.tecci.kick.steelsight = self.tecci.kick.standing +self.tecci.AMMO_MAX = total_ammo_tables.ar_high +self.tecci.AMMO_PICKUP = pickup_tables.ar_high +self.tecci.kick = kick_tables.ar_mid -- Groza -self.groza.kick.standing = {1.3, 1.5, -0.85, 0.85} -self.groza.kick.crouching = self.groza.kick.standing -self.groza.kick.steelsight = self.groza.kick.standing +self.groza.AMMO_MAX = total_ammo_tables.ar_very_low +self.groza.AMMO_PICKUP = pickup_tables.ar_very_low +self.groza.kick = kick_tables.ar_very_high -- AK 7.62 self.akm.fire_mode_data = {fire_rate = 0.1} self.akm.auto = {fire_rate = 0.1} self.akm.stats.concealment = 10 -self.akm.AMMO_PICKUP = {2.2, 3.5} -self.akm.kick.standing = {1.3, 1.5, -1, 1} -self.akm.kick.crouching = self.akm.kick.standing -self.akm.kick.steelsight = self.akm.kick.standing +self.akm.AMMO_MAX = total_ammo_tables.ar_very_low +self.akm.AMMO_PICKUP = pickup_tables.ar_very_low +self.akm.kick = kick_tables.ar_very_high -- Gold AK 7.62 self.akm_gold.fire_mode_data = {fire_rate = 0.1} self.akm_gold.auto = {fire_rate = 0.1} self.akm_gold.stats.concealment = 10 -self.akm_gold.AMMO_PICKUP = {2.2, 3.5} -self.akm_gold.kick.standing = {1.3, 1.5, -1, 1} -self.akm_gold.kick.crouching = self.akm_gold.kick.standing -self.akm_gold.kick.steelsight = self.akm_gold.kick.standing +self.akm_gold.AMMO_MAX = total_ammo_tables.ar_very_low +self.akm_gold.AMMO_PICKUP = pickup_tables.ar_very_low +self.akm_gold.kick = kick_tables.ar_very_high -- AK17 +self.flint.CLIP_AMMO_MAX = 30 self.flint.stats.concealment = 8 -self.flint.AMMO_PICKUP = {2.2, 3.5} -self.flint.kick.standing = {1.45, 1.55, -1.1, 1.1} -self.flint.kick.crouching = self.flint.kick.standing -self.flint.kick.steelsight = self.flint.kick.standing +self.flint.AMMO_MAX = total_ammo_tables.ar_very_low +self.flint.AMMO_PICKUP = pickup_tables.ar_very_low +self.flint.kick = kick_tables.ar_very_high -- AMR self.m16.stats.concealment = 8 -self.m16.AMMO_PICKUP = {2.2, 3.5} -self.m16.kick.standing = {1.3, 1.5, -1.05, 1.05} -self.m16.kick.crouching = self.m16.kick.standing -self.m16.kick.steelsight = self.m16.kick.standing +self.m16.AMMO_MAX = total_ammo_tables.ar_very_low +self.m16.AMMO_PICKUP = pickup_tables.ar_very_low +self.m16.kick = kick_tables.ar_very_high -- Eagle Heavy self.scar.stats.concealment = 10 self.scar.stats.reload = 13 -self.scar.AMMO_PICKUP = {2.2, 3.5} -self.scar.kick.standing = {1.35, 1.5, -1, 1} -self.scar.kick.crouching = self.scar.kick.standing -self.scar.kick.steelsight = self.scar.kick.standing +self.scar.AMMO_MAX = total_ammo_tables.ar_very_low +self.scar.AMMO_PICKUP = pickup_tables.ar_very_low +self.scar.kick = kick_tables.ar_very_high -- Falcon -self.fal.AMMO_PICKUP = {2.2, 3.5} -self.fal.kick.standing = {1.45, 1.55, -1.1, 1.1} -self.fal.kick.crouching = self.fal.kick.standing -self.fal.kick.steelsight = self.fal.kick.standing +self.fal.AMMO_MAX = total_ammo_tables.ar_very_low +self.fal.AMMO_PICKUP = pickup_tables.ar_very_low +self.fal.kick = kick_tables.ar_very_high -- Gewehr self.g3.stats.concealment = 9 -self.g3.AMMO_PICKUP = {2.2, 3.5} -self.g3.kick.standing = {1.45, 1.55, -1.1, 1.1} -self.g3.kick.crouching = self.g3.kick.standing -self.g3.kick.steelsight = self.g3.kick.standing +self.g3.AMMO_MAX = total_ammo_tables.ar_very_low +self.g3.AMMO_PICKUP = pickup_tables.ar_very_low +self.g3.kick = kick_tables.ar_very_high -- KS12 +self.shak12.stats.damage = 60 +self.shak12.stats_modifiers = {damage = 2} self.shak12.stats.concealment = 10 -self.shak12.AMMO_PICKUP = {2.2, 3.5} -self.shak12.kick.standing = {1.3, 1.5, -1, 1} -self.shak12.kick.crouching = self.shak12.kick.standing -self.shak12.kick.steelsight = self.shak12.kick.standing +self.shak12.AMMO_MAX = total_ammo_tables.ar_very_low +self.shak12.AMMO_PICKUP = pickup_tables.ar_very_low +self.shak12.kick = kick_tables.ks12 + + +-- DMRs + +-- Little Friend +self.contraband.AMMO_MAX = total_ammo_tables.dmr_low +self.contraband.AMMO_PICKUP = pickup_tables.dmr_low +self.contraband.stats.damage = 182 +self.contraband.kick = kick_tables.dmr_high + +-- M308 +self.new_m14.AMMO_MAX = total_ammo_tables.dmr +self.new_m14.AMMO_PICKUP = pickup_tables.dmr +self.new_m14.stats.damage = 182 +self.new_m14.stats.concealment = 5 +self.new_m14.kick = kick_tables.dmr_high + +-- Cavity +self.sub2000.CLIP_AMMO_MAX = 20 +self.sub2000.AMMO_MAX = total_ammo_tables.dmr +self.sub2000.AMMO_PICKUP = pickup_tables.dmr +self.sub2000.stats.damage = 182 +self.sub2000.stats.concealment = 18 +self.sub2000.kick = kick_tables.dmr_low + +-- Galant +self.ching.AMMO_MAX = total_ammo_tables.dmr +self.ching.AMMO_PICKUP = pickup_tables.dmr +self.ching.stats.damage = 182 +self.ching.stats.concealment = 10 +self.ching.kick = kick_tables.dmr_low -- SMGs -- Blaster -self.tec9.AMMO_PICKUP = {5, 9} +self.tec9.AMMO_MAX = total_ammo_tables.smg_high +self.tec9.AMMO_PICKUP = pickup_tables.smg_high self.tec9.stats.spread = 11 -self.tec9.kick.standing = {0.9, 1.2, -0.75, 0.75} -self.tec9.kick.crouching = self.tec9.kick.standing -self.tec9.kick.steelsight = self.tec9.kick.standing +self.tec9.kick = kick_tables.ar_mid -- CMP -self.mp9.AMMO_PICKUP = {5, 9} +self.mp9.AMMO_MAX = total_ammo_tables.smg_high +self.mp9.AMMO_PICKUP = pickup_tables.smg_high self.mp9.stats.spread = 11 -self.mp9.kick.standing = {0.9, 1.2, -0.75, 0.75} -self.mp9.kick.crouching = self.mp9.kick.standing -self.mp9.kick.steelsight = self.mp9.kick.standing +self.mp9.kick = kick_tables.ar_mid -- Cobra -self.scorpion.AMMO_PICKUP = {5, 9} +self.scorpion.AMMO_MAX = total_ammo_tables.smg_high +self.scorpion.AMMO_PICKUP = pickup_tables.smg_high self.scorpion.stats.spread = 11 -self.scorpion.kick.standing = {0.9, 1.2, -0.75, 0.75} -self.scorpion.kick.crouching = self.scorpion.kick.standing -self.scorpion.kick.steelsight = self.scorpion.kick.standing +self.scorpion.kick = kick_tables.ar_mid -- Compact-5 -self.new_mp5.AMMO_PICKUP = {5, 9} -self.new_mp5.kick.standing = {0.9, 1.2, -0.75, 0.75} -self.new_mp5.kick.crouching = self.new_mp5.kick.standing -self.new_mp5.kick.steelsight = self.new_mp5.kick.standing +self.new_mp5.AMMO_MAX = total_ammo_tables.smg_high +self.new_mp5.AMMO_PICKUP = pickup_tables.smg_high +self.new_mp5.kick = kick_tables.ar_mid -- Micro Uzi -self.baka.AMMO_PICKUP = {5, 9} +self.baka.AMMO_MAX = total_ammo_tables.smg_high +self.baka.AMMO_PICKUP = pickup_tables.smg_high self.baka.stats.spread = 11 -self.baka.kick.standing = {0.9, 1.2, -0.75, 0.75} -self.baka.kick.crouching = self.baka.kick.standing -self.baka.kick.steelsight = self.baka.kick.standing - --- Uzi -self.uzi.AMMO_PICKUP = {0.9, 3.15} -self.uzi.fire_mode_data.fire_rate = 60 / 850 -self.uzi.kick.standing = {0.9, 1.2, -0.75, 0.75} -self.uzi.kick.crouching = self.uzi.kick.standing -self.uzi.kick.steelsight = self.uzi.kick.standing +self.baka.kick = kick_tables.ar_mid -- Signature -self.shepheard.AMMO_PICKUP = {5, 9} +self.shepheard.AMMO_MAX = total_ammo_tables.smg_mid +self.shepheard.AMMO_PICKUP = pickup_tables.smg_mid self.shepheard.stats.spread = 14 self.shepheard.fire_mode_data.fire_rate = 60 / 850 -self.shepheard.kick.standing = {0.9, 1.2, -0.75, 0.75} -self.shepheard.kick.crouching = self.shepheard.kick.standing -self.shepheard.kick.steelsight = self.shepheard.kick.standing +self.shepheard.kick = kick_tables.ar_high -- Thompson -self.m1928.AMMO_PICKUP = {3, 7} +self.m1928.AMMO_MAX = total_ammo_tables.smg_mid +self.m1928.AMMO_PICKUP = pickup_tables.smg_mid self.m1928.stats.spread = 15 self.m1928.stats.reload = 13 -self.m1928.kick.standing = {1.2, 1.4, -0.75, 0.75} -self.m1928.kick.crouching = self.m1928.kick.standing -self.m1928.kick.steelsight = self.m1928.kick.standing +self.m1928.kick = kick_tables.ar_high -- Heather -self.sr2.AMMO_PICKUP = {3, 7} -self.sr2.kick.standing = {1.2, 1.4, -0.75, 0.75} -self.sr2.kick.crouching = self.sr2.kick.standing -self.sr2.kick.steelsight = self.sr2.kick.standing +self.sr2.AMMO_MAX = total_ammo_tables.smg_mid +self.sr2.AMMO_PICKUP = pickup_tables.smg_mid +self.sr2.kick = kick_tables.ar_high -- Jacket's Piece -self.cobray.AMMO_PICKUP = {3, 7} -self.cobray.kick.standing = {1.2, 1.4, -0.75, 0.75} -self.cobray.kick.crouching = self.cobray.kick.standing -self.cobray.kick.steelsight = self.cobray.kick.standing +self.cobray.AMMO_MAX = total_ammo_tables.smg_mid +self.cobray.AMMO_PICKUP = pickup_tables.smg_mid +self.cobray.kick = kick_tables.ar_high -- Kobus -self.p90.AMMO_PICKUP = {3, 7} -self.p90.kick.standing = {1.2, 1.4, -0.75, 0.75} -self.p90.kick.crouching = self.p90.kick.standing -self.p90.kick.steelsight = self.p90.kick.standing +self.p90.AMMO_MAX = total_ammo_tables.smg_mid +self.p90.AMMO_PICKUP = pickup_tables.smg_mid +self.p90.kick = kick_tables.ar_high -- Vertex -self.polymer.AMMO_PICKUP = {3, 7} -self.polymer.kick.standing = {1.2, 1.4, -0.75, 0.75} -self.polymer.kick.crouching = self.polymer.kick.standing -self.polymer.kick.steelsight = self.polymer.kick.standing +self.polymer.AMMO_MAX = total_ammo_tables.smg_mid +self.polymer.AMMO_PICKUP = pickup_tables.smg_mid +self.polymer.kick = kick_tables.ar_high -- Mark 10 -self.mac10.AMMO_PICKUP = {3, 7} -self.mac10.kick.standing = {1.2, 1.4, -0.75, 0.75} -self.mac10.kick.crouching = self.mac10.kick.standing -self.mac10.kick.steelsight = self.mac10.kick.standing +self.mac10.AMMO_MAX = total_ammo_tables.smg_mid +self.mac10.AMMO_PICKUP = pickup_tables.smg_mid +self.mac10.kick = kick_tables.ar_high + +-- Wasp +self.fmg9.stats.damage = 58 +self.fmg9.stats.spread = 14 +self.fmg9.AMMO_MAX = total_ammo_tables.smg_mid +self.fmg9.AMMO_PICKUP = pickup_tables.smg_mid +self.fmg9.kick = kick_tables.ar_mid +self.fmg9.timers.unequip = 1.2 -- Spec Ops -self.mp7.AMMO_PICKUP = {3, 7} -self.mp7.kick.standing = {1.2, 1.4, -0.75, 0.75} -self.mp7.kick.crouching = self.mp7.kick.standing -self.mp7.kick.steelsight = self.mp7.kick.standing +self.mp7.stats.damage = 58 +self.mp7.AMMO_MAX = total_ammo_tables.smg_mid +self.mp7.AMMO_PICKUP = pickup_tables.smg_mid +self.mp7.kick = kick_tables.ar_high -- Miyaka -self.pm9.AMMO_PICKUP = {3, 7} -self.pm9.kick.standing = {1.2, 1.4, -0.75, 0.75} -self.pm9.kick.crouching = self.pm9.kick.standing -self.pm9.kick.steelsight = self.pm9.kick.standing +self.pm9.AMMO_MAX = total_ammo_tables.smg_mid +self.pm9.AMMO_PICKUP = pickup_tables.smg_mid +self.pm9.kick = kick_tables.ar_high -- Para -self.olympic.AMMO_MAX = 90 -self.olympic.AMMO_PICKUP = {0.9, 3.15} +self.olympic.AMMO_MAX = total_ammo_tables.smg_low +self.olympic.AMMO_PICKUP = pickup_tables.smg_low self.olympic.stats.damage = 80 -self.olympic.kick.standing = {1.2, 1.4, -0.9, 0.9} -self.olympic.kick.crouching = self.olympic.kick.standing -self.olympic.kick.steelsight = self.olympic.kick.standing +self.olympic.kick = kick_tables.ar_very_high --- AKGEN -self.vityaz.AMMO_MAX = 90 -self.vityaz.AMMO_PICKUP = {0.9, 3.15} -self.vityaz.stats.damage = 100 -self.vityaz.kick.standing = {1.4, 1.6, -1, 1} -self.vityaz.kick.crouching = self.vityaz.kick.standing -self.vityaz.kick.steelsight = self.vityaz.kick.standing - --- CR805 -self.hajk.stats.damage = 80 -self.hajk.kick.standing = {1.4, 1.6, -1, 1} -self.hajk.kick.crouching = self.hajk.kick.standing -self.hajk.kick.steelsight = self.hajk.kick.standing +-- Uzi +self.uzi.stats.damage = 80 +self.uzi.AMMO_MAX = total_ammo_tables.smg_low +self.uzi.AMMO_PICKUP = pickup_tables.smg_low +self.uzi.fire_mode_data.fire_rate = 60 / 850 +self.uzi.kick = kick_tables.ar_very_high -- Krinkov self.akmsu.stats.damage = 80 -self.akmsu.kick.standing = {1.4, 1.6, -1, 1} -self.akmsu.kick.crouching = self.akmsu.kick.standing -self.akmsu.kick.steelsight = self.akmsu.kick.standing +self.akmsu.AMMO_MAX = total_ammo_tables.smg_low +self.akmsu.AMMO_PICKUP = pickup_tables.smg_low +self.akmsu.kick = kick_tables.ar_very_high + +-- AKGEN +self.vityaz.AMMO_MAX = total_ammo_tables.smg_very_low +self.vityaz.AMMO_PICKUP = pickup_tables.smg_very_low +self.vityaz.stats.damage = 100 +self.vityaz.kick = kick_tables.ar_very_high -- MP40 self.erma.stats.reload = 13 -self.erma.kick.standing = {1.4, 1.6, -1, 1} -self.erma.kick.crouching = self.erma.kick.standing -self.erma.kick.steelsight = self.erma.kick.standing +self.erma.AMMO_MAX = total_ammo_tables.smg_very_low +self.erma.AMMO_PICKUP = pickup_tables.smg_very_low +self.erma.kick = kick_tables.ar_very_high -- Tatonka -self.coal.AMMO_PICKUP = {0.9, 3.15} -self.coal.kick.standing = {1.4, 1.6, -1, 1} -self.coal.kick.crouching = self.coal.kick.standing -self.coal.kick.steelsight = self.coal.kick.standing +self.coal.stats.reload = 9 +self.coal.AMMO_MAX = total_ammo_tables.smg_very_low +self.coal.AMMO_PICKUP = pickup_tables.smg_very_low +self.coal.kick = kick_tables.ar_very_high -- Pattchet -self.sterling.AMMO_PICKUP = {0.9, 3.15} -self.sterling.kick.standing = {1.4, 1.6, -1, 1} -self.sterling.kick.crouching = self.sterling.kick.standing -self.sterling.kick.steelsight = self.sterling.kick.standing +self.sterling.AMMO_MAX = total_ammo_tables.smg_very_low +self.sterling.AMMO_PICKUP = pickup_tables.smg_very_low +self.sterling.kick = kick_tables.ar_very_high -- Swedish K -self.m45.kick.standing = {1.4, 1.6, -1, 1} -self.m45.kick.crouching = self.m45.kick.standing -self.m45.kick.steelsight = self.m45.kick.standing +self.m45.AMMO_MAX = total_ammo_tables.smg_very_low +self.m45.AMMO_PICKUP = pickup_tables.smg_very_low +self.m45.kick = kick_tables.ar_very_high -- Jackal -self.schakal.kick.standing = {1.4, 1.6, -1, 1} -self.schakal.kick.crouching = self.schakal.kick.standing -self.schakal.kick.steelsight = self.schakal.kick.standing - --- Wasp -self.fmg9.stats.damage = 58 -self.fmg9.stats.spread = 14 -self.fmg9.AMMO_MAX = 150 -self.fmg9.AMMO_PICKUP = {3, 7} -self.fmg9.kick.standing = {1, 1.1, -0.8, 0.8} -self.fmg9.kick.crouching = self.fmg9.kick.standing -self.fmg9.kick.steelsight = self.fmg9.kick.standing -self.fmg9.timers.unequip = 1.2 +self.schakal.AMMO_MAX = total_ammo_tables.smg_very_low +self.schakal.AMMO_PICKUP = pickup_tables.smg_very_low +self.schakal.kick = kick_tables.ar_very_high --- DMRs - --- Little Friend -self.contraband.AMMO_PICKUP = {1.5, 2.5} -self.contraband.stats.damage = 180 -self.contraband.kick.standing = {3.5, 4.5, -0.6, 0.6} -self.contraband.kick.crouching = self.contraband.kick.standing -self.contraband.kick.steelsight = self.contraband.kick.standing - --- Cavity -self.sub2000.AMMO_PICKUP = {1.5, 2.5} -self.sub2000.stats.damage = 180 -self.sub2000.stats.concealment = 18 -self.sub2000.kick.standing = {1.4, 1.6, -0.3, 0.3} -self.sub2000.kick.crouching = self.sub2000.kick.standing -self.sub2000.kick.steelsight = self.sub2000.kick.standing - --- M308 -self.new_m14.AMMO_PICKUP = {1.5, 2.5} -self.new_m14.stats.damage = 180 -self.new_m14.stats.concealment = 5 -self.new_m14.kick.standing = {3.5, 4.5, -0.6, 0.6} -self.new_m14.kick.crouching = self.new_m14.kick.standing -self.new_m14.kick.steelsight = self.new_m14.kick.standing - --- Galant -self.ching.AMMO_PICKUP = {1.5, 2.5} -self.ching.stats.damage = 180 -self.ching.stats.concealment = 10 -self.ching.kick.standing = {1.6, 2, -0.45, 0.45} -self.ching.kick.crouching = self.ching.kick.standing -self.ching.kick.steelsight = self.ching.kick.standing - -- Revolvers --Peacemaker self.peacemaker.AMMO_MAX = 24 -self.peacemaker.stats.damage = 120 +self.peacemaker.stats.damage = 145 self.peacemaker.stats.reload = 17 -self.peacemaker.AMMO_PICKUP = {0.26, 0.67} +self.peacemaker.AMMO_MAX = total_ammo_tables.revolver_ap +self.peacemaker.AMMO_PICKUP = pickup_tables.revolver_ap +self.peacemaker.kick = kick_tables.revolver_ap self.peacemaker.can_shoot_through_enemy = true self.peacemaker.can_shoot_through_shield = true self.peacemaker.can_shoot_through_wall = true self.peacemaker.armor_piercing_chance = 1 +self.peacemaker.has_description = true +self.peacemaker.desc_id = "bm_w_lemming_desc" + -- Angry Tiger -self.rsh12.stats.damage = 120 +self.rsh12.stats.damage = 145 self.rsh12.stats_modifiers = {damage = 2} self.rsh12.stats.reload = 13 -self.rsh12.AMMO_PICKUP = {0.26, 0.67} -self.rsh12.stats_modifiers = {damage = 1} -self.rsh12.kick.standing = self.peacemaker.kick.standing -self.rsh12.kick.crouching = self.peacemaker.kick.standing -self.rsh12.kick.steelsight = self.peacemaker.kick.standing +self.rsh12.stats.spread = 21 +self.rsh12.AMMO_MAX = total_ammo_tables.revolver_ap +self.rsh12.AMMO_PICKUP = pickup_tables.revolver_ap +self.rsh12.kick = kick_tables.revolver_ap -- Bronco -self.new_raging_bull.AMMO_MAX = 36 -self.new_raging_bull.stats.damage = 120 +self.new_raging_bull.stats.damage = 145 self.new_raging_bull.stats_modifiers = {damage = 2} -self.new_raging_bull.AMMO_PICKUP = {1.3, 1.8} +self.new_raging_bull.AMMO_MAX = total_ammo_tables.revolver +self.new_raging_bull.AMMO_PICKUP = pickup_tables.revolver +self.new_raging_bull.kick = kick_tables.revolver self.new_raging_bull.stats.reload = 10 -self.new_raging_bull.kick.standing = {1.6, 2, -0.45, 0.45} -self.new_raging_bull.kick.crouching = self.new_raging_bull.kick.standing -self.new_raging_bull.kick.steelsight = self.new_raging_bull.kick.standing -- Matever -self.mateba.AMMO_MAX = 36 -self.mateba.stats.damage = 120 +self.mateba.stats.damage = 145 self.mateba.stats_modifiers = {damage = 2} -self.mateba.AMMO_PICKUP = {1.3, 1.8} +self.mateba.AMMO_MAX = total_ammo_tables.revolver +self.mateba.AMMO_PICKUP = pickup_tables.revolver +self.mateba.kick = kick_tables.revolver_low self.mateba.stats.reload = 15 -self.mateba.kick.standing = {1.3, 1.6, -0.45, 0.45} -self.mateba.kick.crouching = self.mateba.kick.standing -self.mateba.kick.steelsight = self.mateba.kick.standing -- Castigo -self.chinchilla.AMMO_MAX = 36 -self.chinchilla.stats.damage = 120 +self.chinchilla.stats.damage = 145 self.chinchilla.stats.spread = 21 self.chinchilla.stats_modifiers = {damage = 2} -self.chinchilla.AMMO_PICKUP = {1.3, 1.8} +self.chinchilla.AMMO_MAX = total_ammo_tables.revolver +self.chinchilla.AMMO_PICKUP = pickup_tables.revolver +self.chinchilla.kick = kick_tables.revolver_low self.chinchilla.stats.reload = 13 -self.chinchilla.kick.standing = {1.3, 1.6, -0.45, 0.45} -self.chinchilla.kick.crouching = self.chinchilla.kick.standing -self.chinchilla.kick.steelsight = self.chinchilla.kick.standing -- Kahn -self.korth.AMMO_MAX = 36 self.korth.stats.damage = 72 self.korth.stats.spread = 21 self.korth.stats_modifiers = {damage = 2} -self.korth.AMMO_PICKUP = {1.5, 2} -self.korth.kick.standing = {1.8, 2.2, -0.45, 0.45} -self.korth.kick.crouching = self.korth.kick.standing -self.korth.kick.steelsight = self.korth.kick.standing +self.korth.AMMO_MAX = total_ammo_tables.revolver +self.korth.AMMO_PICKUP = pickup_tables.revolver_high +self.korth.kick = kick_tables.revolver -- Frenchman -self.model3.stats.damage = 175 +self.model3.stats.damage = 180 self.model3.stats.reload = 14 -self.model3.AMMO_PICKUP = {1.5, 2} +self.model3.AMMO_MAX = total_ammo_tables.revolver +self.model3.AMMO_PICKUP = pickup_tables.revolver_high +self.model3.kick = kick_tables.revolver_low + -- Pistols --- 5/7 -self.lemming.stats.damage = 70 +-- Chimano 88 +self.glock_17.AMMO_MAX = total_ammo_tables.pistol_high +self.glock_17.AMMO_PICKUP = pickup_tables.pistol_high +self.glock_17.kick = kick_tables.pistol_low + +-- Chimano Compact +self.g26.AMMO_MAX = total_ammo_tables.pistol_high +self.g26.AMMO_PICKUP = pickup_tables.pistol_high +self.g26.kick = kick_tables.pistol_low + +-- Gruber +self.ppk.AMMO_MAX = total_ammo_tables.pistol_high +self.ppk.AMMO_PICKUP = pickup_tables.pistol_high +self.ppk.kick = kick_tables.pistol_low + +-- Bernetti 9 +self.b92fs.AMMO_MAX = total_ammo_tables.pistol_high +self.b92fs.AMMO_PICKUP = pickup_tables.pistol_high +self.b92fs.kick = kick_tables.pistol_low + +-- M13 +self.legacy.AMMO_MAX = total_ammo_tables.pistol_high +self.legacy.AMMO_PICKUP = pickup_tables.pistol_high +self.legacy.kick = kick_tables.pistol_low + +-- Crosskill Guard +self.shrew.AMMO_MAX = total_ammo_tables.pistol_high +self.shrew.AMMO_PICKUP = pickup_tables.pistol_high +self.shrew.kick = kick_tables.pistol_low + +-- Interceptor +self.usp.AMMO_MAX = total_ammo_tables.pistol_mid +self.usp.AMMO_PICKUP = pickup_tables.pistol_mid +self.usp.kick = kick_tables.pistol_mid --- Baby Deagle -self.sparrow.stats.damage = 140 +-- Signature +self.p226.AMMO_MAX = total_ammo_tables.pistol_mid +self.p226.AMMO_PICKUP = pickup_tables.pistol_mid +self.usp.kick = kick_tables.pistol_mid + +-- Crosskill +self.colt_1911.AMMO_MAX = total_ammo_tables.pistol_mid +self.colt_1911.AMMO_PICKUP = pickup_tables.pistol_mid +self.usp.kick = kick_tables.pistol_mid + +-- Chimano Custom +self.g22c.AMMO_MAX = total_ammo_tables.pistol_mid +self.g22c.AMMO_PICKUP = pickup_tables.pistol_mid +self.g22c.kick = kick_tables.pistol_mid + +-- Broomstick +self.c96.stats.reload = 13 +self.c96.AMMO_MAX = total_ammo_tables.pistol_mid +self.c96.AMMO_PICKUP = pickup_tables.pistol_mid +self.c96.kick = kick_tables.pistol_mid + +-- Contractor +self.packrat.AMMO_MAX = total_ammo_tables.pistol_mid +self.packrat.AMMO_PICKUP = pickup_tables.pistol_mid +self.packrat.kick = kick_tables.pistol_mid + +-- LEO +self.hs2000.AMMO_MAX = total_ammo_tables.pistol_mid +self.hs2000.AMMO_PICKUP = pickup_tables.pistol_mid +self.hs2000.kick = kick_tables.pistol_mid + +-- Holt +self.holt.AMMO_MAX = total_ammo_tables.pistol_mid +self.holt.AMMO_PICKUP = pickup_tables.pistol_mid +self.holt.kick = kick_tables.pistol_mid -- Deagle self.deagle.stats.damage = 140 -self.x_deagle.stats.damage = 140 +self.deagle.AMMO_MAX = total_ammo_tables.pistol_low +self.deagle.AMMO_PICKUP = pickup_tables.pistol_low +self.deagle.kick = kick_tables.pistol_high --- Chunky Crosskil -self.m1911.stats.damage = 135 +-- Beagle +self.sparrow.stats.damage = 140 +self.sparrow.AMMO_MAX = total_ammo_tables.pistol_low +self.sparrow.AMMO_PICKUP = pickup_tables.pistol_low +self.sparrow.kick = kick_tables.pistol_high -- White Streak self.pl14.stats.damage = 140 +self.pl14.AMMO_MAX = total_ammo_tables.pistol_low +self.pl14.AMMO_PICKUP = pickup_tables.pistol_low +self.pl14.kick = kick_tables.pistol_high + +-- Chunky Crosskill +self.m1911.stats.damage = 140 +self.m1911.AMMO_MAX = total_ammo_tables.pistol_low +self.m1911.AMMO_PICKUP = pickup_tables.pistol_low +self.m1911.kick = kick_tables.pistol_high + +-- Gecko M2 +self.maxim9.stats.damage = 140 +self.maxim9.AMMO_MAX = total_ammo_tables.pistol_low +self.maxim9.AMMO_PICKUP = pickup_tables.pistol_low +self.maxim9.kick = kick_tables.pistol_high + +-- Kang Arms +self.type54.AMMO_MAX = total_ammo_tables.pistol_low +self.type54.AMMO_PICKUP = pickup_tables.pistol_low +self.type54.kick = kick_tables.pistol_high + +-- Parabellum +self.breech.AMMO_MAX = total_ammo_tables.pistol_very_low +self.breech.AMMO_PICKUP = pickup_tables.pistol_very_low +self.breech.kick = kick_tables.pistol_high + +-- Bernetti Auto +self.beer.stats.damage = 38 +self.beer.AMMO_MAX = total_ammo_tables.pistol_high +self.beer.AMMO_PICKUP = pickup_tables.autopistol_high +self.beer.kick = kick_tables.pistol_auto + +-- Stryk +self.glock_18c.stats.damage = 45 +self.glock_18c.AMMO_MAX = total_ammo_tables.pistol_mid +self.glock_18c.AMMO_PICKUP = pickup_tables.autopistol_mid +self.glock_18c.kick = kick_tables.pistol_auto + +-- Czech +self.czech.stats.damage = 45 +self.czech.AMMO_MAX = total_ammo_tables.pistol_mid +self.czech.AMMO_PICKUP = pickup_tables.autopistol_mid +self.czech.kick = kick_tables.pistol_auto + +-- Igor +self.stech.stats.damage = 70 +self.stech.AMMO_MAX = total_ammo_tables.pistol_low +self.stech.AMMO_PICKUP = pickup_tables.pistol_mid +self.stech.kick = kick_tables.pistol_auto + +-- 5/7 +self.lemming.AMMO_MAX = total_ammo_tables.pistol_low +self.lemming.AMMO_PICKUP = pickup_tables.pistol_ap +self.lemming.kick = kick_tables.pistol_mid -- Grenade / Rocket launchers @@ -883,93 +1479,178 @@ self.ray.categories = {"grenade_launcher", "heavy"} self.ray.stats.damage = 128 self.ray.stats_modifiers = {damage = 10} self.ray.stats.reload = 8 -self.ray.AMMO_PICKUP = {0.05, 0.38} +self.ray.AMMO_PICKUP = {0.05, 0.05} + +-- RPG +self.rpg7.use_data.selection_index = SELECTION.PRIMARY +self.rpg7.categories = {"grenade_launcher", "heavy"} +self.rpg7.AMMO_MAX = 3 +self.rpg7.stats.damage = 120 +self.rpg7.stats_modifiers = {damage = 200} +self.rpg7.fire_mode_data.fire_rate = 5 +self.rpg7.stats.reload = 7 -- Piglet -self.m32.stats.damage = 42 -self.m32.AMMO_PICKUP = {0.05, 0.45} +self.m32.stats.damage = 82 +self.m32.AMMO_PICKUP = {0.1, 0.1} +self.m32.AMMO_MAX = 18 self.m32.stats.reload = 15 self.m32.fire_mode_data.fire_rate = 60 / 120 -- China Puff self.china.use_data.selection_index = SELECTION.PRIMARY -self.china.stats.damage = 40 +self.china.stats.damage = 82 self.china.stats.concealment = 16 self.china.AMMO_MAX = 9 -self.china.AMMO_PICKUP = {0.05, 0.425} +self.china.AMMO_PICKUP = {0.1, 0.1} -- Arbiter self.arbiter.use_data.selection_index = SELECTION.PRIMARY -self.arbiter.stats.damage = 30 -self.arbiter.AMMO_PICKUP = {0.05, 0.44} +self.arbiter.stats.damage = 70 +self.arbiter.AMMO_PICKUP = {0.1, 0.1} --- Viper -self.ms3gl.use_data.selection_index = SELECTION.PRIMARY -self.ms3gl.stats.damage = 26 -self.ms3gl.AMMO_PICKUP = {0.05, 0.45} +-- Basilisk +self.ms3gl.fire_mode_data = {fire_rate = 0.33, single = {}, burst_cooldown = 1} +self.ms3gl.fire_mode_data.toggable = {"burst", "single"} +self.ms3gl.stats.damage = 56 +self.ms3gl.AMMO_PICKUP = {0.1, 0.1} -- GL40 self.gre_m79.use_data.selection_index = SELECTION.SECONDARY -self.gre_m79.stats.damage = 64 +self.gre_m79.stats.damage = 82 self.gre_m79.AMMO_MAX = 7 -self.gre_m79.AMMO_PICKUP = {0.05, 0.41} +self.gre_m79.AMMO_PICKUP = {0.1, 0.1} -- Compact 40 -self.slap.stats.damage = 64 +self.slap.stats.damage = 82 self.slap.AMMO_MAX = 7 -self.slap.AMMO_PICKUP = {0.05, 0.41} +self.slap.AMMO_PICKUP = {0.1, 0.1} + +-- Underbarrel Grenade Launchers +self.contraband_m203.AMMO_PICKUP = {0.084, 0.084} +self.groza_underbarrel.AMMO_PICKUP = {0.084, 0.084} -- Specials +-- Heavy Crossbow +self.arblast.AMMO_MAX = 45 +self.arblast.stats.damage = 10 +self.arblast.stats.concealment = 24 +self.arblast.single = { + fire_rate = 0.025 +} + -- Light Crossbow -self.frankish.AMMO_MAX = 30 +self.frankish.AMMO_MAX = 45 self.frankish.stats.damage = 50 self.frankish.stats.concealment = 25 self.frankish.use_data.selection_index = SELECTION.SECONDARY +self.frankish.single = { + fire_rate = 0.125 +} -- Pistol Crossbow -self.hunter.AMMO_MAX = 30 +self.hunter.AMMO_MAX = 45 self.hunter.stats.concealment = 30 +self.hunter.single = { + fire_rate = 0.125 +} -- Airbow +self.ecp.AMMO_MAX = 45 self.ecp.stats.damage = 25 self.ecp.use_data.selection_index = SELECTION.SECONDARY -- Plainsrider -self.plainsrider.AMMO_MAX = 30 -self.plainsrider.stats.damage = 65 +self.plainsrider.AMMO_MAX = 45 +self.plainsrider.stats.damage = 80 self.plainsrider.stats.concealment = 23 self.plainsrider.use_data.selection_index = SELECTION.SECONDARY -- Akimbos --- Krinkovs -self.x_akmsu.stats.damage = 80 -self.x_akmsu.AMMO_MAX = 120 -self.x_akmsu.kick.standing = {1.7, 1.9, -1.4, 1.2} -self.x_akmsu.kick.crouching = self.x_akmsu.kick.standing -self.x_akmsu.kick.steelsight = self.x_akmsu.kick.standing - --- Compacts -self.x_mp5.AMMO_MAX = 240 -self.x_mp5.kick.standing = {1.4, 1.6, -0.9, 1.2} -self.x_mp5.kick.crouching = self.x_mp5.kick.standing -self.x_mp5.kick.steelsight = self.x_mp5.kick.standing - --- Heathers -self.x_sr2.AMMO_MAX = 180 -self.x_sr2.stats.reload = 8 -self.x_sr2.kick.standing = {1.5, 1.7, -1.1, 1.1} -self.x_sr2.kick.crouching = self.x_sr2.kick.standing -self.x_sr2.kick.steelsight = self.x_sr2.kick.standing +-- Stryks +self.x_g18c.AMMO_MAX = total_ammo_tables.akimbo_pis_high +self.x_g18c.AMMO_PICKUP = pickup_tables.autopistol_mid +self.x_g18c.stats.concealment = 14 +self.x_g18c.stats.recoil = 12 +self.x_g18c.stats.reload = 8 +self.x_g18c.stats.damage = 45 +self.x_g18c.kick = kick_tables.akimbo_pistol_auto +self.x_g18c.spray = spray_tables.ar_left_low +self.x_g18c.recoil_recovery_timer = recovery_tables.high + +-- Czechs +self.x_czech.AMMO_MAX = total_ammo_tables.akimbo_pis_high +self.x_czech.AMMO_PICKUP = pickup_tables.autopistol_mid +self.x_czech.stats.concealment = 14 +self.x_czech.stats.recoil = 11 +self.x_czech.stats.spread = 15 +self.x_czech.stats.reload = 8 +self.x_czech.stats.damage = 45 +self.x_czech.kick = kick_tables.akimbo_pistol_auto +self.x_g18c.spray = spray_tables.ar_right_low +self.x_g18c.recoil_recovery_timer = recovery_tables.high + +-- Chimano Compacts +self.jowi.AMMO_MAX = total_ammo_tables.akimbo_pis_mid +self.jowi.AMMO_PICKUP = pickup_tables.pistol_mid +self.jowi.kick = kick_tables.pistol_low + +-- Bernetti 9s +self.x_b92fs.AMMO_MAX = total_ammo_tables.akimbo_pis_mid +self.x_b92fs.AMMO_PICKUP = pickup_tables.pistol_mid +self.x_b92fs.kick = kick_tables.pistol_low + +-- Chimano 88s +self.x_g17.AMMO_MAX = total_ammo_tables.akimbo_pis_mid +self.x_g17.AMMO_PICKUP = pickup_tables.pistol_mid +self.x_g17.kick = kick_tables.pistol_low + +-- Crosskills +self.x_1911.AMMO_MAX = total_ammo_tables.akimbo_pis_low +self.x_1911.AMMO_PICKUP = pickup_tables.pistol_low +self.x_1911.kick = kick_tables.pistol_mid + +-- Chimano Customs +self.x_g22c.AMMO_MAX = total_ammo_tables.akimbo_pis_low +self.x_g22c.AMMO_PICKUP = pickup_tables.pistol_low +self.x_g22c.kick = kick_tables.pistol_mid + +-- Interceptors +self.x_usp.AMMO_MAX = total_ammo_tables.akimbo_pis_low +self.x_usp.AMMO_PICKUP = pickup_tables.pistol_low +self.x_usp.kick = kick_tables.pistol_mid + +-- Contractors +self.x_packrat.AMMO_MAX = total_ammo_tables.akimbo_pis_low +self.x_packrat.AMMO_PICKUP = pickup_tables.pistol_low +self.x_packrat.kick = kick_tables.pistol_mid + +-- Deagles +self.x_deagle.stats.damage = 140 +self.x_deagle.AMMO_MAX = total_ammo_tables.akimbo_pis_low +self.x_deagle.AMMO_PICKUP = pickup_tables.pistol_low +self.x_deagle.kick = kick_tables.pistol_high + +-- Castigos +self.x_chinchilla.stats.damage = 120 +self.x_chinchilla.stats.spread = 21 +self.x_chinchilla.stats_modifiers = {damage = 2} +self.x_chinchilla.AMMO_MAX = total_ammo_tables.akimbo_special +self.x_chinchilla.AMMO_PICKUP = pickup_tables.revolver +self.x_chinchilla.kick = kick_tables.revolver_low -- Judges -self.x_judge.rays = 12 -self.x_judge.kick.standing = {2.5, 3, -0.2, 0.2} -self.x_judge.kick.crouching = self.x_judge.kick.standing -self.x_judge.kick.steelsight = self.x_judge.kick.standing +self.x_judge.rays = 8 +self.x_judge.stats.damage = 115 +self.x_judge.stats_modifiers = {damage = 2} +self.x_judge.kick = kick_tables.shot_low +self.x_judge.AMMO_MAX = total_ammo_tables.akimbo_special +self.x_judge.AMMO_PICKUP = pickup_tables.shot_special +self.x_judge.damage_falloff = FALLOFF_TEMPLATE.SHOTGUN_FALL_HIGH -- Flamethrowers @@ -983,14 +1664,16 @@ self.flamethrower_mk2.fire_dot_data = { dot_trigger_max_distance = 3000, dot_tick_period = 0.5 } -self.flamethrower_mk2.AMMO_PICKUP = {4.5, 6.75} +self.flamethrower_mk2.AMMO_PICKUP = {2.65, 3.5} -- removed shit +self.x_akmsu.use_data.selection_index = SELECTION.UNDERBARREL_PRIMARY +self.x_sr2.use_data.selection_index = SELECTION.UNDERBARREL_PRIMARY +self.x_mp5.use_data.selection_index = SELECTION.UNDERBARREL_PRIMARY self.elastic.use_data.selection_index = SELECTION.UNDERBARREL_PRIMARY self.long.use_data.selection_index = SELECTION.UNDERBARREL_PRIMARY -self.arblast.use_data.selection_index = SELECTION.UNDERBARREL_PRIMARY self.x_sko12.use_data.selection_index = SELECTION.UNDERBARREL_PRIMARY self.x_korth.use_data.selection_index = SELECTION.UNDERBARREL_PRIMARY self.x_basset.use_data.selection_index = SELECTION.UNDERBARREL_PRIMARY @@ -1015,7 +1698,6 @@ self.x_uzi.use_data.selection_index = SELECTION.UNDERBARREL_PRIMARY self.x_2006m.use_data.selection_index = SELECTION.UNDERBARREL_PRIMARY self.x_breech.use_data.selection_index = SELECTION.UNDERBARREL_PRIMARY self.x_c96.use_data.selection_index = SELECTION.UNDERBARREL_PRIMARY -self.x_g18c.use_data.selection_index = SELECTION.UNDERBARREL_PRIMARY self.x_hs2000.use_data.selection_index = SELECTION.UNDERBARREL_PRIMARY self.x_p226.use_data.selection_index = SELECTION.UNDERBARREL_PRIMARY self.x_pl14.use_data.selection_index = SELECTION.UNDERBARREL_PRIMARY @@ -1026,7 +1708,6 @@ self.x_maxim9.use_data.selection_index = SELECTION.UNDERBARREL_PRIMARY self.x_shrew.use_data.selection_index = SELECTION.UNDERBARREL_PRIMARY self.x_model3.use_data.selection_index = SELECTION.UNDERBARREL_PRIMARY self.x_beer.use_data.selection_index = SELECTION.UNDERBARREL_PRIMARY -self.x_czech.use_data.selection_index = SELECTION.UNDERBARREL_PRIMARY self.x_stech.use_data.selection_index = SELECTION.UNDERBARREL_PRIMARY self.x_holt.use_data.selection_index = SELECTION.UNDERBARREL_PRIMARY self.x_m1911.use_data.selection_index = SELECTION.UNDERBARREL_PRIMARY @@ -1038,44 +1719,143 @@ self.x_type54.use_data.selection_index = SELECTION.UNDERBARREL_PRIMARY self.x_pm9.use_data.selection_index = SELECTION.UNDERBARREL_PRIMARY self.x_shepheard.use_data.selection_index = SELECTION.UNDERBARREL_PRIMARY self.system.use_data.selection_index = SELECTION.UNDERBARREL_PRIMARY -self.rpg7.use_data.selection_index = SELECTION.UNDERBARREL_PRIMARY -end) -Hooks:PostHook(WeaponTweakData, "_set_overkill_290", "eclipse__set_overkill_290", function(self) - -- NPC weapon edits +-- NPC weapons + -- rifles self.m4_npc.DAMAGE = 1 self.m4_npc.auto.fire_rate = 0.225 - self.r870_npc.DAMAGE = 7 self.m4_yellow_npc.DAMAGE = 1 self.m4_yellow_npc.auto.fire_rate = 0.3 - self.m249_npc.auto.fire_rate = 0.175 - self.ump_npc.auto.fire_rate = 0.25 self.g36_npc.DAMAGE = 1 self.g36_npc.auto.fire_rate = 0.25 - self.mp9_npc.auto.fire_rate = 0.275 + + -- shotguns + self.r870_npc.DAMAGE = 1 + self.r870_npc.CLIP_AMMO_MAX = 8 + self.benelli_npc.DAMAGE = 1 + self.benelli_npc.CLIP_AMMO_MAX = 8 + self.ksg_npc = deep_clone(self.r870_npc) + + -- dozer guns + self.m249_npc.DAMAGE = 1 + self.m249_npc.auto.fire_rate = 0.15 + self.saiga_npc.DAMAGE = 1 + self.saiga_npc.auto.fire_rate = 0.33 + self.saiga_npc.CLIP_AMMO_MAX = 20 + + -- smgs + self.mp5_npc.DAMAGE = 1 self.mp5_npc.auto.fire_rate = 0.25 - self.saiga_npc.auto.fire_rate = 0.25 - self.mac11_npc.auto.fire_rate = 0.2 + self.ump_npc.DAMAGE = 1 + self.ump_npc.auto.fire_rate = 0.25 + self.mp9_npc.DAMAGE = 1 + self.mp9_npc.auto.fire_rate = 0.275 + self.mac11_npc.DAMAGE = 1 + self.mac11_npc.auto.fire_rate = 0.25 + self.shepheard_npc = deep_clone(self.mp5_npc) + + -- misc guns self.raging_bull_npc.DAMAGE = 1 self.ak47_ass_npc.DAMAGE = 1 self.ak47_ass_npc.auto.fire_rate = 0.2 self.ak47_npc.DAMAGE = 1 self.ak47_npc.auto.fire_rate = 0.2 + self.c45_npc.DAMAGE = 1 + self.x_c45_npc.DAMAGE = 1 + self.flamethrower_npc.DAMAGE = 1 self.scar_npc.DAMAGE = 1 self.scar_npc.auto.fire_rate = 0.2 - -- Misc + + +-- Misc self.m249_npc.usage = "is_lmg" - self.r870_npc.CLIP_AMMO_MAX = 8 - self.saiga_npc.CLIP_AMMO_MAX = 20 - self.benelli_npc.CLIP_AMMO_MAX = 8 - self.flamethrower_npc.flame_max_range = 800 -- wow 15m is retarded lmao - self.benelli_npc.sounds.prefix = "benelli_m4_npc" -- Give it a proper sound - self.beretta92_npc.has_suppressor = "suppressed_b" -- suppressed + self.flamethrower_npc.flame_max_range = 800 + self.benelli_npc.sounds.prefix = "benelli_m4_npc" + self.shepheard_npc.sounds.prefix = "shepheard_npc" + self.ksg_npc.sounds.prefix = "keltec_npc" + self.beretta92_npc.has_suppressor = "suppressed_b" self.m14_sniper_npc.trail = "effects/particles/weapons/sniper_trail_marshal" + self.mossberg_npc.anim_usage = "is_shotgun_pump" + self.mossberg_npc.CLIP_AMMO_MAX = 2 + self.mossberg_npc.shell_ejection = "effects/payday2/particles/weapons/shells/shell_empty" + self.mossberg_npc.sounds.prefix = self.huntsman_crew.sounds.prefix + self.mossberg_npc.usage = "is_double_barrel" + -- just copy pasted turret stuff from vanilla ovk145 tweak + self.ceiling_turret_module.HEALTH_INIT = 12500 + self.ceiling_turret_module.SHIELD_HEALTH_INIT = 250 + self.ceiling_turret_module.DAMAGE = 2 + self.aa_turret_module.HEALTH_INIT = 26000 + self.aa_turret_module.SHIELD_HEALTH_INIT = 500 + self.aa_turret_module.DAMAGE = 2 + self.crate_turret_module.HEALTH_INIT = 12500 + self.crate_turret_module.SHIELD_HEALTH_INIT = 500 + self.crate_turret_module.DAMAGE = 2 + + + -- if bot weapons and equipment is installed and fixed weapon balance is on don't make any further changes + if BotWeapons and BotWeapons.settings and BotWeapons.settings.weapon_balance then + return + end + + self.flint_crew.DAMAGE = 1.5 + self.hcar_crew.DAMAGE = 1 + self.tkb_crew.DAMAGE = 1.5 + self.l85a2_crew.DAMAGE = 1.5 end) -local _set_overkill_290_orig = WeaponTweakData._set_overkill_290 -function WeaponTweakData:_set_sm_wish() - _set_overkill_290_orig(self) + +-- easy dmg scaling +function WeaponTweakData:_set_normal() + self.m4_npc.DAMAGE = 0.35 + self.m4_yellow_npc.DAMAGE = 0.35 + self.g36_npc.DAMAGE = 0.35 + self.r870_npc.DAMAGE = 0.25 + self.benelli_npc.DAMAGE = 0.25 + self.m249_npc.DAMAGE = 0.35 + self.saiga_npc.DAMAGE = 0.35 + self.mp5_npc.DAMAGE = 0.35 + self.ump_npc.DAMAGE = 0.35 + self.mp9_npc.DAMAGE = 0.35 + self.raging_bull_npc.DAMAGE = 0.2 + self.ak47_ass_npc.DAMAGE = 0.35 + self.ak47_npc.DAMAGE = 0.35 + self.scar_npc.DAMAGE = 0.35 + self.c45_npc.DAMAGE = 0.35 + self.x_c45_npc.DAMAGE = 0.35 + self.m14_sniper_npc.DAMAGE = 0.25 + self.mac11_npc.DAMAGE = 0.35 + self.flamethrower_npc.DAMAGE = 0.35 +end + +-- normal dmg scaling +function WeaponTweakData:_set_hard() + self.m4_npc.DAMAGE = 0.7 + self.m4_yellow_npc.DAMAGE = 0.7 + self.g36_npc.DAMAGE = 0.7 + self.r870_npc.DAMAGE = 0.4 + self.benelli_npc.DAMAGE = 0.4 + self.m249_npc.DAMAGE = 0.7 + self.saiga_npc.DAMAGE = 0.7 + self.mp5_npc.DAMAGE = 0.7 + self.ump_npc.DAMAGE = 0.7 + self.mp9_npc.DAMAGE = 0.7 + self.raging_bull_npc.DAMAGE = 0.4 + self.ak47_ass_npc.DAMAGE = 0.7 + self.ak47_npc.DAMAGE = 0.7 + self.scar_npc.DAMAGE = 0.7 + self.c45_npc.DAMAGE = 0.7 + self.x_c45_npc.DAMAGE = 0.7 + self.m14_sniper_npc.DAMAGE = 0.5 + self.mac11_npc.DAMAGE = 0.7 + self.flamethrower_npc.DAMAGE = 0.7 end +-- damage reaches intended values after normal and stops scaling +function WeaponTweakData:_set_overkill() +end + +function WeaponTweakData:_set_overkill_145() +end + +function WeaponTweakData:_set_easy_wish() +end diff --git a/main.xml b/main.xml index 5ed19ab..684a6c7 100644 --- a/main.xml +++ b/main.xml @@ -1,15 +1,31 @@ - - - - + + + + + + + + + + + + + + + + + + + - - - - + + + + + @@ -36,5 +52,7 @@ + +
diff --git a/meta.json b/meta.json index e4b27cd..41badef 100644 --- a/meta.json +++ b/meta.json @@ -1,7 +1,7 @@ [ { "ident": "eclipse_difficulty", - "version": "4.11", + "version": "5.0", "patchnotes_url": "https://github.com/mrcreepysos/Eclipse-Difficulty/commits/main", "download_url": "https://github.com/mrcreepysos/Eclipse-Difficulty/archive/refs/heads/main.zip" } diff --git a/mod.lua b/mod.lua index 9aea44c..633fe92 100644 --- a/mod.lua +++ b/mod.lua @@ -1,13 +1,38 @@ +if not EclipseDebug then + EclipseDebug = {} + local log_levels = { + "Debug", + "Warning", + "Error" + } + + function EclipseDebug:log(level, message) + assert(0 < level and level < 4, "Eclipse log level must be between 1-3.") + assert(message ~= nil, "Eclipse empty log message.") + + log(string.format("Eclipse %s: %s", log_levels[level], message)) + end + + function EclipseDebug:log_chat(message) + managers.chat:_receive_message(managers.chat.GAME, "Eclipse", message, Color.green) + end +end + if not StreamHeist then StreamHeist = { mod_path = ModPath, mod_instance = ModInstance, - logging = io.file_is_readable("mods/developer.txt") + save_path = SavePath .. "eclipse.json", + logging = io.file_is_readable("mods/developer.txt"), + required = {}, + settings = { + ponr_assault_text = false, + }, } function StreamHeist:require(file) - local path = self.mod_path .. "req/" .. file + local path = self.mod_path .. "req/" .. file .. ".lua" return io.file_is_readable(path) and blt.vm.dofile(path) end @@ -15,7 +40,7 @@ if not StreamHeist then if self._mission_script_patches == nil then local level_id = Global.game_settings and Global.game_settings.level_id if level_id then - self._mission_script_patches = self:require("mission_script/" .. level_id:gsub("_night$", ""):gsub("_day$", "") .. ".lua") or false + self._mission_script_patches = self:require("mission_script/" .. level_id:gsub("_night$", ""):gsub("_day$", "")) or false end end return self._mission_script_patches @@ -42,7 +67,7 @@ if not StreamHeist then end Global.sh_mod_conflicts = {} - local conflicting_mods = StreamHeist:require("mod_conflicts.lua") or {} + local conflicting_mods = StreamHeist:require("mod_conflicts") or {} for _, mod in pairs(BLT.Mods:Mods()) do if mod:IsEnabled() and conflicting_mods[mod:GetName()] then table.insert(Global.sh_mod_conflicts, mod:GetName()) @@ -61,7 +86,7 @@ if not StreamHeist then mod:SetEnabled(false, true) end end - BLT.Mods:Save() + MenuCallbackHandler:perform_blt_save() end }, { @@ -72,10 +97,62 @@ if not StreamHeist then end end) + -- Create settings menu + Hooks:Add("MenuManagerBuildCustomMenus", "MenuManagerBuildCustomMenusStreamlinedHeisting", function(_, nodes) + + local menu_id = "eclipse_menu" + MenuHelper:NewMenu(menu_id) + + local faction_menu_elements = {} + function MenuCallbackHandler:sh_ponr_assault_text_toggle(item) + local enabled = (item:value() == "on") + StreamHeist.settings.ponr_assault_text = enabled + for _, element in pairs(faction_menu_elements) do + element:set_enabled(not enabled) + end + end + + function MenuCallbackHandler:sh_save() + io.save_as_json(StreamHeist.settings, StreamHeist.save_path) + end + + MenuHelper:AddToggle({ + id = "ponr_assault_text", + title = "eclipse_menu_ponr_assault_text", + desc = "eclipse_menu_ponr_assault_text_desc", + callback = "sh_ponr_assault_text_toggle", + value = StreamHeist.settings.ponr_assault_text, + menu_id = menu_id, + priority = 100 + }) + + nodes[menu_id] = MenuHelper:BuildMenu(menu_id, { back_callback = "sh_save" }) + MenuHelper:AddMenuItem(nodes["blt_options"], menu_id, "eclipse_menu_main") + end) + + -- Load settings + if io.file_is_readable(StreamHeist.save_path) then + local data = io.load_as_json(StreamHeist.save_path) + if data then + local function merge(tbl1, tbl2) + for k, v in pairs(tbl2) do + if type(tbl1[k]) == type(v) then + if type(v) == "table" then + merge(tbl1[k], v) + else + tbl1[k] = v + end + end + end + end + merge(StreamHeist.settings, data) + end + end + -- Notify about required game restart Hooks:Add("MenuManagerPostInitialize", "MenuManagerPostInitializeStreamlinedHeisting", function () Hooks:PostHook(BLTViewModGui, "clbk_toggle_enable_state", "sh_clbk_toggle_enable_state", function (self) - if self._mod:GetName() == "Streamlined Heisting" then + if self._mod:GetName() == "Eclipse" then QuickMenu:new("Information", "A game restart is required to fully " .. (self._mod:IsEnabled() and "enable" or "disable") .. " all parts of Eclipse!", {}, true) end end) @@ -83,21 +160,19 @@ if not StreamHeist then -- Disable some of "The Fixes" TheFixesPreventer = TheFixesPreventer or {} - TheFixesPreventer.shotgun_dozer_face = true TheFixesPreventer.crash_upd_aim_coplogicattack = true TheFixesPreventer.fix_copmovement_aim_state_discarded = true TheFixesPreventer.tank_remove_recoil_anim = true TheFixesPreventer.tank_walk_near_players = true end -local required = {} -if RequiredScript and not required[RequiredScript] then +if RequiredScript and not StreamHeist.required[RequiredScript] then local fname = StreamHeist.mod_path .. RequiredScript:gsub(".+/(.+)", "lua/%1.lua") if io.file_is_readable(fname) then dofile(fname) end - required[RequiredScript] = true + StreamHeist.required[RequiredScript] = true -end \ No newline at end of file +end diff --git a/mod.png b/mod.png index 4aef75d..100bb34 100644 Binary files a/mod.png and b/mod.png differ diff --git a/mod.txt b/mod.txt index 70ea2cc..ce11d38 100644 --- a/mod.txt +++ b/mod.txt @@ -1,13 +1,13 @@ { "name" : "Eclipse", - "description" : "A challenging custom difficulty with overhauled AI, enemies, tactics and more! Includes playerside balance overhaul.", - "author" : "mrcreepy, Hoppip, Rockymoto, RedFlame and many others...", - "contact" : "creepysos#4414", + "description" : "A total PAYDAY overhaul that aims to make the game experience way more challenging and engaging!", + "author" : "nikita, Hoppip, Rockymoto, RedFlame and many others...", + "contact" : "nikita_was_taken on Discord", "image" : "mod.png", "blt_version" : 2, - "version" : "4.11", + "version" : "5.0", "updates" : [{ "identifier" : "eclipse_difficulty", - "host" : {"meta" : "https://raw.githubusercontent.com/mrcreepysos/Eclipse-Difficulty/main/meta.json"} + "host" : { "meta" : "https://raw.githubusercontent.com/mrcreepysos/Eclipse-Difficulty/main/meta.json" } }] } diff --git a/req/mission_script/alex_2.lua b/req/mission_script/alex_2.lua index c49e8c7..590b0ce 100644 --- a/req/mission_script/alex_2.lua +++ b/req/mission_script/alex_2.lua @@ -1,16 +1,16 @@ return { [104480] = { groups = { - common_tank = false, - elite_tank = false, - common_shield = false, - elite_shieldg = false + fbi_tanks = false, + gensec_tanks = false, + fbi_shields = false, + gensec_shields = false } }, [102793] = { groups = { - common_shield = false, - elite_shieldg = false + fbi_shields = false, + gensec_shields = false } } } \ No newline at end of file diff --git a/req/mission_script/bex.lua b/req/mission_script/bex.lua new file mode 100644 index 0000000..0265050 --- /dev/null +++ b/req/mission_script/bex.lua @@ -0,0 +1,6 @@ +return { + [101829] = { + ponr = 240, + ponr_player_mul = {2, 1.5, 1.25, 1} + } +} \ No newline at end of file diff --git a/req/mission_script/big.lua b/req/mission_script/big.lua new file mode 100644 index 0000000..7e74331 --- /dev/null +++ b/req/mission_script/big.lua @@ -0,0 +1,6 @@ +return { + [100809] = { + ponr = 180, + ponr_player_mul = {2, 1.25, 1, 1} + } +} \ No newline at end of file diff --git a/req/mission_script/born.lua b/req/mission_script/born.lua new file mode 100644 index 0000000..25beaf6 --- /dev/null +++ b/req/mission_script/born.lua @@ -0,0 +1,10 @@ +return { + [100628] = { + values = { + enabled = false + } + }, + [100720] = { + set_ponr_state = true + } +} \ No newline at end of file diff --git a/req/mission_script/bph.lua b/req/mission_script/bph.lua new file mode 100644 index 0000000..f669104 --- /dev/null +++ b/req/mission_script/bph.lua @@ -0,0 +1,17 @@ +return { + -- Allow bot navigation earlier + [102736] = { + on_executed = { + { id = 103049, delay = 1 } + } + }, + [101161] = { + values = { + enabled = false + }, + }, + [100268] = { + ponr = 300, + ponr_player_mul = {1.25, 1, 1, 1} + } +} \ No newline at end of file diff --git a/req/mission_script/chca.lua b/req/mission_script/chca.lua index 4d279e4..dda5c8a 100644 --- a/req/mission_script/chca.lua +++ b/req/mission_script/chca.lua @@ -1,14 +1,14 @@ return { [101469] = { groups = { - common_shield = false, - elite_shieldg = false + fbi_shields = false, + gensec_shields = false } }, [101470] = { groups = { - common_shield = false, - elite_shieldg = false + fbi_shields = false, + gensec_shields = false } } } \ No newline at end of file diff --git a/req/mission_script/corp.lua b/req/mission_script/corp.lua new file mode 100644 index 0000000..56f3efa --- /dev/null +++ b/req/mission_script/corp.lua @@ -0,0 +1,36 @@ +return { + [100115] = { + reinforce = { + { + name = "admin", + force = 2, + position = Vector3(-5600, 1200, -200) + }, + { + name = "parkinglot", + force = 2, + position = Vector3(6000, 5100, 0) + }, + { + name = "garden", + force = 2, + position = Vector3(7200, -3900, 10) + }, + { + name = "labroof1", + force = 2, + position = Vector3(4000, 400, 670) + }, + { + name = "labroof2", + force = 2, + position = Vector3(-1200, 2600, 670) + }, + { + name = "statue", + force = 3, + position = Vector3(700, -75, 0) + } + } + } +} \ No newline at end of file diff --git a/req/mission_script/dah.lua b/req/mission_script/dah.lua new file mode 100644 index 0000000..34b8f2a --- /dev/null +++ b/req/mission_script/dah.lua @@ -0,0 +1,10 @@ +return { + [101967] = { + values = { + enabled = false + } + }, + [100614] = { + set_ponr_state = true + } +} \ No newline at end of file diff --git a/req/mission_script/des.lua b/req/mission_script/des.lua index 15c0605..c8d0387 100644 --- a/req/mission_script/des.lua +++ b/req/mission_script/des.lua @@ -14,5 +14,10 @@ return { name = "main_hall" } } - } + }, + -- add point of no return + [100286] = { + ponr = 300, + ponr_player_mul = {1.6, 1.2, 1, 0.8} + } } \ No newline at end of file diff --git a/req/mission_script/dinner.lua b/req/mission_script/dinner.lua new file mode 100644 index 0000000..c8e0036 --- /dev/null +++ b/req/mission_script/dinner.lua @@ -0,0 +1,11 @@ +return { + [103218] = { + values = { + enabled = false + } + }, + [101061] = { + ponr = 200, + ponr_player_mul = {1.5, 1.25, 1, 1} + } +} \ No newline at end of file diff --git a/req/mission_script/election_day_3.lua b/req/mission_script/election_day_3.lua index 92fada9..4c38cd9 100644 --- a/req/mission_script/election_day_3.lua +++ b/req/mission_script/election_day_3.lua @@ -9,5 +9,9 @@ return { values = { delay = 20 } + }, + [104782] = { + ponr = 420, + ponr_player_mul = {1.5, 1.25, 1, 1} } } \ No newline at end of file diff --git a/req/mission_script/election_day_3_skip1.lua b/req/mission_script/election_day_3_skip1.lua index 92fada9..4c38cd9 100644 --- a/req/mission_script/election_day_3_skip1.lua +++ b/req/mission_script/election_day_3_skip1.lua @@ -9,5 +9,9 @@ return { values = { delay = 20 } + }, + [104782] = { + ponr = 420, + ponr_player_mul = {1.5, 1.25, 1, 1} } } \ No newline at end of file diff --git a/req/mission_script/election_day_3_skip2.lua b/req/mission_script/election_day_3_skip2.lua index 92fada9..4c38cd9 100644 --- a/req/mission_script/election_day_3_skip2.lua +++ b/req/mission_script/election_day_3_skip2.lua @@ -9,5 +9,9 @@ return { values = { delay = 20 } + }, + [104782] = { + ponr = 420, + ponr_player_mul = {1.5, 1.25, 1, 1} } } \ No newline at end of file diff --git a/req/mission_script/fex.lua b/req/mission_script/fex.lua new file mode 100644 index 0000000..9ac6f19 --- /dev/null +++ b/req/mission_script/fex.lua @@ -0,0 +1,13 @@ +return { + -- Don't kill off enemies in courtyard/patio + [102903] = { + values = { + enabled = false + } + }, + [102904] = { + values = { + enabled = false + } + } +} \ No newline at end of file diff --git a/req/mission_script/flat.lua b/req/mission_script/flat.lua index feb711c..8018d11 100644 --- a/req/mission_script/flat.lua +++ b/req/mission_script/flat.lua @@ -32,10 +32,7 @@ return { } }, -- add point of no return - [100301] = { - ponr = 60 - }, - [100245] = { - values = {instigator = "criminals_not_downed"} + [101016] = { + ponr = 180 } } \ No newline at end of file diff --git a/req/mission_script/framing_frame_1.lua b/req/mission_script/framing_frame_1.lua new file mode 100644 index 0000000..92e3102 --- /dev/null +++ b/req/mission_script/framing_frame_1.lua @@ -0,0 +1,19 @@ +return { + -- Set difficulty + [100648] = { + difficulty = 0.5 + }, + [101961] = { + values = { + difficulty = 0.5 + } + }, + [100812] = { + values = { + difficulty = 0.5 + }, + on_executed = { + { id = 101495, delay = 0 } + } + } +} \ No newline at end of file diff --git a/req/mission_script/gallery.lua b/req/mission_script/gallery.lua new file mode 100644 index 0000000..92e3102 --- /dev/null +++ b/req/mission_script/gallery.lua @@ -0,0 +1,19 @@ +return { + -- Set difficulty + [100648] = { + difficulty = 0.5 + }, + [101961] = { + values = { + difficulty = 0.5 + } + }, + [100812] = { + values = { + difficulty = 0.5 + }, + on_executed = { + { id = 101495, delay = 0 } + } + } +} \ No newline at end of file diff --git a/req/mission_script/glace.lua b/req/mission_script/glace.lua new file mode 100644 index 0000000..9e34dc9 --- /dev/null +++ b/req/mission_script/glace.lua @@ -0,0 +1,58 @@ +return { + -- Make cloaker spawn participate to group AI + [101320] = { + values = { + participate_to_group_ai = true + } + }, + -- Remove spawn groups closest to broken bridge part + [101176] = { + values = { + spawn_groups = { 100867, 101153, 101157, 101154, 101160, 101156, 101159 } + } + }, + -- Increase spawn group intervals next to prison vans, closest to furthest + [100867] = { + values = { + interval = 90 + } + }, + [101153] = { + values = { + interval = 90 + } + }, + [101157] = { + values = { + interval = 90 + } + }, + [101154] = { + values = { + interval = 60 + } + }, + [101160] = { + values = { + interval = 60 + } + }, + [101156] = { + values = { + interval = 45 + } + }, + [101159] = { + values = { + interval = 45 + } + }, + [100521] = { + values = { + enabled = false + } + }, + [100533] = { + set_ponr_state = true + } +} \ No newline at end of file diff --git a/req/mission_script/hox_1.lua b/req/mission_script/hox_1.lua index 21172fb..3a30631 100644 --- a/req/mission_script/hox_1.lua +++ b/req/mission_script/hox_1.lua @@ -1,10 +1,12 @@ return { -- add point of no return [100580] = { - ponr = 900, - ponr_player_mul = {1.5, 1, 0.85, 0.75} + ponr = 600, + ponr_player_mul = {1.8, 1.5, 1.3, 1.2} }, - [100247] = { - values = {instigator = "criminals_not_downed"} + [100124] = { + values = { + difficulty = 0.75 + } } } \ No newline at end of file diff --git a/req/mission_script/hox_2.lua b/req/mission_script/hox_2.lua index 49c4f44..020a395 100644 --- a/req/mission_script/hox_2.lua +++ b/req/mission_script/hox_2.lua @@ -1,10 +1,7 @@ return { -- add point of no return - [101374] = { - ponr = 50, - ponr_player_mul = {1.5, 1.25, 1, 1} - }, - [103487] = { - values = {instigator = "criminals_not_downed"} + [102109] = { + ponr = 90, + ponr_player_mul = {1.4, 1.25, 1.1, 1} } } \ No newline at end of file diff --git a/req/mission_script/jolly.lua b/req/mission_script/jolly.lua new file mode 100644 index 0000000..00a8f3a --- /dev/null +++ b/req/mission_script/jolly.lua @@ -0,0 +1,7 @@ +return { + [101598] = { + values = { + enabled = false + } + } +} \ No newline at end of file diff --git a/req/mission_script/man.lua b/req/mission_script/man.lua new file mode 100644 index 0000000..aaca269 --- /dev/null +++ b/req/mission_script/man.lua @@ -0,0 +1,6 @@ +return { + [101433] = { + ponr = 60, + ponr_player_mul = {1.25, 1, 1, 0.75} + } +} \ No newline at end of file diff --git a/req/mission_script/mex.lua b/req/mission_script/mex.lua new file mode 100644 index 0000000..abef689 --- /dev/null +++ b/req/mission_script/mex.lua @@ -0,0 +1,10 @@ +return { + [103048] = { + groups = { + fbi_tanks = false, + gensec_tanks = false, + fbi_shields = false, + gensec_shields = false, + } + } +} \ No newline at end of file diff --git a/req/mission_script/mex_cooking.lua b/req/mission_script/mex_cooking.lua new file mode 100644 index 0000000..abef689 --- /dev/null +++ b/req/mission_script/mex_cooking.lua @@ -0,0 +1,10 @@ +return { + [103048] = { + groups = { + fbi_tanks = false, + gensec_tanks = false, + fbi_shields = false, + gensec_shields = false, + } + } +} \ No newline at end of file diff --git a/req/mission_script/mia_2.lua b/req/mission_script/mia_2.lua index 640e3a3..9259301 100644 --- a/req/mission_script/mia_2.lua +++ b/req/mission_script/mia_2.lua @@ -1,10 +1,20 @@ return { -- Boss spawn - [100521] = { + [100154] = { difficulty = 0.1 }, -- Boss dead - [100533] = { + [100153] = { difficulty = 1 - } + }, + [101133] = { + values = { + enemy = "units/payday2/characters/ene_spook_1/ene_spook_1" + } + }, + [101141] = { + values = { + enemy = "units/payday2/characters/ene_spook_1/ene_spook_1" + } + } } \ No newline at end of file diff --git a/req/mission_script/nmh.lua b/req/mission_script/nmh.lua index 832ca6d..3fa1c8c 100644 --- a/req/mission_script/nmh.lua +++ b/req/mission_script/nmh.lua @@ -7,10 +7,8 @@ return { }, -- Alert all civs on mask up and delay panic button SO [102518] = { - values = { - on_executed = { - { id = 102540, delay = 10 } - } + on_executed = { + { id = 102540, delay = 10 } }, func = function() for _, u_data in pairs(managers.enemy:all_civilians()) do diff --git a/req/mission_script/pal.lua b/req/mission_script/pal.lua index 4c6301b..3c6b57b 100644 --- a/req/mission_script/pal.lua +++ b/req/mission_script/pal.lua @@ -1,9 +1,13 @@ return { -- add point of no return [101324] = { - ponr = 75 + ponr = 90, + ponr_player_mul = {1.1, 1, 1, 0.9} }, [101987] = { values = {instigator = "criminals_not_downed"} + }, + [102551] = { + ponr_end = true } } \ No newline at end of file diff --git a/req/mission_script/pbr2.lua b/req/mission_script/pbr2.lua new file mode 100644 index 0000000..251aac4 --- /dev/null +++ b/req/mission_script/pbr2.lua @@ -0,0 +1,11 @@ +return { + [100104] = { + values = { + enabled = false + } + }, + [100980] = { + ponr = 60, + ponr_player_mul = {1.67, 1.34, 1, 1} + } +} \ No newline at end of file diff --git a/req/mission_script/pent.lua b/req/mission_script/pent.lua index 0be858a..07f3ed8 100644 --- a/req/mission_script/pent.lua +++ b/req/mission_script/pent.lua @@ -19,5 +19,9 @@ return { position = Vector3(1600, -1600, 13100) } } + }, + [101607] = { + ponr = 180, + ponr_player_mul = {1.33, 1.15, 1, 0.85} } } \ No newline at end of file diff --git a/req/mission_script/peta2.lua b/req/mission_script/peta2.lua index d376014..3c2df49 100644 --- a/req/mission_script/peta2.lua +++ b/req/mission_script/peta2.lua @@ -16,13 +16,13 @@ return { } }, -- add point of no return - [101723] = { - ponr = 150, - ponr_player_mul = {2, 1.3, 1, 1} + [100038] = { + ponr = 120, + ponr_player_mul = {2, 1.25, 1, 1} }, [101707] = { values = { - delay = 1 + enabled = false } } } \ No newline at end of file diff --git a/req/mission_script/pex.lua b/req/mission_script/pex.lua new file mode 100644 index 0000000..f0617a6 --- /dev/null +++ b/req/mission_script/pex.lua @@ -0,0 +1,6 @@ +return { + [101829] = { + ponr = 300, + ponr_player_mul = {1.5, 1.25, 1, 1} + } +} \ No newline at end of file diff --git a/req/mission_script/red2.lua b/req/mission_script/red2.lua index 856f625..646476f 100644 --- a/req/mission_script/red2.lua +++ b/req/mission_script/red2.lua @@ -1,10 +1,49 @@ return { + -- Disable forced manager flee objective + [100665] = { + values = { + enabled = false + } + }, -- add point of no return [103334] = { - ponr = 240, - ponr_player_mul = {1.25, 1, 0.85, 0.75} + ponr = 150, + ponr_player_mul = {1.1, 0.9, 0.7, 0.5} + }, + -- remove a few cancer dozers + [103603] = { + values = { + enabled = false + } + }, + [104132] = { + values = { + enemy = "units/payday2/characters/ene_spook_1/ene_spook_1" + } + }, + [104710] = { + values = { + enemy = "units/payday2/characters/ene_spook_1/ene_spook_1" + } + }, + [104131] = { + values = { + enemy = "units/payday2/characters/ene_tazer_1/ene_tazer_1" + } + }, + [104169] = { + values = { + enemy = "units/payday2/characters/ene_tazer_1/ene_tazer_1" + } + }, + [104000] = { + values = { + chance = 1 + } + }, + [100225] = { + values = { + amount = 5 + } }, - [101727] = { - values = {instigator = "criminals_not_downed"} - } } diff --git a/req/mission_script/run.lua b/req/mission_script/run.lua index a105204..c81a490 100644 --- a/req/mission_script/run.lua +++ b/req/mission_script/run.lua @@ -1,4 +1,8 @@ return { + [100140] = { + ponr = 480, + ponr_player_mul = {2, 1.5, 1.25, 1} + }, -- ovk145-alike dozer spawn on armitage avenue [103593] = { values = { @@ -34,8 +38,8 @@ return { -- add missing sniper [103582] = { values = { - difficulty_overkill_290 = true, - difficulty_sm_wish = true + difficulty_overkill_145 = true, + difficulty_easy_wish = true } }, [102866] = { @@ -47,5 +51,20 @@ return { values = { enabled = false } + }, + [100029] = { + values = { + interval = 20 + } + }, + [100441] = { + values = { + interval = 20 + } + }, + [103333] = { + values = { + interval = 20 + } } } \ No newline at end of file diff --git a/req/mission_script/rvd2.lua b/req/mission_script/rvd2.lua index c9a8133..c8d5926 100644 --- a/req/mission_script/rvd2.lua +++ b/req/mission_script/rvd2.lua @@ -1,25 +1,27 @@ return { -- Replace dozer spam with less stupid enemies - [101575] = { + [101565] = { values = { enemy = "units/payday2/characters/ene_spook_1/ene_spook_1" } }, - [101182] = { + [101176] = { values = { enemy = "units/payday2/characters/ene_tazer_1/ene_tazer_1" } }, - [101210] = { + [101207] = { values = { enemy = "units/payday2/characters/ene_tazer_1/ene_tazer_1" } }, - -- add point of no return - [101500] = { - ponr = 60 + [102176] = { + values = { + enabled = false + } }, - [100246] = { - values = {instigator = "criminals_not_downed"} + -- instantly enter point of no return upon securing all bags + [100884] = { + set_ponr_state = true } } \ No newline at end of file diff --git a/req/mission_script/sah.lua b/req/mission_script/sah.lua index 3b73358..964f510 100644 --- a/req/mission_script/sah.lua +++ b/req/mission_script/sah.lua @@ -5,14 +5,120 @@ return { enabled = false } }, - [101400] = { - difficulty = 0.5, + -- Loud, slightly delay police response + [100109] = { + values = { + base_delay = 30 + } + }, + [100129] = { + difficulty = 0.4, reinforce = { { name = "auction_room", - force = 5, + force = 2, position = Vector3(0, 2000, -100) + }, + { + name = "outside", + force = 2, + position = Vector3(0, -3300, -50) } + }, + on_executed = { + { id = 100127, delay = 0 }, + { id = 103905, delay = 0 }, + { id = 103910, delay = 0 }, + { id = 103912, delay = 0 }, + { id = 103913, delay = 0 } + } + }, + -- Disable area report triggers + [100140] = { + values = { + enabled = false + } + }, + [106783] = { + values = { + enabled = false + } + }, + [103926] = { + values = { + enabled = false + } + }, + [106784] = { + values = { + enabled = false + } + }, + -- Slow down roof spawns + [102667] = { + values = { + interval = 20 } + }, + [106776] = { + values = { + interval = 20 + } + }, + [106767] = { + values = { + interval = 20 + } + }, + [106764] = { + values = { + interval = 20 + } + }, + [100694] = { + values = { + interval = 30 + } + }, + [100154] = { + values = { + interval = 30 + } + }, + -- Slow down storage spawns + [102303] = { + values = { + interval = 40 + } + }, + [103662] = { + values = { + interval = 20 + } + }, + [104089] = { + values = { + interval = 40 + } + }, + -- Slow down and adjust storage window spawns + [103522] = { + values = { + interval = 60 + }, + groups = { + fbi_shields = false, + fbi_tanks = false, + gensec_shields = false, + gensec_tanks = false + } + }, + [101175] = { + values = { + enabled = false + } + }, + [101177] = { + set_ponr_state = true } } \ No newline at end of file diff --git a/req/mission_script/sand.lua b/req/mission_script/sand.lua new file mode 100644 index 0000000..d64aca2 --- /dev/null +++ b/req/mission_script/sand.lua @@ -0,0 +1,7 @@ +return { + [103870] = { + values = { + enabled = false -- disable vlad dies ponr since it's stupid and can bug out + } + } +} \ No newline at end of file diff --git a/req/mission_script/spa.lua b/req/mission_script/spa.lua index aee3799..60a44ca 100644 --- a/req/mission_script/spa.lua +++ b/req/mission_script/spa.lua @@ -1,15 +1,12 @@ return { -- add point of no return and disable endless assault [100875] = { - ponr = 300, - ponr_player_mul = {1.25, 1, 0.85, 0.75} + ponr = 240, + ponr_player_mul = {1.5, 1, 0.85, 0.75} }, [100877] = { values = { enabled = false } - }, - [101206] = { - values = {instigator = "criminals_not_downed"} } } diff --git a/req/mission_script/trai.lua b/req/mission_script/trai.lua new file mode 100644 index 0000000..52bd7be --- /dev/null +++ b/req/mission_script/trai.lua @@ -0,0 +1,12 @@ +return { + [103501] = { + values = { + enabled = false + } + }, + [103051] = { + values = { + enabled = false + } + } +} \ No newline at end of file diff --git a/req/mission_script/vit.lua b/req/mission_script/vit.lua new file mode 100644 index 0000000..01ee471 --- /dev/null +++ b/req/mission_script/vit.lua @@ -0,0 +1,22 @@ +return { + -- Increase delay on side door spawns + [103347] = { + values = { + interval = 30 + } + }, + [103348] = { + values = { + interval = 30 + } + }, + [100022] = { + ponr = 1200, + ponr_player_mul = {2, 1.5, 1.25, 1} + }, + [103360] = { + values = { + enabled = false + } + } +} \ No newline at end of file diff --git a/req/mission_script/watchdogs_1.lua b/req/mission_script/watchdogs_1.lua index 78eeb48..3e769a5 100644 --- a/req/mission_script/watchdogs_1.lua +++ b/req/mission_script/watchdogs_1.lua @@ -1,16 +1,16 @@ return { [101687] = { groups = { - common_tank = false, - elite_tank = false, - common_shield = false, - elite_shieldg = false + fbi_tanks = false, + gensec_tanks = false, + fbi_shields = false, + gensec_shields = false } }, [102827] = { groups = { - common_tank = false, - elite_tank = false + fbi_tanks = false, + gensec_tanks = false } } } \ No newline at end of file diff --git a/req/mission_script/welcome_to_the_jungle_2.lua b/req/mission_script/welcome_to_the_jungle_2.lua new file mode 100644 index 0000000..ab0a9de --- /dev/null +++ b/req/mission_script/welcome_to_the_jungle_2.lua @@ -0,0 +1,9 @@ +return { + -- Add power cut SO delay + [100313] = { + values = { + base_delay = 10, + base_delay_rand = 10 + } + } +} \ No newline at end of file diff --git a/req/mission_script/wwh.lua b/req/mission_script/wwh.lua new file mode 100644 index 0000000..652e7ed --- /dev/null +++ b/req/mission_script/wwh.lua @@ -0,0 +1,11 @@ +return { + [100361] = { + values = { + enabled = false + } + }, + [100945] = { + ponr = 900, + ponr_player_mul = {2, 1.25, 1, 0.8} + } +} \ No newline at end of file diff --git a/req/mod_conflicts.lua b/req/mod_conflicts.lua index a41521c..47449a4 100644 --- a/req/mod_conflicts.lua +++ b/req/mod_conflicts.lua @@ -23,5 +23,10 @@ return table.list_to_set({ "Graceful Graze", "Better Graze", "Graze doesn't kill civis", - "Graze Plus - Make graze effect apply of opposite side" + "Graze Plus - Make graze effect apply of opposite side", + "Enemy Accuracy Fix", + "Better Cops weapon usage and some tweaks", + "Better Cops weapon usage", + "Better Cops weapon usage and some tweaks for online", + "Better Cops weapon usage for online" }) diff --git a/req/unit_weapons.lua b/req/unit_weapons.lua new file mode 100644 index 0000000..2b7660a --- /dev/null +++ b/req/unit_weapons.lua @@ -0,0 +1,11 @@ +return { + [("units/payday2/characters/ene_cop_3/ene_cop_3"):key()] = "r870", + [("units/payday2/characters/ene_cop_4/ene_cop_4"):key()] = "mp5", + [("units/payday2/characters/ene_fbi_3/ene_fbi_3"):key()] = "r870", + [("units/payday2/characters/ene_medic_m4/ene_medic_m4"):key()] = "mp5", + [("units/payday2/characters/ene_spook_1/ene_spook_1"):key()] = "beretta92", + [("units/payday2/characters/ene_biker_3/ene_biker_3"):key()] = "mossberg", + [("units/pd2_dlc_born/characters/ene_biker_female_3/ene_biker_female_3"):key()] = "mossberg", + [("units/pd2_dlc_drm/characters/ene_bulldozer_minigun_classic/ene_bulldozer_minigun_classic"):key()] = "benelli", + [("units/pd2_dlc_drm/characters/ene_bulldozer_minigun/ene_bulldozer_minigun"):key()] = "benelli", +} \ No newline at end of file diff --git a/supermod.xml b/supermod.xml index 7d9bac6..3b95bdc 100644 --- a/supermod.xml +++ b/supermod.xml @@ -4,6 +4,8 @@ + + @@ -13,7 +15,7 @@ - + @@ -27,6 +29,19 @@ + + + + + + + + + + + + + @@ -34,41 +49,67 @@ + + + + + + + - + + + + - - + + + + + + + + + + + + + + + + + + - + @@ -98,7 +139,6 @@ - @@ -118,7 +158,6 @@ - @@ -132,9 +171,19 @@ - + + + + + + + + + + + @@ -153,9 +202,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tweak/animation.xml b/tweak/animation.xml index 5092273..bca98b4 100644 --- a/tweak/animation.xml +++ b/tweak/animation.xml @@ -1,5 +1,17 @@ + + + + + + + + + + + + @@ -85,6 +97,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -171,8 +238,10 @@ - - + + + + @@ -237,7 +306,9 @@ - + + + @@ -274,4 +345,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tweak/tweak.xml b/tweak/tweak.xml new file mode 100644 index 0000000..52ea44c --- /dev/null +++ b/tweak/tweak.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tweak/units.xml b/tweak/units.xml new file mode 100644 index 0000000..9f9f565 --- /dev/null +++ b/tweak/units.xml @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file