1123 lines
49 KiB
Lua
1123 lines
49 KiB
Lua
---@class AutoMarkMod:DMFMod
|
|
local mod = get_mod("AutoMark")
|
|
local context = mod.context
|
|
local mark_context = mod.mark_context
|
|
local TAG_NAMES = mod.TAG_NAMES
|
|
local mod_settings = mod.settings
|
|
local companion_cancel_mark_breed_settings = mod.companion_cancel_mark_breed_settings
|
|
local noospheric_command_breed_settings = mod.noospheric_command_breed_settings
|
|
local visibility_cache = mod.visibility_cache
|
|
local visibility_check_frame = mod.visibility_check_frame
|
|
local servo_skull_visibility_cache = mod.servo_skull_visibility_cache
|
|
local servo_skull_visibility_check_frame = mod.servo_skull_visibility_check_frame
|
|
|
|
-- Imports
|
|
local Breed = require("scripts/utilities/breed")
|
|
local SpecialRulesSettings = require("scripts/settings/ability/special_rules_settings")
|
|
local Breed_height = Breed.height
|
|
local special_rules = SpecialRulesSettings.special_rules
|
|
|
|
-- Global Cache
|
|
local CLASS = CLASS
|
|
local HEALTH_ALIVE = HEALTH_ALIVE
|
|
local Managers = Managers
|
|
local get_game_object_field = GameSession.game_object_field
|
|
local Actor_unit = Actor.unit
|
|
local Actor_world_bounds = Actor.world_bounds
|
|
local Unit_box = Unit.box
|
|
local Unit_node = Unit.node
|
|
local Unit_world_position = Unit.world_position
|
|
local PhysicsWorld_raycast = PhysicsWorld.raycast
|
|
local PhysicsWorld_make_raycast = PhysicsWorld.make_raycast
|
|
local PhysicsWorld_linear_sphere_sweep = PhysicsWorld.linear_sphere_sweep
|
|
local Raycast_cast = Raycast.cast
|
|
local ScriptUnit_extension = ScriptUnit.extension
|
|
local math_abs = math.abs
|
|
local math_max = math.max
|
|
local math_cos = math.cos
|
|
local math_rad = math.rad
|
|
local Vector3_dot = Vector3.dot
|
|
local Vector3_normalize = Vector3.normalize
|
|
local Vector3_length = Vector3.length
|
|
local Vector3_distance = Vector3.distance
|
|
local Vector3_distance_squared = Vector3.distance_squared
|
|
local Vector3_flat = Vector3.flat
|
|
local Matrix4x4_right = Matrix4x4.right
|
|
local Matrix4x4_forward = Matrix4x4.forward
|
|
local Quaternion_forward = Quaternion.forward
|
|
|
|
-- Constants
|
|
local INDEX_POSITION = 1
|
|
local INDEX_DISTANCE = 2
|
|
local INDEX_NORMAL = 3
|
|
local INDEX_ACTOR = 4
|
|
local COLLISION_FILTER = "filter_player_ping_target_selection"
|
|
local EMPTY_TABLE = {}
|
|
local MAX_VISIBILITY_CHECKS_PER_FRAME = 16
|
|
local VIEW_CHECK_MIN_DISTANCE = 3
|
|
local VIEW_CHECK_MIN_DOT = 0.75
|
|
local AIM_CHECK_FILTER = "filter_player_ping_target_selection"
|
|
local AIM_CHECK_MAX_HITS = 64
|
|
local AIM_CHECK_MIN_RADIUS = 1
|
|
local AIM_CHECK_RADIUS_PER_METER = 0.052
|
|
local omni_rejects = nil
|
|
local function omni_reject(reason)
|
|
if omni_rejects then
|
|
omni_rejects[reason] = (omni_rejects[reason] or 0) + 1
|
|
end
|
|
end
|
|
|
|
-- Params
|
|
local visibility_raycast_object = nil
|
|
|
|
function mod:init_visibility_raycast_objects()
|
|
local smart_targeting_extension = context.smart_targeting_extension
|
|
local physics_world = smart_targeting_extension and smart_targeting_extension._physics_world
|
|
if physics_world then
|
|
visibility_raycast_object = PhysicsWorld_make_raycast(physics_world, "closest", "types", "both", "collision_filter", "filter_minion_line_of_sight_check")
|
|
end
|
|
end
|
|
|
|
function mod:destroy_visibility_raycast_objects()
|
|
visibility_raycast_object = nil
|
|
end
|
|
|
|
local function is_daemonhost_aggroed(unit)
|
|
local game_session = Managers.state.game_session:game_session()
|
|
local game_object_id = Managers.state.unit_spawner:game_object_id(unit)
|
|
local stage = get_game_object_field(game_session, game_object_id, "stage")
|
|
return type(stage) == "number" and stage == 6
|
|
end
|
|
|
|
local function is_target_aggroed(unit)
|
|
local game_session = Managers.state.game_session:game_session()
|
|
local game_object_id = Managers.state.unit_spawner:game_object_id(unit)
|
|
local attacking_unit_id = get_game_object_field(game_session, game_object_id, "target_unit_id")
|
|
return attacking_unit_id ~= -1
|
|
end
|
|
|
|
function mod:is_dormant_daemonhost(target_unit)
|
|
local unit_data_extension = ScriptUnit_extension(target_unit, "unit_data_system")
|
|
local breed_data = unit_data_extension and unit_data_extension._breed
|
|
if breed_data and breed_data.tags.witch and not is_target_aggroed(target_unit) then
|
|
return true
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
local function get_breed_priority(breed_name, breed_data, breed_priorities, attacking_unit_id)
|
|
if breed_data and breed_data.tags.witch then
|
|
if attacking_unit_id == -1 then
|
|
return breed_priorities[breed_name .. "_passive"] or 0, true
|
|
else
|
|
return breed_priorities[breed_name] or 0, false
|
|
end
|
|
else
|
|
return breed_priorities[breed_name] or 0, false
|
|
end
|
|
end
|
|
|
|
-- Check if Target Unit's Breed is Valid for Auto-Mark
|
|
local function is_breed_group_valid(breed_data, class_settings)
|
|
if not breed_data or not class_settings then
|
|
return false
|
|
end
|
|
|
|
-- toggle enemy by type
|
|
if breed_data.tags.elite then
|
|
return class_settings.toggle_elite
|
|
elseif breed_data.tags.special then
|
|
return class_settings.toggle_special
|
|
elseif breed_data.is_boss then
|
|
return class_settings.toggle_boss
|
|
else
|
|
return class_settings.toggle_other
|
|
end
|
|
end
|
|
|
|
function mod:can_focus_target_overwrite(target_unit, target_tag)
|
|
if not target_tag then
|
|
return true
|
|
end
|
|
|
|
local target_buff_extension = ScriptUnit_extension(target_unit, "buff_system")
|
|
if not target_buff_extension then
|
|
return false
|
|
end
|
|
|
|
local focus_target_debuff = target_buff_extension._stacking_buffs["veteran_improved_tag_debuff"]
|
|
local target_stack_count = focus_target_debuff and focus_target_debuff:stack_count() or 0
|
|
local target_tag_name = target_tag and target_tag._template.name
|
|
-- target does not have focus target debuff
|
|
if target_tag_name ~= TAG_NAMES.VETERAN_TAG and target_stack_count <= 0 then
|
|
return true
|
|
end
|
|
|
|
-- latency bug
|
|
if target_tag_name == TAG_NAMES.VETERAN_TAG and target_stack_count <= 0 then
|
|
return false
|
|
end
|
|
|
|
-- focus_target_overwrite not enabled
|
|
if not mod_settings.focus_target_overwrite then
|
|
return false
|
|
end
|
|
|
|
local talent_resource_component = context.talent_resource_component
|
|
if not talent_resource_component then
|
|
return false
|
|
end
|
|
|
|
-- check if player's buff stacks greater than target's debuff stacks
|
|
local player_stack_count = talent_resource_component.current_resource or 0
|
|
if player_stack_count <= target_stack_count then
|
|
return false
|
|
end
|
|
|
|
return player_stack_count == context.focus_target_max_stacks
|
|
or player_stack_count - target_stack_count >= mod_settings.focus_target_overwrite_delta
|
|
end
|
|
|
|
local function is_focus_target_tag_target_valid(target_unit, target_tag, target_breed_name, target_attacking_unit_id, removed_units)
|
|
local target_tag_name = target_tag and target_tag._template.name
|
|
if target_tag_name == TAG_NAMES.COMPANION_TAG or target_tag_name == TAG_NAMES.SERVO_SKULL_TAG then
|
|
if removed_units[target_unit] then
|
|
return false
|
|
end
|
|
end
|
|
|
|
if target_breed_name ~= "cultist_ritualist" and mod_settings.focus_target_ignore_unaggroed and target_attacking_unit_id == -1 then
|
|
return false
|
|
end
|
|
|
|
if not mod:can_focus_target_overwrite(target_unit, target_tag) then
|
|
return false
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
local function is_ritual_started(daemonhost_id, game_session)
|
|
if not daemonhost_id or daemonhost_id == -1 then
|
|
return false
|
|
end
|
|
|
|
local stage = get_game_object_field(game_session, daemonhost_id, "stage")
|
|
return type(stage) == "number" and stage > 1
|
|
end
|
|
|
|
local function is_cyber_mastiff_tag_target_valid(target_unit, target_tag, target_breed_name, target_breed_data, target_attacking_unit_id, game_session, removed_units, distance, companion_position, target_position)
|
|
-- do not target unit tagged by focus target
|
|
local target_tag_name = target_tag and target_tag._template.name
|
|
if target_tag_name == TAG_NAMES.VETERAN_TAG then
|
|
if removed_units[target_unit] then
|
|
return false
|
|
end
|
|
-- do not target pounceble unit marked by other dog
|
|
elseif target_tag_name == TAG_NAMES.COMPANION_TAG then
|
|
local pounce_setting = target_breed_data.companion_pounce_setting
|
|
local pounce_action = pounce_setting and pounce_setting.companion_pounce_action
|
|
if pounce_action == "human" then
|
|
return false
|
|
end
|
|
end
|
|
|
|
-- check if target is aggroed or if ritual is started
|
|
if target_breed_name == "chaos_mutator_ritualist" then
|
|
if not is_ritual_started(target_attacking_unit_id, game_session) then
|
|
return false
|
|
end
|
|
elseif target_breed_name == "cultist_ritualist" then
|
|
-- do nothing
|
|
else
|
|
if mod_settings.companion_mark_ignore_unaggroed and target_attacking_unit_id == -1 then
|
|
return false
|
|
end
|
|
end
|
|
|
|
-- check if target is within range limitation
|
|
local breed_settings = companion_cancel_mark_breed_settings[target_breed_name]
|
|
local player_max_distance
|
|
local companion_range_limitation
|
|
if breed_settings and breed_settings.override then
|
|
player_max_distance = breed_settings.max_distance or 0
|
|
companion_range_limitation = breed_settings.range_limitation or 0
|
|
else
|
|
player_max_distance = mod_settings.companion_mark_max_distance or 0
|
|
companion_range_limitation = mod_settings.companion_range_limitation or 0
|
|
end
|
|
|
|
if player_max_distance > 0 then
|
|
if distance > player_max_distance then
|
|
return false
|
|
end
|
|
end
|
|
|
|
if companion_range_limitation > 0 then
|
|
if not companion_position or not target_position then
|
|
return false
|
|
end
|
|
|
|
if Vector3_distance_squared(companion_position, target_position) > companion_range_limitation * companion_range_limitation then
|
|
return false
|
|
end
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
local function has_enough_capacitance(breed_settings, breed_data, max_ability_charges, remaining_capacitance)
|
|
if not mod_settings.capacitance_retention or not context.has_noospheric_command then
|
|
return true
|
|
end
|
|
|
|
if not breed_data or not max_ability_charges or not remaining_capacitance then
|
|
return false
|
|
end
|
|
|
|
local capacitance_retention_threshold
|
|
if breed_settings and breed_settings.override then
|
|
capacitance_retention_threshold = breed_settings.threshold or 0
|
|
else
|
|
if breed_data.is_boss then
|
|
capacitance_retention_threshold = mod_settings.capacitance_retention_boss_threshold
|
|
elseif breed_data.tags.special then
|
|
capacitance_retention_threshold = mod_settings.capacitance_retention_special_threshold
|
|
else
|
|
capacitance_retention_threshold = mod_settings.capacitance_retention_elite_threshold
|
|
end
|
|
end
|
|
|
|
if 1 / capacitance_retention_threshold < 0 then
|
|
capacitance_retention_threshold = max_ability_charges + capacitance_retention_threshold
|
|
end
|
|
|
|
return remaining_capacitance >= capacitance_retention_threshold
|
|
end
|
|
|
|
local function get_remaining_ability_capacitance()
|
|
local player_ability_extension = context.player_ability_extension
|
|
if not player_ability_extension then
|
|
return 0
|
|
end
|
|
|
|
local remaining_ability_resource = player_ability_extension:remaining_ability_resource("combat_ability")
|
|
local resource_cost_per_charge = player_ability_extension:get_ability_resource_cost_per_charge("combat_ability")
|
|
return remaining_ability_resource / resource_cost_per_charge
|
|
end
|
|
|
|
function mod:has_enough_capacitance(target_unit)
|
|
local unit_data_extension = ScriptUnit_extension(target_unit, "unit_data_system")
|
|
local breed_data = unit_data_extension and unit_data_extension._breed
|
|
local breed_name = breed_data and breed_data.name
|
|
local breed_settings = noospheric_command_breed_settings[breed_name]
|
|
local player_ability_extension = context.player_ability_extension
|
|
local max_ability_charges = player_ability_extension and player_ability_extension:max_ability_charges("combat_ability")
|
|
local remaining_capacitance = get_remaining_ability_capacitance()
|
|
return has_enough_capacitance(breed_settings, breed_data, max_ability_charges, remaining_capacitance)
|
|
end
|
|
|
|
local function is_servo_skull_tag_target_valid(target_unit, target_tag, target_breed_name, target_breed_data, target_attacking_unit_id, game_session, removed_units, companion_position, target_position, max_ability_charges, remaining_capacitance)
|
|
-- do not target unit tagged by focus target
|
|
local target_tag_name = target_tag and target_tag._template.name
|
|
if target_tag_name == TAG_NAMES.VETERAN_TAG then
|
|
if removed_units[target_unit] then
|
|
return false
|
|
end
|
|
end
|
|
|
|
-- check if target is aggroed or if ritual is started
|
|
if target_breed_name == "chaos_mutator_ritualist" then
|
|
if not is_ritual_started(target_attacking_unit_id, game_session) then
|
|
return false
|
|
end
|
|
elseif target_breed_name == "cultist_ritualist" then
|
|
-- do nothing
|
|
else
|
|
if mod_settings.servo_skull_mark_ignore_unaggroed and target_attacking_unit_id == -1 then
|
|
return false
|
|
end
|
|
end
|
|
|
|
-- check if target is within range limitation
|
|
local breed_settings = noospheric_command_breed_settings[target_breed_name]
|
|
local servo_skull_range_limitation
|
|
if breed_settings and breed_settings.override then
|
|
servo_skull_range_limitation = breed_settings.range_limitation or 0
|
|
else
|
|
servo_skull_range_limitation = mod_settings.servo_skull_range_limitation or 0
|
|
end
|
|
|
|
if servo_skull_range_limitation > 0 then
|
|
if not companion_position or not target_position then
|
|
return false
|
|
end
|
|
|
|
if Vector3_distance_squared(companion_position, target_position) > servo_skull_range_limitation * servo_skull_range_limitation then
|
|
return false
|
|
end
|
|
end
|
|
|
|
-- check if capacitance is sufficient
|
|
if not has_enough_capacitance(breed_settings, target_breed_data, max_ability_charges, remaining_capacitance) then
|
|
return false
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
local function is_sticky_targeting(tag_name, tag_context, breed_name, breed_data)
|
|
if tag_name == TAG_NAMES.COMPANION_TAG then
|
|
if not mod_settings.companion_mark_sticky_targeting then
|
|
return false
|
|
end
|
|
|
|
if not tag_context.pounce_start_time then
|
|
return false
|
|
end
|
|
|
|
if not breed_data then
|
|
return false
|
|
end
|
|
|
|
local breed_settings = companion_cancel_mark_breed_settings[breed_name]
|
|
if breed_settings and breed_settings.override then
|
|
return breed_settings.sticky_targeting
|
|
end
|
|
|
|
if breed_data.is_boss then
|
|
return mod_settings.companion_mark_sticky_targeting_boss
|
|
elseif breed_data.tags.special then
|
|
return mod_settings.companion_mark_sticky_targeting_special
|
|
else
|
|
return mod_settings.companion_mark_sticky_targeting_elite
|
|
end
|
|
elseif tag_name == TAG_NAMES.SERVO_SKULL_TAG then
|
|
if not mod_settings.servo_skull_mark_sticky_targeting then
|
|
return false
|
|
end
|
|
|
|
if not tag_context.shoot_start_time then
|
|
return false
|
|
end
|
|
|
|
if not breed_data then
|
|
return false
|
|
end
|
|
|
|
local breed_settings = noospheric_command_breed_settings[breed_name]
|
|
if breed_settings and breed_settings.override then
|
|
return breed_settings.sticky_targeting
|
|
end
|
|
|
|
if breed_data.is_boss then
|
|
return mod_settings.servo_skull_mark_sticky_targeting_boss
|
|
elseif breed_data.tags.special then
|
|
return mod_settings.servo_skull_mark_sticky_targeting_special
|
|
else
|
|
return mod_settings.servo_skull_mark_sticky_targeting_elite
|
|
end
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
local function is_target_visible(ray_origin, up, target_unit_center_pos, half_height, target_unit, fixed_frame)
|
|
if not visibility_raycast_object then
|
|
return false
|
|
end
|
|
|
|
local cached_visibility = visibility_cache[target_unit]
|
|
local last_check_frame = visibility_check_frame[target_unit]
|
|
if cached_visibility ~= nil and fixed_frame - last_check_frame <= 5 then
|
|
return cached_visibility
|
|
end
|
|
|
|
mod.num_visibility_checks_this_frame = mod.num_visibility_checks_this_frame + 1
|
|
if mod.num_visibility_checks_this_frame > MAX_VISIBILITY_CHECKS_PER_FRAME then
|
|
return false
|
|
end
|
|
|
|
local to_target_center = target_unit_center_pos - ray_origin
|
|
local to_target_top = to_target_center + up * half_height
|
|
local hit_top = Raycast_cast(visibility_raycast_object, ray_origin, Vector3_normalize(to_target_top), Vector3_length(to_target_top))
|
|
if not hit_top then
|
|
visibility_cache[target_unit] = true
|
|
visibility_check_frame[target_unit] = fixed_frame
|
|
return true
|
|
end
|
|
|
|
local hit_center = Raycast_cast(visibility_raycast_object, ray_origin, Vector3_normalize(to_target_center), Vector3_length(to_target_center))
|
|
visibility_cache[target_unit] = not hit_center
|
|
visibility_check_frame[target_unit] = fixed_frame
|
|
return not hit_center
|
|
end
|
|
|
|
local function is_cyber_mastiff_target_visible(ray_origin, up, target_unit_center_pos, half_height, target_unit, fixed_frame, target_tag, target_unit_marked_by_execution_order)
|
|
if mod_settings.companion_mark_tagged_always_visible and (target_tag or target_unit_marked_by_execution_order) then
|
|
return true
|
|
end
|
|
|
|
return is_target_visible(ray_origin, up, target_unit_center_pos, half_height, target_unit, fixed_frame)
|
|
end
|
|
|
|
local function is_servo_skull_target_visible(ray_origin, target_unit, fixed_frame, force_check)
|
|
if not visibility_raycast_object then
|
|
return false
|
|
end
|
|
|
|
local cached_visibility = servo_skull_visibility_cache[target_unit]
|
|
local last_check_frame = servo_skull_visibility_check_frame[target_unit]
|
|
if cached_visibility ~= nil and fixed_frame - last_check_frame <= 5 then
|
|
return cached_visibility
|
|
end
|
|
|
|
if not force_check then
|
|
mod.num_visibility_checks_this_frame = mod.num_visibility_checks_this_frame + 1
|
|
if mod.num_visibility_checks_this_frame > MAX_VISIBILITY_CHECKS_PER_FRAME then
|
|
return false
|
|
end
|
|
end
|
|
|
|
local companion_spawner_extension = context.companion_spawner_extension
|
|
local servo_skull_unit = companion_spawner_extension and companion_spawner_extension:spawned_unit_lookup(special_rules.cryptic_servo_skull_hack)
|
|
if not servo_skull_unit then
|
|
return false
|
|
end
|
|
|
|
local target_los_node = Unit_node(target_unit, "enemy_aim_target_03")
|
|
local target_position = Unit_world_position(target_unit, target_los_node)
|
|
local to_target_position = target_position - ray_origin
|
|
local hit = Raycast_cast(visibility_raycast_object, ray_origin, Vector3_normalize(to_target_position), Vector3_length(to_target_position))
|
|
servo_skull_visibility_cache[target_unit] = not hit
|
|
servo_skull_visibility_check_frame[target_unit] = fixed_frame
|
|
return not hit
|
|
end
|
|
|
|
function mod:is_servo_skull_target_visible(target_unit, fixed_frame, force_check)
|
|
local companion_spawner_extension = context.companion_spawner_extension
|
|
local servo_skull_unit = companion_spawner_extension and companion_spawner_extension:spawned_unit_lookup(special_rules.cryptic_servo_skull_hack)
|
|
if not servo_skull_unit then
|
|
return false
|
|
end
|
|
|
|
local servo_skull_los_node = Unit_node(servo_skull_unit, "skull_aim_center")
|
|
local servo_skull_ray_origin = Unit_world_position(servo_skull_unit, servo_skull_los_node)
|
|
return is_servo_skull_target_visible(servo_skull_ray_origin, target_unit, fixed_frame, force_check)
|
|
end
|
|
|
|
function mod:is_target_pingable(target_unit)
|
|
local player = context.player
|
|
local player_unit = player and player.player_unit
|
|
if not player_unit then
|
|
return false
|
|
end
|
|
|
|
local player_unit_data_extension = ScriptUnit_extension(player_unit, "unit_data_system")
|
|
local first_person_component = player_unit_data_extension and player_unit_data_extension:read_component("first_person")
|
|
if not first_person_component then
|
|
return false
|
|
end
|
|
|
|
local target_position = Unit_world_position(target_unit, 1)
|
|
local to_target = target_position - Unit_world_position(player_unit, 1)
|
|
local distance = Vector3_length(to_target)
|
|
if distance < VIEW_CHECK_MIN_DISTANCE then
|
|
return true
|
|
end
|
|
|
|
local forward = Vector3_flat(Quaternion_forward(first_person_component.rotation))
|
|
local dot = Vector3_dot(Vector3_normalize(forward), Vector3_normalize(to_target))
|
|
if dot < VIEW_CHECK_MIN_DOT then
|
|
return mod_settings.omni_mark == true
|
|
end
|
|
|
|
local first_person_extension = ScriptUnit_extension(player_unit, "first_person_system")
|
|
local first_person_unit = first_person_extension and first_person_extension:first_person_unit()
|
|
if not first_person_unit then
|
|
return false
|
|
end
|
|
|
|
local smart_targeting_extension = context.smart_targeting_extension
|
|
local physics_world = smart_targeting_extension and smart_targeting_extension._physics_world
|
|
if not physics_world then
|
|
return false
|
|
end
|
|
|
|
local eye_position = Unit_world_position(first_person_unit, 1)
|
|
local test_distance = Vector3_distance(eye_position, target_position)
|
|
local radius = math.max(AIM_CHECK_MIN_RADIUS, test_distance * AIM_CHECK_RADIUS_PER_METER)
|
|
local to = eye_position + forward * (test_distance + radius)
|
|
local hits = PhysicsWorld_linear_sphere_sweep(physics_world, eye_position, to, radius, AIM_CHECK_MAX_HITS, "types", "both", "collision_filter", AIM_CHECK_FILTER, "report_initial_overlap", true)
|
|
|
|
for i = 1, hits and #hits or 0 do
|
|
if Actor_unit(hits[i].actor) == target_unit then
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
local IGNORE_EXECUTION_ORDER_FORCE_MARK_BREED_NAMES = {
|
|
chaos_poxwalker_bomber = true,
|
|
chaos_ogryn_houndmaster = true,
|
|
}
|
|
local IGNORE_EXECUTION_ORDER_PRIORITY_BREED_NAMES = {
|
|
cultist_ritualist = true,
|
|
chaos_mutator_ritualist = true,
|
|
renegade_netgunner = true,
|
|
}
|
|
local IGNORE_THREAT_PRIORITY_BREED_NAMES = {
|
|
cultist_grenadier = true,
|
|
renegade_grenadier = true,
|
|
cultist_ritualist = true,
|
|
chaos_mutator_ritualist = true,
|
|
}
|
|
function mod:find_auto_mark_target_unit(min_range, max_range, tag_name, tag_context, class_settings, marked_tag)
|
|
local player = context.player
|
|
local player_unit = player and player.player_unit
|
|
local smart_targeting_extension = context.smart_targeting_extension
|
|
local smart_tag_system = context.smart_tag_system
|
|
if not player_unit or not smart_targeting_extension or not smart_tag_system then
|
|
return
|
|
end
|
|
|
|
-- hit unit list: forward raycast, or full-sphere sweep when omni mark is enabled
|
|
local omni_mark = mod_settings.omni_mark
|
|
if mod_settings.debug_mode and not mod.omni_diag_settings_logged then
|
|
mod.omni_diag_settings_logged = true
|
|
mod:diag(string.format("OMNI-DIAG settings: omni_mark=%s ignore_los=%s debug=%s", tostring(omni_mark), tostring(mod_settings.omni_mark_ignore_los), tostring(mod_settings.debug_mode)))
|
|
end
|
|
local ray_origin, forward, right, up = smart_targeting_extension:_targeting_parameters()
|
|
local hits, num_hits, hits_are_units
|
|
if omni_mark then
|
|
-- enumerate smart-taggable units directly; physics sphere sweeps get swamped by world geometry actors
|
|
hits = {}
|
|
num_hits = 0
|
|
local unit_extension_data = smart_tag_system._unit_extension_data
|
|
local max_range_squared = max_range * max_range
|
|
if unit_extension_data then
|
|
for unit, smart_tag_extension in pairs(unit_extension_data) do
|
|
if HEALTH_ALIVE[unit] and smart_tag_extension._target_type == "breed" then
|
|
local position = Unit_world_position(unit, 1)
|
|
if Vector3_distance_squared(position, ray_origin) <= max_range_squared then
|
|
num_hits = num_hits + 1
|
|
hits[num_hits] = { unit = unit, position = position }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
hits_are_units = true
|
|
omni_diag_counter = (omni_diag_counter or 0) + 1
|
|
omni_rejects = mod_settings.debug_mode and omni_diag_counter % 10 == 1 and {} or nil
|
|
if omni_rejects then
|
|
local counts, ordered_names = {}, {}
|
|
for i = 1, num_hits do
|
|
local unit_data_extension = ScriptUnit_extension(hits[i].unit, "unit_data_system")
|
|
local breed = unit_data_extension and unit_data_extension._breed
|
|
local key = breed and breed.name or "?"
|
|
if counts[key] == nil then
|
|
counts[key] = 0
|
|
ordered_names[#ordered_names + 1] = key
|
|
end
|
|
counts[key] = counts[key] + 1
|
|
end
|
|
local name_parts = {}
|
|
for _, n in ipairs(ordered_names) do
|
|
name_parts[#name_parts + 1] = n .. "x" .. counts[n]
|
|
end
|
|
mod:diag(string.format("OMNI-DIAG candidates=%d breeds=[%s]", num_hits, table.concat(name_parts, ",")))
|
|
end
|
|
else
|
|
hits, num_hits = PhysicsWorld_raycast(smart_targeting_extension._physics_world, ray_origin, forward, max_range, "all", "collision_filter", COLLISION_FILTER)
|
|
end
|
|
|
|
if not hits or num_hits <= 0 then
|
|
return
|
|
end
|
|
|
|
-- cache func
|
|
local game_session = Managers.state.game_session:game_session()
|
|
local unit_spawner_manager = Managers.state.unit_spawner
|
|
local get_game_object_id = unit_spawner_manager.game_object_id
|
|
-- cache params
|
|
local fixed_frame = smart_targeting_extension._latest_fixed_frame
|
|
local canceled_units = tag_context and tag_context.canceled_units or EMPTY_TABLE
|
|
local removed_units = tag_context and tag_context.removed_units or EMPTY_TABLE
|
|
local execution_order_units = mark_context.execution_order_units or EMPTY_TABLE
|
|
local breed_priorities = class_settings and class_settings.breed_priorities or EMPTY_TABLE
|
|
local companion_position, max_ability_charges, remaining_capacitance
|
|
if tag_name == TAG_NAMES.COMPANION_TAG then
|
|
local companion_spawner_extension = context.companion_spawner_extension
|
|
local companion_units = companion_spawner_extension and companion_spawner_extension:companion_units()
|
|
local companion_unit = companion_units and companion_units[1]
|
|
companion_position = companion_unit and Unit_world_position(companion_unit, 1)
|
|
elseif tag_name == TAG_NAMES.SERVO_SKULL_TAG then
|
|
local companion_spawner_extension = context.companion_spawner_extension
|
|
local servo_skull_unit = companion_spawner_extension and companion_spawner_extension:spawned_unit_lookup(special_rules.cryptic_servo_skull_hack)
|
|
local servo_skull_los_node = servo_skull_unit and Unit_node(servo_skull_unit, "skull_aim_center")
|
|
companion_position = servo_skull_los_node and Unit_world_position(servo_skull_unit, servo_skull_los_node)
|
|
local player_ability_extension = context.player_ability_extension
|
|
max_ability_charges = player_ability_extension and player_ability_extension:max_ability_charges("combat_ability")
|
|
remaining_capacitance = get_remaining_ability_capacitance()
|
|
end
|
|
local threat_priority = tag_name == TAG_NAMES.COMPANION_TAG and mod_settings.companion_mark_threat_priority or tag_name == TAG_NAMES.SERVO_SKULL_TAG and mod_settings.servo_skull_mark_threat_priority
|
|
local execution_order_priority = tag_name == TAG_NAMES.COMPANION_TAG and mod_settings.execution_order_priority and context.has_execution_order
|
|
local execution_order_force_mark = tag_name == TAG_NAMES.COMPANION_TAG and mod_settings.execution_order_force_mark and context.has_execution_order
|
|
local player_unit_id = get_game_object_id(unit_spawner_manager, player_unit)
|
|
-- init best unit for switch logic
|
|
local best_unit = nil
|
|
local best_unit_tag = nil
|
|
local best_unit_priority = -math.huge
|
|
local best_unit_breed_name = nil
|
|
local best_unit_is_attacking_player = false
|
|
local best_unit_is_dormant_daemonhost = false
|
|
local best_unit_marked_by_execution_order = false
|
|
local best_unit_is_sticky_targeting = false
|
|
local best_unit_distance = math.huge
|
|
local marked_unit = marked_tag and marked_tag._target_unit
|
|
if marked_unit then
|
|
best_unit = marked_unit
|
|
best_unit_distance = Vector3_length(Unit_world_position(best_unit, 1) - ray_origin)
|
|
local best_unit_data_extension = ScriptUnit_extension(best_unit, "unit_data_system")
|
|
local best_unit_breed_data = best_unit_data_extension and best_unit_data_extension._breed
|
|
best_unit_breed_name = best_unit_breed_data and best_unit_breed_data.name
|
|
local best_unit_game_object_id = get_game_object_id(unit_spawner_manager, marked_unit)
|
|
local best_unit_attacking_unit_id = get_game_object_field(game_session, best_unit_game_object_id, "target_unit_id")
|
|
best_unit_priority, best_unit_is_dormant_daemonhost = get_breed_priority(best_unit_breed_name, best_unit_breed_data, breed_priorities, best_unit_attacking_unit_id)
|
|
best_unit_marked_by_execution_order = execution_order_units[best_unit]
|
|
best_unit_is_attacking_player = best_unit_attacking_unit_id == player_unit_id
|
|
best_unit_is_sticky_targeting = is_sticky_targeting(tag_name, tag_context, best_unit_breed_name, best_unit_breed_data)
|
|
end
|
|
|
|
for i = 1, num_hits do
|
|
local hit = hits[i]
|
|
local hit_unit, hit_unit_center_pos
|
|
if hits_are_units then
|
|
hit_unit = hit.unit
|
|
hit_unit_center_pos = hit.position
|
|
else
|
|
local hit_actor = hit[INDEX_ACTOR]
|
|
if not hit_actor then
|
|
omni_reject("no_actor")
|
|
goto continue
|
|
end
|
|
hit_unit = Actor_unit(hit_actor)
|
|
end
|
|
-- ignore player unit, already marked unit and dead unit
|
|
if hit_unit == player_unit or hit_unit == marked_unit or canceled_units[hit_unit] or not HEALTH_ALIVE[hit_unit] then
|
|
omni_reject("skip_player_dead")
|
|
goto continue
|
|
end
|
|
|
|
local hit_unit_data_extension = ScriptUnit_extension(hit_unit, "unit_data_system")
|
|
local hit_unit_breed_data = hit_unit_data_extension and hit_unit_data_extension._breed
|
|
-- ignore untaggable unit
|
|
if not hit_unit_breed_data or hit_unit_breed_data.smart_tag_target_type ~= "breed" or hit_unit_breed_data.faction_name == "imperium" or hit_unit_breed_data.unit_template_name ~= "minion" then
|
|
omni_reject("untaggable")
|
|
goto continue
|
|
end
|
|
|
|
local hit_unit_breed_name = hit_unit_breed_data.name
|
|
if best_unit_is_sticky_targeting and hit_unit_breed_name ~= "renegade_netgunner" then
|
|
omni_reject("sticky1")
|
|
goto continue
|
|
end
|
|
|
|
local hit_unit_game_object_id = get_game_object_id(unit_spawner_manager, hit_unit)
|
|
local hit_unit_attacking_unit_id = get_game_object_field(game_session, hit_unit_game_object_id, "target_unit_id")
|
|
local hit_unit_priority, hit_unit_is_dormant_daemonhost = get_breed_priority(hit_unit_breed_name, hit_unit_breed_data, breed_priorities, hit_unit_attacking_unit_id)
|
|
local hit_unit_marked_by_execution_order = execution_order_units[hit_unit]
|
|
-- filter unit by type and priority
|
|
if hit_unit_priority <= 0 or not is_breed_group_valid(hit_unit_breed_data, class_settings) then
|
|
if not execution_order_force_mark
|
|
or not hit_unit_marked_by_execution_order
|
|
or IGNORE_EXECUTION_ORDER_FORCE_MARK_BREED_NAMES[hit_unit_breed_name]
|
|
or hit_unit_is_dormant_daemonhost
|
|
then
|
|
omni_reject("priority_or_group")
|
|
goto continue
|
|
end
|
|
end
|
|
|
|
local hit_unit_is_attacking_player = hit_unit_attacking_unit_id == player_unit_id
|
|
if best_unit_is_sticky_targeting and not hit_unit_is_attacking_player then
|
|
omni_reject("sticky2")
|
|
goto continue
|
|
end
|
|
|
|
local hit_unit_score = hit_unit_priority
|
|
local best_unit_score = best_unit_priority
|
|
if execution_order_priority and not IGNORE_EXECUTION_ORDER_PRIORITY_BREED_NAMES[hit_unit_breed_name] and not IGNORE_EXECUTION_ORDER_PRIORITY_BREED_NAMES[best_unit_breed_name] then
|
|
hit_unit_score = hit_unit_score + (hit_unit_marked_by_execution_order and 1000 or 0)
|
|
best_unit_score = best_unit_score + (best_unit_marked_by_execution_order and 1000 or 0)
|
|
end
|
|
|
|
if threat_priority and not IGNORE_THREAT_PRIORITY_BREED_NAMES[hit_unit_breed_name] and not IGNORE_THREAT_PRIORITY_BREED_NAMES[best_unit_breed_name] then
|
|
hit_unit_score = hit_unit_score + (hit_unit_is_attacking_player and 100 or 0)
|
|
best_unit_score = best_unit_score + (best_unit_is_attacking_player and 100 or 0)
|
|
end
|
|
|
|
if hit_unit_score < best_unit_score then
|
|
omni_reject("lower_score")
|
|
goto continue
|
|
end
|
|
|
|
if not hit_unit_center_pos then
|
|
hit_unit_center_pos = Actor_world_bounds(hit[INDEX_ACTOR])
|
|
end
|
|
local to_hit_unit_center = hit_unit_center_pos - ray_origin
|
|
local distance = Vector3_length(to_hit_unit_center)
|
|
-- filter unit by range
|
|
if distance < min_range or distance > max_range then
|
|
omni_reject("out_of_range")
|
|
goto continue
|
|
end
|
|
|
|
-- prefer the closer unit on score ties (hit order is arbitrary for the omni sweep)
|
|
if hit_unit_score == best_unit_score and distance >= best_unit_distance then
|
|
omni_reject("tie_distance")
|
|
goto continue
|
|
end
|
|
|
|
local hit_unit_tag = smart_tag_system:unit_tag(hit_unit)
|
|
if tag_name == TAG_NAMES.ENEMY_TAG then
|
|
if hit_unit_tag then
|
|
omni_reject("already_tagged")
|
|
goto continue
|
|
end
|
|
elseif tag_name == TAG_NAMES.VETERAN_TAG then
|
|
if hit_unit_is_dormant_daemonhost then
|
|
if hit_unit_tag then
|
|
omni_reject("vet_tagged")
|
|
goto continue
|
|
end
|
|
else
|
|
if not is_focus_target_tag_target_valid(hit_unit, hit_unit_tag, hit_unit_breed_name, hit_unit_attacking_unit_id, removed_units) then
|
|
omni_reject("vet_invalid")
|
|
goto continue
|
|
end
|
|
end
|
|
elseif tag_name == TAG_NAMES.COMPANION_TAG then
|
|
if not is_cyber_mastiff_tag_target_valid(hit_unit, hit_unit_tag, hit_unit_breed_name, hit_unit_breed_data, hit_unit_attacking_unit_id, game_session, removed_units, distance, companion_position, hit_unit_center_pos) then
|
|
omni_reject("companion_invalid")
|
|
goto continue
|
|
end
|
|
elseif tag_name == TAG_NAMES.SERVO_SKULL_TAG then
|
|
if not is_servo_skull_tag_target_valid(hit_unit, hit_unit_tag, hit_unit_breed_name, hit_unit_breed_data, hit_unit_attacking_unit_id, game_session, removed_units, companion_position, hit_unit_center_pos, max_ability_charges, remaining_capacitance) then
|
|
if omni_rejects then
|
|
local sub
|
|
if hit_unit_tag and hit_unit_tag._template and hit_unit_tag._template.name == TAG_NAMES.VETERAN_TAG then
|
|
sub = "vet_conflict"
|
|
elseif mod_settings.servo_skull_mark_ignore_unaggroed and hit_unit_attacking_unit_id == -1 then
|
|
sub = "unaggroed"
|
|
elseif not mod:has_enough_capacitance(hit_unit) then
|
|
sub = "capacitance"
|
|
else
|
|
sub = "range"
|
|
end
|
|
omni_reject("skull:" .. sub)
|
|
else
|
|
omni_reject("skull_invalid")
|
|
end
|
|
goto continue
|
|
end
|
|
end
|
|
|
|
local visible
|
|
if omni_mark and mod_settings.omni_mark_ignore_los then
|
|
visible = true
|
|
elseif tag_name == TAG_NAMES.SERVO_SKULL_TAG then
|
|
visible = is_servo_skull_target_visible(companion_position, hit_unit, fixed_frame)
|
|
elseif tag_name == TAG_NAMES.COMPANION_TAG then
|
|
local half_height = Breed_height(hit_unit, hit_unit_breed_data) * 0.5
|
|
visible = is_cyber_mastiff_target_visible(ray_origin, up, hit_unit_center_pos, half_height, hit_unit, fixed_frame, hit_unit_tag, hit_unit_marked_by_execution_order)
|
|
else
|
|
local half_height = Breed_height(hit_unit, hit_unit_breed_data) * 0.5
|
|
visible = is_target_visible(ray_origin, up, hit_unit_center_pos, half_height, hit_unit, fixed_frame)
|
|
end
|
|
|
|
if not visible then
|
|
omni_reject("not_visible")
|
|
goto continue
|
|
end
|
|
|
|
best_unit = hit_unit
|
|
best_unit_tag = hit_unit_tag
|
|
best_unit_priority = hit_unit_priority
|
|
best_unit_breed_name = hit_unit_breed_name
|
|
best_unit_is_attacking_player = hit_unit_is_attacking_player
|
|
best_unit_is_dormant_daemonhost = hit_unit_is_dormant_daemonhost
|
|
best_unit_marked_by_execution_order = hit_unit_marked_by_execution_order
|
|
best_unit_is_sticky_targeting = false
|
|
best_unit_distance = distance
|
|
|
|
::continue::
|
|
end
|
|
|
|
if omni_rejects then
|
|
local parts = {}
|
|
for k, v in pairs(omni_rejects) do
|
|
parts[#parts + 1] = k .. "=" .. v
|
|
end
|
|
table.sort(parts)
|
|
mod:diag(string.format("OMNI-DIAG pass[%s] best=%s rejects=[%s]", tostring(tag_name), tostring(best_unit_breed_name), table.concat(parts, ",")))
|
|
mod.omni_last_rejects = table.concat(parts, ",")
|
|
omni_rejects = nil
|
|
end
|
|
|
|
mod.omni_last = { tag_name = tag_name, best = best_unit_breed_name, cands = num_hits }
|
|
|
|
if best_unit ~= marked_unit then
|
|
return best_unit, best_unit_tag, best_unit_is_dormant_daemonhost
|
|
end
|
|
end
|
|
|
|
local ENEMY_EXTENTS = {
|
|
chaos_beast_of_nurgle = {
|
|
half_extent_forward = 1.1,
|
|
half_extent_right = 1.5,
|
|
},
|
|
chaos_ogryn_bulwark = {
|
|
half_extent_right = 0.8,
|
|
},
|
|
chaos_ogryn_executor = {
|
|
half_extent_right = 0.9,
|
|
},
|
|
chaos_ogryn_gunner = {
|
|
half_extent_right = 0.8,
|
|
},
|
|
chaos_ogryn_houndmaster = {
|
|
half_extent_right = 1.4,
|
|
},
|
|
chaos_plague_ogryn = {
|
|
half_extent_right = 1.6,
|
|
},
|
|
chaos_spawn = {
|
|
half_extent_right = 1.2,
|
|
},
|
|
cultist_berzerker = {
|
|
half_extent_right = 0.5,
|
|
},
|
|
cultist_mutant = {
|
|
half_extent_right = 1,
|
|
},
|
|
cultist_mutant_mutator = {
|
|
half_extent_right = 1,
|
|
},
|
|
renegade_berzerker = {
|
|
half_extent_right = 0.5,
|
|
},
|
|
}
|
|
local DISTANCE_EPSILON = 0.4
|
|
local MIN_COSINE = math_cos(math_rad(35))
|
|
function mod:find_focus_target_switch_melee_target_unit(max_range)
|
|
local player = context.player
|
|
local player_unit = player and player.player_unit
|
|
local smart_targeting_extension = context.smart_targeting_extension
|
|
local smart_tag_system = context.smart_tag_system
|
|
if not player_unit or not smart_targeting_extension or not smart_tag_system then
|
|
return
|
|
end
|
|
|
|
local ray_origin, forward, right, up = smart_targeting_extension:_targeting_parameters()
|
|
local hits, num_hits = PhysicsWorld_raycast(smart_targeting_extension._physics_world, ray_origin, forward, max_range, "all", "collision_filter", COLLISION_FILTER)
|
|
if not hits or num_hits <= 0 then
|
|
return
|
|
end
|
|
|
|
local fixed_frame = smart_targeting_extension._latest_fixed_frame
|
|
local best_unit
|
|
local best_unit_dot = -math.huge
|
|
local best_unit_distance = math.huge
|
|
local best_unit_aim_strict = false
|
|
local best_unit_aim_lenient = false
|
|
local best_unit_aim_outer = false
|
|
local best_unit_x_diff_edge = math.huge
|
|
local best_unit_y_diff_edge = math.huge
|
|
for i = 1, num_hits do
|
|
local hit = hits[i]
|
|
local hit_actor = hit[INDEX_ACTOR]
|
|
if not hit_actor then
|
|
goto continue
|
|
end
|
|
|
|
local hit_unit = Actor_unit(hit_actor)
|
|
local hit_position = hit[INDEX_POSITION]
|
|
if hit_unit == player_unit or not HEALTH_ALIVE[hit_unit] then
|
|
goto continue
|
|
end
|
|
|
|
local unit_data_extension = ScriptUnit_extension(hit_unit, "unit_data_system")
|
|
local breed_data = unit_data_extension and unit_data_extension._breed
|
|
-- ignore untaggable unit
|
|
if not breed_data or breed_data.smart_tag_target_type ~= "breed" or breed_data.faction_name == "imperium" or breed_data.unit_template_name ~= "minion" then
|
|
goto continue
|
|
end
|
|
|
|
local hit_unit_pose, _ = Unit_box(hit_unit, true)
|
|
local object_right = Matrix4x4_right(hit_unit_pose)
|
|
local object_forward = Matrix4x4_forward(hit_unit_pose)
|
|
local enemy_extents = ENEMY_EXTENTS[breed_data.name] or EMPTY_TABLE
|
|
local half_extent_right = enemy_extents.half_extent_right or breed_data.half_extent_right or 0.3
|
|
local half_extent_forward = enemy_extents.half_extent_forward or breed_data.half_extent_forward or 0.3
|
|
local world_extents_right = object_right * half_extent_right
|
|
local world_extents_forward = object_forward * half_extent_forward
|
|
local hit_unit_center_pos = Actor_world_bounds(hit_actor)
|
|
local to_hit_unit_center = hit_unit_center_pos - ray_origin
|
|
local to_hit_unit_center_direction = Vector3_normalize(hit_unit_center_pos - ray_origin)
|
|
local half_length = math_max(
|
|
math_abs(Vector3_dot(to_hit_unit_center_direction, world_extents_right + world_extents_forward)),
|
|
math_abs(Vector3_dot(to_hit_unit_center_direction, world_extents_right - world_extents_forward))
|
|
)
|
|
local hit_unit_distance = Vector3_length(to_hit_unit_center) - half_length
|
|
if hit_unit_distance > max_range then
|
|
goto continue
|
|
end
|
|
|
|
local hit_dot = Vector3_dot(forward, to_hit_unit_center_direction)
|
|
if hit_dot < MIN_COSINE then
|
|
goto continue
|
|
end
|
|
|
|
local hit_offset = hit_position - hit_unit_center_pos
|
|
local x_diff = math_abs(Vector3_dot(hit_offset, right))
|
|
local y_diff = math_abs(Vector3_dot(hit_offset, up))
|
|
local half_width = math_max(
|
|
math_abs(Vector3_dot(right, world_extents_right + world_extents_forward)),
|
|
math_abs(Vector3_dot(right, world_extents_right - world_extents_forward))
|
|
)
|
|
local half_height = Breed_height(hit_unit, breed_data) * 0.5
|
|
local x_diff_edge = x_diff - half_width
|
|
local y_diff_edge = y_diff - half_height
|
|
local hit_unit_aim_outer = x_diff_edge <= 0.8 and y_diff_edge <= 0.8
|
|
if not hit_unit_aim_outer then
|
|
goto continue
|
|
end
|
|
|
|
local hit_unit_aim_strict = x_diff <= half_width and y_diff <= half_height
|
|
if hit_unit_distance > 3.5 then
|
|
if not hit_unit_aim_strict then
|
|
goto continue
|
|
end
|
|
|
|
if best_unit_aim_lenient then
|
|
if best_unit_distance <= 3.5 then
|
|
goto continue
|
|
end
|
|
elseif best_unit_aim_outer then
|
|
if best_unit_distance <= 2.5 then
|
|
goto continue
|
|
end
|
|
end
|
|
end
|
|
|
|
local hit_unit_aim_lenient = x_diff_edge <= 0.4 and y_diff_edge <= 0.4
|
|
local distance_diff = math_abs(hit_unit_distance - best_unit_distance)
|
|
if best_unit_aim_strict then
|
|
if best_unit_distance > 3.5 then
|
|
if hit_unit_aim_strict then
|
|
if hit_unit_distance > 3.5 then
|
|
if distance_diff <= DISTANCE_EPSILON then
|
|
if hit_dot <= best_unit_dot then
|
|
goto continue
|
|
end
|
|
elseif hit_unit_distance > best_unit_distance then
|
|
goto continue
|
|
end
|
|
end
|
|
elseif hit_unit_aim_lenient then
|
|
-- Nothing
|
|
elseif hit_unit_aim_outer then
|
|
if hit_unit_distance > 2.5 then
|
|
goto continue
|
|
end
|
|
end
|
|
else
|
|
if distance_diff <= DISTANCE_EPSILON then
|
|
if hit_unit_aim_strict then
|
|
if hit_dot <= best_unit_dot then
|
|
goto continue
|
|
end
|
|
else
|
|
goto continue
|
|
end
|
|
elseif hit_unit_distance < best_unit_distance then
|
|
if not hit_unit_aim_lenient then
|
|
goto continue
|
|
end
|
|
elseif hit_unit_distance > best_unit_distance then
|
|
goto continue
|
|
end
|
|
end
|
|
elseif best_unit_aim_lenient then
|
|
if distance_diff <= DISTANCE_EPSILON then
|
|
if hit_unit_aim_strict then
|
|
-- Nothing
|
|
elseif hit_unit_aim_lenient then
|
|
if x_diff_edge >= best_unit_x_diff_edge and y_diff_edge >= best_unit_y_diff_edge then
|
|
goto continue
|
|
end
|
|
|
|
if hit_dot <= best_unit_dot then
|
|
goto continue
|
|
end
|
|
elseif hit_unit_aim_outer then
|
|
goto continue
|
|
end
|
|
elseif hit_unit_distance < best_unit_distance then
|
|
-- Nothing
|
|
elseif hit_unit_distance > best_unit_distance then
|
|
goto continue
|
|
end
|
|
elseif best_unit_aim_outer then
|
|
if distance_diff <= DISTANCE_EPSILON then
|
|
if hit_unit_aim_lenient then
|
|
-- Nothing
|
|
elseif hit_unit_aim_outer then
|
|
if x_diff_edge >= best_unit_x_diff_edge and y_diff_edge >= best_unit_y_diff_edge then
|
|
goto continue
|
|
end
|
|
|
|
if hit_dot <= best_unit_dot then
|
|
goto continue
|
|
end
|
|
end
|
|
elseif hit_unit_distance < best_unit_distance then
|
|
-- Nothing
|
|
elseif hit_unit_distance > best_unit_distance then
|
|
if not hit_unit_aim_strict then
|
|
goto continue
|
|
end
|
|
end
|
|
end
|
|
|
|
if not is_target_visible(ray_origin, up, hit_unit_center_pos, half_height, hit_unit, fixed_frame) then
|
|
goto continue
|
|
end
|
|
|
|
best_unit = hit_unit
|
|
best_unit_dot = hit_dot
|
|
best_unit_distance = hit_unit_distance
|
|
best_unit_aim_strict = hit_unit_aim_strict
|
|
best_unit_aim_lenient = hit_unit_aim_lenient
|
|
best_unit_aim_outer = hit_unit_aim_outer
|
|
best_unit_x_diff_edge = x_diff_edge
|
|
best_unit_y_diff_edge = y_diff_edge
|
|
|
|
::continue::
|
|
end
|
|
|
|
if best_unit then
|
|
local best_unit_tag = smart_tag_system:unit_tag(best_unit)
|
|
return best_unit, best_unit_tag
|
|
end
|
|
end
|
|
|
|
mod:hook_safe(CLASS.PrecisionTargetFinder, "init",
|
|
function(self, is_server, is_local_unit, player, physics_world, unit)
|
|
visibility_raycast_object = PhysicsWorld_make_raycast(physics_world, "closest", "types", "both", "collision_filter", "filter_minion_line_of_sight_check")
|
|
end)
|