Files
darktide-AutoMark/AutoMark/scripts/mods/AutoMark/AutoMark.lua
T
michael a18194519b review: prod-readiness fix pass (3-agent code review + 2 self-found)
HIGH:
- rpc shadow dropped tagger_game_object_id and target_location (wrong vararg capture,
  args shifted); now forwards all varargs verbatim via unpack
- rpc shadow could NEVER install: ensure() read context.smart_tag_system (client-side
  instance) instead of the updating self; now armed per-instance, client shadow still
  pinned to the context instance only
- on_setting_changed debug_mode branch returned before the snapshot write: Debug Mode
  toggles were dead mid-session and the ON-notify read the stale value (AGENTS.md trap
  again, in my own fix); debug_chat/debug_hud now update the snapshot live too
- capacitance retention unit bug: widget relabel to charges (-10..10) left the legacy
  /100 in load + change handler + breed menu round-trip (*100) + breed setter (/100),
  making every retention threshold 100x too small; removed all four conversions
  (health-threshold percent semantics untouched)

MEDIUM:
- TARGETS capture was observer-non-neutral: MARKED candidate fell through into full
  re-evaluation (burned shared visibility budget); now skips like vanilla
- omni_diag_counter was an implicit global incremented in the prod hot path; now a
  file-local, only touched under debug_mode
- hack skull/check/blacklist state survived mission end (stale unit handles + stamped
  _automark_hack_order flags); mod:reset_servo_skull_local_state() runs on state exit,
  which also clears omni_last/omni_candidates/last_skull_confirm/banners

LOW: dead-tag release now honors per-class reset_cooldown; SERVO_SKULL gate checks class
before doing GameSession reads; mod:diag hard-gated on debug_mode internally (diag_log.txt
is never created for normal users anymore; un-gates OMNI-BOOT/banner/load probes); hits[]
pooled per call; early no-candidates return no longer dangles omni_rejects/omni_hud_units;
omni_last allocation gated on hud_capture; omni_hud_cur reset at entry and per-iteration
before actor resolution (no cross-candidate leakage); builder runs only while a level
world exists (no menu-side pcall spam); debug_hud_y fallback 24->0 matches default;
px unit_text localization added; six new widgets had orphan *_tooltip loc keys - DMF
only shows tooltips via an explicit tooltip field, added
2026-10-04 11:18:23 -04:00

756 lines
36 KiB
Lua

---@class AutoMarkMod:DMFMod
local mod = get_mod("AutoMark")
local breeds = require("scripts/settings/breed/breeds")
local Breed = require("scripts/utilities/breed")
-- Global Cache
local CLASS = CLASS
local ScriptUnit_extension = ScriptUnit.extension
local table_clear = table.clear
-- Smart Tag Names
---@class AutoMarkTagNames
local TAG_NAMES = {
ENEMY_TAG = "enemy_over_here",
VETERAN_TAG = "enemy_over_here_veteran",
COMPANION_TAG = "enemy_companion_target",
SERVO_SKULL_TAG = "servo_skull_enemy_companion_target",
}
mod.TAG_NAMES = TAG_NAMES
-- Mod Settings
---@class AutoMarkModSettings
local mod_settings = {
toggle_mod = mod:get("toggle_mod") or false,
toggle_mod_keybind = mod:get("toggle_mod_keybind") or {},
toggle_mod_notify = mod:get("toggle_mod_notify") or false,
debug_mode = mod:get("debug_mode") or false,
debug_chat = mod:get("debug_chat") or false,
debug_hud = mod:get("debug_hud") or false,
debug_hud_x = mod:get("debug_hud_x") or 25,
debug_hud_y = mod:get("debug_hud_y") or 0,
companion_mark_keybind = mod:get("companion_mark_keybind") or {},
companion_mark_ignore_unaggroed = mod:get("companion_mark_ignore_unaggroed") or false,
companion_range_limitation = mod:get("companion_range_limitation") or 0,
companion_mark_max_distance = mod:get("companion_mark_max_distance") or 0,
companion_mark_threat_priority = mod:get("companion_mark_threat_priority") or false,
execution_order_priority = mod:get("execution_order_priority") or false,
execution_order_force_mark = mod:get("execution_order_force_mark") or false,
companion_mark_tagged_always_visible = mod:get("companion_mark_tagged_always_visible") or false,
companion_mark_sticky_targeting = mod:get("companion_mark_sticky_targeting") or false,
companion_mark_sticky_targeting_elite = mod:get("companion_mark_sticky_targeting_elite") or false,
companion_mark_sticky_targeting_special = mod:get("companion_mark_sticky_targeting_special") or false,
companion_mark_sticky_targeting_boss = mod:get("companion_mark_sticky_targeting_boss") or false,
companion_cancel_mark = mod:get("companion_cancel_mark") or false,
companion_cancel_mark_human = mod:get("companion_cancel_mark_human") or false,
companion_cancel_mark_non_human = mod:get("companion_cancel_mark_non_human") or false,
companion_health_threshold = (mod:get("companion_health_threshold") or 0) / 100,
companion_time_threshold = mod:get("companion_time_threshold") or 0,
companion_distance_threshold = mod:get("companion_distance_threshold") or 0,
servo_skull_mark_keybind = mod:get("servo_skull_mark_keybind") or {},
servo_skull_mark_ignore_unaggroed = mod:get("servo_skull_mark_ignore_unaggroed") or false,
servo_skull_range_limitation = mod:get("servo_skull_range_limitation") or 0,
servo_skull_mark_threat_priority = mod:get("servo_skull_mark_threat_priority") or false,
hack_mark_keybind = mod:get("hack_mark_keybind") or {},
auto_hack = mod:get("auto_hack") or false,
disable_auto_hack_for_noospheric_command = mod:get("disable_auto_hack_for_noospheric_command") or false,
noospheric_command_boost = mod:get("noospheric_command_boost") or false,
noospheric_command_boost_elite = mod:get("noospheric_command_boost_elite") or false,
noospheric_command_boost_special = mod:get("noospheric_command_boost_special") or false,
noospheric_command_boost_boss = mod:get("noospheric_command_boost_boss") or false,
capacitance_retention = mod:get("capacitance_retention") or false,
capacitance_retention_elite_threshold = mod:get("capacitance_retention_elite_threshold") or 0,
capacitance_retention_special_threshold = mod:get("capacitance_retention_special_threshold") or 0,
capacitance_retention_boss_threshold = mod:get("capacitance_retention_boss_threshold") or 0,
servo_skull_mark_sticky_targeting = mod:get("servo_skull_mark_sticky_targeting") or false,
servo_skull_mark_sticky_targeting_elite = mod:get("servo_skull_mark_sticky_targeting_elite") or false,
servo_skull_mark_sticky_targeting_special = mod:get("servo_skull_mark_sticky_targeting_special") or false,
servo_skull_mark_sticky_targeting_boss = mod:get("servo_skull_mark_sticky_targeting_boss") or false,
servo_skull_cancel_mark = mod:get("servo_skull_cancel_mark") or false,
servo_skull_cancel_mark_time_threshold = mod:get("servo_skull_cancel_mark_time_threshold") or 0,
servo_skull_cancel_mark_health_threshold = (mod:get("servo_skull_cancel_mark_health_threshold") or 0) / 100,
focus_target_overwrite = mod:get("focus_target_overwrite") or false,
focus_target_overwrite_delta = mod:get("focus_target_overwrite_delta") or 5,
focus_target_ignore_unaggroed = mod:get("focus_target_ignore_unaggroed") or false,
focus_target_switch = mod:get("focus_target_switch") or false,
focus_target_switch_override_manual = mod:get("focus_target_switch_override_manual") or false,
focus_target_switch_melee = mod:get("focus_target_switch_melee") or false,
focus_target_switch_range = mod:get("focus_target_switch_range") or false,
omni_mark = mod:get("omni_mark") or false,
omni_mark_ignore_los = mod:get("omni_mark_ignore_los") or false,
}
mod.settings = mod_settings
if mod:get("capacitance_retention_elite_threshold_negative_zero") then
mod_settings.capacitance_retention_elite_threshold = -0
mod:set("capacitance_retention_elite_threshold", -0, false)
end
if mod:get("capacitance_retention_special_threshold_negative_zero") then
mod_settings.capacitance_retention_special_threshold = -0
mod:set("capacitance_retention_special_threshold", -0, false)
end
if mod:get("capacitance_retention_boss_threshold_negative_zero") then
mod_settings.capacitance_retention_boss_threshold = -0
mod:set("capacitance_retention_boss_threshold", -0, false)
end
local noospheric_command_breed_settings = mod:get("noospheric_command_breed_settings") or {}
mod.noospheric_command_breed_settings = noospheric_command_breed_settings
for _, breed_settings in pairs(noospheric_command_breed_settings) do
if breed_settings.threshold_negative_zero then
breed_settings.threshold = -0
end
end
local companion_cancel_mark_breed_settings = mod:get("companion_cancel_mark_breed_settings") or {}
mod.companion_cancel_mark_breed_settings = companion_cancel_mark_breed_settings
-- Default Class Settings
---@class AutoMarkDefaultClassSettings
local DEFAULT_CLASS_SETTINGS = {
toggle_class = true,
cooldown = 25,
reset_cooldown = true,
interval = 1,
mark_limit = true,
min_range = 0,
max_range = 100,
override_manual = false,
priority_switch = false,
toggle_elite = true,
toggle_special = true,
toggle_boss = true,
toggle_other = true,
breed_priorities = {},
}
for breed_name, breed_data in pairs(breeds) do
if Breed.is_minion(breed_data) and breed_data.unit_template_name == "minion" and breed_data.smart_tag_target_type == "breed" and breed_data.faction_name ~= "imperium" then
if breed_data.tags.elite then
DEFAULT_CLASS_SETTINGS.breed_priorities[breed_name] = 0
elseif breed_data.tags.special then
DEFAULT_CLASS_SETTINGS.breed_priorities[breed_name] = 1
elseif breed_data.is_boss then
DEFAULT_CLASS_SETTINGS.breed_priorities[breed_name] = 1
if breed_data.tags.witch then
DEFAULT_CLASS_SETTINGS.breed_priorities[breed_name .. "_passive"] = 1
end
else
DEFAULT_CLASS_SETTINGS.breed_priorities[breed_name] = 1
end
end
end
mod.DEFAULT_CLASS_SETTINGS = DEFAULT_CLASS_SETTINGS
-- Context
---@class AutoMarkContext
local context = {
mod_enabled = false,
game_mode_valid = false,
player = nil,
class_name = nil,
talent_resource_component = nil,
disabled_character_state_component = nil,
locomotion_component = nil,
has_companion = false,
has_execution_order = false,
has_focus_target = false,
focus_target_max_stacks = 0,
has_servo_skull = false,
has_noospheric_command = false,
smart_targeting_extension = nil,
companion_spawner_extension = nil,
player_ability_extension = nil,
smart_tag_system = nil,
outline_system = nil,
smoke_fog_system = nil,
force_field_system = nil,
broadphase_system = nil,
side_system = nil,
hud_element_smart_tagging = nil,
companion_command_tap = "double"
}
mod.context = context
-- Auto Mark Settings
local auto_mark_settings = mod:get("auto_mark_settings") or {}
mod.auto_mark_settings = auto_mark_settings
-- Mark States
---@class AutoMarkMarkContext
local mark_context = {
auto_mark_interval = 0,
execution_order_units = setmetatable({}, { __mode = "k" }),
[TAG_NAMES.ENEMY_TAG] = {
tag = nil,
cooldown = 0,
priority_switch_cooldown = 0,
delay = 0,
delay_times = 0,
interval = 0,
manual_unit = nil,
manual_unit_expired_time = nil,
is_manual = false,
},
[TAG_NAMES.VETERAN_TAG] = {
tag = nil,
cooldown = 0,
priority_switch_cooldown = 0,
delay = 0,
delay_times = 0,
interval = 0,
manual_unit = nil,
manual_unit_expired_time = nil,
is_manual = false,
switch_melee_unit = nil,
switch_range_unit = nil,
switch_unit_expired_time = nil,
is_switch_melee = false,
is_switch_range = false,
removed_units = setmetatable({}, { __mode = "k" }),
},
[TAG_NAMES.COMPANION_TAG] = {
tag = nil,
cooldown = 0,
priority_switch_cooldown = 0,
delay = 0,
delay_times = 0,
interval = 0,
manual_unit = nil,
manual_unit_expired_time = nil,
is_manual = false,
pounce_start_time = nil,
is_cancelable = false,
canceled_units = setmetatable({}, { __mode = "k" }),
removed_units = setmetatable({}, { __mode = "k" }),
},
[TAG_NAMES.SERVO_SKULL_TAG] = {
tag = nil,
cooldown = 0,
priority_switch_cooldown = 0,
delay = 0,
delay_times = 0,
interval = 0,
manual_unit = nil,
manual_unit_expired_time = nil,
is_manual = false,
shoot_start_time = nil,
noospheric_command_next_time = math.huge,
servo_skull_lose_sight_time = nil,
canceled_units = setmetatable({}, { __mode = "k" }),
removed_units = setmetatable({}, { __mode = "k" }),
},
}
mod.mark_context = mark_context
-- Enemy Visbility Check
local visibility_cache = setmetatable({}, { __mode = "k" })
local visibility_check_frame = setmetatable({}, { __mode = "k" })
mod.visibility_cache = visibility_cache
mod.visibility_check_frame = visibility_check_frame
local servo_skull_visibility_cache = setmetatable({}, { __mode = "k" })
local servo_skull_visibility_check_frame = setmetatable({}, { __mode = "k" })
mod.servo_skull_visibility_cache = servo_skull_visibility_cache
mod.servo_skull_visibility_check_frame = servo_skull_visibility_check_frame
mod.num_visibility_checks_this_frame = 0
---@class AutoMarkDisablingTypes
local DISABLING_TYPES = {
-- netted = true,
pounced = true,
warp_grabbed = true,
mutant_charged = true,
consumed = true,
grabbed = true,
}
mod.DISABLING_TYPES = DISABLING_TYPES
-- Reset all params
local function reset_context()
-- Reset Mark Params
mark_context.auto_mark_interval = 0
table_clear(mark_context.execution_order_units)
table_clear(visibility_cache)
table_clear(visibility_check_frame)
table_clear(servo_skull_visibility_cache)
table_clear(servo_skull_visibility_check_frame)
for _, tag_name in pairs(TAG_NAMES) do
local tag_context = mark_context[tag_name]
tag_context.tag = nil
tag_context.cooldown = 0
tag_context.priority_switch_cooldown = 0
tag_context.delay = 0
tag_context.delay_times = 0
tag_context.interval = 0
tag_context.manual_unit = nil
tag_context.manual_unit_expired_time = nil
tag_context.is_manual = false
end
local veteran_tag_context = mark_context[TAG_NAMES.VETERAN_TAG]
veteran_tag_context.switch_melee_unit = nil
veteran_tag_context.switch_range_unit = nil
veteran_tag_context.switch_unit_expired_time = nil
veteran_tag_context.is_switch_melee = false
veteran_tag_context.is_switch_range = false
table_clear(veteran_tag_context.removed_units)
local companion_tag_context = mark_context[TAG_NAMES.COMPANION_TAG]
companion_tag_context.pounce_start_time = nil
companion_tag_context.is_cancelable = false
table_clear(companion_tag_context.canceled_units)
table_clear(companion_tag_context.removed_units)
local servo_skull_tag_context = mark_context[TAG_NAMES.SERVO_SKULL_TAG]
servo_skull_tag_context.shoot_start_time = nil
servo_skull_tag_context.noospheric_command_next_time = math.huge
servo_skull_tag_context.servo_skull_lose_sight_time = nil
table_clear(servo_skull_tag_context.canceled_units)
table_clear(servo_skull_tag_context.removed_units)
end
local function destroy_references()
context.player = nil
context.talent_resource_component = nil
context.disabled_character_state_component = nil
context.locomotion_component = nil
context.smart_targeting_extension = nil
context.companion_spawner_extension = nil
context.player_ability_extension = nil
context.smart_tag_system = nil
context.outline_system = nil
context.smoke_fog_system = nil
context.force_field_system = nil
context.broadphase_system = nil
context.side_system = nil
context.hud_element_smart_tagging = nil
mod:destroy_visibility_raycast_objects()
end
-- Load Other Files
mod:io_dofile("AutoMark/scripts/mods/AutoMark/utils/utils")
mod:io_dofile("AutoMark/scripts/mods/AutoMark/context/context")
mod:io_dofile("AutoMark/scripts/mods/AutoMark/setting/class_setting")
mod:io_dofile("AutoMark/scripts/mods/AutoMark/setting/cyber_mastiff_setting")
mod:io_dofile("AutoMark/scripts/mods/AutoMark/setting/servo_skull_setting")
mod:io_dofile("AutoMark/scripts/mods/AutoMark/targeting/targeting")
mod:io_dofile("AutoMark/scripts/mods/AutoMark/targeting/custom_targeting")
mod:io_dofile("AutoMark/scripts/mods/AutoMark/mark/base_mark")
mod:io_dofile("AutoMark/scripts/mods/AutoMark/mark/companion_mark")
mod:io_dofile("AutoMark/scripts/mods/AutoMark/mark/focus_target_mark")
mod:io_dofile("AutoMark/scripts/mods/AutoMark/mark/servo_skull_mark")
mod:io_dofile("AutoMark/scripts/mods/AutoMark/ui/debug_hud")
-- Mod Enabled
mod.on_enabled = function(initial_call)
context.mod_enabled = true
mod:check_game_mode()
mod:init_auto_mark_settings()
-- init cache after mod enabled since all hooks were disabled
mod:init_context()
mod:init_execution_order_units()
mod:init_visibility_raycast_objects()
mod:set_menu_class_settings(mod:get_menu_class_name())
mod:set_menu_servo_skull_breed_settings(mod:get("noospheric_command_boost_breed_name"))
mod:set_menu_cyber_mastiff_breed_settings(mod:get("companion_cancel_mark_breed_name"))
end
-- Mod Disabled
mod.on_disabled = function(initial_call)
context.mod_enabled = false
-- mark info rest
reset_context()
destroy_references()
end
-- Enter/Exit GameplayStateRun
mod.on_game_state_changed = function(status, state_name)
if state_name == "GameplayStateRun" then
if status == "enter" then
mod:check_game_mode()
mod.debug_warn_pending = true
-- game settings cache
mod:init_game_settings()
-- display
mod:set_menu_class_settings(mod:get_menu_class_name())
elseif status == "exit" then
context.game_mode_valid = false
-- menu mark info rest
reset_context()
if mod.reset_servo_skull_local_state then
mod:reset_servo_skull_local_state()
end
mod.omni_candidates = nil
mod.omni_candidates_pass = nil
mod.omni_last = nil
mod.last_skull_confirm = nil
mod.omni_gates_banner_done = nil
end
end
end
-- Mod Setting Change
mod.on_setting_changed = function(setting_id)
if setting_id == "debug_mode" or setting_id == "debug_chat" or setting_id == "debug_hud" then
local value = mod:get(setting_id)
mod_settings[setting_id] = value
if mod_settings.debug_mode then
mod:diag("OMNI-HUD setting changed:", setting_id, "=", tostring(value))
end
if setting_id == "debug_mode" and value then
mod:notify("AutoMark DEBUG ON: silent file diagnostics now write to mods/AutoMark/diag_log.txt and keep growing - remember to turn Debug Mode off when you are done!")
end
return
end
local result = mod:get(setting_id)
local class_name = mod:get("class_selection")
-- Normal Mod Settings
if mod_settings[setting_id] ~= nil then
if setting_id == "companion_health_threshold" or setting_id == "servo_skull_cancel_mark_health_threshold" then
result = result / 100
elseif setting_id == "capacitance_retention_elite_threshold" or setting_id == "capacitance_retention_special_threshold" or setting_id == "capacitance_retention_boss_threshold" then
if result == -0 and 1 / result < 0 then
mod:set(setting_id .. "_negative_zero", true, false)
else
mod:set(setting_id .. "_negative_zero", false, false)
end
end
mod_settings[setting_id] = result
-- Apply Class Settings to Other Classes
elseif setting_id == "apply_button" then
if result == "apply_to_all" then
mod:apply_to_all_classes(class_name)
elseif result == "apply_to_normal" then
mod:apply_to_normal_tag(class_name)
end
mod:set("apply_button", "blank", false)
-- Reset Class Settings
elseif setting_id == "reset_button" then
if result == "reset_all" then
mod:reset_auto_mark_settings()
elseif result == "reset_current" then
mod:reset_class_settings(class_name)
end
mod:set_menu_class_settings(class_name)
mod:set("reset_button", "blank", false)
-- Select Noospheric Command Breed Name
elseif setting_id == "noospheric_command_boost_breed_name" then
mod:set_menu_servo_skull_breed_settings(result)
-- Set Noospheric Command Breed Settings
elseif mod:is_servo_skull_breed_setting_id(setting_id) then
local breed_name = mod:get("noospheric_command_boost_breed_name")
mod:set_servo_skull_breed_setting(breed_name, setting_id, result)
-- Select Companion Cancel Mark Breed Name
elseif setting_id == "companion_cancel_mark_breed_name" then
mod:set_menu_cyber_mastiff_breed_settings(result)
-- Set Companion Cancel Mark Breed Settings
elseif mod:is_cyber_mastiff_breed_setting_id(setting_id) then
local breed_name = mod:get("companion_cancel_mark_breed_name")
mod:set_cyber_mastiff_breed_setting(breed_name, setting_id, result)
-- Set Class Name
elseif setting_id == "class_selection" then
mod:set_menu_class_settings(result)
-- Set Class Settings
else
local class_settings = auto_mark_settings[class_name]
if DEFAULT_CLASS_SETTINGS[setting_id] ~= nil then
class_settings[setting_id] = result
elseif DEFAULT_CLASS_SETTINGS.breed_priorities[setting_id] ~= nil then
if (class_name == "adamant_companion" or class_name == "cryptic_servo_skull") and setting_id == "chaos_poxwalker_bomber" then
class_settings.breed_priorities[setting_id] = 0
mod:set_menu_class_settings(class_name)
else
class_settings.breed_priorities[setting_id] = result
end
end
mod:set("auto_mark_settings", auto_mark_settings, false)
end
end
-- Toggle Mod Enabled/Disabled
mod.toggle_mod = function()
if mod_settings.toggle_mod_notify then
mod:notify("Auto Mark " .. (not mod_settings.toggle_mod and "Enabled" or "Disabled"))
end
mod:set("toggle_mod", not mod_settings.toggle_mod, true)
end
local talent_settings = require("scripts/settings/talent/talent_settings")
local cryptic_talent_settings = talent_settings.cryptic
local function can_noospheric_command_boost()
local player_ability_extension = context.player_ability_extension
if not player_ability_extension then
return false
end
return player_ability_extension:has_enough_ability_charge_percentage("combat_ability", cryptic_talent_settings.servo_skull_shooting_tagging.minimum_capacitance)
end
local function is_training_grounds()
return Managers.state.game_mode:game_mode_name() == "training_grounds"
end
local function is_player_in_platform()
local locomotion_component = context.locomotion_component
local locomotion_parent_unit = locomotion_component and locomotion_component.parent_unit
return locomotion_parent_unit and ScriptUnit_extension(locomotion_parent_unit, "moveable_platform_system")
end
-- Check if Tag is Valid for Current Class
local function is_tag_valid(tag_name)
if tag_name == TAG_NAMES.COMPANION_TAG then
return context.class_name == "adamant" and context.has_companion and not is_player_in_platform()
elseif tag_name == TAG_NAMES.VETERAN_TAG then
return context.class_name == "veteran" and context.has_focus_target
elseif tag_name == TAG_NAMES.ENEMY_TAG then
return context.class_name ~= "veteran" or not context.has_focus_target
elseif tag_name == TAG_NAMES.SERVO_SKULL_TAG then
if context.class_name ~= "cryptic" or not context.has_servo_skull then
return false
end
local state_ok = mod:is_servo_skull_ready_to_shoot()
local charge_ok = is_training_grounds() or can_noospheric_command_boost()
if not (state_ok and charge_ok) and mod_settings.debug_mode then
mod.omni_gate_diag_counter = (mod.omni_gate_diag_counter or 0) + 1
if mod.omni_gate_diag_counter % 15 == 1 then
mod:diag(string.format("OMNI-GATE closed counter=%d state=%s state_ok=%s charge_ok=%s", mod.omni_gate_diag_counter, mod:debug_servo_skull_state(), tostring(state_ok), tostring(charge_ok)))
end
end
return state_ok and charge_ok
end
return false
end
-- Auto-Mark Target Unit with the Tag
local function auto_mark_by_tag(tag_name, t, fixed_frame)
if not is_tag_valid(tag_name) then
return false
end
local tag_context = mark_context[tag_name]
if tag_context.interval > 0 then
return false
end
local marked_tag = tag_context.tag
local marked_tag_is_manual = tag_context.is_manual
local disabled_character_state_component = context.disabled_character_state_component
local is_character_disabled = disabled_character_state_component and disabled_character_state_component.is_disabled and DISABLING_TYPES[disabled_character_state_component.disabling_type]
local target_unit, target_tag, target_is_dormant_daemonhost
-- dead-target tag release: vanilla keeps the tag until the corpse despawns (up to the
-- template lifetime), which starves mark_limit; drop the tag as soon as the target dies
if marked_tag and not marked_tag_is_manual then
local marked_target = marked_tag._target_unit
if marked_target and HEALTH_ALIVE and not HEALTH_ALIVE[marked_target] then
local smart_tag_system = context.smart_tag_system
local player_unit = context.player and context.player.player_unit
if smart_tag_system and player_unit then
mod:print_debug("Auto Mark target dead, releasing tag")
smart_tag_system:cancel_tag(marked_tag._id, player_unit, true)
marked_tag = nil
if mod:get_class_settings(tag_name).reset_cooldown then
tag_context.cooldown = 0
end
end
end
end
if is_character_disabled and (tag_name == TAG_NAMES.COMPANION_TAG or tag_name == TAG_NAMES.SERVO_SKULL_TAG) then
local disabling_unit = disabled_character_state_component and disabled_character_state_component.disabling_unit
if disabling_unit and (not marked_tag or marked_tag._target_unit ~= disabling_unit) then
mod:print_debug("Auto Mark Disabling Unit")
target_unit = disabling_unit
end
else
local class_settings = mod:get_class_settings(tag_name)
-- mark when cooldown is zero
local is_cooldown_ready = tag_context.cooldown <= 0 and (not class_settings.mark_limit or not marked_tag)
-- mark when priority switch is on
local is_priority_switch = tag_context.priority_switch_cooldown <= 0 and class_settings.priority_switch and marked_tag
if class_settings.toggle_class and (class_settings.override_manual or not marked_tag_is_manual) then
if is_cooldown_ready then
target_unit, target_tag, target_is_dormant_daemonhost = mod:find_auto_mark_target_unit(class_settings.min_range, class_settings.max_range, tag_name, tag_context, class_settings)
elseif is_priority_switch then
target_unit, target_tag, target_is_dormant_daemonhost = mod:find_auto_mark_target_unit(class_settings.min_range, class_settings.max_range, tag_name, tag_context, class_settings, marked_tag)
end
end
end
if target_unit then
if tag_name == TAG_NAMES.VETERAN_TAG and target_is_dormant_daemonhost then
mod:set_auto_mark(TAG_NAMES.ENEMY_TAG, target_unit, target_tag)
else
mod:set_auto_mark(tag_name, target_unit, target_tag)
if tag_name == TAG_NAMES.SERVO_SKULL_TAG and mod.arm_skull_mark_check then
mod:arm_skull_mark_check(target_unit)
end
end
return true
end
if tag_name == TAG_NAMES.VETERAN_TAG then
if mod_settings.focus_target_overwrite and marked_tag then
local marked_unit = marked_tag._target_unit
if not mod:is_dormant_daemonhost(marked_unit) and mod:can_focus_target_overwrite(marked_unit, marked_tag) and mod:is_target_pingable(marked_unit) then
mod:print_debug("Focus Target Overwrite")
if tag_context.is_switch_melee then
tag_context.switch_melee_unit = marked_unit
elseif tag_context.is_switch_range then
tag_context.switch_range_unit = marked_unit
end
if marked_tag_is_manual then
mod:on_manual_mark(tag_context, marked_unit)
end
mod:set_auto_mark(tag_name, marked_unit, target_tag)
return true
end
end
elseif tag_name == TAG_NAMES.SERVO_SKULL_TAG then
if mod_settings.noospheric_command_boost and context.has_noospheric_command and marked_tag and t >= tag_context.noospheric_command_next_time and can_noospheric_command_boost() then
local marked_unit = marked_tag._target_unit
if is_character_disabled
or not mod:is_dormant_daemonhost(marked_unit)
and mod:is_noospheric_command_boost_breed_valid(marked_unit)
and mod:has_enough_capacitance(marked_unit)
and mod:is_servo_skull_target_visible(marked_unit, fixed_frame, true)
and mod:is_target_pingable(marked_unit)
then
mod:print_debug("Noospheric Command Boost")
if marked_tag_is_manual then
mod:on_manual_mark(tag_context, marked_unit)
end
mod:set_auto_mark(tag_name, marked_unit, target_tag)
return true
end
end
end
return false
end
-- Auto-Mark
local function auto_mark(dt, t, fixed_frame)
-- calculate interval
if mark_context.auto_mark_interval > 0 then
mark_context.auto_mark_interval = mark_context.auto_mark_interval - dt
end
-- calculate cooldown and delay for all tags
local skip = false
for _, tag_name in pairs(TAG_NAMES) do
local tag_context = mark_context[tag_name]
if tag_context.delay > 0 then
tag_context.delay = tag_context.delay - dt
if tag_context.delay <= 0 then
tag_context.delay = 0
tag_context.delay_times = 0
else
skip = true
end
end
if tag_context.interval > 0 then
tag_context.interval = tag_context.interval - dt
end
if tag_context.cooldown > 0 then
tag_context.cooldown = tag_context.cooldown - dt
end
if tag_context.priority_switch_cooldown > 0 then
tag_context.priority_switch_cooldown = tag_context.priority_switch_cooldown - dt
end
end
-- pause auto mark for a period of time after it is executed.
if mark_context.auto_mark_interval > 0 or skip then
return
end
-- all kinds of tag to mark
if auto_mark_by_tag(TAG_NAMES.COMPANION_TAG, t, fixed_frame) then
return
end
if auto_mark_by_tag(TAG_NAMES.SERVO_SKULL_TAG, t, fixed_frame) then
return
end
if auto_mark_by_tag(TAG_NAMES.VETERAN_TAG, t, fixed_frame) then
return
end
if auto_mark_by_tag(TAG_NAMES.ENEMY_TAG, t, fixed_frame) then
return
end
end
local function clean_visibility_cache(fixed_frame)
if fixed_frame % 20 == 0 then
local frame_threshold = fixed_frame - 5
for cached_unit, check_frame in pairs(visibility_check_frame) do
if check_frame < frame_threshold then
visibility_cache[cached_unit] = nil
visibility_check_frame[cached_unit] = nil
end
end
for cached_unit, check_frame in pairs(servo_skull_visibility_check_frame) do
if check_frame < frame_threshold then
servo_skull_visibility_cache[cached_unit] = nil
servo_skull_visibility_check_frame[cached_unit] = nil
end
end
end
end
local function clean_expired_units(t)
for _, tag_name in pairs(TAG_NAMES) do
local tag_context = mark_context[tag_name]
if tag_context.manual_unit_expired_time and t >= tag_context.manual_unit_expired_time then
tag_context.manual_unit = nil
tag_context.manual_unit_expired_time = nil
end
if tag_context.switch_unit_expired_time and t >= tag_context.switch_unit_expired_time then
tag_context.switch_melee_unit = nil
tag_context.switch_range_unit = nil
tag_context.switch_unit_expired_time = nil
end
local canceled_units = tag_context.canceled_units
if canceled_units then
for unit, expired_time in pairs(canceled_units) do
if t >= expired_time then
canceled_units[unit] = nil
end
end
end
local removed_units = tag_context.removed_units
if removed_units then
for unit, expired_time in pairs(removed_units) do
if t >= expired_time then
removed_units[unit] = nil
end
end
end
end
end
-- Main Entry For Auto Mark
mod:hook_safe(CLASS.PlayerUnitSmartTargetingExtension, "fixed_update",
function(self, unit, dt, t, fixed_frame)
if self._player.viewport_name ~= "player1" then
return
end
if not mod.omni_gates_banner_done and mod_settings.debug_mode then
mod.omni_gates_banner_done = true
local game_mode_manager = Managers.state.game_mode
mod:diag(string.format("OMNI-GATES class=%s companion=%s skull=%s mode=%s valid=%s toggle=%s debug=%s", tostring(context.class_name), tostring(context.has_companion), tostring(context.has_servo_skull), game_mode_manager and tostring(game_mode_manager:game_mode_name()) or "nil", tostring(context.game_mode_valid), tostring(mod_settings.toggle_mod), tostring(mod_settings.debug_mode)))
end
if mod.debug_warn_pending and context.game_mode_valid then
mod.debug_warn_pending = false
if mod_settings.toggle_mod and mod_settings.debug_mode then
mod:notify("AutoMark DEBUG MODE is ON: file diagnostics in mods/AutoMark/diag_log.txt keep growing - turn it off when you are done!")
end
end
if mod_settings.toggle_mod and context.game_mode_valid then
mod.num_visibility_checks_this_frame = 0
clean_expired_units(t)
clean_visibility_cache(fixed_frame)
mod:auto_cancel_companion_mark(dt, t)
mod:auto_cancel_servo_skull_mark(dt, t, fixed_frame)
mod:auto_hack(dt, t, fixed_frame)
auto_mark(dt, t, fixed_frame)
end
end)