diff --git a/AutoMark/scripts/mods/AutoMark/AutoMark_localization.lua b/AutoMark/scripts/mods/AutoMark/AutoMark_localization.lua index 5330f28..290e399 100644 --- a/AutoMark/scripts/mods/AutoMark/AutoMark_localization.lua +++ b/AutoMark/scripts/mods/AutoMark/AutoMark_localization.lua @@ -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", diff --git a/AutoMark/scripts/mods/AutoMark/ui/debug_hud.lua b/AutoMark/scripts/mods/AutoMark/ui/debug_hud.lua index 506d1a6..bfe324a 100644 --- a/AutoMark/scripts/mods/AutoMark/ui/debug_hud.lua +++ b/AutoMark/scripts/mods/AutoMark/ui/debug_hud.lua @@ -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 - 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) - --- 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 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 + return nil +end - if want ~= last_debug_hud then - last_debug_hud = want - recreate_timer = 0.5 - 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 recreate_timer <= 0 then - 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 - recreate_timer = recreate_timer - dt - if recreate_timer > 0 then - return +local function ensure_font() + if font_path then + return font_path 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 - end - if self._player.viewport_name ~= "player1" then - return - end +local cached = { x = 0, top_y = 0, line_h = 0, font_px = 0, panel_w = 0, panel_h = 0, pad = 0 } - hud_build_timer = hud_build_timer - dt - if hud_build_timer <= 0 then - hud_build_timer = 0.2 - 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 - mod:diag("OMNI-HUD builder error:", tostring(err)) - end +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 + return #text * font_px * 0.55 +end + +local function draw_inner(dt) + local gui_handle = ensure_gui() + if not gui_handle then + return + end + + 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) + 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 then + mod:diag("OMNI-HUD builder error:", tostring(err)) + end + end + + draw(dt) +end diff --git a/AutoMark/scripts/mods/AutoMark/ui/hud_element_debug.lua b/AutoMark/scripts/mods/AutoMark/ui/hud_element_debug.lua deleted file mode 100644 index d2997ba..0000000 --- a/AutoMark/scripts/mods/AutoMark/ui/hud_element_debug.lua +++ /dev/null @@ -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