From 2fe4ec1eef907ddc0ba5d6e5a04e920c43d7d8d8 Mon Sep 17 00:00:00 2001 From: nikita <59087849+mrcreepysos@users.noreply.github.com> Date: Sun, 21 Jul 2024 02:14:54 +0300 Subject: [PATCH] sh catchup Co-Authored-By: Sebastian Gabl (aka Hoppip) --- lua/copdamage.lua | 9 ++- lua/coplogictravel.lua | 100 ++++++++++++++++++---------- lua/groupaistatebesiege.lua | 128 ++++++++++++++++++++++++++++++++++-- lua/teamailogicidle.lua | 24 +++++++ supermod.xml | 1 + 5 files changed, 214 insertions(+), 48 deletions(-) create mode 100644 lua/teamailogicidle.lua diff --git a/lua/copdamage.lua b/lua/copdamage.lua index ca89c34..31aee9e 100644 --- a/lua/copdamage.lua +++ b/lua/copdamage.lua @@ -42,11 +42,10 @@ function CopDamage:_sync_dismember(attacker_unit, ...) end end --- Don't set suppression to maximum on hit, increase by a static value instead -local build_suppression_original = CopDamage.build_suppression -function CopDamage:build_suppression(amount, ...) - return build_suppression_original(self, amount == "max" and 2 or amount, ...) -end +-- Additional suppression on hit +Hooks:PreHook(CopDamage, "_on_damage_received", "sh__on_damage_received", function (self, damage_info) + self:build_suppression(4 * damage_info.damage / self._HEALTH_INIT, nil) +end) -- Give flamethrowers a damage multiplier against dozers Hooks:PreHook(CopDamage, "damage_fire", "eclipse_damage_fire", function(self, attack_data) diff --git a/lua/coplogictravel.lua b/lua/coplogictravel.lua index 654e2e4..11a48a9 100644 --- a/lua/coplogictravel.lua +++ b/lua/coplogictravel.lua @@ -50,12 +50,12 @@ function CopLogicTravel._begin_coarse_pathing(data, my_data, ...) my_data.coarse_path = { { data.unit:movement():nav_tracker():nav_segment(), - mvector3.copy(data.m_pos), + mvector3.copy(data.m_pos) }, { nav_seg, - pos, - }, + pos + } } end @@ -68,7 +68,7 @@ function CopLogicTravel._get_allowed_travel_nav_segs(data, ...) 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) +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() @@ -137,16 +137,14 @@ function CopLogicTravel._get_exact_move_pos(data, nav_index, ...) 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 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 = nav_manager:find_cover_in_nav_seg_2(nav_seg_id, to_pos) if cover then nav_manager:reserve_cover(cover, data.pos_rsrv_id) - my_data.moving_to_cover = { cover } + my_data.moving_to_cover = { cover } to_pos = cover[1] else mvector3.step(tmp_vec1, to_pos, nav_seg_pos, 200) @@ -159,7 +157,7 @@ function CopLogicTravel._get_exact_move_pos(data, nav_index, ...) pos_from = nav_seg_pos, pos_to = tmp_vec1, allow_entry = true, - trace = true, + trace = true } nav_manager:raycast(ray_params) to_pos = ray_params.trace[1] @@ -170,30 +168,59 @@ end local _determine_destination_occupation_original = CopLogicTravel._determine_destination_occupation function CopLogicTravel._determine_destination_occupation(data, objective, ...) - if objective.type ~= "defend_area" or objective.cover or objective.pos or data.kpr_keep_position then + if objective.cover or objective.pos or data.kpr_keep_position then return _determine_destination_occupation_original(data, objective, ...) end - local near_pos = objective.follow_unit and objective.follow_unit:movement():nav_tracker():field_position() - local cover = CopLogicTravel._find_cover(data, objective.nav_seg, near_pos) - if cover then - return { - type = "defend", - seg = objective.nav_seg, - cover = { - cover, - }, - radius = objective.radius, - } - else - near_pos = CopLogicTravel._get_pos_on_wall(managers.navigation:find_random_position_in_segment(objective.nav_seg), 500) - return { - type = "defend", - seg = objective.nav_seg, - pos = near_pos, - radius = objective.radius, - } + if alive(objective.follow_unit) then + local follow_tracker = objective.follow_unit:movement():nav_tracker() + local follow_nav_seg = follow_tracker:nav_segment() + local follow_pos = follow_tracker:field_position() + + local cover + if data.attention_obj and data.attention_obj.nav_tracker and data.attention_obj.reaction >= AIAttentionObject.REACT_COMBAT then + cover = managers.navigation:find_cover_in_nav_seg_3(follow_nav_seg, nil, follow_pos, threat_pos) + end + + if not cover and data.team.foes.criminal1 then + cover = CopLogicTravel._find_cover(data, objective.nav_seg or follow_nav_seg, follow_pos) + end + + if cover then + return { + type = "defend", + cover = { + cover + } + } + else + return { + type = "defend", + pos = CopLogicTravel._get_pos_on_wall(follow_pos, objective.called and 600) + } + end + elseif objective.type == "defend_area" then + local cover = CopLogicTravel._find_cover(data, objective.nav_seg) + if cover then + return { + type = "defend", + seg = objective.nav_seg, + cover = { + cover + }, + radius = objective.radius + } + else + return { + type = "defend", + seg = objective.nav_seg, + pos = CopLogicTravel._get_pos_on_wall(managers.navigation:find_random_position_in_segment(objective.nav_seg), 500), + radius = objective.radius + } + end end + + return _determine_destination_occupation_original(data, objective, ...) end function CopLogicTravel._get_pos_behind_unit(data, unit, min_dis, max_dis) @@ -217,10 +244,10 @@ function CopLogicTravel._get_pos_behind_unit(data, unit, min_dis, max_dis) local ray_params = { trace = true, pos_from = unit_pos, - pos_to = pos, + pos_to = pos } local rsrv_desc = { - radius = 40, + radius = 40 } repeat @@ -250,7 +277,7 @@ function CopLogicTravel._get_pos_behind_unit(data, unit, min_dis, max_dis) end -- 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) +Hooks:PostHook(CopLogicTravel, "action_complete_clbk", "sh_action_complete_clbk", function (data, action) if action:type() ~= "walk" then return end @@ -269,11 +296,11 @@ end) -- 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) +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", + type = "idle" }) end end) @@ -298,9 +325,10 @@ function CopLogicTravel._on_destination_reached(data, ...) return _on_destination_reached_original(data, ...) end + -- Play generic radio report chatter during travel while unalerted -Hooks:PostHook(CopLogicTravel, "queued_update", "sh_queued_update", function(data) +Hooks:PostHook(CopLogicTravel, "queued_update", "sh_queued_update", function (data) if data.cool and data.char_tweak.chatter and data.char_tweak.chatter.report then managers.groupai:state():chk_say_enemy_chatter(data.unit, data.m_pos, "report") end -end) +end) \ No newline at end of file diff --git a/lua/groupaistatebesiege.lua b/lua/groupaistatebesiege.lua index e287294..913d15b 100644 --- a/lua/groupaistatebesiege.lua +++ b/lua/groupaistatebesiege.lua @@ -973,14 +973,128 @@ function GroupAIStateBesiege:_choose_best_group(best_groups, total_weight) return best_grp, best_grp_type 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, ...) - local group = _spawn_in_group_original(self, spawn_group, ...) - if group then - group.spawn_group_element = spawn_group.mission_element - return group +-- Fix amount_min not being enforced when possible and save spawn group element in group description for debugging stuck groups +function GroupAIStateBesiege:_spawn_in_group(spawn_group, spawn_group_type, grp_objective, ai_task) + local spawn_group_desc = tweak_data.group_ai.enemy_spawn_groups[spawn_group_type] + + local wanted_nr_units + if type(spawn_group_desc.amount) == "number" then + wanted_nr_units = spawn_group_desc.amount + else + wanted_nr_units = math.random(spawn_group_desc.amount[1], spawn_group_desc.amount[2]) + end + + local valid_unit_types = {} + self._extract_group_desc_structure(spawn_group_desc.spawn, valid_unit_types) + + local total_weight = 0 + local unit_categories = tweak_data.group_ai.unit_categories + for _, spawn_entry in ipairs(valid_unit_types) do + local cat_data = unit_categories[spawn_entry.unit] + if not cat_data then + StreamHeist:error("Unit category %s in spawn group type %s doesn't exist", spawn_entry.unit, spawn_group_type) + return + end + + local special_type = not cat_data.is_captain and cat_data.special_type + if special_type and managers.job:current_spawn_limit(special_type) < self:_get_special_unit_type_count(special_type) + (spawn_entry.amount_min or 0) then + spawn_group.delay_t = self._t + 10 + return + end + + total_weight = total_weight + spawn_entry.freq + end + + for _, sp_data in ipairs(spawn_group.spawn_pts) do + sp_data.delay_t = self._t + math.rand(0.5) end + + local group_size = 0 + local spawn_task = { + objective = not grp_objective.element and self._create_objective_from_group_objective(grp_objective), + units_remaining = {}, + spawn_group = spawn_group, + spawn_group_type = spawn_group_type, + ai_task = ai_task + } + + table.insert(self._spawning_groups, spawn_task) + + local function _add_unit_type_to_spawn_task(i, spawn_entry) + local add_amount = 1 + if spawn_entry.amount_min then + add_amount = math.max(spawn_entry.amount_min, add_amount) + spawn_entry.amount_min = nil + end + + if wanted_nr_units < add_amount then + add_amount = wanted_nr_units + StreamHeist:warn("Can not satisfy amount_min for unit category %s in spawn group type %s", spawn_entry.unit, spawn_group_type) + end + + spawn_task.units_remaining[spawn_entry.unit] = spawn_task.units_remaining[spawn_entry.unit] or { + amount = 0, + spawn_entry = spawn_entry + } + spawn_task.units_remaining[spawn_entry.unit].amount = spawn_task.units_remaining[spawn_entry.unit].amount + add_amount + + group_size = group_size + add_amount + wanted_nr_units = wanted_nr_units - add_amount + + if spawn_entry.amount_max then + if add_amount >= spawn_entry.amount_max then + table.remove(valid_unit_types, i) + total_weight = total_weight - spawn_entry.freq + return true + else + spawn_entry.amount_max = spawn_entry.amount_max - add_amount + end + end + end + + local i = 1 + while wanted_nr_units > 0 and i <= #valid_unit_types do + local spawn_entry = valid_unit_types[i] + local entry_removed = spawn_entry.amount_min and _add_unit_type_to_spawn_task(i, spawn_entry) + if not entry_removed then + i = i + 1 + end + end + + while wanted_nr_units > 0 and #valid_unit_types > 0 do + local roll = math.random() * total_weight + local rand_entry + + i = 1 + repeat + rand_entry = valid_unit_types[i] + roll = roll - rand_entry.freq + i = i + 1 + until roll <= 0 + + local cat_data = unit_categories[rand_entry.unit] + local special_type = not cat_data.is_captain and cat_data.special_type + if special_type and managers.job:current_spawn_limit(special_type) <= self:_get_special_unit_type_count(special_type) then + table.remove(valid_unit_types, i - 1) + total_weight = total_weight - rand_entry.freq + else + _add_unit_type_to_spawn_task(i - 1, rand_entry) + end + end + + local group = self:_create_group({ + size = group_size, + type = spawn_group_type + }) + + group.objective = grp_objective + group.objective.moving_out = true + group.team = self._teams[spawn_group.team_id or tweak_data.levels:get_default_team_ID("combatant")] + group.spawn_group_element = spawn_group.mission_element + + spawn_task.group = group + + return group end -- Make a generic group voice function instead of individual ones and make retiring groups play retreat lines diff --git a/lua/teamailogicidle.lua b/lua/teamailogicidle.lua new file mode 100644 index 0000000..90c9946 --- /dev/null +++ b/lua/teamailogicidle.lua @@ -0,0 +1,24 @@ +local tmp_vec = Vector3() + +-- Improve following in big nav segments +function TeamAILogicIdle._check_should_relocate(data, my_data, objective) + local follow_movement = objective.follow_unit:movement() + + local max_allowed_dis_xy = 500 + local max_allowed_dis_z = 250 + + if follow_movement:nav_tracker():nav_segment() == data.unit:movement():nav_tracker():nav_segment() then + max_allowed_dis_xy = max_allowed_dis_xy * 3 + max_allowed_dis_z = max_allowed_dis_z * 2 + end + + mvector3.set(tmp_vec, follow_movement:m_newest_pos()) + mvector3.subtract(tmp_vec, data.m_pos) + + if math.abs(tmp_vec.z) > max_allowed_dis_z then + return true + end + + mvector3.set_z(tmp_vec, 0) + return mvector3.length(tmp_vec) > max_allowed_dis_xy +end \ No newline at end of file diff --git a/supermod.xml b/supermod.xml index b01b32b..7cf7721 100644 --- a/supermod.xml +++ b/supermod.xml @@ -188,6 +188,7 @@ +