From 00253de11f493d7cfad5badb25e75a5c674ec89a Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Sat, 5 Sep 2026 20:54:57 +0000 Subject: [PATCH 1/3] Show the cursor of the followed player in surveillance A label under the camera shows what the player being followed holds in their cursor, item and count or the ghost item, so that using a deconstruction planner or similar can be seen. It is hidden while the camera shows a fixed location. Closes #427 --- exp_scenario/module/gui/surveillance.lua | 58 ++++++++++++++++++++++++ exp_scenario/module/locale/en.cfg | 3 ++ exp_scenario/module/locale/zh-CN.cfg | 3 ++ exp_scenario/module/locale/zh-TW.cfg | 3 ++ 4 files changed, 67 insertions(+) diff --git a/exp_scenario/module/gui/surveillance.lua b/exp_scenario/module/gui/surveillance.lua index e9910723..05d9ee21 100644 --- a/exp_scenario/module/gui/surveillance.lua +++ b/exp_scenario/module/gui/surveillance.lua @@ -6,6 +6,8 @@ local Gui = require("modules/exp_gui") local ElementsExtra = require("modules/exp_scenario/gui/elements") local Roles = require("modules/exp_roles") +local format_string = string.format + --- @class ExpGui_Surveillance.elements local Elements = {} @@ -206,6 +208,60 @@ function Elements.camera.refresh_online() end end +--- Label showing what the player a camera follows holds in their cursor +--- @class ExpGui_Surveillance.elements.cursor_label: ExpElement +--- @field data table The camera the label belongs to +--- @overload fun(parent: LuaGuiElement, camera: LuaGuiElement): LuaGuiElement +Elements.cursor_label = Gui.define("surveillance/cursor_label") + :track_all_elements() + :draw{ + type = "label", + caption = { "exp-gui_surveillance.caption-cursor-empty" }, + } + :style{ + width = 480, + } + :element_data( + Gui.from_argument(1) + ) --[[@as any]] + +--- Calculate the caption describing the cursor of a player +--- @param player LuaPlayer +--- @return LocalisedString +function Elements.cursor_label.calculate_caption(player) + local cursor_stack = player.cursor_stack + if cursor_stack and cursor_stack.valid_for_read then + local item = format_string("[item=%s,quality=%s]", cursor_stack.name, cursor_stack.quality.name) + return { "exp-gui_surveillance.caption-cursor", item, cursor_stack.count } + end + + local cursor_ghost = player.cursor_ghost --[[@as ItemIDAndQualityIDPair?]] + if cursor_ghost then + local prototype = cursor_ghost.name --[[@as LuaItemPrototype]] + return { "exp-gui_surveillance.caption-cursor-ghost", "[item=" .. prototype.name .. "]" } + end + + return { "exp-gui_surveillance.caption-cursor-empty" } +end + +--- Refresh a label, hidden when the camera is not following a player +--- @param cursor_label LuaGuiElement +--- @param target_player LuaPlayer? +function Elements.cursor_label.refresh(cursor_label, target_player) + cursor_label.visible = target_player ~= nil + if target_player then + cursor_label.caption = Elements.cursor_label.calculate_caption(target_player) + end +end + +--- Refresh the labels of all online cameras +function Elements.cursor_label.refresh_online() + for _, cursor_label in Elements.cursor_label:online_elements() do + local camera = Elements.cursor_label.data[cursor_label] + Elements.cursor_label.refresh(cursor_label, Elements.camera.data[camera]) + end +end + --- Container added to the screen Elements.container = Gui.define("surveillance/container") :draw(function(def, parent) @@ -214,6 +270,7 @@ Elements.container = Gui.define("surveillance/container") local target_player = Gui.get_player(parent) local camera = Elements.camera(screen_frame, target_player) + Elements.cursor_label(screen_frame, camera) local type_dropdown_data = { camera = camera, @@ -248,6 +305,7 @@ return { [e.on_tick] = Elements.camera.refresh_online, }, on_nth_tick = { + [10] = Elements.cursor_label.refresh_online, [600] = Elements.type_dropdown.refresh_online, } } diff --git a/exp_scenario/module/locale/en.cfg b/exp_scenario/module/locale/en.cfg index 80e8a1ec..eda13142 100644 --- a/exp_scenario/module/locale/en.cfg +++ b/exp_scenario/module/locale/en.cfg @@ -489,6 +489,9 @@ caption-set-location=Set type-player=Player type-static=Static type-loop=Loop +caption-cursor=Cursor: __1__ x__2__ +caption-cursor-ghost=Cursor: __1__ (ghost) +caption-cursor-empty=Cursor: empty [exp-gui_task-list] caption-main=Task List [img=info] diff --git a/exp_scenario/module/locale/zh-CN.cfg b/exp_scenario/module/locale/zh-CN.cfg index bf65db42..29343d58 100644 --- a/exp_scenario/module/locale/zh-CN.cfg +++ b/exp_scenario/module/locale/zh-CN.cfg @@ -474,6 +474,9 @@ caption-set-location=設 type-player=用戶 type-static=靜態 type-loop=循環 +caption-cursor=游標: __1__ x__2__ +caption-cursor-ghost=游標: __1__ (幻影) +caption-cursor-empty=游標: 空 [exp-gui_task-list] caption-main=工作流程 [img=info] diff --git a/exp_scenario/module/locale/zh-TW.cfg b/exp_scenario/module/locale/zh-TW.cfg index bf65db42..29343d58 100644 --- a/exp_scenario/module/locale/zh-TW.cfg +++ b/exp_scenario/module/locale/zh-TW.cfg @@ -474,6 +474,9 @@ caption-set-location=設 type-player=用戶 type-static=靜態 type-loop=循環 +caption-cursor=游標: __1__ x__2__ +caption-cursor-ghost=游標: __1__ (幻影) +caption-cursor-empty=游標: 空 [exp-gui_task-list] caption-main=工作流程 [img=info] From 4d976a9e352d40607882ecad9f35e2442d054e17 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Sun, 6 Sep 2026 00:24:28 +0000 Subject: [PATCH 2/3] Show the cursor as a slot in the surveillance header The cursor was a line of text under the camera, which read as an afterthought next to the framed header. It is now a slot button in the header after the zoom buttons: the item sprite with its count, greyed out for a ghost, an empty slot for an empty cursor, and the item name in the tooltip. The window keeps its shape whatever the item is called. --- exp_scenario/module/gui/surveillance.lua | 76 ++++++++++++++---------- exp_scenario/module/locale/en.cfg | 6 +- exp_scenario/module/locale/zh-CN.cfg | 6 +- exp_scenario/module/locale/zh-TW.cfg | 6 +- 4 files changed, 55 insertions(+), 39 deletions(-) diff --git a/exp_scenario/module/gui/surveillance.lua b/exp_scenario/module/gui/surveillance.lua index 05d9ee21..11057e0a 100644 --- a/exp_scenario/module/gui/surveillance.lua +++ b/exp_scenario/module/gui/surveillance.lua @@ -6,8 +6,6 @@ local Gui = require("modules/exp_gui") local ElementsExtra = require("modules/exp_scenario/gui/elements") local Roles = require("modules/exp_roles") -local format_string = string.format - --- @class ExpGui_Surveillance.elements local Elements = {} @@ -208,57 +206,75 @@ function Elements.camera.refresh_online() end end ---- Label showing what the player a camera follows holds in their cursor ---- @class ExpGui_Surveillance.elements.cursor_label: ExpElement ---- @field data table The camera the label belongs to +--- Slot in the header showing what the player a camera follows holds in their cursor +--- @class ExpGui_Surveillance.elements.cursor_slot: ExpElement +--- @field data table The camera the slot belongs to --- @overload fun(parent: LuaGuiElement, camera: LuaGuiElement): LuaGuiElement -Elements.cursor_label = Gui.define("surveillance/cursor_label") +Elements.cursor_slot = Gui.define("surveillance/cursor_slot") :track_all_elements() :draw{ - type = "label", - caption = { "exp-gui_surveillance.caption-cursor-empty" }, + type = "sprite-button", + style = "slot_button", + tooltip = { "exp-gui_surveillance.tooltip-cursor-empty" }, } :style{ - width = 480, + height = 24, + width = 24, + padding = 0, } :element_data( Gui.from_argument(1) ) --[[@as any]] ---- Calculate the caption describing the cursor of a player +--- @class ExpGui_Surveillance.elements.cursor_slot.display_data +--- @field sprite SpritePath? Nil when the cursor is empty +--- @field number number? Nil for a ghost, which has no count +--- @field tooltip LocalisedString + +--- Calculate what a slot shows for the cursor of a player --- @param player LuaPlayer ---- @return LocalisedString -function Elements.cursor_label.calculate_caption(player) +--- @return ExpGui_Surveillance.elements.cursor_slot.display_data +function Elements.cursor_slot.calculate_display_data(player) local cursor_stack = player.cursor_stack if cursor_stack and cursor_stack.valid_for_read then - local item = format_string("[item=%s,quality=%s]", cursor_stack.name, cursor_stack.quality.name) - return { "exp-gui_surveillance.caption-cursor", item, cursor_stack.count } + return { + sprite = "item/" .. cursor_stack.name, + number = cursor_stack.count, + tooltip = { "exp-gui_surveillance.tooltip-cursor", cursor_stack.prototype.localised_name, cursor_stack.count }, + } end local cursor_ghost = player.cursor_ghost --[[@as ItemIDAndQualityIDPair?]] if cursor_ghost then local prototype = cursor_ghost.name --[[@as LuaItemPrototype]] - return { "exp-gui_surveillance.caption-cursor-ghost", "[item=" .. prototype.name .. "]" } + return { + sprite = "item/" .. prototype.name, + tooltip = { "exp-gui_surveillance.tooltip-cursor-ghost", prototype.localised_name }, + } end - return { "exp-gui_surveillance.caption-cursor-empty" } + return { tooltip = { "exp-gui_surveillance.tooltip-cursor-empty" } } end ---- Refresh a label, hidden when the camera is not following a player ---- @param cursor_label LuaGuiElement +--- Refresh a slot, a ghost is shown greyed out and the slot is hidden when the camera is not following a player +--- @param cursor_slot LuaGuiElement --- @param target_player LuaPlayer? -function Elements.cursor_label.refresh(cursor_label, target_player) - cursor_label.visible = target_player ~= nil - if target_player then - cursor_label.caption = Elements.cursor_label.calculate_caption(target_player) - end +function Elements.cursor_slot.refresh(cursor_slot, target_player) + cursor_slot.visible = target_player ~= nil + if not target_player then return end + + local display_data = Elements.cursor_slot.calculate_display_data(target_player) + cursor_slot.sprite = display_data.sprite or "" + cursor_slot.number = display_data.number + cursor_slot.tooltip = display_data.tooltip + cursor_slot.enabled = display_data.sprite == nil or display_data.number ~= nil end ---- Refresh the labels of all online cameras -function Elements.cursor_label.refresh_online() - for _, cursor_label in Elements.cursor_label:online_elements() do - local camera = Elements.cursor_label.data[cursor_label] - Elements.cursor_label.refresh(cursor_label, Elements.camera.data[camera]) +--- Refresh the slots of all online cameras +function Elements.cursor_slot.refresh_online() + for _, cursor_slot in Elements.cursor_slot:online_elements() do + local camera = Elements.cursor_slot.data[cursor_slot] + Elements.cursor_slot.refresh(cursor_slot, Elements.camera.data[camera]) end end @@ -270,7 +286,6 @@ Elements.container = Gui.define("surveillance/container") local target_player = Gui.get_player(parent) local camera = Elements.camera(screen_frame, target_player) - Elements.cursor_label(screen_frame, camera) local type_dropdown_data = { camera = camera, @@ -281,6 +296,7 @@ Elements.container = Gui.define("surveillance/container") Elements.type_dropdown(button_flow, type_dropdown_data) Elements.zoom_out_button(button_flow, camera) Elements.zoom_in_button(button_flow, camera) + Elements.cursor_slot(button_flow, camera) return Gui.elements.screen_frame.get_root_element(screen_frame) end) @@ -305,7 +321,7 @@ return { [e.on_tick] = Elements.camera.refresh_online, }, on_nth_tick = { - [10] = Elements.cursor_label.refresh_online, + [10] = Elements.cursor_slot.refresh_online, [600] = Elements.type_dropdown.refresh_online, } } diff --git a/exp_scenario/module/locale/en.cfg b/exp_scenario/module/locale/en.cfg index eda13142..baaab220 100644 --- a/exp_scenario/module/locale/en.cfg +++ b/exp_scenario/module/locale/en.cfg @@ -489,9 +489,9 @@ caption-set-location=Set type-player=Player type-static=Static type-loop=Loop -caption-cursor=Cursor: __1__ x__2__ -caption-cursor-ghost=Cursor: __1__ (ghost) -caption-cursor-empty=Cursor: empty +tooltip-cursor=__1__ x__2__ +tooltip-cursor-ghost=__1__ (ghost) +tooltip-cursor-empty=Empty cursor [exp-gui_task-list] caption-main=Task List [img=info] diff --git a/exp_scenario/module/locale/zh-CN.cfg b/exp_scenario/module/locale/zh-CN.cfg index 29343d58..8a8a26e8 100644 --- a/exp_scenario/module/locale/zh-CN.cfg +++ b/exp_scenario/module/locale/zh-CN.cfg @@ -474,9 +474,9 @@ caption-set-location=設 type-player=用戶 type-static=靜態 type-loop=循環 -caption-cursor=游標: __1__ x__2__ -caption-cursor-ghost=游標: __1__ (幻影) -caption-cursor-empty=游標: 空 +tooltip-cursor=__1__ x__2__ +tooltip-cursor-ghost=__1__ (幻影) +tooltip-cursor-empty=游標為空 [exp-gui_task-list] caption-main=工作流程 [img=info] diff --git a/exp_scenario/module/locale/zh-TW.cfg b/exp_scenario/module/locale/zh-TW.cfg index 29343d58..8a8a26e8 100644 --- a/exp_scenario/module/locale/zh-TW.cfg +++ b/exp_scenario/module/locale/zh-TW.cfg @@ -474,9 +474,9 @@ caption-set-location=設 type-player=用戶 type-static=靜態 type-loop=循環 -caption-cursor=游標: __1__ x__2__ -caption-cursor-ghost=游標: __1__ (幻影) -caption-cursor-empty=游標: 空 +tooltip-cursor=__1__ x__2__ +tooltip-cursor-ghost=__1__ (幻影) +tooltip-cursor-empty=游標為空 [exp-gui_task-list] caption-main=工作流程 [img=info] From 1de50da0be09758240636ff155ddf60f19773054 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Sun, 6 Sep 2026 01:44:40 +0100 Subject: [PATCH 3/3] Adjust button formatting --- exp_scenario/module/gui/surveillance.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/exp_scenario/module/gui/surveillance.lua b/exp_scenario/module/gui/surveillance.lua index 11057e0a..11b1cd58 100644 --- a/exp_scenario/module/gui/surveillance.lua +++ b/exp_scenario/module/gui/surveillance.lua @@ -218,9 +218,9 @@ Elements.cursor_slot = Gui.define("surveillance/cursor_slot") tooltip = { "exp-gui_surveillance.tooltip-cursor-empty" }, } :style{ - height = 24, - width = 24, - padding = 0, + height = 26, + width = 26, + margin = { -1, -1, -1, -2 }, } :element_data( Gui.from_argument(1) @@ -286,6 +286,7 @@ Elements.container = Gui.define("surveillance/container") local target_player = Gui.get_player(parent) local camera = Elements.camera(screen_frame, target_player) + Elements.cursor_slot(button_flow, camera) local type_dropdown_data = { camera = camera, @@ -296,7 +297,6 @@ Elements.container = Gui.define("surveillance/container") Elements.type_dropdown(button_flow, type_dropdown_data) Elements.zoom_out_button(button_flow, camera) Elements.zoom_in_button(button_flow, camera) - Elements.cursor_slot(button_flow, camera) return Gui.elements.screen_frame.get_root_element(screen_frame) end)