refactor: debug overlay abandons the UIHud element pipeline entirely — draws direct to level_world screen gui (Gui.rect dark panel + Gui.slug_text lines) per FpsDoctor's proven pattern in the same mods dir: per-world gui handle validation, pcall+backoff, 5Hz rebuild, RESOLUTION_LOOKUP scaling; X/Y now anchor top-left; deletes hud_element_debug.lua + require-path/injection/recreate machinery; works hub/MeatGrinder/mission, applies live

This commit is contained in:
2026-10-04 10:40:18 -04:00
parent ce5eb10f04
commit fede4eb18f
3 changed files with 153 additions and 206 deletions
@@ -73,9 +73,9 @@ local localization = {
["zh-tw"] = "除錯HUD面板",
},
debug_hud_tooltip = {
en = "Shows a live text panel (left side of the screen) with current mark targets, cooldowns, skull/dog state and server accept results. Applied at the next mission start.",
["zh-cn"] = "在屏幕左侧显示实时文本面板:当前标记目标、冷却、骷髅/狗状态及服务器接受结果。下个任务开始时生效。",
["zh-tw"] = "在螢幕左側顯示即時文字面板:目前標記目標、冷卻、骷髏/狗狀態及伺服器接受結果。下個任務開始時生效。",
en = "Shows a live overlay panel (dark box, top-left by default) with current mark targets, cooldowns, skull/dog state and server accept results. Applies immediately, works in hub, Meat Grinder and missions.",
["zh-cn"] = "显示实时悬浮面板(深色底,默认左上):当前标记目标、冷却、骷髅/狗状态及服务器接受结果。立即生效,大厅/训练场/任务均可用。",
["zh-tw"] = "顯示即時浮動面板(深色底,預設左上):目前標記目標、冷卻、骷髏/狗狀態及伺服器接受結果。立即生效,大廳/訓練場/任務均可用。",
},
debug_hud_x = {
en = "Overlay X",
@@ -93,9 +93,9 @@ local localization = {
["zh-tw"] = "距螢幕左緣的距離(UI像素),立即生效。",
},
debug_hud_y_tooltip = {
en = "Vertical offset from screen middle (positive = down), in UI pixels. Applies immediately.",
["zh-cn"] = "相对屏幕垂直中线的偏移(正值向下),单位UI像素,立即生效。",
["zh-tw"] = "相對螢幕垂直中線的偏移(正值向下),單位UI像素,立即生效。",
en = "Distance from the top edge of the screen, in UI pixels. Applies immediately.",
["zh-cn"] = "距屏幕顶部的距离(UI像素),立即生效。",
["zh-tw"] = "距螢幕頂緣的距離(UI像素),立即生效。",
},
omni_mark = {
en = "360° Marking",
+141 -94
View File
@@ -1,104 +1,66 @@
-- AutoMark debug HUD (mod side): injects the HUD element definition at mission
-- HUD creation and feeds it a rebuilt status line buffer at 5 Hz.
-- AutoMark debug HUD (mod side): a live status overlay drawn straight onto the
-- level world's screen gui (the pattern FpsDoctor uses in this very mods folder).
-- Deliberately avoids the UIHud element pipeline: direct Gui.rect + Gui.slug_text
-- with per-world gui handle validation, pcall-guarded with backoff on failure.
local mod = get_mod("AutoMark")
local mod_settings = mod.settings
local TAG_NAMES = mod.TAG_NAMES
local Managers = Managers
local World = World
local Gui = Gui
local Vector3 = Vector3
local Vector2 = Vector2
local Color = Color
local RESOLUTION_LOOKUP = RESOLUTION_LOOKUP
local ScriptUnit_extension = ScriptUnit.extension
mod.debug_hud_last = {}
mod:diag("OMNI-HUD load: settings debug_hud=", tostring(mod_settings.debug_hud), " chat=", tostring(mod_settings.debug_chat), " debug=", tostring(mod_settings.debug_mode))
local ELEMENT_CLASS_NAME = "HudElementAutoMarkDebug"
local ELEMENT_FILENAME = "AutoMark/scripts/mods/AutoMark/ui/hud_element_debug"
local FONT_TYPE = "arial"
local REBUILD_INTERVAL = 0.2
local DRAW_FAIL_BACKOFF = 120
-- the game HUD requires element files through the game require system; without
-- registering here, require(ELEMENT_FILENAME) from UIHud._add_element never finds
-- the mod file (DMF loads mod scripts through its own file loader, not package.path)
mod:add_require_path(ELEMENT_FILENAME)
local gui, gui_world, font_path
local fail_cooldown = 0
local rebuild_acc = REBUILD_INTERVAL
local first_draw_logged = false
mod:hook("UIHud", "init",
function(func, self, elements, visibility_groups, params)
if elements then
if not mod.omni_hud_init_count then
mod.omni_hud_init_count = 0
local function level_world()
local world_manager = Managers.world
if world_manager and world_manager:has_world("level_world") then
return world_manager:world("level_world")
end
mod.omni_hud_init_count = mod.omni_hud_init_count + 1
mod:diag("OMNI-HUD init#", mod.omni_hud_init_count, "debug_hud=", tostring(mod_settings.debug_hud), " elements=", tostring(#elements))
local idx = nil
for i = 1, #elements do
if elements[i].class_name == ELEMENT_CLASS_NAME then
idx = i
break
end
end
if mod_settings.debug_hud and not idx then
elements[#elements + 1] = {
class_name = ELEMENT_CLASS_NAME,
filename = ELEMENT_FILENAME,
use_hud_scale = true,
visibility_groups = { "alive", "dead" },
}
if not mod.debug_hud_inject_logged then
mod.debug_hud_inject_logged = true
mod:diag("OMNI-HUD inject: ADDED")
end
elseif not mod_settings.debug_hud and idx then
table.remove(elements, idx)
end
end
return func(self, elements, visibility_groups, params)
end)
return nil
end
-- live toggling: UIHud init only runs on HUD creation, so when the setting flips we
-- rebuild the HUD on the spot (pattern borrowed from NumericUI's recreate_hud)
local recreate_timer = 0
local last_debug_hud = nil
mod.update = function (dt)
local want = mod_settings.debug_hud and true or false
if last_debug_hud == nil then
last_debug_hud = want
return
local function ensure_gui()
local world = level_world()
if not world then
gui = nil
gui_world = nil
font_path = nil
return nil
end
if want ~= last_debug_hud then
last_debug_hud = want
recreate_timer = 0.5
return
if gui and gui_world == world then
return gui
end
gui = World.create_screen_gui(world, "immediate")
gui_world = world
font_path = nil
return gui
end
if recreate_timer <= 0 then
return
local function ensure_font()
if font_path then
return font_path
end
recreate_timer = recreate_timer - dt
if recreate_timer > 0 then
return
end
local ok, err = pcall(function()
local ui_manager = Managers.ui
local hud = ui_manager and ui_manager._hud
if not hud or not hud._element_definitions or not hud._visibility_groups then
return
end
local player = Managers.player and Managers.player:local_player(1)
if not player then
return
end
local elements = hud._element_definitions
local visibility_groups = hud._visibility_groups
hud:destroy()
ui_manager:create_player_hud(player:peer_id(), player:local_player_id(), elements, visibility_groups)
mod:diag("OMNI-HUD: live recreate, debug_hud ->", tostring(want))
end)
if not ok then
mod:diag("OMNI-HUD: live recreate FAILED:", tostring(err))
local font_data = Managers.font and Managers.font:data_by_type(FONT_TYPE)
if font_data then
font_path = font_data.path
end
return font_path
end
local function unit_breed_name(unit)
@@ -170,23 +132,108 @@ function mod:build_debug_hud_lines()
return buf
end
local hud_build_timer = 0
mod:hook_safe(CLASS.PlayerUnitSmartTargetingExtension, "fixed_update",
function(self, unit, dt, t, fixed_frame)
if not mod_settings.debug_hud or not mod_settings.toggle_mod then
return
local cached = { x = 0, top_y = 0, line_h = 0, font_px = 0, panel_w = 0, panel_h = 0, pad = 0 }
local function text_width(gui_handle, font, font_px, text)
local ok, min, max = pcall(Gui.slug_text_extents, gui_handle, text, font, font_px)
if ok and min and max then
return max[1] - min[1]
end
if self._player.viewport_name ~= "player1" then
return #text * font_px * 0.55
end
local function draw_inner(dt)
local gui_handle = ensure_gui()
if not gui_handle then
return
end
hud_build_timer = hud_build_timer - dt
if hud_build_timer <= 0 then
hud_build_timer = 0.2
local font = ensure_font()
if not font then
return
end
local buf = mod.debug_hud_last
local n = #buf
if n == 0 then
return
end
local r = RESOLUTION_LOOKUP
local scale = (r and r.scale) or 1.0
local sw = (r and r.width) or 1920
local font_px = math.max(8, math.floor(16 * scale + 0.5))
local line_h = math.floor(20 * scale + 0.5)
local pad = math.floor(6 * scale + 0.5)
local margin_x = math.floor((mod_settings.debug_hud_x or 25) * scale + 0.5)
local margin_y = math.floor((mod_settings.debug_hud_y or 24) * scale + 0.5)
local maxw = 0
for i = 1, n do
local w = text_width(gui_handle, font, font_px, buf[i])
if w > maxw then
maxw = w
end
end
cached.x = math.min(margin_x, math.max(0, sw - maxw - pad * 2 - 8))
cached.top_y = margin_y
cached.line_h = line_h
cached.font_px = font_px
cached.panel_w = maxw + pad * 2
cached.panel_h = n * line_h + pad * 2
cached.pad = pad
Gui.rect(gui_handle,
Vector3(cached.x - pad, cached.top_y - pad, 899),
Vector2(cached.panel_w, cached.panel_h),
Color(165, 10, 10, 10))
for i = 1, n do
local line = buf[i]
local col = Color(235, 235, 220, 150)
if line:find("REJECTED") or line:find("OFF") or line:find("FAIL") then
col = Color(235, 255, 110, 100)
elseif i == 1 then
col = Color(235, 140, 235, 255)
end
Gui.slug_text(gui_handle, line, font, font_px, Vector3(cached.x, cached.top_y + line_h * (i - 1), 900), nil, col)
end
if not first_draw_logged then
first_draw_logged = true
mod:diag("OMNI-HUD first draw OK, lines=", n)
end
end
local function draw(dt)
if fail_cooldown > 0 then
fail_cooldown = fail_cooldown - 1
return
end
local ok, err = pcall(draw_inner, dt)
if not ok then
gui, gui_world, font_path = nil, nil, nil
fail_cooldown = DRAW_FAIL_BACKOFF
mod:diag("OMNI-HUD draw failed, backing off:", tostring(err))
end
end
mod.update = function (dt)
if not mod_settings.debug_hud or not mod_settings.toggle_mod then
return
end
rebuild_acc = rebuild_acc + dt
if rebuild_acc >= REBUILD_INTERVAL then
rebuild_acc = 0
local ok, err = pcall(mod.build_debug_hud_lines, mod)
if not ok and mod_settings.debug_mode and not mod.debug_hud_err_logged then
mod.debug_hud_err_logged = true
if not ok then
mod:diag("OMNI-HUD builder error:", tostring(err))
end
end
end)
draw(dt)
end
@@ -1,100 +0,0 @@
-- AutoMark debug HUD element: plain-text live status panel (fork feature).
-- Instantiated by the game HUD pipeline (UIHud._add_element requires this file
-- by definition.filename). Content is produced by AutoMark mod side
-- (ui/debug_hud.lua -> mod.debug_hud_last) so this file stays dumb.
local UIWorkspaceSettings = require("scripts/settings/ui/ui_workspace_settings")
local UIWidget = require("scripts/managers/ui/ui_widget")
require("scripts/foundation/utilities/color")
local mod = get_mod("AutoMark")
local HudElementAutoMarkDebug = class("HudElementAutoMarkDebug", "HudElementBase")
local panel_size = { 860, 800 }
local scenegraph_definition = {
screen = UIWorkspaceSettings.screen,
panel = {
horizontal_alignment = "left",
parent = "screen",
vertical_alignment = "middle",
size = panel_size,
position = {
25,
0,
10,
},
},
}
local widget_definitions = {
automark_debug = UIWidget.create_definition({
{
pass_type = "text",
style_id = "debug_text",
value_id = "debug_text",
style = {
drop_shadow = true,
font_size = 19,
font_type = "proxima_nova_bold",
horizontal_alignment = "left",
vertical_alignment = "top",
text_horizontal_alignment = "left",
text_vertical_alignment = "top",
line_spacing = 1.2,
size = panel_size,
offset = {
0,
0,
0,
},
text_color = Color.terminal_text_header(255, true),
},
},
}, "panel"),
}
HudElementAutoMarkDebug.init = function (self, parent, draw_layer, start_scale)
HudElementAutoMarkDebug.super.init(self, parent, draw_layer, start_scale, {
scenegraph_definition = scenegraph_definition,
widget_definitions = widget_definitions,
})
self._rebuild_time = 0
self._last_text = nil
end
HudElementAutoMarkDebug.update = function (self, dt, t, ui_renderer, render_settings, input_service)
HudElementAutoMarkDebug.super.update(self, dt, t, ui_renderer, render_settings, input_service)
self._rebuild_time = self._rebuild_time - dt
if self._rebuild_time > 0 then
return
end
self._rebuild_time = 0.2
local panel = self._ui_scenegraph and self._ui_scenegraph.panel
if panel and panel.local_position then
local lp = panel.local_position
local x = mod.settings.debug_hud_x or 25
local y = mod.settings.debug_hud_y or 0
if lp[1] ~= x or lp[2] ~= y then
lp[1] = x
lp[2] = y
end
end
local buf = mod.debug_hud_last
local text = table.concat(buf, "\n")
if text ~= self._last_text then
self._last_text = text
local widget = self._widgets_by_name.automark_debug
if widget then
widget.content.debug_text = text
widget.dirty = true
end
end
end
return HudElementAutoMarkDebug