From b1c0f2535bc2ac743821996eeea42e44a33e36bf Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Sat, 20 Jun 2026 12:07:03 +0000 Subject: [PATCH 01/33] Migrate player list gui to scenario Move modules/gui/player-list into exp_scenario/gui/player_list using the new exp_gui element API, matching the other scenario guis. - Store the selected player and action on the container's player data instead of a datastore, folding the datastore on_update reactivity into explicit setter functions - Switch the actions config to injected accessor functions and the exp-gui_player-list locale namespace - Add the locale section, register in control.lua, and disable the legacy module in the file loader Co-Authored-By: Claude Opus 4.8 (1M context) --- exp_legacy/module/config/_file_loader.lua | 2 +- .../module/config/gui/player_list_actions.lua | 35 +- exp_scenario/module/control.lua | 1 + exp_scenario/module/gui/player_list.lua | 518 ++++++++++++++++++ exp_scenario/module/locale/en.cfg | 16 + 5 files changed, 554 insertions(+), 18 deletions(-) create mode 100644 exp_scenario/module/gui/player_list.lua diff --git a/exp_legacy/module/config/_file_loader.lua b/exp_legacy/module/config/_file_loader.lua index 91be3f15..45461057 100644 --- a/exp_legacy/module/config/_file_loader.lua +++ b/exp_legacy/module/config/_file_loader.lua @@ -24,7 +24,7 @@ return { --- GUI "modules.gui.rocket-info", "modules.gui.warp-list", - "modules.gui.player-list", + -- "modules.gui.player-list", -- migrated to exp_scenario/gui/player_list "modules.gui.vlayer", "modules.gui._role_updates", diff --git a/exp_legacy/module/config/gui/player_list_actions.lua b/exp_legacy/module/config/gui/player_list_actions.lua index 3c1d675c..2e5e2094 100644 --- a/exp_legacy/module/config/gui/player_list_actions.lua +++ b/exp_legacy/module/config/gui/player_list_actions.lua @@ -14,9 +14,10 @@ local Jail = require("modules.exp_legacy.modules.control.jail") --- @dep modules local Colors = require("modules/exp_util/include/color") local format_player_name = ExpUtil.format_player_name_locale -local SelectedPlayer, SelectedAction -local function set_datastores(player, action) - SelectedPlayer, SelectedAction = player, action +--- Accessors injected by the gui so the actions can read the selected player and set the selected action +local get_selected_player, set_selected_action +local function set_accessors(player_getter, action_setter) + get_selected_player, set_selected_action = player_getter, action_setter end -- auth that will only allow when on player's of lower roles @@ -30,7 +31,7 @@ end -- gets the action player and a coloured name for the action to be used on local function get_action_player_name(player) - local selected_player_name = SelectedPlayer:get(player) + local selected_player_name = get_selected_player(player) local selected_player = game.players[selected_player_name] local selected_player_color = format_player_name(selected_player) return selected_player_name, selected_player_color @@ -63,7 +64,7 @@ end --- Teleports the user to the action player -- @element goto_player -local goto_player = new_button("utility/export", { "player-list.goto-player" }) +local goto_player = new_button("utility/export", { "exp-gui_player-list.goto-player" }) :on_click(function(def, player, element) local selected_player_name = get_action_player_name(player) local selected_player = game.players[selected_player_name] @@ -76,7 +77,7 @@ local goto_player = new_button("utility/export", { "player-list.goto-player" }) --- Teleports the action player to the user -- @element bring_player -local bring_player = new_button("utility/import", { "player-list.bring-player" }) +local bring_player = new_button("utility/import", { "exp-gui_player-list.bring-player" }) :on_click(function(def, player, element) local selected_player_name = get_action_player_name(player) local selected_player = game.players[selected_player_name] @@ -89,13 +90,13 @@ local bring_player = new_button("utility/import", { "player-list.bring-player" } --- Reports the action player, requires a reason to be given -- @element report_player -local report_player = new_button("utility/spawn_flag", { "player-list.report-player" }) +local report_player = new_button("utility/spawn_flag", { "exp-gui_player-list.report-player" }) :on_click(function(def, player, element) local selected_player_name = get_action_player_name(player) if Reports.is_reported(selected_player_name, player.name) then player.print({ "exp-commands_report.already-reported" }, Colors.orange_red) else - SelectedAction:set(player, "command/report") + set_selected_action(player, "command/report") end end) @@ -109,9 +110,9 @@ end --- Gives the action player a warning, requires a reason -- @element warn_player -local warn_player = new_button("utility/spawn_flag", { "player-list.warn-player" }) +local warn_player = new_button("utility/spawn_flag", { "exp-gui_player-list.warn-player" }) :on_click(function(def, player, element) - SelectedAction:set(player, "command/give-warning") + set_selected_action(player, "command/give-warning") end) local function warn_player_callback(player, reason) @@ -123,13 +124,13 @@ end --- Jails the action player, requires a reason -- @element jail_player -local jail_player = new_button("utility/multiplayer_waiting_icon", { "player-list.jail-player" }) +local jail_player = new_button("utility/multiplayer_waiting_icon", { "exp-gui_player-list.jail-player" }) :on_click(function(def, player, element) local selected_player_name, selected_player_color = get_action_player_name(player) if Jail.is_jailed(selected_player_name) then player.print({ "exp-commands_jail.already-jailed", selected_player_color }, Colors.orange_red) else - SelectedAction:set(player, "command/jail") + set_selected_action(player, "command/jail") end end) @@ -142,9 +143,9 @@ end --- Kicks the action player, requires a reason -- @element kick_player -local kick_player = new_button("utility/warning_icon", { "player-list.kick-player" }) +local kick_player = new_button("utility/warning_icon", { "exp-gui_player-list.kick-player" }) :on_click(function(def, player, element) - SelectedAction:set(player, "command/kick") + set_selected_action(player, "command/kick") end) local function kick_player_callback(player, reason) @@ -154,9 +155,9 @@ end --- Bans the action player, requires a reason -- @element ban_player -local ban_player = new_button("utility/danger_icon", { "player-list.ban-player" }) +local ban_player = new_button("utility/danger_icon", { "exp-gui_player-list.ban-player" }) :on_click(function(def, player, element) - SelectedAction:set(player, "command/ban") + set_selected_action(player, "command/ban") end) local function ban_player_callback(player, reason) @@ -165,7 +166,7 @@ local function ban_player_callback(player, reason) end return { - set_datastores = set_datastores, + set_accessors = set_accessors, buttons = { ["command/teleport"] = { auth = function(player, selected_player) diff --git a/exp_scenario/module/control.lua b/exp_scenario/module/control.lua index 65e35d78..83fe6e25 100644 --- a/exp_scenario/module/control.lua +++ b/exp_scenario/module/control.lua @@ -73,6 +73,7 @@ add(require("modules/exp_scenario/gui/elements")) add(require("modules/exp_scenario/gui/landfill_blueprint")) add(require("modules/exp_scenario/gui/module_inserter")) add(require("modules/exp_scenario/gui/player_bonus")) +add(require("modules/exp_scenario/gui/player_list")) add(require("modules/exp_scenario/gui/player_stats")) add(require("modules/exp_scenario/gui/production_stats")) add(require("modules/exp_scenario/gui/quick_actions")) diff --git a/exp_scenario/module/gui/player_list.lua b/exp_scenario/module/gui/player_list.lua new file mode 100644 index 00000000..0dc4583d --- /dev/null +++ b/exp_scenario/module/gui/player_list.lua @@ -0,0 +1,518 @@ +--[[-- Gui - Player List +Adds a player list to show names and play time; also includes action buttons which can perform actions on players. +The selected player and action used to be stored in a datastore; they are now kept on the container's player data. +]] + +local ExpUtil = require("modules/exp_util") +local Gui = require("modules/exp_gui") +local Roles = require("modules/exp_legacy/expcore/roles") +local config = require("modules/exp_legacy/config/gui/player_list_actions") + +--- @class ExpGui_PlayerList.elements +local Elements = {} + +local online_time_format = ExpUtil.format_time_factory_locale{ format = "short", hours = true, minutes = true } +local afk_time_format = ExpUtil.format_time_factory_locale{ format = "long", minutes = true } + +--[[ +The selected player (the player an action will be performed on) and the selected action (which opens the reason bar) +are stored per viewing player on the container's player data. The setters drive the gui updates directly, replacing +the reactive datastore on_update callbacks used by the legacy version. +]] + +--- @class ExpGui_PlayerList.selection +--- @field selected_player string? Name of the player actions will be performed on +--- @field selected_action string? Name of the action awaiting a reason + +--- Get the selection state for a player, creating it if needed +--- @param player LuaPlayer +--- @return ExpGui_PlayerList.selection +local function get_selection(player) + local selection = Elements.container.data[player] + if not selection then + selection = {} + Elements.container.data[player] = selection + end + return selection +end + +--- Get the name of the player that actions will be performed on +--- @param player LuaPlayer +--- @return string? +local function get_selected_player(player) + return get_selection(player).selected_player +end + +--- Get the name of the action awaiting a reason +--- @param player LuaPlayer +--- @return string? +local function get_selected_action(player) + return get_selection(player).selected_action +end + +--- Style an open action bar button to show if it is the selected player +--- @param button LuaGuiElement +--- @param selected boolean +local function style_open_button(button, selected) + button.style = selected and "tool_button" or "frame_button" + local style = button.style + style.padding = -2 + style.width = 8 + style.height = 14 +end + +--- Update the action bar buttons to match the current selection for a player +--- @param action_bar LuaGuiElement +--- @param player LuaPlayer +--- @param selected_player LuaPlayer +local function update_action_bar(action_bar, player, selected_player) + action_bar.visible = true + for action_name, buttons in pairs(config.buttons) do + local flow = action_bar[action_name] + if buttons.auth and not buttons.auth(player, selected_player) then + flow.visible = false + else + flow.visible = Roles.player_allowed(player, action_name) + end + end +end + +--- Refresh all gui elements for a player to match their current selection +--- @param player LuaPlayer +local function update_player_ui(player) + local selection = get_selection(player) + + -- Clear the selection if the selected player has gone offline + local selected_player = selection.selected_player and game.players[selection.selected_player] + if selected_player and not selected_player.connected then + selection.selected_player = nil + selection.selected_action = nil + selected_player = nil + end + + for _, player_table in Elements.player_table:online_elements(player) do + local refs = Elements.player_table.data[player_table] + + -- Update the action bar and reason bar visibility + if selected_player then + update_action_bar(refs.action_bar, player, selected_player) + else + refs.action_bar.visible = false + end + refs.reason_bar.visible = selected_player ~= nil and selection.selected_action ~= nil + + -- Highlight the open button of the selected player + for player_name, row in pairs(refs.rows) do + style_open_button(row.open_button, player_name == selection.selected_player) + end + end +end + +--- Set the player that actions will be performed on, nil to clear the selection +--- @param player LuaPlayer +--- @param selected_player_name string? +local function set_selected_player(player, selected_player_name) + local selection = get_selection(player) + selection.selected_player = selected_player_name + if not selected_player_name then + selection.selected_action = nil + end + update_player_ui(player) +end + +--- Toggle the player that actions will be performed on +--- @param player LuaPlayer +--- @param selected_player_name string +local function toggle_selected_player(player, selected_player_name) + if get_selected_player(player) == selected_player_name then + set_selected_player(player, nil) + else + set_selected_player(player, selected_player_name) + end +end + +--- Set the action awaiting a reason, nil to clear it +--- @param player LuaPlayer +--- @param selected_action string? +local function set_selected_action(player, selected_action) + get_selection(player).selected_action = selected_action + update_player_ui(player) +end + +-- Inject the accessors the actions config needs to read the selected player and set the selected action +config.set_accessors(get_selected_player, set_selected_action) + +--- Button used to open the action bar for a player +--- @class ExpGui_PlayerList.elements.open_action_bar: ExpElement +--- @field data table +--- @overload fun(parent: LuaGuiElement, player_name: string): LuaGuiElement +Elements.open_action_bar = Gui.define("player_list/open_action_bar") + :draw{ + type = "sprite-button", + sprite = "utility/expand_dots", + tooltip = { "exp-gui_player-list.open-action-bar" }, + style = "frame_button", + } + :style{ + padding = -2, + width = 8, + height = 14, + } + :element_data( + Gui.from_argument(1) + ) + :on_click(function(def, player, element) + --- @cast def ExpGui_PlayerList.elements.open_action_bar + toggle_selected_player(player, def.data[element]) + end) --[[ @as any ]] + +--- Button used to close the action bar +--- @class ExpGui_PlayerList.elements.close_action_bar: ExpElement +--- @overload fun(parent: LuaGuiElement): LuaGuiElement +Elements.close_action_bar = Gui.define("player_list/close_action_bar") + :draw{ + type = "sprite-button", + sprite = "utility/close_black", + tooltip = { "exp-gui_player-list.close-action-bar" }, + style = "slot_sized_button_red", + } + :style(Gui.styles.sprite{ + size = 20, + padding = -1, + top_margin = -1, + right_margin = -1, + }) + :on_click(function(def, player, element) + set_selected_player(player, nil) + end) --[[ @as any ]] + +--- Button used to confirm a reason +--- @class ExpGui_PlayerList.elements.reason_confirm: ExpElement +--- @overload fun(parent: LuaGuiElement): LuaGuiElement +Elements.reason_confirm = Gui.define("player_list/reason_confirm") + :draw{ + type = "sprite-button", + sprite = "utility/confirm_slot", + tooltip = { "exp-gui_player-list.reason-confirm" }, + style = "slot_sized_button_green", + } + :style(Gui.styles.sprite{ + size = 30, + padding = -1, + left_margin = -2, + right_margin = -1, + }) + :on_click(function(def, player, element) + local action_name = get_selected_action(player) + local button_data = action_name and config.buttons[action_name] + if button_data and button_data.reason_callback then + local reason = element.parent.entry.text + if reason == nil or not reason:find("%S") then reason = "no reason given" end + button_data.reason_callback(player, reason) + end + element.parent.entry.text = "" + set_selected_player(player, nil) + end) --[[ @as any ]] + +--- Clickable player name label, left click opens the map and right click toggles the action bar +--- @class ExpGui_PlayerList.elements.player_name_label: ExpElement +--- @field data table +--- @overload fun(parent: LuaGuiElement, opts: { name: string, tooltip: LocalisedString }): LuaGuiElement +Elements.player_name_label = Gui.define("player_list/player_name_label") + :draw{ + type = "label", + caption = Gui.from_argument("name"), + tooltip = Gui.from_argument("tooltip"), + } + :style{ + padding = { 0, 2, 0, 0 }, + } + :element_data( + Gui.from_argument("name") + ) + :on_click(function(def, player, element, event) + --- @cast def ExpGui_PlayerList.elements.player_name_label + local selected_player_name = def.data[element] + local selected_player = game.players[selected_player_name] + if event.button == defines.mouse_button_type.left then + -- Left click opens remote view at the player + player.set_controller{ + type = defines.controllers.remote, + position = selected_player.physical_position, + surface = selected_player.physical_surface, + } + else + -- Right click toggles the action bar + toggle_selected_player(player, selected_player_name) + end + end) --[[ @as any ]] + +--- @class ExpGui_PlayerList.player_data +--- @field name string +--- @field tag string +--- @field role_name string +--- @field chat_color Color +--- @field caption LocalisedString +--- @field tooltip LocalisedString + +--- Get the caption and tooltip for a player time +--- @param online_time number +--- @param afk_time number +--- @return LocalisedString, LocalisedString +local function get_time_formats(online_time, afk_time) + local tick = game.tick > 0 and game.tick or 1 + local percent = math.round(online_time / tick, 3) * 100 + local caption = online_time_format(online_time) + local tooltip = { "exp-gui_player-list.afk-time", percent, afk_time_format(afk_time) } + return caption, tooltip +end + +--- Get a list of all online players sorted by their highest role +--- @return ExpGui_PlayerList.player_data[] +local function get_player_list_order() + -- Sort all the online players into roles + local players = {} + for _, player in pairs(game.connected_players) do + local highest_role = Roles.get_player_highest_role(player) + if not players[highest_role.name] then + players[highest_role.name] = {} + end + table.insert(players[highest_role.name], player) + end + + -- Flatten the roles into a single ordered list + local count = 0 + local player_list_order = {} + for _, role_name in pairs(Roles.config.order) do + if players[role_name] then + for _, player in pairs(players[role_name]) do + count = count + 1 + local caption, tooltip = get_time_formats(player.online_time, player.afk_time) + player_list_order[count] = { + name = player.name, + tag = player.tag, + role_name = role_name, + chat_color = player.chat_color, + caption = caption, + tooltip = tooltip, + } + end + end + end + + return player_list_order +end + +--- @class ExpGui_PlayerList.elements.player_table.row +--- @field open_button LuaGuiElement +--- @field name_label LuaGuiElement +--- @field time_label LuaGuiElement + +--- @class ExpGui_PlayerList.elements.player_table.data +--- @field rows table +--- @field action_bar LuaGuiElement +--- @field reason_bar LuaGuiElement + +--- Scroll table containing a row for each online player +--- @class ExpGui_PlayerList.elements.player_table: ExpElement +--- @field data table +--- @overload fun(parent: LuaGuiElement): LuaGuiElement +Elements.player_table = Gui.define("player_list/player_table") + :track_all_elements() + :draw(function(def, parent) + local scroll_table = Gui.elements.scroll_table(parent, 184, 3, "scroll") + scroll_table.style.padding = { 1, 0, 1, 2 } + return scroll_table + end) --[[ @as any ]] + +--- Add a player row to the table and store its elements +--- @param player_table LuaGuiElement +--- @param player_data ExpGui_PlayerList.player_data +function Elements.player_table.add_row(player_table, player_data) + local rows = Elements.player_table.data[player_table].rows + + -- Add the button used to open the action bar + local open_flow = player_table.add{ type = "flow" } + local open_button = Elements.open_action_bar(open_flow, player_data.name) + + -- Add the clickable player name + local name_label = Elements.player_name_label(player_table, { + name = player_data.name, + tooltip = { "exp-gui_player-list.open-map", player_data.name, player_data.tag, player_data.role_name }, + }) + name_label.style.font_color = player_data.chat_color + + -- Add the time played label + local alignment = Gui.elements.aligned_flow(player_table) + local time_label = alignment.add{ + type = "label", + caption = player_data.caption, + tooltip = player_data.tooltip, + } + time_label.style.padding = 0 + + rows[player_data.name] = { open_button = open_button, name_label = name_label, time_label = time_label } +end + +--- Rebuild all rows of a player table, used when the sort order changes +--- @param player_table LuaGuiElement +function Elements.player_table.rebuild(player_table) + player_table.clear() + Elements.player_table.data[player_table].rows = {} + for _, player_data in ipairs(get_player_list_order()) do + Elements.player_table.add_row(player_table, player_data) + end +end + +--- Remove a single player row from the table +--- @param player_table LuaGuiElement +--- @param player_name string +function Elements.player_table.remove_row(player_table, player_name) + local rows = Elements.player_table.data[player_table].rows + local row = rows[player_name] + if not row then return end + rows[player_name] = nil + Gui.destroy_if_valid(row.open_button.parent) + Gui.destroy_if_valid(row.name_label) + Gui.destroy_if_valid(row.time_label.parent) +end + +--- Refresh the time labels for all rows of a player table +--- @param player_table LuaGuiElement +function Elements.player_table.refresh_times(player_table) + for player_name, row in pairs(Elements.player_table.data[player_table].rows) do + local listed_player = game.players[player_name] + if listed_player and listed_player.connected then + local caption, tooltip = get_time_formats(listed_player.online_time, listed_player.afk_time) + row.time_label.caption = caption + row.time_label.tooltip = tooltip + end + end +end + +--- Build the action bar footer containing the close button and the per action button flows +--- @param container LuaGuiElement +--- @return LuaGuiElement +local function build_action_bar(container) + local action_bar = Gui.elements.subframe_base(container, "subfooter_frame", "action_bar") + local style = action_bar.style + style.height = 35 + style.padding = { 1, 3 } + action_bar.visible = false + + Elements.close_action_bar(action_bar) + for action_name, button_data in pairs(config.buttons) do + local permission_flow = action_bar.add{ type = "flow", name = action_name } + permission_flow.visible = false + for _, button in ipairs(button_data) do + button(permission_flow) + end + end + + return action_bar +end + +--- Build the reason bar footer containing the reason entry and confirm button +--- @param container LuaGuiElement +--- @return LuaGuiElement +local function build_reason_bar(container) + local reason_bar = Gui.elements.subframe_base(container, "subfooter_frame", "reason_bar") + local style = reason_bar.style + style.height = 35 + style.padding = { -1, 3 } + reason_bar.visible = false + + local reason_field = reason_bar.add{ + name = "entry", + type = "textfield", + style = "stretchable_textfield", + tooltip = { "exp-gui_player-list.reason-entry" }, + } + local entry_style = reason_field.style + entry_style.padding = 0 + entry_style.height = 28 + entry_style.minimal_width = 160 + + Elements.reason_confirm(reason_bar) + + return reason_bar +end + +--- Container added to the left gui flow +--- @class ExpGui_PlayerList.elements.container: ExpElement +--- @field data table +Elements.container = Gui.define("player_list/container") + :draw(function(def, parent) + local container = Gui.elements.container(parent) + + local player = Gui.get_player(parent) + def.data[player] = def.data[player] or {} -- Selection state for this player + + local player_table = Elements.player_table(container) + local action_bar = build_action_bar(container) + local reason_bar = build_reason_bar(container) + + Elements.player_table.data[player_table] = { + rows = {}, + action_bar = action_bar, + reason_bar = reason_bar, + } + Elements.player_table.rebuild(player_table) + + return Gui.elements.container.get_root_element(container) + end) --[[ @as any ]] + +--- Add the element to the left flow with a toolbar button +Gui.add_left_element(Elements.container, true) +Gui.toolbar.create_button{ + name = "toggle_player_list", + left_element = Elements.container, + sprite = "entity/character", + tooltip = { "exp-gui_player-list.main-tooltip" }, + visible = function(player, element) + return Roles.player_allowed(player, "gui/player-list") + end +} + +--- Refresh the play times for all online players +local function refresh_times() + for _, player_table in Elements.player_table:online_elements() do + Elements.player_table.refresh_times(player_table) + end +end + +--- Rebuild the player list for all online players, used when the sort order changes +local function redraw_player_list() + for _, player_table in Elements.player_table:online_elements() do + Elements.player_table.rebuild(player_table) + update_player_ui(Gui.get_player(player_table)) + end +end + +--- Remove a player from the list when they leave and clear any selection pointing at them +--- @param event EventData.on_player_left_game +local function on_player_left_game(event) + local left_player = game.players[event.player_index] + for _, player_table in Elements.player_table:online_elements() do + Elements.player_table.remove_row(player_table, left_player.name) + local viewing_player = Gui.get_player(player_table) + if get_selected_player(viewing_player) == left_player.name then + set_selected_player(viewing_player, nil) + end + end +end + +local e = defines.events + +return { + elements = Elements, + events = { + [e.on_player_joined_game] = redraw_player_list, + [e.on_player_left_game] = on_player_left_game, + [Roles.events.on_role_assigned] = redraw_player_list, + [Roles.events.on_role_unassigned] = redraw_player_list, + }, + on_nth_tick = { + [1800] = refresh_times, + } +} diff --git a/exp_scenario/module/locale/en.cfg b/exp_scenario/module/locale/en.cfg index d2c5cf99..6a02d27a 100644 --- a/exp_scenario/module/locale/en.cfg +++ b/exp_scenario/module/locale/en.cfg @@ -324,6 +324,22 @@ tooltip-character_reach_distance_bonus=Character reach distance bonus caption-personal_battery_recharge=Battery tooltip-personal_battery_recharge=Armor battery recharge +[exp-gui_player-list] +main-tooltip=Player List +open-action-bar=Options +close-action-bar=Close Options +reason-confirm=Confirm Reason +reason-entry=Enter Reason +goto-player=Goto player +bring-player=Bring player +report-player=Report player +warn-player=Warn player +jail-player=Jail player +kick-player=Kick player +ban-player=Ban player +afk-time=__1__% of total map time\nLast moved __2__ ago +open-map=__1__ __2__\n__3__\nClick to open map + [exp-gui_player-stats] tooltip-main=Player Stats caption-main=Player Stats From 4bd539426853703c13b90d43eb63886627023a48 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Mon, 22 Jun 2026 20:10:20 +0000 Subject: [PATCH 02/33] Align names and call patterns to best practice Apply the conventions from the rocket info gui review to the player list, and remove the now migrated legacy module. - Move row building onto the table def as calculate_row_data, and refresh via refresh_player which computes the row data once per player - Store the selection on the container with private/public accessor methods instead of poking element data directly - Store the selected player as a LuaPlayer rather than a string and update the actions config to match - Use :style{} on the player table and make the action and reason bars their own element definitions - Delete the migrated legacy gui and its file loader entry Co-Authored-By: Claude Opus 4.8 (1M context) --- exp_legacy/module/config/_file_loader.lua | 1 - .../module/config/gui/player_list_actions.lua | 37 +- exp_legacy/module/modules/gui/player-list.lua | 448 ------------- exp_scenario/module/gui/player_list.lua | 587 +++++++++--------- 4 files changed, 322 insertions(+), 751 deletions(-) delete mode 100644 exp_legacy/module/modules/gui/player-list.lua diff --git a/exp_legacy/module/config/_file_loader.lua b/exp_legacy/module/config/_file_loader.lua index 45461057..fba1c006 100644 --- a/exp_legacy/module/config/_file_loader.lua +++ b/exp_legacy/module/config/_file_loader.lua @@ -24,7 +24,6 @@ return { --- GUI "modules.gui.rocket-info", "modules.gui.warp-list", - -- "modules.gui.player-list", -- migrated to exp_scenario/gui/player_list "modules.gui.vlayer", "modules.gui._role_updates", diff --git a/exp_legacy/module/config/gui/player_list_actions.lua b/exp_legacy/module/config/gui/player_list_actions.lua index 2e5e2094..15b03f33 100644 --- a/exp_legacy/module/config/gui/player_list_actions.lua +++ b/exp_legacy/module/config/gui/player_list_actions.lua @@ -30,11 +30,10 @@ local function auth_lower_role(player, selected_player_name) end -- gets the action player and a coloured name for the action to be used on -local function get_action_player_name(player) - local selected_player_name = get_selected_player(player) - local selected_player = game.players[selected_player_name] +local function get_action_player(player) + local selected_player = get_selected_player(player) --[[ @as LuaPlayer ]] local selected_player_color = format_player_name(selected_player) - return selected_player_name, selected_player_color + return selected_player, selected_player_color end -- teleports one player to another @@ -66,8 +65,7 @@ end -- @element goto_player local goto_player = new_button("utility/export", { "exp-gui_player-list.goto-player" }) :on_click(function(def, player, element) - local selected_player_name = get_action_player_name(player) - local selected_player = game.players[selected_player_name] + local selected_player = get_action_player(player) if not player.character or not selected_player.character then player.print({ "expcore-commands.reject-player-alive" }, Colors.orange_red) else @@ -79,8 +77,7 @@ local goto_player = new_button("utility/export", { "exp-gui_player-list.goto-pla -- @element bring_player local bring_player = new_button("utility/import", { "exp-gui_player-list.bring-player" }) :on_click(function(def, player, element) - local selected_player_name = get_action_player_name(player) - local selected_player = game.players[selected_player_name] + local selected_player = get_action_player(player) if not player.character or not selected_player.character then player.print({ "expcore-commands.reject-player-alive" }, Colors.orange_red) else @@ -92,8 +89,8 @@ local bring_player = new_button("utility/import", { "exp-gui_player-list.bring-p -- @element report_player local report_player = new_button("utility/spawn_flag", { "exp-gui_player-list.report-player" }) :on_click(function(def, player, element) - local selected_player_name = get_action_player_name(player) - if Reports.is_reported(selected_player_name, player.name) then + local selected_player = get_action_player(player) + if Reports.is_reported(selected_player.name, player.name) then player.print({ "exp-commands_report.already-reported" }, Colors.orange_red) else set_selected_action(player, "command/report") @@ -101,11 +98,11 @@ local report_player = new_button("utility/spawn_flag", { "exp-gui_player-list.re end) local function report_player_callback(player, reason) - local selected_player_name, selected_player_color = get_action_player_name(player) + local selected_player, selected_player_color = get_action_player(player) local by_player_name_color = format_player_name(player) game.print{ "exp-commands_reports.response", selected_player_color, reason } Roles.print_to_roles_higher("Trainee", { "exp-commands_reports.response-admin", selected_player_color, by_player_name_color, reason }) - Reports.report_player(selected_player_name, player.name, reason) + Reports.report_player(selected_player.name, player.name, reason) end --- Gives the action player a warning, requires a reason @@ -116,18 +113,18 @@ local warn_player = new_button("utility/spawn_flag", { "exp-gui_player-list.warn end) local function warn_player_callback(player, reason) - local selected_player_name, selected_player_color = get_action_player_name(player) + local selected_player, selected_player_color = get_action_player(player) local by_player_name_color = format_player_name(player) game.print{ "exp-commands_warnings.create", selected_player_color, by_player_name_color, reason } - Warnings.add_warning(selected_player_name, player.name, reason) + Warnings.add_warning(selected_player.name, player.name, reason) end --- Jails the action player, requires a reason -- @element jail_player local jail_player = new_button("utility/multiplayer_waiting_icon", { "exp-gui_player-list.jail-player" }) :on_click(function(def, player, element) - local selected_player_name, selected_player_color = get_action_player_name(player) - if Jail.is_jailed(selected_player_name) then + local selected_player, selected_player_color = get_action_player(player) + if Jail.is_jailed(selected_player.name) then player.print({ "exp-commands_jail.already-jailed", selected_player_color }, Colors.orange_red) else set_selected_action(player, "command/jail") @@ -135,10 +132,10 @@ local jail_player = new_button("utility/multiplayer_waiting_icon", { "exp-gui_pl end) local function jail_player_callback(player, reason) - local selected_player_name, selected_player_color = get_action_player_name(player) + local selected_player, selected_player_color = get_action_player(player) local by_player_name_color = format_player_name(player) game.print{ "exp-commands_jail.jailed", selected_player_color, by_player_name_color, reason } - Jail.jail_player(selected_player_name, player.name, reason) + Jail.jail_player(selected_player.name, player.name, reason) end --- Kicks the action player, requires a reason @@ -149,7 +146,7 @@ local kick_player = new_button("utility/warning_icon", { "exp-gui_player-list.ki end) local function kick_player_callback(player, reason) - local selected_player = get_action_player_name(player) + local selected_player = get_action_player(player) game.kick_player(selected_player, reason) end @@ -161,7 +158,7 @@ local ban_player = new_button("utility/danger_icon", { "exp-gui_player-list.ban- end) local function ban_player_callback(player, reason) - local selected_player = get_action_player_name(player) + local selected_player = get_action_player(player) game.ban_player(selected_player, reason) end diff --git a/exp_legacy/module/modules/gui/player-list.lua b/exp_legacy/module/modules/gui/player-list.lua deleted file mode 100644 index 56183c8f..00000000 --- a/exp_legacy/module/modules/gui/player-list.lua +++ /dev/null @@ -1,448 +0,0 @@ ---[[-- Gui Module - Player List - - Adds a player list to show names and play time; also includes action buttons which can preform actions to players - @gui Player-List - @alias player_list -]] - --- luacheck:ignore 211/Colors -local ExpUtil = require("modules/exp_util") -local Gui = require("modules/exp_gui") -local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles -local Datastore = require("modules.exp_legacy.expcore.datastore") --- @dep expcore.datastore -local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event -local config = require("modules.exp_legacy.config.gui.player_list_actions") --- @dep config.gui.player_list_actions - ---- Stores all data for the warp gui -local PlayerListData = Datastore.connect("PlayerListData") -PlayerListData:set_serializer(Datastore.name_serializer) -local SelectedPlayer = PlayerListData:combine("SelectedPlayer") -local SelectedAction = PlayerListData:combine("SelectedAction") - --- Set the config to use these stores -config.set_datastores(SelectedPlayer, SelectedAction) - ---- Button used to open the action bar --- @element open_action_bar -local open_action_bar = Gui.define("open_action_bar") - :draw{ - type = "sprite-button", - sprite = "utility/expand_dots", - tooltip = { "player-list.open-action-bar" }, - style = "frame_button", - name = Gui.from_name, - } - :style{ - padding = -2, - width = 8, - height = 14, - } - :on_click(function(def, player, element) - local selected_player_name = element.parent.name - local old_selected_player_name = SelectedPlayer:get(player) - if selected_player_name == old_selected_player_name then - SelectedPlayer:remove(player) - else - SelectedPlayer:set(player, selected_player_name) - end - end) - ---- Button used to close the action bar --- @element close_action_bar -local close_action_bar = Gui.define("close_action_bar") - :draw{ - type = "sprite-button", - sprite = "utility/close_black", - tooltip = { "player-list.close-action-bar" }, - style = "slot_sized_button_red", - } - :style(Gui.styles.sprite{ - size = 20, - padding = -1, - top_margin = -1, - right_margin = -1, - }) - :on_click(function(def, player, element) - SelectedPlayer:remove(player) - SelectedAction:remove(player) - end) - ---- Button used to confirm a reason --- @element reason_confirm -local reason_confirm = Gui.define("reason_confirm") - :draw{ - type = "sprite-button", - sprite = "utility/confirm_slot", - tooltip = { "player-list.reason-confirm" }, - style = "slot_sized_button_green", - } - :style(Gui.styles.sprite{ - size = 30, - padding = -1, - left_margin = -2, - right_margin = -1, - }) - :on_click(function(def, player, element) - local reason = element.parent.entry.text - local action_name = SelectedAction:get(player) - local reason_callback = config.buttons[action_name].reason_callback - if reason == nil or not reason:find("%S") then reason = "no reason given" end - reason_callback(player, reason) - SelectedPlayer:remove(player) - SelectedAction:remove(player) - element.parent.entry.text = "" - end) - ---- Set of elements that are used to make up a row of the player table --- @element add_player_base -local add_player_base = Gui.define("add_player_base") - :draw(function(_, parent, player_data) - -- Add the button to open the action bar - local toggle_action_bar_flow = parent.add{ type = "flow", name = player_data.name } - open_action_bar(toggle_action_bar_flow) - - -- Add the player name - local player_name = parent.add{ - type = "label", - name = "player-name-" .. player_data.index, - caption = player_data.name, - tooltip = { "player-list.open-map", player_data.name, player_data.tag, player_data.role_name }, - } - player_name.style.padding = { 0, 2, 0, 0 } - player_name.style.font_color = player_data.chat_color - - -- Add the time played label - local alignment = Gui.elements.aligned_flow(parent, { name = "player-time-" .. player_data.index }) - local time_label = alignment.add{ - name = "label", - type = "label", - caption = player_data.caption, - tooltip = player_data.tooltip, - } - time_label.style.padding = 0 - - return player_name - end) - :on_click(function(def, player, element, event) - local selected_player_name = element.caption - local selected_player = game.players[selected_player_name] - if event.button == defines.mouse_button_type.left then - -- LMB will open the map to the selected player - player.set_controller{ - type = defines.controllers.remote, - position = selected_player.physical_position, - surface = selected_player.physical_surface - } - else - -- RMB will toggle the settings - local old_selected_player_name = SelectedPlayer:get(player) - if selected_player_name == old_selected_player_name then - SelectedPlayer:remove(player) - SelectedAction:remove(player) - else - SelectedPlayer:set(player, selected_player_name) - end - end - end) - --- Removes the three elements that are added as part of the base -local function remove_player_base(parent, player) - Gui.destroy_if_valid(parent[player.name]) - Gui.destroy_if_valid(parent["player-name-" .. player.index]) - Gui.destroy_if_valid(parent["player-time-" .. player.index]) -end - --- Update the time label for a player using there player time data -local function update_player_base(parent, player_time) - local time_element = parent[player_time.element_name] - if time_element and time_element.valid then - time_element.label.caption = player_time.caption - time_element.label.tooltip = player_time.tooltip - end -end - ---- Adds all the buttons and flows that make up the action bar --- @element add_action_bar -local add_action_bar_buttons = Gui.define("add_action_bar_buttons") - :draw(function(_, parent) - close_action_bar(parent) - -- Loop over all the buttons in the config - for action_name, button_data in pairs(config.buttons) do - -- Added the permission flow - local permission_flow = parent.add{ type = "flow", name = action_name } - permission_flow.visible = false - -- Add the buttons under that permission - for _, button in ipairs(button_data) do - button(permission_flow) - end - end - - return parent - end) - ---- Updates the visible state of the action bar buttons -local function update_action_bar(element) - local player = Gui.get_player(element) - local selected_player_name = SelectedPlayer:get(player) - - if not selected_player_name then - -- Hide the action bar when no player is selected - element.visible = false - else - local selected_player = game.players[selected_player_name] - if not selected_player.connected then - -- If the player is offline then reest stores - element.visible = false - SelectedPlayer:remove(player) - SelectedAction:remove(player) - else - -- Otherwise check what actions the player is allowed to use - element.visible = true - for action_name, buttons in pairs(config.buttons) do - if buttons.auth and not buttons.auth(player, selected_player) then - element[action_name].visible = false - elseif Roles.player_allowed(player, action_name) then - element[action_name].visible = true - end - end - end - end -end - ---- Main player list container for the left flow --- @element player_list_container -local player_list_container = Gui.define("player_list_container") - :draw(function(definition, parent) - -- Draw the internal container - local container = Gui.elements.container(parent) - - -- Draw the scroll table for the players - local scroll_table = Gui.elements.scroll_table(container, 184, 3, "scroll") - - -- Change the style of the scroll table - local scroll_table_style = scroll_table.style - scroll_table_style.padding = { 1, 0, 1, 2 } - - -- Add the action bar - local action_bar = Gui.elements.footer(container, { name = "action_bar", no_flow = true }) - - -- Change the style of the action bar - local action_bar_style = action_bar.style - action_bar_style.height = 35 - action_bar_style.padding = { 1, 3 } - action_bar.visible = false - - -- Add the buttons to the action bar - add_action_bar_buttons(action_bar) - - -- Add the reason bar - local reason_bar = Gui.elements.footer(container, { name = "reason_bar", no_flow = true }) - - -- Change the style of the reason bar - local reason_bar_style = reason_bar.style - reason_bar_style.height = 35 - reason_bar_style.padding = { -1, 3 } - reason_bar.visible = false - - -- Add the text entry for the reason bar - local reason_field = - reason_bar.add{ - name = "entry", - type = "textfield", - style = "stretchable_textfield", - tooltip = { "player-list.reason-entry" }, - } - - -- Change the style of the text entry - local reason_entry_style = reason_field.style - reason_entry_style.padding = 0 - reason_entry_style.height = 28 - reason_entry_style.minimal_width = 160 - - -- Add the confirm reason button - reason_confirm(reason_bar) - - -- Return the exteral container - return container.parent - end) - ---- Add the element to the left flow with a toolbar button -Gui.add_left_element(player_list_container, true) -Gui.toolbar.create_button{ - name = "player_list_toggle", - left_element = player_list_container, - sprite = "entity/character", - tooltip = { "player-list.main-tooltip" }, - visible = function(player, element) - return Roles.player_allowed(player, "gui/player-list") - end -} - -local online_time_format = ExpUtil.format_time_factory_locale{ format = "short", hours = true, minutes = true } -local afk_time_format = ExpUtil.format_time_factory_locale{ format = "long", minutes = true } - --- Get caption and tooltip format for a player -local function get_time_formats(online_time, afk_time) - local tick = game.tick > 0 and game.tick or 1 - local percent = math.round(online_time / tick, 3) * 100 - local caption = online_time_format(online_time) - local tooltip = { "player-list.afk-time", percent, afk_time_format(afk_time) } - return caption, tooltip -end - --- Get the player time to be used to update time label -local function get_player_times() - local ctn = 0 - local player_times = {} - for _, player in pairs(game.connected_players) do - ctn = ctn + 1 - -- Add the player time details to the array - local caption, tooltip = get_time_formats(player.online_time, player.afk_time) - player_times[ctn] = { - element_name = "player-time-" .. player.index, - caption = caption, - tooltip = tooltip, - } - end - - return player_times -end - --- Get a sorted list of all online players -local function get_player_list_order() - -- Sort all the online players into roles - local players = {} - for _, player in pairs(game.connected_players) do - local highest_role = Roles.get_player_highest_role(player) - if not players[highest_role.name] then - players[highest_role.name] = {} - end - table.insert(players[highest_role.name], player) - end - - -- Sort the players from roles into a set order - local ctn = 0 - local player_list_order = {} - for _, role_name in pairs(Roles.config.order) do - if players[role_name] then - for _, player in pairs(players[role_name]) do - ctn = ctn + 1 - -- Add the player data to the array - local caption, tooltip = get_time_formats(player.online_time, player.afk_time) - player_list_order[ctn] = { - name = player.name, - index = player.index, - tag = player.tag, - role_name = role_name, - chat_color = player.chat_color, - caption = caption, - tooltip = tooltip, - } - end - end - end - - --[[Adds fake players to the player list - local tick = game.tick+1 - for i = 1, 10 do - local online_time = math.random(1, tick) - local afk_time = math.random(online_time-(tick/10), tick) - local caption, tooltip = get_time_formats(online_time, afk_time) - player_list_order[ctn+i] = { - name='Player '..i, - index=0-i, - tag='', - role_name = 'Fake Player', - chat_color = table.get_random(Colors), - caption = caption, - tooltip = tooltip - } - end--]] - - return player_list_order -end - ---- Update the play times every 30 sections -Event.on_nth_tick(1800, function() - local player_times = get_player_times() - for _, player in pairs(game.connected_players) do - local container = Gui.get_left_element(player_list_container, player) - local scroll_table = container.frame.scroll.table - for _, player_time in pairs(player_times) do - update_player_base(scroll_table, player_time) - end - end -end) - ---- When a player leaves only remove they entry -Event.add(defines.events.on_player_left_game, function(event) - local remove_player = game.players[event.player_index] - for _, player in pairs(game.connected_players) do - local container = Gui.get_left_element(player_list_container, player) - local scroll_table = container.frame.scroll.table - remove_player_base(scroll_table, remove_player) - - local selected_player_name = SelectedPlayer:get(player) - if selected_player_name == remove_player.name then - SelectedPlayer:remove(player) - SelectedAction:remove(player) - end - end -end) - ---- All other events require a full redraw of the table -local function redraw_player_list() - local player_list_order = get_player_list_order() - for _, player in pairs(game.connected_players) do - local container = Gui.get_left_element(player_list_container, player) - local scroll_table = container.frame.scroll.table - scroll_table.clear() - for _, next_player_data in ipairs(player_list_order) do - add_player_base(scroll_table, next_player_data) - end - end -end - -Event.add(defines.events.on_player_joined_game, redraw_player_list) -Event.add(Roles.events.on_role_assigned, redraw_player_list) -Event.add(Roles.events.on_role_unassigned, redraw_player_list) - ---- When the action player is changed the action bar will update -SelectedPlayer:on_update(function(player_name, selected_player) - local player = game.players[player_name] - local container = Gui.get_left_element(player_list_container, player) - local scroll_table = container.frame.scroll.table - update_action_bar(container.frame.action_bar) - for _, next_player in pairs(game.connected_players) do - local element = scroll_table[next_player.name][open_action_bar.name] - local style = "frame_button" - if next_player.name == selected_player then - style = "tool_button" - end - element.style = style - local element_style = element.style --[[@as LuaStyle]] - element_style.padding = -2 - element_style.width = 8 - element_style.height = 14 - end -end) - ---- When the action name is changed the reason input will update -SelectedAction:on_update(function(player_name, selected_action) - local player = game.players[player_name] - local container = Gui.get_left_element(player_list_container, player) - local element = container.frame.reason_bar - if selected_action then - -- if there is a new value then check the player is still online - local selected_player_name = SelectedPlayer:get(player_name) - local selected_player = game.players[selected_player_name] - if selected_player.connected then - element.visible = true - else - -- Clear if the player is offline - SelectedPlayer:remove(player) - SelectedAction:remove(player) - end - else - element.visible = false - end -end) diff --git a/exp_scenario/module/gui/player_list.lua b/exp_scenario/module/gui/player_list.lua index 0dc4583d..6dd02289 100644 --- a/exp_scenario/module/gui/player_list.lua +++ b/exp_scenario/module/gui/player_list.lua @@ -1,6 +1,5 @@ --[[-- Gui - Player List Adds a player list to show names and play time; also includes action buttons which can perform actions on players. -The selected player and action used to be stored in a datastore; they are now kept on the container's player data. ]] local ExpUtil = require("modules/exp_util") @@ -14,138 +13,22 @@ local Elements = {} local online_time_format = ExpUtil.format_time_factory_locale{ format = "short", hours = true, minutes = true } local afk_time_format = ExpUtil.format_time_factory_locale{ format = "long", minutes = true } ---[[ -The selected player (the player an action will be performed on) and the selected action (which opens the reason bar) -are stored per viewing player on the container's player data. The setters drive the gui updates directly, replacing -the reactive datastore on_update callbacks used by the legacy version. -]] - ---- @class ExpGui_PlayerList.selection ---- @field selected_player string? Name of the player actions will be performed on ---- @field selected_action string? Name of the action awaiting a reason - ---- Get the selection state for a player, creating it if needed ---- @param player LuaPlayer ---- @return ExpGui_PlayerList.selection -local function get_selection(player) - local selection = Elements.container.data[player] - if not selection then - selection = {} - Elements.container.data[player] = selection - end - return selection +--- Get the caption and tooltip for a player time +--- @param online_time number +--- @param afk_time number +--- @return LocalisedString, LocalisedString +local function get_time_formats(online_time, afk_time) + local tick = game.tick > 0 and game.tick or 1 + local percent = math.round(online_time / tick, 3) * 100 + local caption = online_time_format(online_time) + local tooltip = { "exp-gui_player-list.afk-time", percent, afk_time_format(afk_time) } + return caption, tooltip end ---- Get the name of the player that actions will be performed on ---- @param player LuaPlayer ---- @return string? -local function get_selected_player(player) - return get_selection(player).selected_player -end - ---- Get the name of the action awaiting a reason ---- @param player LuaPlayer ---- @return string? -local function get_selected_action(player) - return get_selection(player).selected_action -end - ---- Style an open action bar button to show if it is the selected player ---- @param button LuaGuiElement ---- @param selected boolean -local function style_open_button(button, selected) - button.style = selected and "tool_button" or "frame_button" - local style = button.style - style.padding = -2 - style.width = 8 - style.height = 14 -end - ---- Update the action bar buttons to match the current selection for a player ---- @param action_bar LuaGuiElement ---- @param player LuaPlayer ---- @param selected_player LuaPlayer -local function update_action_bar(action_bar, player, selected_player) - action_bar.visible = true - for action_name, buttons in pairs(config.buttons) do - local flow = action_bar[action_name] - if buttons.auth and not buttons.auth(player, selected_player) then - flow.visible = false - else - flow.visible = Roles.player_allowed(player, action_name) - end - end -end - ---- Refresh all gui elements for a player to match their current selection ---- @param player LuaPlayer -local function update_player_ui(player) - local selection = get_selection(player) - - -- Clear the selection if the selected player has gone offline - local selected_player = selection.selected_player and game.players[selection.selected_player] - if selected_player and not selected_player.connected then - selection.selected_player = nil - selection.selected_action = nil - selected_player = nil - end - - for _, player_table in Elements.player_table:online_elements(player) do - local refs = Elements.player_table.data[player_table] - - -- Update the action bar and reason bar visibility - if selected_player then - update_action_bar(refs.action_bar, player, selected_player) - else - refs.action_bar.visible = false - end - refs.reason_bar.visible = selected_player ~= nil and selection.selected_action ~= nil - - -- Highlight the open button of the selected player - for player_name, row in pairs(refs.rows) do - style_open_button(row.open_button, player_name == selection.selected_player) - end - end -end - ---- Set the player that actions will be performed on, nil to clear the selection ---- @param player LuaPlayer ---- @param selected_player_name string? -local function set_selected_player(player, selected_player_name) - local selection = get_selection(player) - selection.selected_player = selected_player_name - if not selected_player_name then - selection.selected_action = nil - end - update_player_ui(player) -end - ---- Toggle the player that actions will be performed on ---- @param player LuaPlayer ---- @param selected_player_name string -local function toggle_selected_player(player, selected_player_name) - if get_selected_player(player) == selected_player_name then - set_selected_player(player, nil) - else - set_selected_player(player, selected_player_name) - end -end - ---- Set the action awaiting a reason, nil to clear it ---- @param player LuaPlayer ---- @param selected_action string? -local function set_selected_action(player, selected_action) - get_selection(player).selected_action = selected_action - update_player_ui(player) -end - --- Inject the accessors the actions config needs to read the selected player and set the selected action -config.set_accessors(get_selected_player, set_selected_action) - --- Button used to open the action bar for a player --- @class ExpGui_PlayerList.elements.open_action_bar: ExpElement ---- @field data table ---- @overload fun(parent: LuaGuiElement, player_name: string): LuaGuiElement +--- @field data table +--- @overload fun(parent: LuaGuiElement, selected_player: LuaPlayer): LuaGuiElement Elements.open_action_bar = Gui.define("player_list/open_action_bar") :draw{ type = "sprite-button", @@ -163,9 +46,21 @@ Elements.open_action_bar = Gui.define("player_list/open_action_bar") ) :on_click(function(def, player, element) --- @cast def ExpGui_PlayerList.elements.open_action_bar - toggle_selected_player(player, def.data[element]) + Elements.container.toggle_selected_player(player, def.data[element]) + Elements.player_table.refresh_player(player) end) --[[ @as any ]] +--- Set whether an open action bar button shows as highlighted +--- @param open_button LuaGuiElement +--- @param highlighted boolean +function Elements.open_action_bar.set_highlight(open_button, highlighted) + open_button.style = highlighted and "tool_button" or "frame_button" + local style = open_button.style + style.padding = -2 + style.width = 8 + style.height = 14 +end + --- Button used to close the action bar --- @class ExpGui_PlayerList.elements.close_action_bar: ExpElement --- @overload fun(parent: LuaGuiElement): LuaGuiElement @@ -182,8 +77,9 @@ Elements.close_action_bar = Gui.define("player_list/close_action_bar") top_margin = -1, right_margin = -1, }) - :on_click(function(def, player, element) - set_selected_player(player, nil) + :on_click(function(_, player) + Elements.container.set_selected_player(player, nil) + Elements.player_table.refresh_player(player) end) --[[ @as any ]] --- Button used to confirm a reason @@ -202,8 +98,8 @@ Elements.reason_confirm = Gui.define("player_list/reason_confirm") left_margin = -2, right_margin = -1, }) - :on_click(function(def, player, element) - local action_name = get_selected_action(player) + :on_click(function(_, player, element) + local action_name = Elements.container.get_selected_action(player) local button_data = action_name and config.buttons[action_name] if button_data and button_data.reason_callback then local reason = element.parent.entry.text @@ -211,13 +107,14 @@ Elements.reason_confirm = Gui.define("player_list/reason_confirm") button_data.reason_callback(player, reason) end element.parent.entry.text = "" - set_selected_player(player, nil) + Elements.container.set_selected_player(player, nil) + Elements.player_table.refresh_player(player) end) --[[ @as any ]] --- Clickable player name label, left click opens the map and right click toggles the action bar --- @class ExpGui_PlayerList.elements.player_name_label: ExpElement ---- @field data table ---- @overload fun(parent: LuaGuiElement, opts: { name: string, tooltip: LocalisedString }): LuaGuiElement +--- @field data table +--- @overload fun(parent: LuaGuiElement, opts: { name: string, player: LuaPlayer, tooltip: LocalisedString }): LuaGuiElement Elements.player_name_label = Gui.define("player_list/player_name_label") :draw{ type = "label", @@ -228,12 +125,11 @@ Elements.player_name_label = Gui.define("player_list/player_name_label") padding = { 0, 2, 0, 0 }, } :element_data( - Gui.from_argument("name") + Gui.from_argument("player") ) :on_click(function(def, player, element, event) --- @cast def ExpGui_PlayerList.elements.player_name_label - local selected_player_name = def.data[element] - local selected_player = game.players[selected_player_name] + local selected_player = def.data[element] if event.button == defines.mouse_button_type.left then -- Left click opens remote view at the player player.set_controller{ @@ -243,66 +139,11 @@ Elements.player_name_label = Gui.define("player_list/player_name_label") } else -- Right click toggles the action bar - toggle_selected_player(player, selected_player_name) + Elements.container.toggle_selected_player(player, selected_player) + Elements.player_table.refresh_player(player) end end) --[[ @as any ]] ---- @class ExpGui_PlayerList.player_data ---- @field name string ---- @field tag string ---- @field role_name string ---- @field chat_color Color ---- @field caption LocalisedString ---- @field tooltip LocalisedString - ---- Get the caption and tooltip for a player time ---- @param online_time number ---- @param afk_time number ---- @return LocalisedString, LocalisedString -local function get_time_formats(online_time, afk_time) - local tick = game.tick > 0 and game.tick or 1 - local percent = math.round(online_time / tick, 3) * 100 - local caption = online_time_format(online_time) - local tooltip = { "exp-gui_player-list.afk-time", percent, afk_time_format(afk_time) } - return caption, tooltip -end - ---- Get a list of all online players sorted by their highest role ---- @return ExpGui_PlayerList.player_data[] -local function get_player_list_order() - -- Sort all the online players into roles - local players = {} - for _, player in pairs(game.connected_players) do - local highest_role = Roles.get_player_highest_role(player) - if not players[highest_role.name] then - players[highest_role.name] = {} - end - table.insert(players[highest_role.name], player) - end - - -- Flatten the roles into a single ordered list - local count = 0 - local player_list_order = {} - for _, role_name in pairs(Roles.config.order) do - if players[role_name] then - for _, player in pairs(players[role_name]) do - count = count + 1 - local caption, tooltip = get_time_formats(player.online_time, player.afk_time) - player_list_order[count] = { - name = player.name, - tag = player.tag, - role_name = role_name, - chat_color = player.chat_color, - caption = caption, - tooltip = tooltip, - } - end - end - end - - return player_list_order -end - --- @class ExpGui_PlayerList.elements.player_table.row --- @field open_button LuaGuiElement --- @field name_label LuaGuiElement @@ -319,48 +160,119 @@ end --- @overload fun(parent: LuaGuiElement): LuaGuiElement Elements.player_table = Gui.define("player_list/player_table") :track_all_elements() - :draw(function(def, parent) - local scroll_table = Gui.elements.scroll_table(parent, 184, 3, "scroll") - scroll_table.style.padding = { 1, 0, 1, 2 } - return scroll_table - end) --[[ @as any ]] + :draw(function(_, parent) + return Gui.elements.scroll_table(parent, 184, 3, "scroll") + end) + :style{ + padding = { 1, 0, 1, 2 }, + } --[[ @as any ]] + +--- Store the action and reason bars associated with a player table +--- @param player_table LuaGuiElement +--- @param action_bar LuaGuiElement +--- @param reason_bar LuaGuiElement +function Elements.player_table.set_bars(player_table, action_bar, reason_bar) + Elements.player_table.data[player_table] = { + rows = {}, + action_bar = action_bar, + reason_bar = reason_bar, + } +end + +--- @class ExpGui_PlayerList.elements.player_table.row_data +--- @field player LuaPlayer +--- @field tag string +--- @field role_name string +--- @field chat_color Color +--- @field caption LocalisedString +--- @field tooltip LocalisedString + +--- Calculate the ordered list of online players sorted by their highest role +--- @return ExpGui_PlayerList.elements.player_table.row_data[] +function Elements.player_table.calculate_row_data() + -- Sort all the online players into roles + local players = {} + for _, player in pairs(game.connected_players) do + local highest_role = Roles.get_player_highest_role(player) + if not players[highest_role.name] then + players[highest_role.name] = {} + end + table.insert(players[highest_role.name], player) + end + + -- Flatten the roles into a single ordered list + local count = 0 + local row_data = {} + for _, role_name in pairs(Roles.config.order) do + if players[role_name] then + for _, player in pairs(players[role_name]) do + count = count + 1 + local caption, tooltip = get_time_formats(player.online_time, player.afk_time) + row_data[count] = { + player = player, + tag = player.tag, + role_name = role_name, + chat_color = player.chat_color, + caption = caption, + tooltip = tooltip, + } + end + end + end + + return row_data +end + +--- Calculate the latest play time captions for each online player keyed by name +--- @return table +function Elements.player_table.calculate_time_data() + local time_data = {} + for _, player in pairs(game.connected_players) do + local caption, tooltip = get_time_formats(player.online_time, player.afk_time) + time_data[player.name] = { caption = caption, tooltip = tooltip } + end + return time_data +end --- Add a player row to the table and store its elements --- @param player_table LuaGuiElement ---- @param player_data ExpGui_PlayerList.player_data -function Elements.player_table.add_row(player_table, player_data) +--- @param row_data ExpGui_PlayerList.elements.player_table.row_data +function Elements.player_table.add_row(player_table, row_data) local rows = Elements.player_table.data[player_table].rows + local player = row_data.player -- Add the button used to open the action bar local open_flow = player_table.add{ type = "flow" } - local open_button = Elements.open_action_bar(open_flow, player_data.name) + local open_button = Elements.open_action_bar(open_flow, player) -- Add the clickable player name local name_label = Elements.player_name_label(player_table, { - name = player_data.name, - tooltip = { "exp-gui_player-list.open-map", player_data.name, player_data.tag, player_data.role_name }, + name = player.name, + player = player, + tooltip = { "exp-gui_player-list.open-map", player.name, row_data.tag, row_data.role_name }, }) - name_label.style.font_color = player_data.chat_color + name_label.style.font_color = row_data.chat_color -- Add the time played label local alignment = Gui.elements.aligned_flow(player_table) local time_label = alignment.add{ type = "label", - caption = player_data.caption, - tooltip = player_data.tooltip, + caption = row_data.caption, + tooltip = row_data.tooltip, } time_label.style.padding = 0 - rows[player_data.name] = { open_button = open_button, name_label = name_label, time_label = time_label } + rows[player.name] = { open_button = open_button, name_label = name_label, time_label = time_label } end --- Rebuild all rows of a player table, used when the sort order changes --- @param player_table LuaGuiElement -function Elements.player_table.rebuild(player_table) +--- @param row_data ExpGui_PlayerList.elements.player_table.row_data[] +function Elements.player_table.rebuild(player_table, row_data) player_table.clear() Elements.player_table.data[player_table].rows = {} - for _, player_data in ipairs(get_player_list_order()) do - Elements.player_table.add_row(player_table, player_data) + for _, row in ipairs(row_data) do + Elements.player_table.add_row(player_table, row) end end @@ -377,91 +289,199 @@ function Elements.player_table.remove_row(player_table, player_name) Gui.destroy_if_valid(row.time_label.parent) end ---- Refresh the time labels for all rows of a player table +--- Refresh the time labels of a player table from precomputed time data --- @param player_table LuaGuiElement -function Elements.player_table.refresh_times(player_table) +--- @param time_data table +function Elements.player_table.refresh_times(player_table, time_data) for player_name, row in pairs(Elements.player_table.data[player_table].rows) do - local listed_player = game.players[player_name] - if listed_player and listed_player.connected then - local caption, tooltip = get_time_formats(listed_player.online_time, listed_player.afk_time) - row.time_label.caption = caption - row.time_label.tooltip = tooltip + local data = time_data[player_name] + if data then + row.time_label.caption = data.caption + row.time_label.tooltip = data.tooltip end end end ---- Build the action bar footer containing the close button and the per action button flows ---- @param container LuaGuiElement ---- @return LuaGuiElement -local function build_action_bar(container) - local action_bar = Gui.elements.subframe_base(container, "subfooter_frame", "action_bar") - local style = action_bar.style - style.height = 35 - style.padding = { 1, 3 } - action_bar.visible = false +--- Refresh the action bar, reason bar and row highlights for a player to match their selection +--- @param player LuaPlayer +function Elements.player_table.refresh_player(player) + -- Clear the selection if the selected player has gone offline + local selected_player = Elements.container.get_selected_player(player) + if selected_player and not selected_player.connected then + Elements.container.set_selected_player(player, nil) + selected_player = nil + end + local selected_action = Elements.container.get_selected_action(player) - Elements.close_action_bar(action_bar) - for action_name, button_data in pairs(config.buttons) do - local permission_flow = action_bar.add{ type = "flow", name = action_name } - permission_flow.visible = false - for _, button in ipairs(button_data) do - button(permission_flow) + for _, player_table in Elements.player_table:online_elements(player) do + local element_data = Elements.player_table.data[player_table] + + -- Update the action bar and reason bar visibility + if selected_player then + Elements.action_bar.refresh(element_data.action_bar, player, selected_player) + else + element_data.action_bar.visible = false + end + element_data.reason_bar.visible = selected_player ~= nil and selected_action ~= nil + + -- Highlight the open button of the selected player + for player_name, row in pairs(element_data.rows) do + Elements.open_action_bar.set_highlight(row.open_button, selected_player ~= nil and player_name == selected_player.name) end end - - return action_bar end ---- Build the reason bar footer containing the reason entry and confirm button ---- @param container LuaGuiElement ---- @return LuaGuiElement -local function build_reason_bar(container) - local reason_bar = Gui.elements.subframe_base(container, "subfooter_frame", "reason_bar") - local style = reason_bar.style - style.height = 35 - style.padding = { -1, 3 } - reason_bar.visible = false +--- Action bar footer holding the close button and the per action button flows +--- @class ExpGui_PlayerList.elements.action_bar: ExpElement +--- @overload fun(parent: LuaGuiElement): LuaGuiElement +Elements.action_bar = Gui.define("player_list/action_bar") + :draw(function(_, parent) + local action_bar = Gui.elements.subframe_base(parent, "subfooter_frame", "action_bar") + action_bar.visible = false - local reason_field = reason_bar.add{ - name = "entry", - type = "textfield", - style = "stretchable_textfield", - tooltip = { "exp-gui_player-list.reason-entry" }, - } - local entry_style = reason_field.style - entry_style.padding = 0 - entry_style.height = 28 - entry_style.minimal_width = 160 + Elements.close_action_bar(action_bar) + for action_name, button_data in pairs(config.buttons) do + local permission_flow = action_bar.add{ type = "flow", name = action_name } + permission_flow.visible = false + for _, button in ipairs(button_data) do + button(permission_flow) + end + end - Elements.reason_confirm(reason_bar) + return action_bar + end) + :style{ + height = 35, + padding = { 1, 3 }, + } --[[ @as any ]] - return reason_bar +--- Update the action bar buttons to match the selection for a player +--- @param action_bar LuaGuiElement +--- @param player LuaPlayer +--- @param selected_player LuaPlayer +function Elements.action_bar.refresh(action_bar, player, selected_player) + action_bar.visible = true + for action_name, buttons in pairs(config.buttons) do + local flow = action_bar[action_name] + if buttons.auth and not buttons.auth(player, selected_player) then + flow.visible = false + else + flow.visible = Roles.player_allowed(player, action_name) + end + end end +--- Reason bar footer holding the reason entry and confirm button +--- @class ExpGui_PlayerList.elements.reason_bar: ExpElement +--- @overload fun(parent: LuaGuiElement): LuaGuiElement +Elements.reason_bar = Gui.define("player_list/reason_bar") + :draw(function(_, parent) + local reason_bar = Gui.elements.subframe_base(parent, "subfooter_frame", "reason_bar") + reason_bar.visible = false + + local reason_field = reason_bar.add{ + name = "entry", + type = "textfield", + style = "stretchable_textfield", + tooltip = { "exp-gui_player-list.reason-entry" }, + } + local entry_style = reason_field.style + entry_style.padding = 0 + entry_style.height = 28 + entry_style.minimal_width = 160 + + Elements.reason_confirm(reason_bar) + return reason_bar + end) + :style{ + height = 35, + padding = { -1, 3 }, + } --[[ @as any ]] + +--- @class ExpGui_PlayerList.elements.container.selection +--- @field selected_player LuaPlayer? +--- @field selected_action string? + --- Container added to the left gui flow --- @class ExpGui_PlayerList.elements.container: ExpElement ---- @field data table +--- @field data table Elements.container = Gui.define("player_list/container") - :draw(function(def, parent) + :draw(function(_, parent) local container = Gui.elements.container(parent) - local player = Gui.get_player(parent) - def.data[player] = def.data[player] or {} -- Selection state for this player - local player_table = Elements.player_table(container) - local action_bar = build_action_bar(container) - local reason_bar = build_reason_bar(container) + local action_bar = Elements.action_bar(container) + local reason_bar = Elements.reason_bar(container) + Elements.player_table.set_bars(player_table, action_bar, reason_bar) - Elements.player_table.data[player_table] = { - rows = {}, - action_bar = action_bar, - reason_bar = reason_bar, - } - Elements.player_table.rebuild(player_table) + local row_data = Elements.player_table.calculate_row_data() + Elements.player_table.rebuild(player_table, row_data) return Gui.elements.container.get_root_element(container) end) --[[ @as any ]] +--- Get or create the selection state for a player +--- @param player LuaPlayer +--- @return ExpGui_PlayerList.elements.container.selection +function Elements.container._get_selection(player) + local selection = Elements.container.data[player] + if not selection then + selection = {} + Elements.container.data[player] = selection + end + return selection +end + +--- Get the player that actions will be performed on +--- @param player LuaPlayer +--- @return LuaPlayer? +function Elements.container.get_selected_player(player) + return Elements.container._get_selection(player).selected_player +end + +--- Get the action awaiting a reason +--- @param player LuaPlayer +--- @return string? +function Elements.container.get_selected_action(player) + return Elements.container._get_selection(player).selected_action +end + +--- Set the player that actions will be performed on, nil to clear the selection +--- @param player LuaPlayer +--- @param selected_player LuaPlayer? +function Elements.container.set_selected_player(player, selected_player) + local selection = Elements.container._get_selection(player) + selection.selected_player = selected_player + if not selected_player then + selection.selected_action = nil + end +end + +--- Toggle the player that actions will be performed on +--- @param player LuaPlayer +--- @param selected_player LuaPlayer +function Elements.container.toggle_selected_player(player, selected_player) + if Elements.container.get_selected_player(player) == selected_player then + Elements.container.set_selected_player(player, nil) + else + Elements.container.set_selected_player(player, selected_player) + end +end + +--- Set the action awaiting a reason, nil to clear it +--- @param player LuaPlayer +--- @param selected_action string? +function Elements.container.set_selected_action(player, selected_action) + Elements.container._get_selection(player).selected_action = selected_action +end + +-- Inject the accessors the actions config needs; setting an action also refreshes the gui to show the reason bar +local function select_action(player, selected_action) + Elements.container.set_selected_action(player, selected_action) + Elements.player_table.refresh_player(player) +end +config.set_accessors(Elements.container.get_selected_player, select_action) + --- Add the element to the left flow with a toolbar button Gui.add_left_element(Elements.container, true) Gui.toolbar.create_button{ @@ -474,18 +494,20 @@ Gui.toolbar.create_button{ end } ---- Refresh the play times for all online players -local function refresh_times() +--- Rebuild the player list for all online players, used when the sort order changes +local function redraw_player_list() + local row_data = Elements.player_table.calculate_row_data() for _, player_table in Elements.player_table:online_elements() do - Elements.player_table.refresh_times(player_table) + Elements.player_table.rebuild(player_table, row_data) + Elements.player_table.refresh_player(Gui.get_player(player_table)) end end ---- Rebuild the player list for all online players, used when the sort order changes -local function redraw_player_list() +--- Refresh the play times for all online players +local function refresh_player_times() + local time_data = Elements.player_table.calculate_time_data() for _, player_table in Elements.player_table:online_elements() do - Elements.player_table.rebuild(player_table) - update_player_ui(Gui.get_player(player_table)) + Elements.player_table.refresh_times(player_table, time_data) end end @@ -496,8 +518,9 @@ local function on_player_left_game(event) for _, player_table in Elements.player_table:online_elements() do Elements.player_table.remove_row(player_table, left_player.name) local viewing_player = Gui.get_player(player_table) - if get_selected_player(viewing_player) == left_player.name then - set_selected_player(viewing_player, nil) + if Elements.container.get_selected_player(viewing_player) == left_player then + Elements.container.set_selected_player(viewing_player, nil) + Elements.player_table.refresh_player(viewing_player) end end end @@ -513,6 +536,6 @@ return { [Roles.events.on_role_unassigned] = redraw_player_list, }, on_nth_tick = { - [1800] = refresh_times, + [1800] = refresh_player_times, } } From df129b5e9acaf7ea1df141ed304c65d669a1138a Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Tue, 23 Jun 2026 18:22:37 +0000 Subject: [PATCH 03/33] Address player list review feedback - Rename the player table element data class to element_data - Remove the redundant offline selection clear; on_player_left_game already clears any selection pointing at a player who disconnects - Move the action bar and reason bar visibility into their own refresh methods so refresh_player just delegates to them Co-Authored-By: Claude Opus 4.8 (1M context) --- exp_scenario/module/gui/player_list.lua | 36 +++++++++++++------------ 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/exp_scenario/module/gui/player_list.lua b/exp_scenario/module/gui/player_list.lua index 6dd02289..52509ebc 100644 --- a/exp_scenario/module/gui/player_list.lua +++ b/exp_scenario/module/gui/player_list.lua @@ -149,14 +149,14 @@ Elements.player_name_label = Gui.define("player_list/player_name_label") --- @field name_label LuaGuiElement --- @field time_label LuaGuiElement ---- @class ExpGui_PlayerList.elements.player_table.data +--- @class ExpGui_PlayerList.elements.player_table.element_data --- @field rows table --- @field action_bar LuaGuiElement --- @field reason_bar LuaGuiElement --- Scroll table containing a row for each online player --- @class ExpGui_PlayerList.elements.player_table: ExpElement ---- @field data table +--- @field data table --- @overload fun(parent: LuaGuiElement): LuaGuiElement Elements.player_table = Gui.define("player_list/player_table") :track_all_elements() @@ -305,24 +305,13 @@ end --- Refresh the action bar, reason bar and row highlights for a player to match their selection --- @param player LuaPlayer function Elements.player_table.refresh_player(player) - -- Clear the selection if the selected player has gone offline local selected_player = Elements.container.get_selected_player(player) - if selected_player and not selected_player.connected then - Elements.container.set_selected_player(player, nil) - selected_player = nil - end local selected_action = Elements.container.get_selected_action(player) for _, player_table in Elements.player_table:online_elements(player) do local element_data = Elements.player_table.data[player_table] - - -- Update the action bar and reason bar visibility - if selected_player then - Elements.action_bar.refresh(element_data.action_bar, player, selected_player) - else - element_data.action_bar.visible = false - end - element_data.reason_bar.visible = selected_player ~= nil and selected_action ~= nil + Elements.action_bar.refresh(element_data.action_bar, player, selected_player) + Elements.reason_bar.refresh(element_data.reason_bar, selected_player, selected_action) -- Highlight the open button of the selected player for player_name, row in pairs(element_data.rows) do @@ -355,11 +344,16 @@ Elements.action_bar = Gui.define("player_list/action_bar") padding = { 1, 3 }, } --[[ @as any ]] ---- Update the action bar buttons to match the selection for a player +--- Update the action bar buttons to match the selection for a player, hidden when nothing is selected --- @param action_bar LuaGuiElement --- @param player LuaPlayer ---- @param selected_player LuaPlayer +--- @param selected_player LuaPlayer? function Elements.action_bar.refresh(action_bar, player, selected_player) + if not selected_player then + action_bar.visible = false + return + end + action_bar.visible = true for action_name, buttons in pairs(config.buttons) do local flow = action_bar[action_name] @@ -398,6 +392,14 @@ Elements.reason_bar = Gui.define("player_list/reason_bar") padding = { -1, 3 }, } --[[ @as any ]] +--- Update the reason bar visibility to match the selection, shown only while an action awaits a reason +--- @param reason_bar LuaGuiElement +--- @param selected_player LuaPlayer? +--- @param selected_action string? +function Elements.reason_bar.refresh(reason_bar, selected_player, selected_action) + reason_bar.visible = selected_player ~= nil and selected_action ~= nil +end + --- @class ExpGui_PlayerList.elements.container.selection --- @field selected_player LuaPlayer? --- @field selected_action string? From f3a04fd26c6b72ad1ed41d07b904ac52aaac86e4 Mon Sep 17 00:00:00 2001 From: PHIDIAS Date: Thu, 25 Jun 2026 05:25:39 +0900 Subject: [PATCH 04/33] remove personal-logistic (#428) * Delete exp_legacy/module/modules/data/personal-logistic.lua * Update _file_loader.lua * Update roles.lua * Delete exp_legacy/module/config/personal_logistic.lua --- exp_legacy/module/config/_file_loader.lua | 1 - exp_legacy/module/config/expcore/roles.lua | 1 - .../module/config/personal_logistic.lua | 1641 ----------------- .../module/modules/data/personal-logistic.lua | 89 - 4 files changed, 1732 deletions(-) delete mode 100644 exp_legacy/module/config/personal_logistic.lua delete mode 100644 exp_legacy/module/modules/data/personal-logistic.lua diff --git a/exp_legacy/module/config/_file_loader.lua b/exp_legacy/module/config/_file_loader.lua index 4562a7d5..fae86b7c 100644 --- a/exp_legacy/module/config/_file_loader.lua +++ b/exp_legacy/module/config/_file_loader.lua @@ -17,7 +17,6 @@ return { "modules.data.alt-view", "modules.data.tag", -- 'modules.data.bonus', - "modules.data.personal-logistic", "modules.data.language", --"modules.data.toolbar", diff --git a/exp_legacy/module/config/expcore/roles.lua b/exp_legacy/module/config/expcore/roles.lua index 46e26d73..a42a0a52 100644 --- a/exp_legacy/module/config/expcore/roles.lua +++ b/exp_legacy/module/config/expcore/roles.lua @@ -236,7 +236,6 @@ Roles.new_role("Member", "Mem") "gui/tool", "command/save-quickbar", "command/vlayer-info", - "command/personal-logistic", "command/lawnmower", "command/waterfill", "command/artillery", diff --git a/exp_legacy/module/config/personal_logistic.lua b/exp_legacy/module/config/personal_logistic.lua deleted file mode 100644 index d8e4e7bb..00000000 --- a/exp_legacy/module/config/personal_logistic.lua +++ /dev/null @@ -1,1641 +0,0 @@ ---- PL Settings --- @config Personal Logistic - -return { - start = 0, - production_required = { - ["belt"] = 100, - ["power"] = 20, - ["miner"] = 20, - ["furnace"] = 20, - ["machine"] = 20, - ["pole"] = 20, - ["bot"] = 20, - ["inserter"] = 20, - ["chest"] = 20, - ["rail"] = 100, - ["module"] = 20, - ["defense"] = 20, - ["rocket"] = 20, - ["ammo"] = 20, - ["armor"] = 3, - ["armor_equipment"] = 4, - ["weapon"] = 0, - }, - request = { - -- belt - ["transport-belt"] = { - key = 1, - upgrade_of = nil, - type = "belt", - stack = 100, - min = 800, - max = 800, - }, - ["underground-belt"] = { - key = 2, - upgrade_of = nil, - type = "belt", - stack = 50, - min = 250, - max = 250, - }, - ["splitter"] = { - key = 3, - upgrade_of = nil, - type = "belt", - stack = 50, - min = 100, - max = 100, - }, - ["fast-transport-belt"] = { - key = 11, - upgrade_of = "transport-belt", - type = "belt", - stack = 100, - min = 800, - max = 800, - }, - ["fast-underground-belt"] = { - key = 12, - upgrade_of = "underground-belt", - type = "belt", - stack = 50, - min = 250, - max = 250, - }, - ["fast-splitter"] = { - key = 13, - upgrade_of = "splitter", - type = "belt", - stack = 50, - min = 100, - max = 100, - }, - ["express-transport-belt"] = { - key = 21, - upgrade_of = "fast-transport-belt", - type = "belt", - stack = 100, - min = 800, - max = 800, - }, - ["express-underground-belt"] = { - key = 22, - upgrade_of = "fast-underground-belt", - type = "belt", - stack = 50, - min = 250, - max = 250, - }, - ["express-splitter"] = { - key = 23, - upgrade_of = "fast-splitter", - type = "belt", - stack = 50, - min = 100, - max = 100, - }, - -- power - ["solar-panel"] = { - key = 4, - upgrade_of = nil, - type = "power", - stack = 50, - min = 0, - max = 0, - }, - ["accumulator"] = { - key = 5, - upgrade_of = nil, - type = "power", - stack = 50, - min = 0, - max = 0, - }, - ["boiler"] = { - key = 6, - upgrade_of = nil, - type = nil, - stack = 50, - min = 0, - max = 0, - }, - ["steam-engine"] = { - key = 7, - upgrade_of = nil, - type = nil, - stack = 10, - min = 0, - max = 0, - }, - -- miner - ["burner-mining-drill"] = { - key = 8, - upgrade_of = nil, - type = "miner", - stack = 50, - min = 0, - max = 0, - }, - ["electric-mining-drill"] = { - key = 9, - upgrade_of = "burner-mining-drill", - type = "miner", - stack = 50, - min = 250, - max = 250, - }, - ["pumpjack"] = { - key = 10, - upgrade_of = nil, - type = "miner", - stack = 20, - min = 20, - max = 20, - }, - -- furnace - ["stone-furnace"] = { - key = 18, - upgrade_of = nil, - type = "furnace", - stack = 50, - min = 0, - max = 0, - }, - ["steel-furnace"] = { - key = 19, - upgrade_of = "stone-furnace", - type = "furnace", - stack = 50, - min = 0, - max = 0, - }, - ["electric-furnace"] = { - key = 20, - upgrade_of = "steel-furnace", - type = "furnace", - stack = 50, - min = 0, - max = 0, - }, - -- machine - ["assembling-machine-1"] = { - key = 28, - upgrade_of = nil, - type = "machine", - stack = 50, - min = 0, - max = 50, - }, - ["assembling-machine-2"] = { - key = 29, - upgrade_of = "assembling-machine-1", - type = "machine", - stack = 50, - min = 50, - max = 50, - }, - ["assembling-machine-3"] = { - key = 30, - upgrade_of = "assembling-machine-2", - type = "machine", - stack = 50, - min = 50, - max = 50, - }, - ["oil-refinery"] = { - key = 24, - upgrade_of = nil, - type = "machine", - stack = 10, - min = 0, - max = 0, - }, - ["chemical-plant"] = { - key = 25, - upgrade_of = nil, - type = "machine", - stack = 10, - min = 0, - max = 0, - }, - ["centrifuge"] = { - key = 26, - upgrade_of = nil, - type = nil, - stack = 50, - min = 0, - max = 0, - }, - ["lab"] = { - key = 27, - upgrade_of = nil, - type = nil, - stack = 10, - min = 0, - max = 0, - }, - ["nuclear-reactor"] = { - key = 14, - upgrade_of = nil, - type = nil, - stack = 10, - min = 0, - max = 0, - }, - ["heat-pipe"] = { - key = 15, - upgrade_of = nil, - type = nil, - stack = 50, - min = 0, - max = 0, - }, - ["heat-exchanger"] = { - key = 16, - upgrade_of = nil, - type = nil, - stack = 50, - min = 0, - max = 0, - }, - ["steam-turbine"] = { - key = 17, - upgrade_of = nil, - type = nil, - stack = 10, - min = 0, - max = 0, - }, - ["rocket-silo"] = { - key = 80, - upgrade_of = nil, - type = nil, - stack = 1, - min = 0, - max = 0, - }, - -- pole - ["small-electric-pole"] = { - key = 31, - upgrade_of = nil, - type = "pole", - stack = 50, - min = 150, - max = 150, - }, - ["medium-electric-pole"] = { - key = 32, - upgrade_of = "small-electric-pole", - type = "pole", - stack = 50, - min = 150, - max = 150, - }, - ["big-electric-pole"] = { - key = 33, - upgrade_of = nil, - type = "pole", - stack = 50, - min = 150, - max = 150, - }, - ["substation"] = { - key = 34, - upgrade_of = nil, - type = "pole", - stack = 50, - min = 50, - max = 50, - }, - -- bot - ["roboport"] = { - key = 35, - upgrade_of = nil, - type = "bot", - stack = 10, - min = 20, - max = 20, - }, - ["construction-robot"] = { - key = 36, - upgrade_of = nil, - type = "bot", - stack = 50, - min = 100, - max = 100, - }, - ["logistic-robot"] = { - key = 37, - upgrade_of = nil, - type = "bot", - stack = 50, - min = 10, - max = 10, - }, - ["cliff-explosives"] = { - key = 38, - upgrade_of = nil, - type = "bot", - stack = 20, - min = 80, - max = 80, - }, - ["repair-pack"] = { - key = 39, - upgrade_of = nil, - type = "bot", - stack = 100, - min = 100, - max = 100, - }, - ["landfill"] = { - key = 40, - upgrade_of = nil, - type = "bot", - stack = 100, - min = 300, - max = 300, - }, - -- ore - ["wood"] = { - key = 151, - upgrade_of = nil, - type = nil, - stack = 100, - min = 0, - max = 0, - }, - ["coal"] = { - key = 152, - upgrade_of = nil, - type = nil, - stack = 50, - min = 0, - max = 0, - }, - ["stone"] = { - key = 153, - upgrade_of = nil, - type = nil, - stack = 50, - min = 0, - max = 0, - }, - ["iron-ore"] = { - key = 154, - upgrade_of = nil, - type = nil, - stack = 50, - min = 0, - max = 0, - }, - ["copper-ore"] = { - key = 155, - upgrade_of = nil, - type = nil, - stack = 50, - min = 0, - max = 0, - }, - ["uranium-ore"] = { - key = 156, - upgrade_of = nil, - type = nil, - stack = 50, - min = 0, - max = 0, - }, - ["raw-fish"] = { - key = 157, - upgrade_of = nil, - type = nil, - stack = 100, - min = 0, - max = 0, - }, - ["iron-stick"] = { - key = 158, - upgrade_of = nil, - type = nil, - stack = 100, - min = 0, - max = 0, - }, - ["iron-gear-wheel"] = { - key = 159, - upgrade_of = nil, - type = nil, - stack = 100, - min = 0, - max = 0, - }, - ["satellite"] = { - key = 160, - upgrade_of = nil, - type = nil, - stack = 1, - min = 0, - max = 0, - }, - -- inserter - ["burner-inserter"] = { - key = 41, - upgrade_of = nil, - type = "inserter", - stack = 50, - min = 0, - max = 50, - }, - ["inserter"] = { - key = 42, - upgrade_of = "burner-inserter", - type = "inserter", - stack = 50, - min = 0, - max = 50, - }, - ["long-handed-inserter"] = { - key = 43, - upgrade_of = nil, - type = "inserter", - stack = 50, - min = 0, - max = 0, - }, - ["fast-inserter"] = { - key = 44, - upgrade_of = "inserter", - type = "inserter", - stack = 50, - min = 0, - max = 50, - }, - ["filter-inserter"] = { - key = 45, - upgrade_of = nil, - type = "inserter", - stack = 50, - min = 50, - max = 50, - }, - ["stack-inserter"] = { - key = 46, - upgrade_of = "fast-inserter", - type = "inserter", - stack = 50, - min = 100, - max = 100, - }, - ["stack-filter-inserter"] = { - key = 47, - upgrade_of = nil, - type = "inserter", - stack = 50, - min = 50, - max = 50, - }, - -- pipe - ["pipe"] = { - key = 48, - upgrade_of = nil, - type = nil, - stack = 100, - min = 200, - max = 200, - }, - ["pipe-to-ground"] = { - key = 49, - upgrade_of = nil, - type = nil, - stack = 50, - min = 150, - max = 150, - }, - ["pump"] = { - key = 50, - upgrade_of = nil, - type = nil, - stack = 50, - min = 100, - max = 100, - }, - ["storage-tank"] = { - key = 59, - upgrade_of = nil, - type = nil, - stack = 50, - min = 50, - max = 50, - }, - -- chest - ["wooden-chest"] = { - key = 51, - upgrade_of = nil, - type = "chest", - stack = 50, - min = 0, - max = 50, - }, - ["iron-chest"] = { - key = 52, - upgrade_of = "wooden-chest", - type = "chest", - stack = 50, - min = 0, - max = 50, - }, - ["steel-chest"] = { - key = 53, - upgrade_of = "iron-chest", - type = "chest", - stack = 50, - min = 50, - max = 100, - }, - ["passive-provider-chest"] = { - key = 54, - upgrade_of = nil, - type = "chest", - stack = 50, - min = 50, - max = 50, - }, - ["storage-chest"] = { - key = 55, - upgrade_of = nil, - type = "chest", - stack = 50, - min = 50, - max = 50, - }, - ["requester-chest"] = { - key = 56, - upgrade_of = nil, - type = "chest", - stack = 50, - min = 50, - max = 50, - }, - ["buffer-chest"] = { - key = 57, - upgrade_of = nil, - type = "chest", - stack = 50, - min = 50, - max = 50, - }, - ["active-provider-chest"] = { - key = 58, - upgrade_of = nil, - type = "chest", - stack = 50, - min = 50, - max = 50, - }, - -- rail - ["rail"] = { - key = 61, - upgrade_of = nil, - type = "rail", - stack = 100, - min = 1500, - max = 1500, - }, - ["train-stop"] = { - key = 62, - upgrade_of = nil, - type = "rail", - stack = 10, - min = 20, - max = 20, - }, - ["rail-signal"] = { - key = 63, - upgrade_of = nil, - type = "rail", - stack = 50, - min = 150, - max = 150, - }, - ["rail-chain-signal"] = { - key = 64, - upgrade_of = nil, - type = "rail", - stack = 50, - min = 150, - max = 150, - }, - ["locomotive"] = { - key = 65, - upgrade_of = nil, - type = "rail", - stack = 5, - min = 0, - max = 0, - }, - ["cargo-wagon"] = { - key = 66, - upgrade_of = nil, - type = "rail", - stack = 5, - min = 0, - max = 0, - }, - ["fluid-wagon"] = { - key = 67, - upgrade_of = nil, - type = "rail", - stack = 5, - min = 0, - max = 0, - }, - ["artillery-wagon"] = { - key = 68, - upgrade_of = nil, - type = "rail", - stack = 5, - min = 0, - max = 0, - }, - -- circuit - ["constant-combinator"] = { - key = 71, - upgrade_of = nil, - type = nil, - stack = 50, - min = 50, - max = 50, - }, - ["arithmetic-combinator"] = { - key = 72, - upgrade_of = nil, - type = nil, - stack = 50, - min = 50, - max = 50, - }, - ["decider-combinator"] = { - key = 73, - upgrade_of = nil, - type = nil, - stack = 50, - min = 50, - max = 50, - }, - ["small-lamp"] = { - key = 74, - upgrade_of = nil, - type = nil, - stack = 50, - min = 50, - max = 50, - }, - ["red-wire"] = { - key = 75, - upgrade_of = nil, - type = nil, - stack = 200, - min = 200, - max = 200, - }, - ["green-wire"] = { - key = 76, - upgrade_of = nil, - type = nil, - stack = 200, - min = 200, - max = 200, - }, - ["copper-cable"] = { - key = 77, - upgrade_of = nil, - type = nil, - stack = 200, - min = 200, - max = 200, - }, - ["power-switch"] = { - key = 78, - upgrade_of = nil, - type = nil, - stack = 50, - min = 0, - max = 0, - }, - ["programmable-speaker"] = { - key = 79, - upgrade_of = nil, - type = nil, - stack = 50, - min = 10, - max = 10, - }, - ["offshore-pump"] = { - key = 60, - upgrade_of = nil, - type = nil, - stack = 20, - min = 20, - max = 20, - }, - -- module - ["beacon"] = { - key = 81, - upgrade_of = nil, - type = "module", - stack = 10, - min = 120, - max = 120, - }, - ["speed-module"] = { - key = 82, - upgrade_of = nil, - type = "module", - stack = 50, - min = 500, - max = 500, - }, - ["speed-module-2"] = { - key = 83, - upgrade_of = "speed-module", - type = "module", - stack = 50, - min = 0, - max = 0, - }, - ["speed-module-3"] = { - key = 84, - upgrade_of = "speed-module-2", - type = "module", - stack = 50, - min = 500, - max = 500, - }, - ["productivity-module"] = { - key = 85, - upgrade_of = nil, - type = "module", - stack = 50, - min = 0, - max = 0, - }, - ["productivity-module-2"] = { - key = 86, - upgrade_of = "productivity-module", - type = "module", - stack = 50, - min = 0, - max = 0, - }, - ["productivity-module-3"] = { - key = 87, - upgrade_of = "productivity-module-2", - type = "module", - stack = 50, - min = 0, - max = 0, - }, - ["effectivity-module"] = { - key = 88, - upgrade_of = nil, - type = "module", - stack = 50, - min = 0, - max = 0, - }, - ["effectivity-module-2"] = { - key = 89, - upgrade_of = "effectivity-module", - type = "module", - stack = 50, - min = 0, - max = 0, - }, - ["effectivity-module-3"] = { - key = 90, - upgrade_of = "effectivity-module-2", - type = "module", - stack = 50, - min = 0, - max = 0, - }, - -- defense - ["stone-wall"] = { - key = 91, - upgrade_of = nil, - type = "defense", - stack = 100, - min = 0, - max = 0, - }, - ["gate"] = { - key = 92, - upgrade_of = nil, - type = "defense", - stack = 50, - min = 0, - max = 0, - }, - ["gun-turret"] = { - key = 93, - upgrade_of = nil, - type = "defense", - stack = 50, - min = 0, - max = 0, - }, - ["laser-turret"] = { - key = 94, - upgrade_of = nil, - type = "defense", - stack = 50, - min = 100, - max = 100, - }, - ["flamethrower-turret"] = { - key = 95, - upgrade_of = nil, - type = "defense", - stack = 50, - min = 0, - max = 0, - }, - ["artillery-turret"] = { - key = 96, - upgrade_of = nil, - type = "defense", - stack = 10, - min = 0, - max = 0, - }, - -- rocket - ["rocket"] = { - key = 101, - upgrade_of = nil, - type = "rocket", - stack = 200, - min = 0, - max = 0, - }, - ["explosive-rocket"] = { - key = 102, - upgrade_of = "rocket", - type = "rocket", - stack = 200, - min = 0, - max = 0, - }, - ["atomic-bomb"] = { - key = 103, - upgrade_of = "explosive-rocket", - type = "rocket", - stack = 10, - min = 0, - max = 0, - }, - ["rocket-launcher"] = { - key = 104, - upgrade_of = nil, - type = "weapon", - stack = 5, - min = 1, - max = 1, - }, - ["flamethrower"] = { - key = 105, - upgrade_of = nil, - type = "weapon", - stack = 5, - min = 0, - max = 0, - }, - ["pistol"] = { - key = 106, - upgrade_of = nil, - type = "weapon", - stack = 5, - min = 0, - max = 0, - }, - ["submachine-gun"] = { - key = 107, - upgrade_of = nil, - type = "weapon", - stack = 5, - min = 0, - max = 0, - }, - ["shotgun"] = { - key = 108, - upgrade_of = nil, - type = "weapon", - stack = 5, - min = 0, - max = 0, - }, - ["combat-shotgun"] = { - key = 109, - upgrade_of = nil, - type = "weapon", - stack = 5, - min = 0, - max = 0, - }, - ["land-mine"] = { - key = 110, - upgrade_of = nil, - type = nil, - stack = 100, - min = 0, - max = 0, - }, - -- ammo - ["firearm-magazine"] = { - key = 111, - upgrade_of = nil, - type = "ammo", - stack = 200, - min = 0, - max = 0, - }, - ["piercing-rounds-magazine"] = { - key = 112, - upgrade_of = "firearm-magazine", - type = "ammo", - stack = 200, - min = 0, - max = 0, - }, - ["uranium-rounds-magazine"] = { - key = 113, - upgrade_of = "piercing-rounds-magazine", - type = "ammo", - stack = 200, - min = 0, - max = 0, - }, - ["flamethrower-ammo"] = { - key = 114, - upgrade_of = nil, - type = "ammo", - stack = 100, - min = 0, - max = 0, - }, - ["shotgun-shell"] = { - key = 115, - upgrade_of = nil, - type = "ammo", - stack = 200, - min = 0, - max = 0, - }, - ["piercing-shotgun-shell"] = { - key = 116, - upgrade_of = nil, - type = "ammo", - stack = 200, - min = 0, - max = 0, - }, - ["cannon-shell"] = { - key = 117, - upgrade_of = nil, - type = "ammo", - stack = 200, - min = 0, - max = 0, - }, - ["explosive-cannon-shell"] = { - key = 118, - upgrade_of = nil, - type = "ammo", - stack = 200, - min = 0, - max = 0, - }, - ["uranium-cannon-shell"] = { - key = 119, - upgrade_of = nil, - type = "ammo", - stack = 200, - min = 0, - max = 0, - }, - ["explosive-uranium-cannon-shell"] = { - key = 120, - upgrade_of = nil, - type = "ammo", - stack = 200, - min = 0, - max = 0, - }, - ["grenade"] = { - key = 97, - upgrade_of = nil, - type = "ammo", - stack = 100, - min = 0, - max = 0, - }, - ["cluster-grenade"] = { - key = 98, - upgrade_of = nil, - type = "ammo", - stack = 100, - min = 0, - max = 0, - }, - ["artillery-shell"] = { - key = 121, - upgrade_of = nil, - type = nil, - stack = 1, - min = 0, - max = 0, - }, - ["poison-capsule"] = { - key = 122, - upgrade_of = nil, - type = nil, - stack = 100, - min = 0, - max = 0, - }, - ["slowdown-capsule"] = { - key = 123, - upgrade_of = nil, - type = nil, - stack = 100, - min = 0, - max = 0, - }, - ["defender-capsule"] = { - key = 124, - upgrade_of = nil, - type = nil, - stack = 100, - min = 0, - max = 0, - }, - ["distractor-capsule"] = { - key = 125, - upgrade_of = nil, - type = nil, - stack = 100, - min = 0, - max = 0, - }, - ["destroyer-capsule"] = { - key = 126, - upgrade_of = nil, - type = nil, - stack = 100, - min = 0, - max = 0, - }, - ["car"] = { - key = 127, - upgrade_of = nil, - type = nil, - stack = 1, - min = 0, - max = 0, - }, - ["tank"] = { - key = 128, - upgrade_of = nil, - type = nil, - stack = 1, - min = 0, - max = 0, - }, - ["spidertron"] = { - key = 129, - upgrade_of = nil, - type = nil, - stack = 1, - min = 0, - max = 5, - }, - ["spidertron-remote"] = { - key = 130, - upgrade_of = nil, - type = nil, - stack = 1, - min = 0, - max = 5, - }, - ["radar"] = { - key = 99, - upgrade_of = nil, - type = nil, - stack = 50, - min = 50, - max = 50, - }, - -- armor - ["light-armor"] = { - key = 131, - upgrade_of = nil, - type = "armor", - stack = 1, - min = 0, - max = 1, - }, - ["heavy-armor"] = { - key = 132, - upgrade_of = "light-armor", - type = "armor", - stack = 1, - min = 0, - max = 1, - }, - ["modular-armor"] = { - key = 133, - upgrade_of = "heavy-armor", - type = "armor", - stack = 1, - min = 0, - max = 1, - }, - ["power-armor"] = { - key = 134, - upgrade_of = "modular-armor", - type = "armor", - stack = 1, - min = 0, - max = 1, - }, - ["power-armor-mk2"] = { - key = 135, - upgrade_of = "power-armor", - type = "armor", - stack = 1, - min = 0, - max = 1, - }, - -- armor equipment - ["solar-panel-equipment"] = { - key = 136, - upgrade_of = nil, - type = "armor_equipment", - stack = 20, - min = 0, - max = 5, - }, - ["fusion-reactor-equipment"] = { - key = 137, - upgrade_of = "solar-panel-equipment", - type = "armor_equipment", - stack = 20, - min = 0, - max = 5, - }, - ["belt-immunity-equipment"] = { - key = 138, - upgrade_of = nil, - type = "armor_equipment", - stack = 20, - min = 0, - max = 5, - }, - ["night-vision-equipment"] = { - key = 139, - upgrade_of = nil, - type = "armor_equipment", - stack = 20, - min = 0, - max = 5, - }, - ["personal-laser-defense-equipment"] = { - key = 140, - upgrade_of = nil, - type = "armor_equipment", - stack = 20, - min = 0, - max = 20, - }, - ["personal-roboport-equipment"] = { - key = 141, - upgrade_of = nil, - type = "armor_equipment", - stack = 20, - min = 0, - max = 0, - }, - ["personal-roboport-mk2-equipment"] = { - key = 142, - upgrade_of = "personal-roboport-equipment", - type = "armor_equipment", - stack = 20, - min = 0, - max = 5, - }, - ["energy-shield-equipment"] = { - key = 143, - upgrade_of = nil, - type = "armor_equipment", - stack = 20, - min = 0, - max = 0, - }, - ["energy-shield-mk2-equipment"] = { - key = 144, - upgrade_of = "energy-shield-equipment", - type = "armor_equipment", - stack = 20, - min = 0, - max = 5, - }, - ["battery-equipment"] = { - key = 145, - upgrade_of = nil, - type = "armor_equipment", - stack = 20, - min = 0, - max = 0, - }, - ["battery-mk2-equipment"] = { - key = 146, - upgrade_of = "battery-equipment", - type = "armor_equipment", - stack = 20, - min = 0, - max = 5, - }, - ["exoskeleton-equipment"] = { - key = 147, - upgrade_of = nil, - type = "armor_equipment", - stack = 20, - min = 0, - max = 5, - }, - ["discharge-defense-equipment"] = { - key = 148, - upgrade_of = nil, - type = "armor_equipment", - stack = 20, - min = 0, - max = 0, - }, - ["discharge-defense-remote"] = { - key = 149, - upgrade_of = nil, - type = nil, - stack = 1, - min = 0, - max = 0, - }, - ["artillery-targeting-remote"] = { - key = 150, - upgrade_of = nil, - type = nil, - stack = 1, - min = 1, - max = 1, - }, - -- path - ["stone-brick"] = { - key = 100, - upgrade_of = nil, - type = nil, - stack = 100, - min = 0, - max = 0, - }, - ["concrete"] = { - key = 69, - upgrade_of = nil, - type = nil, - stack = 100, - min = 0, - max = 0, - }, - ["hazard-concrete"] = { - key = 70, - upgrade_of = nil, - type = nil, - stack = 100, - min = 0, - max = 0, - }, - ["refined-concrete"] = { - key = 188, - upgrade_of = nil, - type = nil, - stack = 100, - min = 0, - max = 0, - }, - ["refined-hazard-concrete"] = { - key = 189, - upgrade_of = nil, - type = nil, - stack = 100, - min = 0, - max = 0, - }, - ["crude-oil-barrel"] = { - key = 161, - upgrade_of = nil, - type = nil, - stack = 10, - min = 0, - max = 0, - }, - ["heavy-oil-barrel"] = { - key = 162, - upgrade_of = nil, - type = nil, - stack = 10, - min = 0, - max = 0, - }, - ["light-oil-barrel"] = { - key = 163, - upgrade_of = nil, - type = nil, - stack = 10, - min = 0, - max = 0, - }, - ["lubricant-barrel"] = { - key = 164, - upgrade_of = nil, - type = nil, - stack = 10, - min = 0, - max = 0, - }, - ["petroleum-gas-barrel"] = { - key = 165, - upgrade_of = nil, - type = nil, - stack = 10, - min = 0, - max = 0, - }, - ["sulfuric-acid-barrel"] = { - key = 166, - upgrade_of = nil, - type = nil, - stack = 10, - min = 0, - max = 0, - }, - ["water-barrel"] = { - key = 167, - upgrade_of = nil, - type = nil, - stack = 10, - min = 0, - max = 0, - }, - ["empty-barrel"] = { - key = 168, - upgrade_of = nil, - type = nil, - stack = 10, - min = 0, - max = 0, - }, - ["uranium-fuel-cell"] = { - key = 169, - upgrade_of = nil, - type = nil, - stack = 50, - min = 0, - max = 0, - }, - ["used-up-uranium-fuel-cell"] = { - key = 170, - upgrade_of = nil, - type = nil, - stack = 50, - min = 0, - max = 0, - }, - -- science and circuit - ["automation-science-pack"] = { - key = 171, - upgrade_of = nil, - type = nil, - stack = 200, - min = 0, - max = 0, - }, - ["logistic-science-pack"] = { - key = 172, - upgrade_of = nil, - type = nil, - stack = 200, - min = 0, - max = 0, - }, - ["military-science-pack"] = { - key = 173, - upgrade_of = nil, - type = nil, - stack = 200, - min = 0, - max = 0, - }, - ["chemical-science-pack"] = { - key = 174, - upgrade_of = nil, - type = nil, - stack = 200, - min = 0, - max = 0, - }, - ["production-science-pack"] = { - key = 175, - upgrade_of = nil, - type = nil, - stack = 200, - min = 0, - max = 0, - }, - ["utility-science-pack"] = { - key = 176, - upgrade_of = nil, - type = nil, - stack = 200, - min = 0, - max = 0, - }, - ["space-science-pack"] = { - key = 177, - upgrade_of = nil, - type = nil, - stack = 2000, - min = 0, - max = 0, - }, - ["electronic-circuit"] = { - key = 178, - upgrade_of = nil, - type = nil, - stack = 200, - min = 0, - max = 0, - }, - ["advanced-circuit"] = { - key = 179, - upgrade_of = nil, - type = nil, - stack = 200, - min = 0, - max = 0, - }, - ["processing-unit"] = { - key = 180, - upgrade_of = nil, - type = nil, - stack = 100, - min = 0, - max = 0, - }, - -- intermediate - ["iron-plate"] = { - key = 181, - upgrade_of = nil, - type = nil, - stack = 100, - min = 0, - max = 0, - }, - ["copper-plate"] = { - key = 182, - upgrade_of = nil, - type = nil, - stack = 100, - min = 0, - max = 0, - }, - ["steel-plate"] = { - key = 183, - upgrade_of = nil, - type = nil, - stack = 100, - min = 0, - max = 0, - }, - ["plastic-bar"] = { - key = 184, - upgrade_of = nil, - type = nil, - stack = 100, - min = 0, - max = 0, - }, - ["sulfur"] = { - key = 185, - upgrade_of = nil, - type = nil, - stack = 50, - min = 0, - max = 0, - }, - ["battery"] = { - key = 186, - upgrade_of = nil, - type = nil, - stack = 200, - min = 0, - max = 0, - }, - ["explosives"] = { - key = 187, - upgrade_of = nil, - type = nil, - stack = 50, - min = 0, - max = 0, - }, - ["engine-unit"] = { - key = 191, - upgrade_of = nil, - type = nil, - stack = 50, - min = 0, - max = 0, - }, - ["electric-engine-unit"] = { - key = 192, - upgrade_of = nil, - type = nil, - stack = 50, - min = 0, - max = 0, - }, - ["flying-robot-frame"] = { - key = 193, - upgrade_of = nil, - type = nil, - stack = 50, - min = 0, - max = 0, - }, - ["rocket-control-unit"] = { - key = 194, - upgrade_of = nil, - type = nil, - stack = 10, - min = 0, - max = 0, - }, - ["low-density-structure"] = { - key = 195, - upgrade_of = nil, - type = nil, - stack = 10, - min = 0, - max = 0, - }, - ["solid-fuel"] = { - key = 196, - upgrade_of = nil, - type = nil, - stack = 50, - min = 0, - max = 0, - }, - ["rocket-fuel"] = { - key = 197, - upgrade_of = nil, - type = nil, - stack = 10, - min = 0, - max = 0, - }, - ["nuclear-fuel"] = { - key = 198, - upgrade_of = nil, - type = nil, - stack = 1, - min = 0, - max = 0, - }, - ["uranium-235"] = { - key = 199, - upgrade_of = nil, - type = nil, - stack = 100, - min = 0, - max = 0, - }, - ["uranium-238"] = { - key = 200, - upgrade_of = nil, - type = nil, - stack = 100, - min = 0, - max = 0, - }, - }, -} diff --git a/exp_legacy/module/modules/data/personal-logistic.lua b/exp_legacy/module/modules/data/personal-logistic.lua deleted file mode 100644 index 4d5abaa5..00000000 --- a/exp_legacy/module/modules/data/personal-logistic.lua +++ /dev/null @@ -1,89 +0,0 @@ -local Commands = require("modules/exp_commands") -local config = require("modules.exp_legacy.config.personal_logistic") --- @dep config.personal-logistic - ----@param target LuaEntity | LuaPlayer ----@param amount number -local function pl(target, amount) - local c - local s - - --- @cast target any Remove cast once implemented - error("Needs updating to use 2.0 logistics") - - if target.object_name == "LuaPlayer" then - c = target.clear_personal_logistic_slot - s = target.set_personal_logistic_slot - else - c = target.clear_vehicle_logistic_slot - s = target.set_vehicle_logistic_slot - end - - for _, v in pairs(config.request) do - c(config.start + v.key) - end - - if (amount < 0) then - return - end - - local stats = target.force.get_item_production_statistics(target.surface) - - for k, v in pairs(config.request) do - local v_min = math.ceil(v.min * amount) - local v_max = math.ceil(v.max * amount) - - if v.stack ~= nil and v.stack ~= 1 and v.type ~= "weapon" then - v_min = math.floor(v_min / v.stack) * v.stack - v_max = math.ceil(v_max / v.stack) * v.stack - end - - if v.upgrade_of == nil then - if v.type ~= nil then - if stats.get_input_count(k) < config.production_required[v.type] then - if v_min > 0 then - if v_min == v_max then - v_min = math.floor((v_max * 0.5) / v.stack) * v.stack - end - else - v_min = 0 - end - end - end - - s(config.start + v.key, { name = k, min = v_min, max = v_max }) - else - if v.type ~= nil then - if stats.get_input_count(k) >= config.production_required[v.type] then - s(config.start + v.key, { name = k, min = v_min, max = v_max }) - local vuo = v.upgrade_of - - while (vuo ~= nil) do - s(config.start + config.request[vuo].key, { name = vuo, min = 0, max = 0 }) - vuo = config.request[vuo].upgrade_of - end - else - s(config.start + v.key, { name = k, min = 0, max = v_max }) - end - end - end - end -end - -Commands.new("personal-logistic", "Set Personal Logistic (-1 to cancel all) (Select spidertron to edit spidertron)") - :argument("amount", "", Commands.types.integer_range(-1, 10)) - :add_aliases{ "pl" } - :add_flags{ "disabled" } -- Remove once implemented above - :register(function(player, amount) - --- @cast amount number - if player.force.technologies["logistic-robotics"].researched then - if player.selected ~= nil then - if player.selected.name == "spidertron" then - pl(player.selected, amount / 10) - end - else - pl(player, amount / 10) - end - else - return Commands.status.error("Personal Logistic not researched") - end - end) From dc015bc609eb91d7f611a07105d96931d55261ee Mon Sep 17 00:00:00 2001 From: PHIDIAS Date: Thu, 25 Jun 2026 05:30:46 +0900 Subject: [PATCH 05/33] bonus changes (#429) * Delete exp_legacy/module/modules/data/bonus.lua * Update _file_loader.lua * Update roles.lua * Update bonus.lua * Update player_bonus.lua * Update bonus.lua --------- Co-authored-by: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> --- exp_legacy/module/config/_file_loader.lua | 1 - exp_legacy/module/config/bonus.lua | 231 ++------------------- exp_legacy/module/config/expcore/roles.lua | 2 - exp_legacy/module/modules/data/bonus.lua | 99 --------- exp_scenario/module/control/bonus.lua | 4 +- exp_scenario/module/gui/player_bonus.lua | 5 +- 6 files changed, 19 insertions(+), 323 deletions(-) delete mode 100644 exp_legacy/module/modules/data/bonus.lua diff --git a/exp_legacy/module/config/_file_loader.lua b/exp_legacy/module/config/_file_loader.lua index fae86b7c..b0868f01 100644 --- a/exp_legacy/module/config/_file_loader.lua +++ b/exp_legacy/module/config/_file_loader.lua @@ -16,7 +16,6 @@ return { "modules.data.quickbar", "modules.data.alt-view", "modules.data.tag", - -- 'modules.data.bonus', "modules.data.language", --"modules.data.toolbar", diff --git a/exp_legacy/module/config/bonus.lua b/exp_legacy/module/config/bonus.lua index 67e3d0be..5c1a3c22 100644 --- a/exp_legacy/module/config/bonus.lua +++ b/exp_legacy/module/config/bonus.lua @@ -3,21 +3,17 @@ return { --[[ - TODO - force bonus - quick health regeneration - Base point is equal to the amount of standard value in each parameter. CMMS CRS CCS CISB CHB CRDB PBR - STD 30 90 32 20 16 12 60 - = 260 + STD 20 60 24 10 12 8 40 + = 174 - MAX 60 180 64 40 32 24 120 - = 480 + MAX 40 120 48 20 24 16 80 + = 348 ]] points = { - base = 260, + base = 174, increase_percentage_per_role_level = 0.03, role_name = "Member", }, @@ -27,8 +23,7 @@ return { name = "character_mining_speed_modifier", scale = 1, cost = 10, - max_value = 6, - initial_value = 3, + max_value = 4, value_step = 0.5, is_percentage = true, }, @@ -36,8 +31,7 @@ return { name = "character_running_speed_modifier", scale = 1, cost = 60, - max_value = 3, - initial_value = 1.5, + max_value = 2, value_step = 0.25, is_percentage = true, }, @@ -45,33 +39,29 @@ return { name = "character_crafting_speed_modifier", scale = 1, cost = 4, - max_value = 16, - initial_value = 8, + max_value = 12, value_step = 1, is_percentage = true, }, { name = "character_inventory_slots_bonus", - cost = 2, scale = 10, - max_value = 200, - initial_value = 100, + cost = 2, + max_value = 100, value_step = 10, }, { name = "character_health_bonus", scale = 50, cost = 4, - max_value = 400, - initial_value = 200, + max_value = 300, value_step = 50, }, { name = "character_reach_distance_bonus", - cost = 1, scale = 1, - max_value = 24, - initial_value = 12, + cost = 1, + max_value = 16, value_step = 2, combined_bonus = { "character_resource_reach_distance_bonus", @@ -80,104 +70,15 @@ return { }, { name = "personal_battery_recharge", - initial_value = 6, - max_value = 12, - value_step = 1, scale = 4, cost = 40, + max_value = 8, + value_step = 1, is_special = true, }, - --[[ - ['character_item_pickup_distance_bonus'] = { - initial_value = 0, - max_value = 20, - value_step = 1, - scale = 1, - cost = 1, - is_percentage = false - }, - ['character_loot_pickup_distance_bonus'] = { - initial_value = 0, - max_value = 20, - value_step = 1, - scale = 1, - cost = 1, - is_percentage = false - }, - ['character_item_drop_distance_bonus'] = { - initial_value = 0, - max_value = 20, - value_step = 1, - scale = 1, - cost = 1, - is_percentage = false - } - ]] }, force_bonus = { - --[[ - ['character_mining_speed_modifier'] = { - initial_value = 0, - max_value = 6, - value_step = 0.5, - scale = 1, - cost = 10, - is_percentage = true - }, - ['character_running_speed_modifier'] = { - initial_value = 0, - max_value = 3, - value_step = 0.25, - scale = 1, - cost = 40, - is_percentage = true - }, - ['character_crafting_speed_modifier'] = { - initial_value = 0, - max_value = 16, - value_step = 1, - scale = 1, - cost = 4, - is_percentage = true - }, - ['character_inventory_slots_bonus'] = { - initial_value = 0, - max_value = 200, - value_step = 10, - scale = 100, - cost = 2, - is_percentage = false - }, - ['character_health_bonus'] = { - initial_value = 0, - max_value = 400, - value_step = 50, - cost = 4, - is_percentage = false - }, - ['character_reach_distance_bonus'] = { - initial_value = 0, - max_value = 24, - value_step = 2, - scale = 1, - cost = 1, - is_percentage = false, - combined_bonus = { - 'character_resource_reach_distance_bonus', - 'character_build_distance_bonus' - } - }, - ['worker_robots_speed_modifier'] = { - initial_value = 0, - max_value = 0, - value_step = 0, - scale = 1, - cost = 1, - is_percentage = false - }, - ]] ["worker_robots_battery_modifier"] = { - initial_value = 1, max_value = 1, value_step = 1, scale = 1, @@ -185,7 +86,6 @@ return { is_percentage = false, }, ["worker_robots_storage_bonus"] = { - initial_value = 1, max_value = 1, value_step = 1, scale = 1, @@ -193,114 +93,13 @@ return { is_percentage = false, }, ["following_robots_lifetime_modifier"] = { - initial_value = 1, max_value = 1, value_step = 1, scale = 1, cost = 1, is_percentage = false, }, - --[[ - ['character_item_pickup_distance_bonus'] = { - initial_value = 0, - max_value = 20, - value_step = 1, - scale = 1, - cost = 1, - is_percentage = false - }, - ['character_loot_pickup_distance_bonus'] = { - initial_value = 0, - max_value = 20, - value_step = 1, - scale = 1, - cost = 1, - is_percentage = false - }, - ['character_item_drop_distance_bonus'] = { - initial_value = 0, - max_value = 20, - value_step = 1, - scale = 1, - cost = 1, - is_percentage = false - }, - ['character_trash_slot_count'] = { - initial_value = 0, - max_value = 0, - value_step = 0, - scale = 1, - cost = 1, - is_percentage = false - }, - ['mining_drill_productivity_bonus'] = { - initial_value = 0, - max_value = 0, - value_step = 0, - scale = 1, - cost = 1, - is_percentage = false - }, - ['train_braking_force_bonus'] = { - initial_value = 0, - max_value = 0, - value_step = 0, - scale = 1, - cost = 1, - is_percentage = false - }, - ['laboratory_speed_modifier'] = { - initial_value = 0, - max_value = 0, - value_step = 0, - scale = 1, - cost = 1, - is_percentage = false - }, - ['laboratory_productivity_bonus'] = { - initial_value = 0, - max_value = 0, - value_step = 0, - scale = 1, - cost = 1, - is_percentage = false - }, - ['inserter_stack_size_bonus'] = { - initial_value = 0, - max_value = 0, - value_step = 0, - scale = 1, - cost = 1, - is_percentage = false - }, - ['stack_inserter_capacity_bonus'] = { - initial_value = 0, - max_value = 0, - value_step = 0, - scale = 1, - cost = 1, - is_percentage = false - }, - ['artillery_range_modifier'] = { - initial_value = 0, - max_value = 0, - value_step = 0, - scale = 1, - cost = 1, - is_percentage = false - } - ]] }, surface_bonus = { - --[[ - ['solar_power_multiplier'] = { - initial_value = 1, - max_value = 1000, - value_step = 1, - scale = 1, - cost = 1, - is_percentage = false - } - ]] }, } diff --git a/exp_legacy/module/config/expcore/roles.lua b/exp_legacy/module/config/expcore/roles.lua index a42a0a52..2dd07f77 100644 --- a/exp_legacy/module/config/expcore/roles.lua +++ b/exp_legacy/module/config/expcore/roles.lua @@ -80,7 +80,6 @@ Roles.new_role("Moderator", "Mod") "command/clear-inventory", "command/kill-enemies", "command/remove-enemies", - --'command/bonus', "command/home", "command/set-home", "command/get-home", @@ -167,7 +166,6 @@ Roles.new_role("Sponsor", "Spon") "gui/rocket-info/toggle-active", "gui/rocket-info/remote_launch", "gui/bonus", - --"command/bonus", "command/home", "command/set-home", "command/get-home", diff --git a/exp_legacy/module/modules/data/bonus.lua b/exp_legacy/module/modules/data/bonus.lua deleted file mode 100644 index 6ebf2a34..00000000 --- a/exp_legacy/module/modules/data/bonus.lua +++ /dev/null @@ -1,99 +0,0 @@ ---[[-- Commands Module - Bonus - - Adds a command that allows players to have increased stats - @data Bonus -]] - -local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles -local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event -local config = require("modules.exp_legacy.config.bonus") --- @dep config.bonuses -local Commands = require("modules/exp_commands") - --- Stores the bonus for the player -local PlayerData = require("modules.exp_legacy.expcore.player_data") --- @dep expcore.player_data -local PlayerBonus = PlayerData.Settings:combine("Bonus") -PlayerBonus:set_default(0) -PlayerBonus:set_metadata{ - permission = "command/bonus", - stringify = function(value) - if not value or value == 0 then - return "None set" - end - - return value - end, -} - ---- Apply a bonus to a player -local function apply_bonus(player, stage) - if not player.character then - return - end - - for k, v in pairs(config.player_bonus) do - player[k] = v.value * stage / 10 - - if v.combined_bonus then - for i = 1, #v.combined_bonus, 1 do - player[v.combined_bonus[i]] = v.value * stage / 10 - end - end - end -end - ---- When store is updated apply new bonus to the player -PlayerBonus:on_update(function(player_name, player_bonus) - apply_bonus(game.players[player_name], player_bonus or 0) -end) - ---- Changes the amount of bonus you receive -Commands.new("bonus", { "bonus.description" }) - :optional("amount", { "bonus.arg-amount" }, Commands.types.integer_range(0, 10)) - :register(function(player, amount) - --- @cast amount number? - if amount then - PlayerBonus:set(player, amount) - return Commands.status.success{ "bonus.set", amount } - else - return Commands.status.success{ "bonus.get", PlayerBonus:get(player) } - end - end) - ---- When a player respawns re-apply bonus -Event.add(defines.events.on_player_respawned, function(event) - local player = game.players[event.player_index] - apply_bonus(player, PlayerBonus:get(player)) -end) - ---- Remove bonus if a player no longer has access to the command -local function role_update(event) - local player = game.players[event.player_index] - if not Roles.player_allowed(player, "command/bonus") then - apply_bonus(player, 0) - end -end - ---- When a player dies allow them to have instant respawn -Event.add(defines.events.on_player_died, function(event) - local player = game.players[event.player_index] - - if Roles.player_has_flag(player, "instant-respawn") then - player.ticks_to_respawn = 120 - end -end) - -Event.add(defines.events.on_player_created, function(event) - if event.player_index ~= 1 then - return - end - - for k, v in pairs(config.force_bonus) do - game.players[event.player_index].force[k] = v.value - end - - for k, v in pairs(config.surface_bonus) do - game.players[event.player_index].surface[k] = v.value - end -end) - -Event.add(Roles.events.on_role_assigned, role_update) -Event.add(Roles.events.on_role_unassigned, role_update) diff --git a/exp_scenario/module/control/bonus.lua b/exp_scenario/module/control/bonus.lua index e253b0f6..f8b532cb 100644 --- a/exp_scenario/module/control/bonus.lua +++ b/exp_scenario/module/control/bonus.lua @@ -11,7 +11,7 @@ local config = require("modules/exp_legacy/config/bonus") local function apply_force_bonus(event) local force = event.force for k, v in pairs(config.force_bonus) do - force[k] = v.initial_value + force[k] = math.floor(v.max_value / 2) end end @@ -19,7 +19,7 @@ end local function apply_surface_bonus(event) local surface = assert(game.get_surface(event.surface_index)) for k, v in pairs(config.surface_bonus) do - surface[k] = v.initial_value + surface[k] = math.floor(v.max_value / 2) end end diff --git a/exp_scenario/module/gui/player_bonus.lua b/exp_scenario/module/gui/player_bonus.lua index 82595d71..79c9eac6 100644 --- a/exp_scenario/module/gui/player_bonus.lua +++ b/exp_scenario/module/gui/player_bonus.lua @@ -16,7 +16,6 @@ local Elements = {} --- @field cost number --- @field scale number --- @field max_value number ---- @field initial_value number --- @field is_percentage boolean --- @field is_special boolean --- @field value_step number @@ -229,7 +228,7 @@ Elements.bonus_slider = Gui.define("player_bonus/bonus_slider") local player = Gui.get_player(parent) local value = Elements.container.get_player_bonus(player, bonus_data.name) if not value then - value = bonus_data.initial_value + value = math.floor(bonus_data.initial_value / 2) elements.apply_button.enabled = true end @@ -290,7 +289,7 @@ function Elements.bonus_slider.reset_value(slider) local player = Gui.get_player(slider) local element_data = Elements.bonus_slider.data[slider] local bonus_data = element_data.bonus_data - local value = Elements.container.get_player_bonus(player, bonus_data.name) or bonus_data.initial_value + local value = Elements.container.get_player_bonus(player, bonus_data.name) or math.floor(bonus_data.initial_value / 2) slider.slider_value = value element_data.label.caption = Elements.bonus_slider.calculate_slider_caption(bonus_data, value) element_data.previous_value = value From 8c351a8411d1d5dd4d945ff5cb2b54ebd477dac6 Mon Sep 17 00:00:00 2001 From: PHIDIAS Date: Thu, 25 Jun 2026 05:33:31 +0900 Subject: [PATCH 06/33] Update protection.lua (#431) --- exp_legacy/module/config/protection.lua | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/exp_legacy/module/config/protection.lua b/exp_legacy/module/config/protection.lua index 37c78f79..d598fb65 100644 --- a/exp_legacy/module/config/protection.lua +++ b/exp_legacy/module/config/protection.lua @@ -5,15 +5,13 @@ return { repeat_lifetime = 3600 * 20, --- @setting repeat_lifetime The length of time, in ticks, that protected removals will be remembered for refresh_rate = 3600 * 5, --- @setting refresh_rate How often the age of protected removals are checked against repeat_lifetime always_protected_names = { --- @setting always_protected_names Names of entities which are always protected - }, always_protected_types = { --- @setting always_protected_types Types of entities which are always protected - "boiler", "generator", "offshore-pump", "power-switch", "reactor", "rocket-silo", + "boiler", "generator", "offshore-pump", "reactor", "heat-exchanger", "heat-pipe", "fusion-reactor", "fusion-generator", "power-switch", "rocket-silo", }, always_trigger_repeat_names = { --- @setting always_trigger_repeat_names Names of entities which always trigger repeated removal protection - }, always_trigger_repeat_types = { --- @setting always_trigger_repeat_types Types of entities which always trigger repeated removal protection - "reactor", "rocket-silo", + "reactor", "fusion-reactor", "rocket-silo", }, } From 7c9d47b6db330775946aff82d5b6f59414afb9f9 Mon Sep 17 00:00:00 2001 From: PHIDIAS Date: Thu, 25 Jun 2026 05:34:51 +0900 Subject: [PATCH 07/33] remove landfill blueprint (#433) * Delete exp_scenario/module/gui/landfill_blueprint.lua * Update control.lua * Update roles.lua * Update en.cfg * Update zh-CN.cfg * Update zh-TW.cfg --- exp_legacy/module/config/expcore/roles.lua | 1 - exp_scenario/module/control.lua | 1 - .../module/gui/landfill_blueprint.lua | 175 ------------------ exp_scenario/module/locale/en.cfg | 6 +- exp_scenario/module/locale/zh-CN.cfg | 4 - exp_scenario/module/locale/zh-TW.cfg | 4 - 6 files changed, 1 insertion(+), 190 deletions(-) delete mode 100644 exp_scenario/module/gui/landfill_blueprint.lua diff --git a/exp_legacy/module/config/expcore/roles.lua b/exp_legacy/module/config/expcore/roles.lua index 2dd07f77..a392953f 100644 --- a/exp_legacy/module/config/expcore/roles.lua +++ b/exp_legacy/module/config/expcore/roles.lua @@ -289,7 +289,6 @@ local default = Roles.new_role("Guest", "") "gui/research", "gui/autofill", "gui/module", - "gui/landfill", "gui/production", } diff --git a/exp_scenario/module/control.lua b/exp_scenario/module/control.lua index b031e221..5d8654f9 100644 --- a/exp_scenario/module/control.lua +++ b/exp_scenario/module/control.lua @@ -70,7 +70,6 @@ add(require("modules/exp_scenario/control/station_auto_name")) --- Guis add(require("modules/exp_scenario/gui/autofill")) add(require("modules/exp_scenario/gui/elements")) -add(require("modules/exp_scenario/gui/landfill_blueprint")) add(require("modules/exp_scenario/gui/module_inserter")) add(require("modules/exp_scenario/gui/player_bonus")) add(require("modules/exp_scenario/gui/player_stats")) diff --git a/exp_scenario/module/gui/landfill_blueprint.lua b/exp_scenario/module/gui/landfill_blueprint.lua deleted file mode 100644 index 0eae3367..00000000 --- a/exp_scenario/module/gui/landfill_blueprint.lua +++ /dev/null @@ -1,175 +0,0 @@ ---[[-- Gui - Landfill Blueprint -Adds a button to the toolbar which adds landfill to the held blueprint -]] - -local Gui = require("modules/exp_gui") -local Roles = require("modules/exp_legacy/expcore/roles") - ---- @param box BoundingBox -local function rotate_bounding_box(box) - box.left_top.x, box.left_top.y, box.right_bottom.x, box.right_bottom.y - = -box.right_bottom.y, box.left_top.x, -box.left_top.y, box.right_bottom.x -end - -local function curve_flip_lr(oc) - local nc = table.deep_copy(oc) - - for r = 1, 8 do - for c = 1, 8 do - nc[r][c] = oc[r][9 - c] - end - end - - return nc -end - -local function curve_flip_d(oc) - local nc = table.deep_copy(oc) - - for r = 1, 8 do - for c = 1, 8 do - nc[r][c] = oc[c][r] - end - end - - return nc -end - -local curve_masks = {} do - local curves = { { - { 0, 0, 0, 0, 0, 1, 0, 0 }, - { 0, 0, 0, 0, 1, 1, 1, 0 }, - { 0, 0, 0, 1, 1, 1, 1, 0 }, - { 0, 0, 0, 1, 1, 1, 0, 0 }, - { 0, 0, 1, 1, 1, 0, 0, 0 }, - { 0, 0, 1, 1, 1, 0, 0, 0 }, - { 0, 0, 1, 1, 0, 0, 0, 0 }, - { 0, 0, 1, 1, 0, 0, 0, 0 }, - } } - - curves[6] = curve_flip_d(curves[1]) - curves[3] = curve_flip_lr(curves[6]) - curves[4] = curve_flip_d(curves[3]) - curves[5] = curve_flip_lr(curves[4]) - curves[2] = curve_flip_d(curves[5]) - curves[7] = curve_flip_lr(curves[2]) - curves[8] = curve_flip_d(curves[7]) - - for i, map in ipairs(curves) do - local index = 0 - local mask = {} - curve_masks[i] = mask - - for row = 1, 8 do - for col = 1, 8 do - if map[row][col] == 1 then - index = index + 1 - mask[index] = { - x = col - 5, - y = row - 5, - } - end - end - end - end -end - -local rolling_stocks = {} -for name, _ in pairs(prototypes.get_entity_filtered{ { filter = "rolling-stock" } }) do - rolling_stocks[name] = true -end - ---- @param blueprint LuaItemStack ---- @return table -local function landfill_gui_add_landfill(blueprint) - local entities = assert(blueprint.get_blueprint_entities()) - local tile_index = 0 - local new_tiles = {} - - for _, entity in pairs(entities) do - if rolling_stocks[entity.name] or entity.name == "offshore-pump" then - goto continue - end - - if entity.name == "curved-rail" then - -- Curved rail - local curve_mask = curve_masks[entity.direction or 8] - for _, offset in ipairs(curve_mask) do - tile_index = tile_index + 1 - new_tiles[tile_index] = { - name = "landfill", - position = { entity.position.x + offset.x, entity.position.y + offset.y }, - } - end - else - -- Any other entity - local proto = prototypes.entity[entity.name] - if proto.collision_mask["ground-tile"] ~= nil then - goto continue - end - - -- Rotate the collision box to be north facing - local box = proto.collision_box or proto.selection_box - if entity.direction then - if entity.direction ~= defines.direction.north then - rotate_bounding_box(box) - if entity.direction ~= defines.direction.east then - rotate_bounding_box(box) - if entity.direction ~= defines.direction.south then - rotate_bounding_box(box) - end - end - end - end - - -- Add the landfill - for y = math.floor(entity.position.y + box.left_top.y), math.floor(entity.position.y + box.right_bottom.y), 1 do - for x = math.floor(entity.position.x + box.left_top.x), math.floor(entity.position.x + box.right_bottom.x), 1 do - tile_index = tile_index + 1 - new_tiles[tile_index] = { - name = "landfill", - position = { x, y }, - } - end - end - end - - ::continue:: - end - - local old_tiles = blueprint.get_blueprint_tiles() - - if old_tiles then - for _, old_tile in pairs(old_tiles) do - tile_index = tile_index + 1 - new_tiles[tile_index] = { - name = "landfill", - position = old_tile.position, - } - end - end - - return { tiles = new_tiles } -end - ---- Add the toolbar button -Gui.toolbar.create_button{ - name = "trigger_landfill_blueprint", - sprite = "item/landfill", - tooltip = { "exp-gui_landfill-blueprint.tooltip-main" }, - visible = function(player, element) - return Roles.player_allowed(player, "gui/landfill") - end -}:on_click(function(def, player, element) - local stack = player.cursor_stack - if stack and stack.valid_for_read and stack.type == "blueprint" and stack.is_blueprint_setup() then - local modified = landfill_gui_add_landfill(stack) - if modified and next(modified.tiles) then - stack.set_blueprint_tiles(modified.tiles) - end - else - player.print{ "exp-gui_landfill-blueprint.error-no-blueprint" } - end -end) - -return {} diff --git a/exp_scenario/module/locale/en.cfg b/exp_scenario/module/locale/en.cfg index ac5ec9cb..c75a927d 100644 --- a/exp_scenario/module/locale/en.cfg +++ b/exp_scenario/module/locale/en.cfg @@ -295,10 +295,6 @@ tooltip-amount=Amount of items to insert into the __1__ slots invalid=Autofill set to maximum amount: __1__ __2__ for __3__ inserted=Inserted __1__ __2__ into __3__ -[exp-gui_landfill-blueprint] -tooltip-main=Landfill Blueprint -error-no-blueprint=You need to hold the blueprint in cursor - [exp-gui_module-inserter] tooltip-main=Module Inserter caption-main=Modules @@ -572,4 +568,4 @@ chat-found=You cannot have __1__ in your inventory, so it was placed into the ch chat-jailed=__1__ was jailed because they removed too many protected entities. Please wait for a moderator. [exp_report-jail] -chat-jailed=__1__ was jailed because they were reported too many times. Please wait for a moderator. \ No newline at end of file +chat-jailed=__1__ was jailed because they were reported too many times. Please wait for a moderator. diff --git a/exp_scenario/module/locale/zh-CN.cfg b/exp_scenario/module/locale/zh-CN.cfg index da26d0fa..b08f6ba1 100644 --- a/exp_scenario/module/locale/zh-CN.cfg +++ b/exp_scenario/module/locale/zh-CN.cfg @@ -295,10 +295,6 @@ tooltip-amount=自動填入 __1__ 的數量 invalid=自動填入最大值 __1__ __2__ 給 __3__ inserted=自動填入 __1__ __2__ 到 __3__ -[exp-gui_landfill-blueprint] -tooltip-main=藍圖填海 -error-no-blueprint=您需要將藍圖保持在遊標處 - [exp-gui_module-inserter] tooltip-main=模組 caption-main=Modules diff --git a/exp_scenario/module/locale/zh-TW.cfg b/exp_scenario/module/locale/zh-TW.cfg index da26d0fa..b08f6ba1 100644 --- a/exp_scenario/module/locale/zh-TW.cfg +++ b/exp_scenario/module/locale/zh-TW.cfg @@ -295,10 +295,6 @@ tooltip-amount=自動填入 __1__ 的數量 invalid=自動填入最大值 __1__ __2__ 給 __3__ inserted=自動填入 __1__ __2__ 到 __3__ -[exp-gui_landfill-blueprint] -tooltip-main=藍圖填海 -error-no-blueprint=您需要將藍圖保持在遊標處 - [exp-gui_module-inserter] tooltip-main=模組 caption-main=Modules From 986341217793459b59fbfc25a47a7f2b98fff37b Mon Sep 17 00:00:00 2001 From: PHIDIAS Date: Thu, 25 Jun 2026 06:11:37 +0900 Subject: [PATCH 08/33] Remove unused properties from vlayer energy calculations (#426) Removed unused day time, day length, and tick properties from energy calculations. --- exp_legacy/module/modules/control/vlayer.lua | 6 ------ 1 file changed, 6 deletions(-) diff --git a/exp_legacy/module/modules/control/vlayer.lua b/exp_legacy/module/modules/control/vlayer.lua index fd32a966..ed9703c4 100644 --- a/exp_legacy/module/modules/control/vlayer.lua +++ b/exp_legacy/module/modules/control/vlayer.lua @@ -514,9 +514,6 @@ function vlayer.get_statistics() energy_sustained = vdp * gsm, energy_capacity = vlayer_data.properties.capacity * mega, energy_storage = vlayer_data.storage.energy, - day_time = math.floor(vlayer_data.surface.daytime * vlayer_data.surface.ticks_per_day), - day_length = vlayer_data.surface.ticks_per_day, - tick = game.tick, } end @@ -540,9 +537,6 @@ function vlayer.get_circuits() energy_sustained = "signal-S", energy_capacity = "signal-C", energy_storage = "signal-E", - day_time = "signal-D", - day_length = "signal-L", - tick = "signal-T", } end From aa7fe73bb01387555c202b51ab98e9bd9d9491db Mon Sep 17 00:00:00 2001 From: PHIDIAS Date: Fri, 26 Jun 2026 07:14:14 +0900 Subject: [PATCH 09/33] vlayer info add quick action (#438) * Update vlayer.lua * Add vlayer command to quick actions --- exp_scenario/module/commands/vlayer.lua | 10 +++++++++- exp_scenario/module/gui/quick_actions.lua | 2 ++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/exp_scenario/module/commands/vlayer.lua b/exp_scenario/module/commands/vlayer.lua index 4a21f13f..f4b38e96 100644 --- a/exp_scenario/module/commands/vlayer.lua +++ b/exp_scenario/module/commands/vlayer.lua @@ -5,8 +5,12 @@ Adds a virtual layer to store power to save space. local Commands = require("modules/exp_commands") local vlayer = require("modules.exp_legacy.modules.control.vlayer") +--- @class ExpCommand_vlayer.commands +local commands = {} + --- Print all vlayer information -Commands.new("vlayer-info", { "exp-commands_vlayer.description" }) +--- @class ExpCommands_vlayer.commands.vlayer: ExpCommand +commands.vlayer = Commands.new("vlayer-info", { "exp-commands_vlayer.description" }) :register(function(player) local index = 3 local response = { "", "exp-commands_vlayer.title" } --- @type LocalisedString @@ -16,3 +20,7 @@ Commands.new("vlayer-info", { "exp-commands_vlayer.description" }) end return Commands.status.success(response) end) + +return { + commands = commands, +} diff --git a/exp_scenario/module/gui/quick_actions.lua b/exp_scenario/module/gui/quick_actions.lua index 28ea5af4..28c82117 100644 --- a/exp_scenario/module/gui/quick_actions.lua +++ b/exp_scenario/module/gui/quick_actions.lua @@ -11,6 +11,7 @@ local addon_research = require("modules/exp_scenario/commands/research") local addon_trains = require("modules/exp_scenario/commands/trains") local addon_teleport = require("modules/exp_scenario/commands/teleport") local addon_waterfill = require("modules/exp_scenario/commands/waterfill") +local addon_vlayer = require("modules/exp_scenario/commands/vlayer") --- @class ExpGui_QuickActions.elements local Elements = {} @@ -51,6 +52,7 @@ new_quick_action("spawn", addon_teleport.commands.spawn, function(def, player, e end) new_quick_action("waterfill", addon_waterfill.commands.waterfill) +new_quick_action("vlayer", addon_vlayer.commands.vlayer) --- Container added to the left gui flow --- @class ExpGui_QuickActions.elements.container: ExpElement From 2a1a5c952bc4a3f0746c84e4c3df58ecdc5b5004 Mon Sep 17 00:00:00 2001 From: PHIDIAS Date: Fri, 26 Jun 2026 07:15:31 +0900 Subject: [PATCH 10/33] home add quick action (#436) * Update home.lua * Update quick_actions.lua --------- Co-authored-by: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> --- exp_scenario/module/commands/home.lua | 16 +++++++++++----- exp_scenario/module/gui/quick_actions.lua | 5 +++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/exp_scenario/module/commands/home.lua b/exp_scenario/module/commands/home.lua index ebf9e0d8..42ca6538 100644 --- a/exp_scenario/module/commands/home.lua +++ b/exp_scenario/module/commands/home.lua @@ -4,10 +4,12 @@ Adds a command that allows setting and teleporting to your home position local ExpUtil = require("modules/exp_util") local teleport = ExpUtil.teleport_player - local Commands = require("modules/exp_commands") local Storage = require("modules/exp_util/storage") +--- @class ExpCommand_Home.commands +local commands = {} + --- @type table> local homes = {} -- homes[player_index][surface_index] = { home_pos, return_pos } Storage.register(homes, function(tbl) @@ -28,7 +30,8 @@ local function align_to_grid(position) end --- Teleports you to your home location on the current surface -Commands.new("home", { "exp-commands_home.description-home" }) +--- @class ExpCommand_Home.commands.home: ExpCommand +commands.home = Commands.new("home", { "exp-commands_home.description-home" }) :add_flags{ "character_only" } :register(function(player) local surface = player.surface @@ -50,7 +53,8 @@ Commands.new("home", { "exp-commands_home.description-home" }) end) --- Teleports you to your previous location on the current surface -Commands.new("return", { "exp-commands_home.description-return" }) +--- @class ExpCommand_Home.commands._return: ExpCommand +commands._return = Commands.new("return", { "exp-commands_home.description-return" }) :add_flags{ "character_only" } :register(function(player) local surface = player.surface @@ -72,7 +76,8 @@ Commands.new("return", { "exp-commands_home.description-return" }) end) --- Sets your home location on your current surface to your current position -Commands.new("set-home", { "exp-commands_home.description-set" }) +--- @class ExpCommand_Home.commands.set_home: ExpCommand +commands.set_home = Commands.new("set-home", { "exp-commands_home.description-set" }) :add_flags{ "character_only" } :register(function(player) local home_position, floor_position = align_to_grid(player.position) @@ -97,7 +102,8 @@ Commands.new("set-home", { "exp-commands_home.description-set" }) end) --- Gets your home location on your current surface, is allowed in remote view -Commands.new("get-home", { "exp-commands_home.description-get" }) +--- @class ExpCommand_Home.commands.get_home: ExpCommand +commands.get_home = Commands.new("get-home", { "exp-commands_home.description-get" }) :register(function(player) local surface = player.surface diff --git a/exp_scenario/module/gui/quick_actions.lua b/exp_scenario/module/gui/quick_actions.lua index 28c82117..1a25af12 100644 --- a/exp_scenario/module/gui/quick_actions.lua +++ b/exp_scenario/module/gui/quick_actions.lua @@ -11,6 +11,7 @@ local addon_research = require("modules/exp_scenario/commands/research") local addon_trains = require("modules/exp_scenario/commands/trains") local addon_teleport = require("modules/exp_scenario/commands/teleport") local addon_waterfill = require("modules/exp_scenario/commands/waterfill") +local addon_home = require("modules/exp_scenario/commands/home") local addon_vlayer = require("modules/exp_scenario/commands/vlayer") --- @class ExpGui_QuickActions.elements @@ -52,6 +53,10 @@ new_quick_action("spawn", addon_teleport.commands.spawn, function(def, player, e end) new_quick_action("waterfill", addon_waterfill.commands.waterfill) +new_quick_action("home", addon_home.commands.home) +new_quick_action("return", addon_home.commands._return) +new_quick_action("set-home", addon_home.commands.set_home) +new_quick_action("get-home", addon_home.commands.get_home) new_quick_action("vlayer", addon_vlayer.commands.vlayer) --- Container added to the left gui flow From 8190b2165d3a747647a69cfc1c4034234b920ec4 Mon Sep 17 00:00:00 2001 From: PHIDIAS Date: Fri, 26 Jun 2026 19:37:00 +0900 Subject: [PATCH 11/33] mine depletion (#439) * Update mine_depletion.lua * Update miner.lua * Update mine_depletion.lua * Compatibility with 2.0 and 2.1 --------- Co-authored-by: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> --- exp_legacy/module/config/miner.lua | 1 + .../module/control/mine_depletion.lua | 54 +++++++++++++++++-- 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/exp_legacy/module/config/miner.lua b/exp_legacy/module/config/miner.lua index c6fceb31..f6fcf84c 100644 --- a/exp_legacy/module/config/miner.lua +++ b/exp_legacy/module/config/miner.lua @@ -4,4 +4,5 @@ return { fluid = true, --- @setting fluid When true, checks for for fluid pipes when removing miners chest = true, --- @setting chest When true, checks for for chest when removing miners + beacon = true, --- @setting beacon When true, checks for for beacon when removing miners } diff --git a/exp_scenario/module/control/mine_depletion.lua b/exp_scenario/module/control/mine_depletion.lua index 2764ebcf..799d0649 100644 --- a/exp_scenario/module/control/mine_depletion.lua +++ b/exp_scenario/module/control/mine_depletion.lua @@ -88,6 +88,37 @@ local function try_deconstruct_output_chest(entity) order_deconstruction_async:start_after(10, target) end +--- Check if beacon should be deconstructed +--- @param entity LuaEntity +local function try_deconstruct_nearby_beacon(entity) + -- Get a valid chest as the target + local beacons = entity.get_beacons() + local beacon_in_use = false + + -- if beacon is not supported + if not beacons then + return + end + + for _, b in pairs(beacons) do + if not b.to_be_deconstructed() then + for _, r in pairs(b.get_beacon_effect_receivers()) do + if r ~= entity then + if not r.to_be_deconstructed() then + beacon_in_use = true + break + end + end + end + + -- Deconstruct the beacon + if not beacon_in_use then + order_deconstruction_async:start_after(10, { name = b.name, position = b.position }) + end + end + end +end + --- Check if a miner should be deconstructed --- @param entity LuaEntity local function try_deconstruct_miner(entity) @@ -117,8 +148,13 @@ local function try_deconstruct_miner(entity) try_deconstruct_output_chest(entity) end + -- Try deconstruct the beacon + if config.beacon then + try_deconstruct_nearby_beacon(entity) + end + -- Skip pipe build if not required - if not config.fluid or #entity.fluidbox == 0 then + if not config.fluid or entity.fluids_count > 1 then return end @@ -185,11 +221,19 @@ local function try_deconstruct_miner(entity) end end +local max_quality = "" + +for _, q in pairs(prototypes.quality) do + if not q.next then + max_quality = q.name + end +end + --- Get the max mining radius local max_mining_radius = 0 for _, proto in pairs(prototypes.get_entity_filtered{ { filter = "type", type = "mining-drill" } }) do - if proto.mining_drill_radius > max_mining_radius then - max_mining_radius = proto.mining_drill_radius + if proto.mining_drill_radius then + max_mining_radius = math.max(proto.get_mining_drill_radius(max_quality), max_mining_radius) end end @@ -214,8 +258,8 @@ local function on_resource_depleted(event) -- Check which could have reached this resource for _, drill in pairs(drills) do local radius = drill.prototype.mining_drill_radius - local dx = math.abs(drill.position.x - resource.position.x) - local dy = math.abs(drill.position.y - resource.position.y) + local dx = math.abs(drill.position.x - position.x) + local dy = math.abs(drill.position.y - position.y) if dx <= radius and dy <= radius then try_deconstruct_miner(drill) end From d548cebc17601c886a9c8169944fa9905b8245a3 Mon Sep 17 00:00:00 2001 From: PHIDIAS Date: Fri, 26 Jun 2026 19:43:09 +0900 Subject: [PATCH 12/33] repair add quick action, changed to selection (#437) * Update repair.lua * Update repair.lua * Update quick_actions.lua * Update en.cfg * Update repair.lua * Refactor repair command to use area selection * Update en.cfg * Fixes --------- Co-authored-by: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> --- exp_legacy/module/config/repair.lua | 8 -- exp_scenario/module/commands/repair.lua | 111 +++++++++++++--------- exp_scenario/module/gui/quick_actions.lua | 2 + exp_scenario/module/locale/en.cfg | 3 +- 4 files changed, 68 insertions(+), 56 deletions(-) diff --git a/exp_legacy/module/config/repair.lua b/exp_legacy/module/config/repair.lua index 2b8d86b5..e2fd69ad 100644 --- a/exp_legacy/module/config/repair.lua +++ b/exp_legacy/module/config/repair.lua @@ -2,14 +2,6 @@ -- @config Repair return { - disallow = { --- @setting disallow items in this list will never be repaired - ["loader"] = true, - ["fast-loader"] = true, - ["express-loader"] = true, - ["electric-energy-interface"] = true, - ["infinity-chest"] = true, - }, - max_range = 50, --- @setting max_range the max range that can be used with the repair command allow_blueprint_repair = false, --- @setting allow_blueprint_repair when true will allow blueprints (things not destroyed by biters) to be build instantly using the repair command allow_ghost_revive = true, --- @setting allow_ghost_revive when true will allow ghosts (things destroyed by biters) to be build instantly using the repair command allow_heal_entities = true, --- @setting allow_heal_entities when true will heal entities to full health that are within range diff --git a/exp_scenario/module/commands/repair.lua b/exp_scenario/module/commands/repair.lua index b52a4cfc..bef7629b 100644 --- a/exp_scenario/module/commands/repair.lua +++ b/exp_scenario/module/commands/repair.lua @@ -2,56 +2,73 @@ Adds a command that allows an admin to repair and revive a large area ]] +local AABB = require("modules/exp_util/aabb") local Commands = require("modules/exp_commands") local config = require("modules.exp_legacy.config.repair") --- @dep config.repair +local Selection = require("modules/exp_util/selection") +local SelectArea = Selection.connect("ExpCommand_Waterfill") ---- Repairs entities on your force around you -Commands.new("repair", { "exp-commands_repair.description" }) - :argument("range", { "exp-commands_repair.arg-range" }, Commands.types.integer_range(1, config.max_range)) - :register(function(player, range) - --- @cast range number - local force = player.force - local surface = player.surface -- Allow remote view - local position = player.position -- Allow remote view - local response = { "" } --- @type LocalisedString +--- @class ExpCommands_Repair.commands +local commands = {} - if config.allow_ghost_revive then - local revive_count = 0 - local entities = surface.find_entities_filtered{ - type = "entity-ghost", - position = position, - radius = range, - force = force, - } - - local param = { raise_revive = true } --- @type LuaEntity.silent_revive_param - for _, entity in ipairs(entities) do - if not config.disallow[entity.ghost_name] and (config.allow_blueprint_repair or entity.created_by_corpse) then - revive_count = revive_count + 1 - entity.silent_revive(param) - end - end - - response[#response + 1] = { "exp-commands_repair.response-revive", revive_count } +--- Toggle player selection mode +--- @class ExpCommands_Repair.commands.repair: ExpCommand +commands.repair = Commands.new("repair", { "exp-commands_repair.description" }) + :register(function(player) + if SelectArea:stop(player) then + return Commands.status.success{ "exp-commands_repair.exit" } end - - if config.allow_heal_entities then - local healed_count = 0 - local entities = surface.find_entities_filtered{ - position = position, - radius = range, - force = force, - } - - for _, entity in ipairs(entities) do - if entity.health and entity.max_health and entity.health ~= entity.max_health then - healed_count = healed_count + 1 - entity.health = entity.max_health - end - end - - response[#response + 1] = { "exp-commands_repair.response-heal", healed_count } - end - - return Commands.status.success(response) + SelectArea:start(player) + return Commands.status.success{ "exp-commands_repair.enter" } end) + +--- When an area is selected to be converted to water +SelectArea:on_selection(function(event) + local player = assert(game.get_player(event.player_index)) + local area = AABB.expand(event.area) + local surface = event.surface + local force = player.force + local response = { "" } --- @type LocalisedString + + if config.allow_ghost_revive then + local revive_count = 0 + local entities = surface.find_entities_filtered{ + type = "entity-ghost", + area = area, + force = force, + } + + local param = { raise_revive = true } --- @type LuaEntity.silent_revive_param + for _, entity in ipairs(entities) do + if not (entity.ghost_prototype and entity.ghost_prototype.hidden) and (config.allow_blueprint_repair or entity.created_by_corpse) then + revive_count = revive_count + 1 + entity.silent_revive(param) + end + end + + response[#response + 1] = { "exp-commands_repair.response-revive", revive_count } + end + + if config.allow_heal_entities then + local healed_count = 0 + local entities = surface.find_entities_filtered{ + area = area, + force = force, + } + + for _, entity in ipairs(entities) do + if entity.health and entity.max_health and entity.health ~= entity.max_health then + healed_count = healed_count + 1 + entity.health = entity.max_health + end + end + + response[#response + 1] = { "exp-commands_repair.response-heal", healed_count } + end + + return player.print(response) +end) + +return { + commands = commands, +} diff --git a/exp_scenario/module/gui/quick_actions.lua b/exp_scenario/module/gui/quick_actions.lua index 1a25af12..b8f7e605 100644 --- a/exp_scenario/module/gui/quick_actions.lua +++ b/exp_scenario/module/gui/quick_actions.lua @@ -13,6 +13,7 @@ local addon_teleport = require("modules/exp_scenario/commands/teleport") local addon_waterfill = require("modules/exp_scenario/commands/waterfill") local addon_home = require("modules/exp_scenario/commands/home") local addon_vlayer = require("modules/exp_scenario/commands/vlayer") +local addon_repair = require("modules/exp_scenario/commands/repair") --- @class ExpGui_QuickActions.elements local Elements = {} @@ -58,6 +59,7 @@ new_quick_action("return", addon_home.commands._return) new_quick_action("set-home", addon_home.commands.set_home) new_quick_action("get-home", addon_home.commands.get_home) new_quick_action("vlayer", addon_vlayer.commands.vlayer) +new_quick_action("repair", addon_repair.commands.repair) --- Container added to the left gui flow --- @class ExpGui_QuickActions.elements.container: ExpElement diff --git a/exp_scenario/module/locale/en.cfg b/exp_scenario/module/locale/en.cfg index c75a927d..731d80af 100644 --- a/exp_scenario/module/locale/en.cfg +++ b/exp_scenario/module/locale/en.cfg @@ -155,7 +155,8 @@ machine-count=And you will need __1__ machines (with the same speed as this one) [exp-commands_repair] description=Repairs entities on your force around you. -arg-range=Range to repair entities within. +enter=Entered selection mode, select the area. +exit=Exited selection mode. # Intentional space left on the two lines below so they can be joined together. response-revive=__1__ entities were revived. response-heal=__1__ entities were healed. From 21779988f0b90f0c24727729c708897d06210b5f Mon Sep 17 00:00:00 2001 From: PHIDIAS Date: Fri, 26 Jun 2026 19:54:05 +0900 Subject: [PATCH 13/33] lawnmower changed to selection (#434) * Update lawnmower.lua * Update quick_actions.lua * Update lawnmower.lua * update locale * Update lawnmower.lua * Fix game.get_player --------- Co-authored-by: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> --- exp_scenario/module/commands/lawnmower.lua | 73 +++++++++++++++------- exp_scenario/module/gui/quick_actions.lua | 2 + exp_scenario/module/locale/en.cfg | 4 ++ 3 files changed, 56 insertions(+), 23 deletions(-) diff --git a/exp_scenario/module/commands/lawnmower.lua b/exp_scenario/module/commands/lawnmower.lua index e002f21b..f75f2fdc 100644 --- a/exp_scenario/module/commands/lawnmower.lua +++ b/exp_scenario/module/commands/lawnmower.lua @@ -2,32 +2,58 @@ Adds a command that clean up biter corpse and nuclear hole ]] +local AABB = require("modules/exp_util/aabb") local Commands = require("modules/exp_commands") -local config = require("modules.exp_legacy.config.lawnmower") --- @dep config.lawnmower +local Selection = require("modules/exp_util/selection") +local SelectArea = Selection.connect("ExpCommand_Lawnmower") +local config = require("modules.exp_legacy.config.lawnmower") -Commands.new("lawnmower", { "exp-commands_lawnmower.description" }) - :argument("range", { "exp-commands_lawnmower.arg-range" }, Commands.types.integer_range(1, 200)) - :register(function(player, range) - --- @cast range number - local surface = player.surface - - -- Intentionally left as player.position to allow use in remote view - local entities = surface.find_entities_filtered{ position = player.position, radius = range, type = "corpse" } - for _, entity in pairs(entities) do - if (entity.name ~= "transport-caution-corpse" and entity.name ~= "invisible-transport-caution-corpse") then - entity.destroy() - end - end - - local replace_tiles = {} - local tiles = surface.find_tiles_filtered{ position = player.position, radius = range, name = { "nuclear-ground" } } - for i, tile in pairs(tiles) do - replace_tiles[i] = { name = "grass-1", position = tile.position } +--- @class ExpCommand_Lawnmower.commands +local commands = {} + +--- Toggle player selection mode for lawnmower +--- @class ExpCommands_Lawnmower.commands.lawnmower: ExpCommand +--- @overload fun(player: LuaPlayer) +commands.lawnmower = Commands.new("lawnmower", { "exp-commands_lawnmower.description" }) + :register(function(player) + if SelectArea:stop(player) then + return Commands.status.success{ "exp-commands_lawnmower.exit" } end - surface.set_tiles(replace_tiles) - surface.destroy_decoratives{ position = player.position, radius = range } - end) + SelectArea:start(player) + return Commands.status.success{ "exp-commands_lawnmower.enter" } + end) --[[ @as any ]] + +--- When an area is selected to be handled +SelectArea:on_selection(function(event) + local player = assert(game.get_player(event.player_index)) + local area = AABB.expand(event.area) + local surface = event.surface + local area_size = (area.right_bottom.x - area.left_top.x) * (area.right_bottom.y - area.left_top.y) + + if area_size > 1000 then + player.print({ "exp-commands_lawnmower.area-too-large", 1000, area_size }, Commands.print_settings.error) + return + end + + local entities = surface.find_entities_filtered{ area = area, type = "corpse" } + for _, entity in pairs(entities) do + if (entity.name ~= "transport-caution-corpse" and entity.name ~= "invisible-transport-caution-corpse") then + entity.destroy() + end + end + + local replace_tiles = {} + local tiles = surface.find_tiles_filtered{ area = area, name = { "nuclear-ground" } } + for i, tile in pairs(tiles) do + replace_tiles[i] = { name = "grass-1", position = tile.position } + end + + surface.set_tiles(replace_tiles) + surface.destroy_decoratives{ area = area } + + player.print({ "exp-commands_lawnmower.complete", #replace_tiles }, Commands.print_settings.default) +end) --- @param event EventData.on_built_entity | EventData.on_robot_built_entity | EventData.script_raised_built | EventData.script_raised_revive local function destroy_decoratives(event) @@ -48,5 +74,6 @@ if config.destroy_decoratives then end return { - events = events + events = events, + commands = commands, } diff --git a/exp_scenario/module/gui/quick_actions.lua b/exp_scenario/module/gui/quick_actions.lua index b8f7e605..17847a8f 100644 --- a/exp_scenario/module/gui/quick_actions.lua +++ b/exp_scenario/module/gui/quick_actions.lua @@ -11,6 +11,7 @@ local addon_research = require("modules/exp_scenario/commands/research") local addon_trains = require("modules/exp_scenario/commands/trains") local addon_teleport = require("modules/exp_scenario/commands/teleport") local addon_waterfill = require("modules/exp_scenario/commands/waterfill") +local addon_lawnmower = require("modules/exp_scenario/commands/lawnmower") local addon_home = require("modules/exp_scenario/commands/home") local addon_vlayer = require("modules/exp_scenario/commands/vlayer") local addon_repair = require("modules/exp_scenario/commands/repair") @@ -54,6 +55,7 @@ new_quick_action("spawn", addon_teleport.commands.spawn, function(def, player, e end) new_quick_action("waterfill", addon_waterfill.commands.waterfill) +new_quick_action("lawnmower", addon_lawnmower.commands.lawnmower) new_quick_action("home", addon_home.commands.home) new_quick_action("return", addon_home.commands._return) new_quick_action("set-home", addon_home.commands.set_home) diff --git a/exp_scenario/module/locale/en.cfg b/exp_scenario/module/locale/en.cfg index 731d80af..70572e03 100644 --- a/exp_scenario/module/locale/en.cfg +++ b/exp_scenario/module/locale/en.cfg @@ -103,6 +103,10 @@ lower-role=You must have a higher role than the target. [exp-commands_lawnmower] description=Clean up biter corpse, decoratives and nuclear hole. arg-range=Range to clean up. +enter=Entered selection mode, select the area to fill with water. +exit=Exited selection mode. +area-too-large=Selected area is too large, must be less than __1__ tiles, selected __2__. +complete=__1__ tiles were handled. [exp-commands_locate] description=Opens remote view at the location of the player's last location. From 3a8d83f9818af25fc0ee5a2115fec0aec2464c38 Mon Sep 17 00:00:00 2001 From: PHIDIAS Date: Fri, 26 Jun 2026 19:56:24 +0900 Subject: [PATCH 14/33] research changes (#430) * Add event handlers for research reversed and queued * Update roles.lua * Remove bonus inventory settings from research.lua Removed bonus inventory configuration for mining productivity. * Update en.cfg * Update zh-CN.cfg * Update zh-TW.cfg * Update research.lua * Delete exp_scenario/module/commands/research.lua * Update quick_actions.lua * Update research.lua * Update research.lua * Update research.lua --- exp_legacy/module/config/expcore/roles.lua | 1 - exp_legacy/module/config/research.lua | 10 --- exp_scenario/module/commands/research.lua | 90 ---------------------- exp_scenario/module/control/research.lua | 4 +- exp_scenario/module/gui/quick_actions.lua | 6 +- exp_scenario/module/locale/en.cfg | 6 -- exp_scenario/module/locale/zh-CN.cfg | 6 -- exp_scenario/module/locale/zh-TW.cfg | 6 -- 8 files changed, 4 insertions(+), 125 deletions(-) delete mode 100644 exp_scenario/module/commands/research.lua diff --git a/exp_legacy/module/config/expcore/roles.lua b/exp_legacy/module/config/expcore/roles.lua index a392953f..3b1b8023 100644 --- a/exp_legacy/module/config/expcore/roles.lua +++ b/exp_legacy/module/config/expcore/roles.lua @@ -206,7 +206,6 @@ Roles.new_role("Veteran", "Vet") "command/clear-ground-items", "command/clear-blueprints-radius", "command/set-trains-to-automatic", - "command/set-auto-research", } :set_auto_assign_condition(function(player) if player.online_time >= hours10 then diff --git a/exp_legacy/module/config/research.lua b/exp_legacy/module/config/research.lua index e9e53f63..c6316b6e 100644 --- a/exp_legacy/module/config/research.lua +++ b/exp_legacy/module/config/research.lua @@ -12,16 +12,6 @@ return { -- this enable 20 more inventory for each mining productivity level up to 4 bonus_inventory = { enabled = true, - log = { - ["base"] = { - ["name"] = "mining-productivity-4", - ["level"] = 4 - }, - ["space-age"] = { - ["name"] = "mining-productivity-3", - ["level"] = 3 - }, - }, res = { -- Mining Productivity ["mining-productivity"] = true, diff --git a/exp_scenario/module/commands/research.lua b/exp_scenario/module/commands/research.lua deleted file mode 100644 index 3d00d122..00000000 --- a/exp_scenario/module/commands/research.lua +++ /dev/null @@ -1,90 +0,0 @@ ---[[-- Commands - Research -Adds a command to enable automatic research queueing -]] - -local Storage = require("modules/exp_util/storage") -local Commands = require("modules/exp_commands") -local format_player_name = Commands.format_player_name_locale - -local config = require("modules.exp_legacy.config.research") --- @dep config.research - ---- @class ExpCommands_Research.commands -local commands = {} - -local research = { - res_queue_enable = false -} - -Storage.register(research, function(tbl) - research = tbl -end) - ---- @param force LuaForce ---- @param silent boolean True when no message should be printed -local function queue_research(force, silent) - local res_q = force.research_queue - local res = force.technologies[config.bonus_inventory.log[config.mod_set].name] - - if #res_q < config.queue_amount then - for i = #res_q, config.queue_amount - 1 do - force.add_research(res) - - if not silent then - game.print{ "exp-commands_research.queue", res.name, res.level + i } - end - end - end -end - ---- @param state boolean? use nil to toggle current state ---- @return boolean # New auto research state -local function set_auto_research(state) - local new_state - if state == nil then - new_state = not research.res_queue_enable - else - new_state = state ~= false - end - - research.res_queue_enable = new_state - return new_state -end - ---- Sets the auto research state ---- @class ExpCommand_Artillery.commands.artillery: ExpCommand ---- @overload fun(player: LuaPlayer, state: boolean?) -commands.set_auto_research = Commands.new("set-auto-research", { "exp-commands_research.description" }) - :optional("state", { "exp-commands_research.arg-state" }, Commands.types.boolean) - :add_aliases{ "auto-research" } - :register(function(player, state) - --- @cast state boolean? - local enabled = set_auto_research(state) - - if enabled then - queue_research(player.force --[[@as LuaForce]], true) - end - - local player_name = format_player_name(player) - game.print{ "exp-commands_research.auto-research", player_name, enabled } - end) --[[ @as any ]] - ---- @param event EventData.on_research_finished -local function on_research_finished(event) - if not research.res_queue_enable then return end - - local force = event.research.force - local log_research = assert(config.bonus_inventory.log[config.mod_set], "Unknown mod set: " .. tostring(config.mod_set)) - local technology = assert(force.technologies[log_research.name], "Unknown technology: " .. tostring(log_research.name)) - if technology.level > log_research.level then - queue_research(force, event.by_script) - end -end - -local e = defines.events - -return { - commands = commands, - events = { - [e.on_research_finished] = on_research_finished, - }, -} diff --git a/exp_scenario/module/control/research.lua b/exp_scenario/module/control/research.lua index 7b8dcf77..4d885740 100644 --- a/exp_scenario/module/control/research.lua +++ b/exp_scenario/module/control/research.lua @@ -12,7 +12,7 @@ local function on_research_finished(event) if config.bonus_inventory.enabled and config.bonus_inventory.res[research_name] then event.research.force[config.bonus_inventory.name] = math.min((event.research.level - 1) * config.bonus_inventory.rate, config.bonus_inventory.limit) end - + if config.pollution_ageing_by_research and config.bonus_inventory.res[research_name] then game.map_settings.pollution.ageing = math.min(10, event.research.level / 5) end @@ -41,6 +41,8 @@ local e = defines.events return { events = { [e.on_research_finished] = on_research_finished, + [e.on_research_reversed] = on_research_finished, [e.on_research_started] = on_research_started, + [e.on_research_queued] = on_research_started, } } diff --git a/exp_scenario/module/gui/quick_actions.lua b/exp_scenario/module/gui/quick_actions.lua index 17847a8f..3bead1e8 100644 --- a/exp_scenario/module/gui/quick_actions.lua +++ b/exp_scenario/module/gui/quick_actions.lua @@ -7,7 +7,6 @@ local Commands = require("modules/exp_commands") local Roles = require("modules/exp_legacy/expcore/roles") local addon_artillery = require("modules/exp_scenario/commands/artillery") -local addon_research = require("modules/exp_scenario/commands/research") local addon_trains = require("modules/exp_scenario/commands/trains") local addon_teleport = require("modules/exp_scenario/commands/teleport") local addon_waterfill = require("modules/exp_scenario/commands/waterfill") @@ -48,12 +47,9 @@ end new_quick_action("artillery", addon_artillery.commands.artillery) new_quick_action("trains", addon_trains.commands.set_trains_to_automatic) -new_quick_action("research", addon_research.commands.set_auto_research) - -new_quick_action("spawn", addon_teleport.commands.spawn, function(def, player, element, event) +new_quick_action("spawn", addon_teleport.commands.spawn, function(_def, player, _element, _event) addon_teleport.commands.spawn(player, player) end) - new_quick_action("waterfill", addon_waterfill.commands.waterfill) new_quick_action("lawnmower", addon_lawnmower.commands.lawnmower) new_quick_action("home", addon_home.commands.home) diff --git a/exp_scenario/module/locale/en.cfg b/exp_scenario/module/locale/en.cfg index 70572e03..51f105f5 100644 --- a/exp_scenario/module/locale/en.cfg +++ b/exp_scenario/module/locale/en.cfg @@ -187,12 +187,6 @@ list-element=__1__: __2__ removed-all=__1__ has has all of their reports removed by __2__. removed=__1__ has a report removed by __2__. -[exp-commands_research] -description=Sets research to be automatically queued. -arg-state=State to set, default is to toggle. -auto-research=__1__ set auto research to __2__ -queue=[color=255, 255, 255] Research added to queue - [technology=__1__] - __2__[/color] - [exp-commands_roles] description-assign=Assigns a role to a player. description-unassign=Unassigns a role from a player. diff --git a/exp_scenario/module/locale/zh-CN.cfg b/exp_scenario/module/locale/zh-CN.cfg index b08f6ba1..625f2394 100644 --- a/exp_scenario/module/locale/zh-CN.cfg +++ b/exp_scenario/module/locale/zh-CN.cfg @@ -182,12 +182,6 @@ list-element=__1__: __2__ removed-all=__1__ 被舉報的所有紀錄已被 __2__ 清除。 removed=__1__ 被舉報的一個紀錄已被 __2__ 清除。 -[exp-commands_research] -description=啟用自動研究 -arg-state=狀態 -auto-research=__1__ 把自動研究設置為 __2__ 。 -queue=[color=255, 255, 255] 研究已加入隊列 - [technology=__1__] - __2__[/color] - [exp-commands_roles] description-assign=為用戶指配用戶組 description-unassign=為用戶取消指配用戶組 diff --git a/exp_scenario/module/locale/zh-TW.cfg b/exp_scenario/module/locale/zh-TW.cfg index b08f6ba1..625f2394 100644 --- a/exp_scenario/module/locale/zh-TW.cfg +++ b/exp_scenario/module/locale/zh-TW.cfg @@ -182,12 +182,6 @@ list-element=__1__: __2__ removed-all=__1__ 被舉報的所有紀錄已被 __2__ 清除。 removed=__1__ 被舉報的一個紀錄已被 __2__ 清除。 -[exp-commands_research] -description=啟用自動研究 -arg-state=狀態 -auto-research=__1__ 把自動研究設置為 __2__ 。 -queue=[color=255, 255, 255] 研究已加入隊列 - [technology=__1__] - __2__[/color] - [exp-commands_roles] description-assign=為用戶指配用戶組 description-unassign=為用戶取消指配用戶組 From 31c18c7851ff52b05a05918250a8446b27c56189 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Fri, 26 Jun 2026 12:41:49 +0100 Subject: [PATCH 15/33] Style and locale fixes --- exp_legacy/module/config/gui/player_list_actions.lua | 4 ++-- exp_scenario/module/commands/clear_inventory.lua | 2 +- exp_scenario/module/gui/player_list.lua | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/exp_legacy/module/config/gui/player_list_actions.lua b/exp_legacy/module/config/gui/player_list_actions.lua index 15b03f33..f692b47f 100644 --- a/exp_legacy/module/config/gui/player_list_actions.lua +++ b/exp_legacy/module/config/gui/player_list_actions.lua @@ -67,7 +67,7 @@ local goto_player = new_button("utility/export", { "exp-gui_player-list.goto-pla :on_click(function(def, player, element) local selected_player = get_action_player(player) if not player.character or not selected_player.character then - player.print({ "expcore-commands.reject-player-alive" }, Colors.orange_red) + player.print({ "exp-commands-parse.player-alive" }, Colors.orange_red) else teleport(player, selected_player) end @@ -79,7 +79,7 @@ local bring_player = new_button("utility/import", { "exp-gui_player-list.bring-p :on_click(function(def, player, element) local selected_player = get_action_player(player) if not player.character or not selected_player.character then - player.print({ "expcore-commands.reject-player-alive" }, Colors.orange_red) + player.print({ "exp-commands-parse.player-alive" }, Colors.orange_red) else teleport(selected_player, player) end diff --git a/exp_scenario/module/commands/clear_inventory.lua b/exp_scenario/module/commands/clear_inventory.lua index 9a37fc84..ed17db7a 100644 --- a/exp_scenario/module/commands/clear_inventory.lua +++ b/exp_scenario/module/commands/clear_inventory.lua @@ -14,7 +14,7 @@ Commands.new("clear-inventory", { "exp-commands_clear-inventory.description" }) :register(function(player, other_player) local inventory = other_player.get_main_inventory() if not inventory then - return Commands.status.error{ "expcore-commands.reject-player-alive" } + return Commands.status.error{ "exp-commands-parse.player-alive" } end transfer_inventory{ diff --git a/exp_scenario/module/gui/player_list.lua b/exp_scenario/module/gui/player_list.lua index 52509ebc..3b5379b3 100644 --- a/exp_scenario/module/gui/player_list.lua +++ b/exp_scenario/module/gui/player_list.lua @@ -71,12 +71,12 @@ Elements.close_action_bar = Gui.define("player_list/close_action_bar") tooltip = { "exp-gui_player-list.close-action-bar" }, style = "slot_sized_button_red", } - :style(Gui.styles.sprite{ - size = 20, + :style{ + size = 30, padding = -1, top_margin = -1, right_margin = -1, - }) + } :on_click(function(_, player) Elements.container.set_selected_player(player, nil) Elements.player_table.refresh_player(player) @@ -92,12 +92,12 @@ Elements.reason_confirm = Gui.define("player_list/reason_confirm") tooltip = { "exp-gui_player-list.reason-confirm" }, style = "slot_sized_button_green", } - :style(Gui.styles.sprite{ + :style{ size = 30, padding = -1, left_margin = -2, right_margin = -1, - }) + } :on_click(function(_, player, element) local action_name = Elements.container.get_selected_action(player) local button_data = action_name and config.buttons[action_name] @@ -382,7 +382,7 @@ Elements.reason_bar = Gui.define("player_list/reason_bar") local entry_style = reason_field.style entry_style.padding = 0 entry_style.height = 28 - entry_style.minimal_width = 160 + entry_style.minimal_width = 158 Elements.reason_confirm(reason_bar) return reason_bar From cb89c08c9f8974c31ba7a9ae235c9d0ebb1926cd Mon Sep 17 00:00:00 2001 From: PHIDIAS Date: Fri, 26 Jun 2026 20:43:00 +0900 Subject: [PATCH 16/33] Update vlayer.lua (#440) --- exp_legacy/module/modules/control/vlayer.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/exp_legacy/module/modules/control/vlayer.lua b/exp_legacy/module/modules/control/vlayer.lua index ed9703c4..39346063 100644 --- a/exp_legacy/module/modules/control/vlayer.lua +++ b/exp_legacy/module/modules/control/vlayer.lua @@ -349,7 +349,7 @@ function vlayer.create_input_interface(surface, position, circuit, last_user) end interface.destructible = false - interface.minable = false + interface.minable_flag = false interface.operable = true return interface @@ -427,7 +427,7 @@ function vlayer.create_output_interface(surface, position, circuit, last_user) end interface.destructible = false - interface.minable = false + interface.minable_flag = false interface.operable = true return interface @@ -568,7 +568,7 @@ function vlayer.create_circuit_interface(surface, position, circuit, last_user) end interface.destructible = false - interface.minable = false + interface.minable_flag = false interface.operable = true return interface @@ -647,7 +647,7 @@ function vlayer.create_energy_interface(surface, position, last_user) end interface.destructible = false - interface.minable = false + interface.minable_flag = false interface.operable = false interface.electric_buffer_size = 0 interface.power_production = 0 From 71ab3e7be485b147622f44d3a30b25ec080dddb3 Mon Sep 17 00:00:00 2001 From: PHIDIAS Date: Fri, 26 Jun 2026 21:15:49 +0900 Subject: [PATCH 17/33] clear blueprint changed to selection (#435) * Update surface.lua * Update en.cfg * Update surface.lua * Update en.cfg * Fix description keys for ground items and blueprints * Update roles.lua * Update surface.lua * Update quick_actions.lua * Update en.cfg * Update en.cfg * Update surface.lua * Update zh-CN.cfg * Update zh-TW.cfg * . * . * . * Many fixes and reverts --------- Co-authored-by: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> --- exp_legacy/module/config/expcore/roles.lua | 4 +- exp_scenario/module/commands/surface.lua | 131 +++++++++++---------- exp_scenario/module/gui/quick_actions.lua | 50 +++++--- exp_scenario/module/locale/en.cfg | 19 ++- exp_scenario/module/locale/zh-CN.cfg | 14 +-- exp_scenario/module/locale/zh-TW.cfg | 14 +-- 6 files changed, 124 insertions(+), 108 deletions(-) diff --git a/exp_legacy/module/config/expcore/roles.lua b/exp_legacy/module/config/expcore/roles.lua index 3b1b8023..df346afb 100644 --- a/exp_legacy/module/config/expcore/roles.lua +++ b/exp_legacy/module/config/expcore/roles.lua @@ -125,7 +125,7 @@ Roles.new_role("Trainee", "TrMod") "command/search-online", "command/search-amount", "command/search-recent", - "command/clear-blueprints", + "command/clear-blueprints-surface", "gui/playerdata", } @@ -204,7 +204,7 @@ Roles.new_role("Veteran", "Vet") :allow{ "command/chat-commands", "command/clear-ground-items", - "command/clear-blueprints-radius", + "command/clear-blueprints", "command/set-trains-to-automatic", } :set_auto_assign_condition(function(player) diff --git a/exp_scenario/module/commands/surface.lua b/exp_scenario/module/commands/surface.lua index 5bcdeab9..1d98e96a 100644 --- a/exp_scenario/module/commands/surface.lua +++ b/exp_scenario/module/commands/surface.lua @@ -2,12 +2,17 @@ Adds a command that clear item on ground so blueprint can deploy safely ]] +local AABB = require("modules/exp_util/aabb") +local Commands = require("modules/exp_commands") local ExpUtil = require("modules/exp_util") local move_items = ExpUtil.move_items_to_surface - -local Commands = require("modules/exp_commands") +local Selection = require("modules/exp_util/selection") +local SelectArea = Selection.connect("ExpCommand_ClearBlueprint") local format_player_name = Commands.format_player_name_locale +--- @class ExpCommand_ClearBlueprint.commands +local commands = {} + --- @param surface LuaSurface --- @return LuaItemStack[] local function get_ground_items(surface) @@ -19,71 +24,73 @@ local function get_ground_items(surface) return items end ---- Clear all items on the ground, optional to select a single surface -Commands.new("clear-ground-items", { "exp-commands_surface.description-items" }) +--- Clear all items on the ground on a surface +commands.clear_ground_items = Commands.new("clear-ground-items", { "exp-commands_surface.description-items" }) :optional("surface", { "exp-commands_surface.arg-surface" }, Commands.types.surface) + :defaults{ + surface = function(player) return player.surface end + } :register(function(player, surface) - --- @cast surface LuaSurface? - local player_name = format_player_name(player) - if surface then - move_items{ - surface = surface, - items = get_ground_items(surface), - allow_creation = true, - name = "iron-chest", - } - game.print{ "exp-commands_surface.items-surface", player_name, surface.localised_name } - else - for _, surface in pairs(game.surfaces) do - move_items{ - surface = surface, - items = get_ground_items(surface), - allow_creation = true, - name = "iron-chest", - } - end - game.print{ "exp-commands_surface.items-all", player_name } - end - end) - ---- Clear all blueprints, optional to select a single surface -Commands.new("clear-blueprints", { "exp-commands_surface.description-blueprints" }) - :optional("surface", { "exp-commands_surface.arg-surface" }, Commands.types.surface) - :register(function(player, surface) - --- @cast surface LuaSurface? - local player_name = format_player_name(player) - if surface then - local entities = surface.find_entities_filtered{ type = "entity-ghost" } - for _, entity in ipairs(entities) do - entity.destroy() - end - game.print{ "exp-commands_surface.blueprint-surface", player_name, surface.localised_name } - else - for _, surface in pairs(game.surfaces) do - local entities = surface.find_entities_filtered{ type = "entity-ghost" } - for _, entity in ipairs(entities) do - entity.destroy() - end - end - game.print{ "exp-commands_surface.blueprint-all", player_name } - end - end) - ---- Clear all blueprints in a radius around you -Commands.new("clear-blueprints-radius", { "exp-commands_surface.description-radius" }) - :argument("radius", { "exp-commands_surface.arg-radius" }, Commands.types.number_range(1, 100)) - :register(function(player, radius) - --- @cast radius number - local player_name = format_player_name(player) - local entities = player.surface.find_entities_filtered{ - type = "entity-ghost", - position = player.position, - radius = radius, + --- @cast surface LuaSurface + move_items{ + surface = surface, + items = get_ground_items(surface), + allow_creation = true, + name = "iron-chest", } + local player_name = format_player_name(player) + game.print{ "exp-commands_surface.items", player_name, surface.localised_name } + end) +--- Clear all blueprints on a surface +commands.clear_blueprints_surface = Commands.new("clear-blueprints-surface", { "exp-commands_surface.description-blueprints-surface" }) + :optional("surface", { "exp-commands_surface.arg-surface" }, Commands.types.surface) + :defaults{ + surface = function(player) return player.surface end + } + :register(function(player, surface) + --- @cast surface LuaSurface + local entities = surface.find_entities_filtered{ type = "entity-ghost" } for _, entity in ipairs(entities) do entity.destroy() end - - game.print{ "exp-commands_surface.blueprint-radius", player_name, radius, player.surface.localised_name } + local player_name = format_player_name(player) + game.print{ "exp-commands_surface.blueprints", player_name, surface.localised_name } end) + +--- Clear all blueprint in the area, selected by toggle player selection mode +--- @class ExpCommands_ClearBlueprint.commands.clear_blueprint: ExpCommand +--- @overload fun(player: LuaPlayer) +commands.clear_blueprints = Commands.new("clear-blueprints", { "exp-commands_surface.description-blueprints" }) + :register(function(player) + if SelectArea:stop(player) then + return Commands.status.success{ "exp-commands_surface.exit" } + end + + SelectArea:start(player) + return Commands.status.success{ "exp-commands_surface.enter" } + end) --[[ @as any ]] + +--- When an area is selected +SelectArea:on_selection(function(event) + local player = assert(game.get_player(event.player_index)) + local area = AABB.expand(event.area) + local surface = event.surface + local area_size = (area.right_bottom.x - area.left_top.x) * (area.right_bottom.y - area.left_top.y) + + if area_size > 1000 then + player.print({ "exp-commands_surface.area-too-large", 1000, area_size }, Commands.print_settings.error) + return + end + + local entities = surface.find_entities_filtered{ type = "entity-ghost", area = area } + for _, entity in ipairs(entities) do + entity.destroy() + end + + game.print({ "exp-commands_surface.complete", #entities }, Commands.print_settings.default) +end) + +return { + commands = commands, +} diff --git a/exp_scenario/module/gui/quick_actions.lua b/exp_scenario/module/gui/quick_actions.lua index 3bead1e8..056a213a 100644 --- a/exp_scenario/module/gui/quick_actions.lua +++ b/exp_scenario/module/gui/quick_actions.lua @@ -10,6 +10,7 @@ local addon_artillery = require("modules/exp_scenario/commands/artillery") local addon_trains = require("modules/exp_scenario/commands/trains") local addon_teleport = require("modules/exp_scenario/commands/teleport") local addon_waterfill = require("modules/exp_scenario/commands/waterfill") +local addon_surface = require("modules/exp_scenario/commands/surface") local addon_lawnmower = require("modules/exp_scenario/commands/lawnmower") local addon_home = require("modules/exp_scenario/commands/home") local addon_vlayer = require("modules/exp_scenario/commands/vlayer") @@ -21,15 +22,23 @@ local Elements = {} --- @type table local Actions = {} ---- @param name string --- @param command ExpCommand | function (this is needed because of the overload on commands) --- @param on_click? ExpElement.EventHandler -local function new_quick_action(name, command, on_click) - local element = Gui.define("quick_actions/" .. name) +local function new_quick_action(command, on_click) + local command_name = command.name + + local element = Gui.define("quick_actions/" .. command_name) :draw{ type = "button", - caption = { "exp-gui_quick-actions.caption-" .. name }, - tooltip = { "exp-gui_quick-actions.tooltip-" .. name }, + caption = { "?", + { "exp-gui_quick-actions.caption-" .. command_name }, + command_name, + }, + tooltip = { "?", + { "exp-gui_quick-actions.tooltip-" .. command_name }, + command.description, + "" + }, } :style{ width = 160, @@ -38,26 +47,31 @@ local function new_quick_action(name, command, on_click) command(player) end) - Elements[name] = element - Actions[name] = { + Elements[command_name] = element + Actions[command_name] = { command = command --[[ @as ExpCommand ]], element = element, } end -new_quick_action("artillery", addon_artillery.commands.artillery) -new_quick_action("trains", addon_trains.commands.set_trains_to_automatic) -new_quick_action("spawn", addon_teleport.commands.spawn, function(_def, player, _element, _event) +new_quick_action(addon_artillery.commands.artillery) +new_quick_action(addon_trains.commands.set_trains_to_automatic) + +new_quick_action(addon_teleport.commands.spawn, function(def, player, element, event) addon_teleport.commands.spawn(player, player) end) -new_quick_action("waterfill", addon_waterfill.commands.waterfill) -new_quick_action("lawnmower", addon_lawnmower.commands.lawnmower) -new_quick_action("home", addon_home.commands.home) -new_quick_action("return", addon_home.commands._return) -new_quick_action("set-home", addon_home.commands.set_home) -new_quick_action("get-home", addon_home.commands.get_home) -new_quick_action("vlayer", addon_vlayer.commands.vlayer) -new_quick_action("repair", addon_repair.commands.repair) + +new_quick_action(addon_waterfill.commands.waterfill) +new_quick_action(addon_lawnmower.commands.lawnmower) +new_quick_action(addon_surface.commands.clear_ground_items) +new_quick_action(addon_surface.commands.clear_blueprints_surface) +new_quick_action(addon_surface.commands.clear_blueprints) +new_quick_action(addon_home.commands.home) +new_quick_action(addon_home.commands._return) +new_quick_action(addon_home.commands.set_home) +new_quick_action(addon_home.commands.get_home) +new_quick_action(addon_vlayer.commands.vlayer) +new_quick_action(addon_repair.commands.repair) --- Container added to the left gui flow --- @class ExpGui_QuickActions.elements.container: ExpElement diff --git a/exp_scenario/module/locale/en.cfg b/exp_scenario/module/locale/en.cfg index cec48c84..ce446356 100644 --- a/exp_scenario/module/locale/en.cfg +++ b/exp_scenario/module/locale/en.cfg @@ -220,14 +220,14 @@ follow-self=You can not follow yourself. [exp-commands_surface] description-items=Clear all items on the ground. description-blueprints=Clear all blueprints. -description-radius=Clear all blueprints in an radius around you. +description-blueprints-surface=Clear all blueprints on the current surface. arg-surface=Surface to clear on, default all. -arg-radius=Radius to clear. -items-surface=__1__ cleared all items on the ground of __2__. -items-all=__1__ cleared all items on the ground for all surfaces. -blueprints-surface=__1__ cleared all blueprints on __2__. -blueprints-all=__1__ cleared all blueprints for all surfaces. -blueprints-radius=__1__ cleared all blueprints in a __2__ radius around them on __3__. +items=__1__ cleared all items on the ground of __2__. +blueprints=__1__ cleared all blueprints on __2__. +enter=Entered selection mode, select the area. +exit=Exited selection mode. +area-too-large=Selected area is too large, must be less than __1__, selected __2__. +complete=__1__ blueprint ghost were removed. [exp-commands_trains] description=Set All Trains to Automatic, does not effect trains with passengers. @@ -347,15 +347,10 @@ caption-net=Net [exp-gui_quick-actions] tooltip-main=Quick Actions caption-artillery=Artillery -tooltip-artillery=Select artillery targets caption-research=Auto Research -tooltip-research=Toggle auto research queue caption-spawn=Teleport Spawn -tooltip-spawn-tooltip=Teleport to spawn caption-trains=Set Auto Train -tooltip-trains=Set all trains to automatic caption-waterfill=Waterfill -tooltip-waterfill=Change tiles to water [exp-gui_readme] main-tooltip=Information diff --git a/exp_scenario/module/locale/zh-CN.cfg b/exp_scenario/module/locale/zh-CN.cfg index 625f2394..bf65db42 100644 --- a/exp_scenario/module/locale/zh-CN.cfg +++ b/exp_scenario/module/locale/zh-CN.cfg @@ -215,14 +215,14 @@ follow-self=你不能追蹤自己 [exp-commands_surface] description-items=清除地面上的物品 description-blueprints=清除藍圖 -description-radius=清除周邊的藍圖 +description-blueprints-surface=清除藍圖 arg-surface=位面 -arg-radius=周邊半徑 -items-surface=__1__ 清除了所有在 __2__ 中掉地上的東西。 -items-all=__1__ 清除了所有掉地上的東西。 -blueprints-surface=__1__ 清除了所有在 __2__ 中在地上的藍圖。 -blueprints-all=__1__ 清除了所有在地上的藍圖。 -blueprints-radius=__1__ 清除了在 __3__ 上,周邊 __2__ 格的地上的藍圖。 +items=__1__ 清除了所有在 __2__ 中掉地上的東西。 +blueprints=__1__ 清除了所有在 __2__ 中在地上的藍圖。 +enter=現在進入區域選擇 +exit=已進入區域選擇 +area-too-large=區域太大了,需少過 __1__ 格,你選了 __2__ 格。 +complete=__1__ 個地上的藍圖已被清除。 [exp-commands_trains] description=把火車設置為自動模式 diff --git a/exp_scenario/module/locale/zh-TW.cfg b/exp_scenario/module/locale/zh-TW.cfg index 625f2394..bf65db42 100644 --- a/exp_scenario/module/locale/zh-TW.cfg +++ b/exp_scenario/module/locale/zh-TW.cfg @@ -215,14 +215,14 @@ follow-self=你不能追蹤自己 [exp-commands_surface] description-items=清除地面上的物品 description-blueprints=清除藍圖 -description-radius=清除周邊的藍圖 +description-blueprints-surface=清除藍圖 arg-surface=位面 -arg-radius=周邊半徑 -items-surface=__1__ 清除了所有在 __2__ 中掉地上的東西。 -items-all=__1__ 清除了所有掉地上的東西。 -blueprints-surface=__1__ 清除了所有在 __2__ 中在地上的藍圖。 -blueprints-all=__1__ 清除了所有在地上的藍圖。 -blueprints-radius=__1__ 清除了在 __3__ 上,周邊 __2__ 格的地上的藍圖。 +items=__1__ 清除了所有在 __2__ 中掉地上的東西。 +blueprints=__1__ 清除了所有在 __2__ 中在地上的藍圖。 +enter=現在進入區域選擇 +exit=已進入區域選擇 +area-too-large=區域太大了,需少過 __1__ 格,你選了 __2__ 格。 +complete=__1__ 個地上的藍圖已被清除。 [exp-commands_trains] description=把火車設置為自動模式 From aa75a43aa9835a800544973b649cb781182f0519 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Fri, 26 Jun 2026 14:19:15 +0100 Subject: [PATCH 18/33] Ignore web dirs for lint --- .luarc.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.luarc.json b/.luarc.json index e9d2165e..ba4b39f0 100644 --- a/.luarc.json +++ b/.luarc.json @@ -1,7 +1,7 @@ { "$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json", "workspace.ignoreDir": [ - ".vscode", ".github", "luals", "factorio" + ".vscode", ".github", "luals", "factorio", "web" ], "completion.requireSeparator": "/", "runtime.pluginArgs": [ From 6b99378fa2789c138eddb30c026e92052e48eb4a Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Fri, 26 Jun 2026 14:20:10 +0100 Subject: [PATCH 19/33] Fix use of `MapSubscriber` --- exp_groups/web/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/exp_groups/web/index.tsx b/exp_groups/web/index.tsx index e0e8044a..e9fbc797 100644 --- a/exp_groups/web/index.tsx +++ b/exp_groups/web/index.tsx @@ -66,9 +66,9 @@ function ExpGroupsPage() { } export class WebPlugin extends BaseWebPlugin { - groups = new lib.EventSubscriber(messages.GroupUpdatedEvent, this.control); - assignments = new lib.EventSubscriber(messages.ManualAssignmentUpdatedEvent, this.control); - roleMappings = new lib.EventSubscriber(messages.RoleMappingUpdatedEvent, this.control); + groups = new lib.MapSubscriber(messages.GroupUpdatedEvent, this.control); + assignments = new lib.MapSubscriber(messages.ManualAssignmentUpdatedEvent, this.control); + roleMappings = new lib.MapSubscriber(messages.RoleMappingUpdatedEvent, this.control); async init() { this.pages = [ From 9755ec8e3a071cfc0de0d2aefec123e83be0c91f Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Fri, 26 Jun 2026 14:20:32 +0100 Subject: [PATCH 20/33] Use `/sc` rather than `/c` --- exp_groups/instance.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exp_groups/instance.ts b/exp_groups/instance.ts index 516c34bf..2421a92c 100644 --- a/exp_groups/instance.ts +++ b/exp_groups/instance.ts @@ -166,6 +166,6 @@ export class InstancePlugin extends BaseInstancePlugin { } async luaSend(receiver: string, json: any) { - await this.instance.sendRcon(`/c exp_groups.${receiver}(helpers.json_to_table[=[${JSON.stringify(json)}]=])`, true) + await this.instance.sendRcon(`/sc exp_groups.${receiver}(helpers.json_to_table[=[${JSON.stringify(json)}]=])`, true) } } From f4766cd20e206fac678663808fba9932f4144173 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Fri, 26 Jun 2026 14:21:28 +0100 Subject: [PATCH 21/33] Fix redirect after group creation --- exp_groups/web/components/GroupForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exp_groups/web/components/GroupForm.tsx b/exp_groups/web/components/GroupForm.tsx index 0dea9d2f..14e3aa1a 100644 --- a/exp_groups/web/components/GroupForm.tsx +++ b/exp_groups/web/components/GroupForm.tsx @@ -18,7 +18,7 @@ export default function GroupForm({ open, setOpen }: { const group = await control.send(new messages.GroupCreateRequest( values.name, new messages.GroupPermissions(Boolean(values.isBlacklist), []) )); - navigate(`/exp_groups/${group.id}/view`); + navigate(`/permission_groups/${group.id}/view`); setOpen(false); } From 147c586c4b635f6a1dc6a4829ebf131dd720b2de Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Fri, 26 Jun 2026 14:30:35 +0100 Subject: [PATCH 22/33] Remove invalid item from file loader --- exp_scenario/module/commands/home.lua | 4 ++++ exp_scenario/module/control.lua | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/exp_scenario/module/commands/home.lua b/exp_scenario/module/commands/home.lua index 42ca6538..fb201fa0 100644 --- a/exp_scenario/module/commands/home.lua +++ b/exp_scenario/module/commands/home.lua @@ -120,3 +120,7 @@ commands.get_home = Commands.new("get-home", { "exp-commands_home.description-ge local _, floor_position = align_to_grid(player_home[1]) return Commands.status.success{ "exp-commands_home.home-get", surface.localised_name, floor_position.x, floor_position.y } end) + +return { + commands = commands, +} \ No newline at end of file diff --git a/exp_scenario/module/control.lua b/exp_scenario/module/control.lua index 56f78abe..ad7066b6 100644 --- a/exp_scenario/module/control.lua +++ b/exp_scenario/module/control.lua @@ -11,7 +11,6 @@ require("modules/exp_scenario/commands/_types") --- Commands with events add(require("modules/exp_scenario/commands/protected_entities")) add(require("modules/exp_scenario/commands/protected_tags")) -add(require("modules/exp_scenario/commands/research")) --- Commands require("modules/exp_scenario/commands/admin_chat") From 3b6163451c7158a774e0f55a1814b38284054e9d Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Fri, 26 Jun 2026 14:34:12 +0100 Subject: [PATCH 23/33] Fix missed use of `bonus.initial_value` --- exp_scenario/module/gui/player_bonus.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exp_scenario/module/gui/player_bonus.lua b/exp_scenario/module/gui/player_bonus.lua index 79c9eac6..7c5fd84b 100644 --- a/exp_scenario/module/gui/player_bonus.lua +++ b/exp_scenario/module/gui/player_bonus.lua @@ -228,7 +228,7 @@ Elements.bonus_slider = Gui.define("player_bonus/bonus_slider") local player = Gui.get_player(parent) local value = Elements.container.get_player_bonus(player, bonus_data.name) if not value then - value = math.floor(bonus_data.initial_value / 2) + value = math.floor(bonus_data.max_value / 2) elements.apply_button.enabled = true end @@ -289,7 +289,7 @@ function Elements.bonus_slider.reset_value(slider) local player = Gui.get_player(slider) local element_data = Elements.bonus_slider.data[slider] local bonus_data = element_data.bonus_data - local value = Elements.container.get_player_bonus(player, bonus_data.name) or math.floor(bonus_data.initial_value / 2) + local value = Elements.container.get_player_bonus(player, bonus_data.name) or math.floor(bonus_data.max_value / 2) slider.slider_value = value element_data.label.caption = Elements.bonus_slider.calculate_slider_caption(bonus_data, value) element_data.previous_value = value From 9019be307c71a5be7bff320467568f0a80837cb4 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Fri, 26 Jun 2026 14:51:43 +0100 Subject: [PATCH 24/33] Update to new version of `SubscriptionRequest` --- exp_groups/controller.ts | 4 ++-- exp_groups/instance.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/exp_groups/controller.ts b/exp_groups/controller.ts index 6f94b2c0..14d13ef2 100644 --- a/exp_groups/controller.ts +++ b/exp_groups/controller.ts @@ -191,8 +191,8 @@ export class ControllerPlugin extends BaseControllerPlugin { async handleResolvedAssignmentSubscription(request: lib.SubscriptionRequest) { // Check for any missing assignments to be computed on demand - const filters = Array.isArray(request.filters) ? request.filters : [request.filters!]; - if (request.filters && filters.length) { + const filters = request.filters.toJSON(); + if (filters.length) { const missing = filters.filter(name => !this.resolvedAssignments.has(name)); if (missing.length) { this.resolvedAssignments.setMany(await this.computeResolvedAssignments(missing)); diff --git a/exp_groups/instance.ts b/exp_groups/instance.ts index 2421a92c..d3ed3cc4 100644 --- a/exp_groups/instance.ts +++ b/exp_groups/instance.ts @@ -45,7 +45,7 @@ export class InstancePlugin extends BaseInstancePlugin { async onStart() { // We use Date.now() because we need to manually initialise the groups on the lua side await this.instance.sendTo("controller", new lib.SubscriptionRequest( - `exp_groups:${messages.GroupUpdatedEvent.name}`, true, Date.now() + `exp_groups:${messages.GroupUpdatedEvent.name}`, "subscribe", Date.now() )); const groups = await this.instance.sendTo("controller", new messages.GroupListRequest()) await this.luaSendInitialGroups(groups); @@ -118,13 +118,13 @@ export class InstancePlugin extends BaseInstancePlugin { async subscribePlayerAssignment(playerName: string) { await this.instance.sendTo("controller", new lib.SubscriptionRequest( - `exp_groups:${messages.ResolvedAssignmentUpdatedEvent.name}`, true, 0, playerName + `exp_groups:${messages.ResolvedAssignmentUpdatedEvent.name}`, "subscribe", 0, lib.SubscriptionFilters.fromShorthand(playerName) )); } async unsubscribePlayerAssignment(playerName: string) { await this.instance.sendTo("controller", new lib.SubscriptionRequest( - `exp_groups:${messages.ResolvedAssignmentUpdatedEvent.name}`, false, 0, playerName + `exp_groups:${messages.ResolvedAssignmentUpdatedEvent.name}`, "unsubscribe", 0, lib.SubscriptionFilters.fromShorthand(playerName) )); } From 7be2f8d4909a07dd51ce4dc0ecaac25cf99630b3 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Fri, 26 Jun 2026 15:02:08 +0100 Subject: [PATCH 25/33] Fix lint errors --- exp_legacy/module/modules/data/quickbar.lua | 5 +++++ exp_scenario/module/control/mine_depletion.lua | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/exp_legacy/module/modules/data/quickbar.lua b/exp_legacy/module/modules/data/quickbar.lua index 6a6b38f9..52f8a4be 100644 --- a/exp_legacy/module/modules/data/quickbar.lua +++ b/exp_legacy/module/modules/data/quickbar.lua @@ -43,10 +43,14 @@ local ignored_items = { --- Saves your quickbar preset to the script-output folder Commands.new("save-quickbar", "Saves your Quickbar preset items to file") :add_aliases{ "save-toolbar" } + :add_flags{ "disabled" } :register(function(player) local filters = {} + error("2.1 changes to get_quick_bar_slot beak compatibility with 2.0; waiting for upstream") + -- Upstream may add method to compat, or change inventory sync to have a quickbar only mode for i = 1, 100 do + --[[ local slot = player.get_quick_bar_slot(i) -- Need to filter out blueprint and blueprint books because the slot is a LuaItemPrototype and does not contain a way to export blueprint data if slot ~= nil then @@ -55,6 +59,7 @@ Commands.new("save-quickbar", "Saves your Quickbar preset items to file") filters[i] = slot.name end end + ]] end if next(filters) then diff --git a/exp_scenario/module/control/mine_depletion.lua b/exp_scenario/module/control/mine_depletion.lua index 799d0649..922b1751 100644 --- a/exp_scenario/module/control/mine_depletion.lua +++ b/exp_scenario/module/control/mine_depletion.lua @@ -233,7 +233,7 @@ end local max_mining_radius = 0 for _, proto in pairs(prototypes.get_entity_filtered{ { filter = "type", type = "mining-drill" } }) do if proto.mining_drill_radius then - max_mining_radius = math.max(proto.get_mining_drill_radius(max_quality), max_mining_radius) + max_mining_radius = math.max(assert(proto.get_mining_drill_radius(max_quality)), max_mining_radius) end end From 95bb8ac3044398d48616e66b963a8164f5e8d40e Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Fri, 26 Jun 2026 15:09:11 +0100 Subject: [PATCH 26/33] Move logo out of legacy --- .../modules => exp_scenario/module}/gui/logo.png | Bin exp_scenario/module/gui/readme.lua | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename {exp_legacy/module/modules => exp_scenario/module}/gui/logo.png (100%) diff --git a/exp_legacy/module/modules/gui/logo.png b/exp_scenario/module/gui/logo.png similarity index 100% rename from exp_legacy/module/modules/gui/logo.png rename to exp_scenario/module/gui/logo.png diff --git a/exp_scenario/module/gui/readme.lua b/exp_scenario/module/gui/readme.lua index 54c5af15..ed0cf6ea 100644 --- a/exp_scenario/module/gui/readme.lua +++ b/exp_scenario/module/gui/readme.lua @@ -200,7 +200,7 @@ define_tab( local container = parent.add{ type = "flow", direction = "vertical" } local top_flow = container.add{ type = "flow" } - top_flow.add{ type = "sprite", sprite = "file/modules/exp_legacy/modules/gui/logo.png" } + top_flow.add{ type = "sprite", sprite = "file/modules/exp_scenario/gui/logo.png" } local center_flow = top_flow.add{ type = "flow", direction = "vertical" } center_flow.style.horizontal_align = "center" @@ -208,7 +208,7 @@ define_tab( Gui.elements.title_label(center_flow, 62, { "exp-gui_readme.welcome-title", server_details.name }) Gui.elements.centered_label(center_flow, 380, server_details.welcome) - top_flow.add{ type = "sprite", sprite = "file/modules/exp_legacy/modules/gui/logo.png" } + top_flow.add{ type = "sprite", sprite = "file/modules/exp_scenario/gui/logo.png" } Gui.elements.bar(container) container.add{ type = "flow" }.style.height = 4 From 8b3c43784706c040f77309e023d9b132bf7eb955 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Fri, 26 Jun 2026 15:22:06 +0100 Subject: [PATCH 27/33] Move debug GUI out of legacy --- exp_scenario/module/control.lua | 2 ++ .../module}/gui/debug/_g_view.lua | 4 ++-- .../module}/gui/debug/event_view.lua | 8 ++++---- .../module}/gui/debug/expcore_datastore_view.lua | 6 +++--- .../module}/gui/debug/expcore_gui_view.lua | 4 ++-- .../module}/gui/debug/global_view.lua | 4 ++-- .../module}/gui/debug/main_view.lua | 16 ++++++++-------- .../module}/gui/debug/model.lua | 2 +- .../module}/gui/debug/package_view.lua | 4 ++-- .../module}/gui/debug/redmew_global_view.lua | 4 ++-- .../module/gui/debug/shim.lua | 6 +++--- 11 files changed, 31 insertions(+), 29 deletions(-) rename {exp_legacy/module/modules => exp_scenario/module}/gui/debug/_g_view.lua (94%) rename {exp_legacy/module/modules => exp_scenario/module}/gui/debug/event_view.lua (95%) rename {exp_legacy/module/modules => exp_scenario/module}/gui/debug/expcore_datastore_view.lua (95%) rename {exp_legacy/module/modules => exp_scenario/module}/gui/debug/expcore_gui_view.lua (96%) rename {exp_legacy/module/modules => exp_scenario/module}/gui/debug/global_view.lua (95%) rename {exp_legacy/module/modules => exp_scenario/module}/gui/debug/main_view.lua (83%) rename {exp_legacy/module/modules => exp_scenario/module}/gui/debug/model.lua (94%) rename {exp_legacy/module/modules => exp_scenario/module}/gui/debug/package_view.lua (96%) rename {exp_legacy/module/modules => exp_scenario/module}/gui/debug/redmew_global_view.lua (95%) rename exp_legacy/module/utils/gui.lua => exp_scenario/module/gui/debug/shim.lua (97%) diff --git a/exp_scenario/module/control.lua b/exp_scenario/module/control.lua index ad7066b6..3b00dcde 100644 --- a/exp_scenario/module/control.lua +++ b/exp_scenario/module/control.lua @@ -81,3 +81,5 @@ add(require("modules/exp_scenario/gui/rocket_info")) add(require("modules/exp_scenario/gui/science_production")) add(require("modules/exp_scenario/gui/surveillance")) add(require("modules/exp_scenario/gui/task_list")) +add(require("modules/exp_scenario/gui/debug/shim")) +add(require("modules/exp_scenario/gui/debug/event_view")) diff --git a/exp_legacy/module/modules/gui/debug/_g_view.lua b/exp_scenario/module/gui/debug/_g_view.lua similarity index 94% rename from exp_legacy/module/modules/gui/debug/_g_view.lua rename to exp_scenario/module/gui/debug/_g_view.lua index 107f9e14..49e93645 100644 --- a/exp_legacy/module/modules/gui/debug/_g_view.lua +++ b/exp_scenario/module/gui/debug/_g_view.lua @@ -1,5 +1,5 @@ -local Gui = require("modules.exp_legacy.utils.gui") --- @dep utils.gui -local Model = require("modules.exp_legacy.modules.gui.debug.model") --- @dep modules.gui.debug.model +local Gui = require("modules/exp_scenario/gui/debug/shim") +local Model = require("modules/exp_scenario/gui/debug/model") local Color = require("modules/exp_util/include/color") local dump = Model.dump diff --git a/exp_legacy/module/modules/gui/debug/event_view.lua b/exp_scenario/module/gui/debug/event_view.lua similarity index 95% rename from exp_legacy/module/modules/gui/debug/event_view.lua rename to exp_scenario/module/gui/debug/event_view.lua index b2d84ded..e7261105 100644 --- a/exp_legacy/module/modules/gui/debug/event_view.lua +++ b/exp_scenario/module/gui/debug/event_view.lua @@ -1,7 +1,6 @@ -local Event = require("modules/exp_legacy/utils/event") local Storage = require("modules/exp_util/storage") -local Gui = require("modules.exp_legacy.utils.gui") -local Model = require("modules.exp_legacy.modules.gui.debug.model") +local Gui = require("modules/exp_scenario/gui/debug/shim") +local Model = require("modules/exp_scenario/gui/debug/model") local format = string.format local insert = table.insert @@ -14,6 +13,7 @@ local events_to_keep = 10 -- Local vars local Public = { name = "Events", + events = {} } local name_lookup = {} @@ -168,7 +168,7 @@ Gui.on_click( -- Event registers (TODO: turn to removable hooks.. maybe) for name, id in pairs(events) do name_lookup[id] = name - Event.add(id, event_callback) + Public.events[id] = event_callback end return Public diff --git a/exp_legacy/module/modules/gui/debug/expcore_datastore_view.lua b/exp_scenario/module/gui/debug/expcore_datastore_view.lua similarity index 95% rename from exp_legacy/module/modules/gui/debug/expcore_datastore_view.lua rename to exp_scenario/module/gui/debug/expcore_datastore_view.lua index 97b9e4f0..10bb9263 100644 --- a/exp_legacy/module/modules/gui/debug/expcore_datastore_view.lua +++ b/exp_scenario/module/gui/debug/expcore_datastore_view.lua @@ -1,7 +1,7 @@ -local Gui = require("modules.exp_legacy.utils.gui") --- @dep utils.gui -local Datastore = require("modules.exp_legacy.expcore.datastore") --- @dep expcore.datastore +local Gui = require("modules/exp_scenario/gui/debug/shim") +local Datastore = require("modules.exp_legacy.expcore.datastore") local Color = require("modules/exp_util/include/color") -local Model = require("modules.exp_legacy.modules.gui.debug.model") --- @dep modules.gui.debug.model +local Model = require("modules/exp_scenario/gui/debug/model") local dump = Model.dump local concat = table.concat diff --git a/exp_legacy/module/modules/gui/debug/expcore_gui_view.lua b/exp_scenario/module/gui/debug/expcore_gui_view.lua similarity index 96% rename from exp_legacy/module/modules/gui/debug/expcore_gui_view.lua rename to exp_scenario/module/gui/debug/expcore_gui_view.lua index 366157b3..4fd18849 100644 --- a/exp_legacy/module/modules/gui/debug/expcore_gui_view.lua +++ b/exp_scenario/module/gui/debug/expcore_gui_view.lua @@ -3,8 +3,8 @@ local ExpData = require("modules/exp_gui/data") local ExpIter = require("modules/exp_gui/iter") local Color = require("modules/exp_util/include/color") -local Gui = require("modules.exp_legacy.utils.gui") --- @dep utils.gui -local Model = require("modules.exp_legacy.modules.gui.debug.model") --- @dep modules.gui.debug.model +local Gui = require("modules/exp_scenario/gui/debug/shim") +local Model = require("modules/exp_scenario/gui/debug/model") local dump = Model.dump local dump_text = Model.dump_text diff --git a/exp_legacy/module/modules/gui/debug/global_view.lua b/exp_scenario/module/gui/debug/global_view.lua similarity index 95% rename from exp_legacy/module/modules/gui/debug/global_view.lua rename to exp_scenario/module/gui/debug/global_view.lua index 03da4ae9..73ae4d06 100644 --- a/exp_legacy/module/modules/gui/debug/global_view.lua +++ b/exp_scenario/module/gui/debug/global_view.lua @@ -1,5 +1,5 @@ -local Gui = require("modules.exp_legacy.utils.gui") --- @dep utils.gui -local Model = require("modules.exp_legacy.modules.gui.debug.model") --- @dep modules.gui.debug.model +local Gui = require("modules/exp_scenario/gui/debug/shim") +local Model = require("modules/exp_scenario/gui/debug/model") local Color = require("modules/exp_util/include/color") local dump = Model.dump diff --git a/exp_legacy/module/modules/gui/debug/main_view.lua b/exp_scenario/module/gui/debug/main_view.lua similarity index 83% rename from exp_legacy/module/modules/gui/debug/main_view.lua rename to exp_scenario/module/gui/debug/main_view.lua index 393bdade..bd2b5eb7 100644 --- a/exp_legacy/module/modules/gui/debug/main_view.lua +++ b/exp_scenario/module/gui/debug/main_view.lua @@ -1,16 +1,16 @@ -local Gui = require("modules.exp_legacy.utils.gui") --- @dep utils.gui +local Gui = require("modules/exp_scenario/gui/debug/shim") local Color = require("modules/exp_util/include/color") local Public = {} local pages = { - require("modules.exp_legacy.modules.gui.debug.redmew_global_view"), - require("modules.exp_legacy.modules.gui.debug.expcore_datastore_view"), - require("modules.exp_legacy.modules.gui.debug.expcore_gui_view"), - require("modules.exp_legacy.modules.gui.debug.global_view"), - require("modules.exp_legacy.modules.gui.debug.package_view"), - require("modules.exp_legacy.modules.gui.debug._g_view"), - require("modules.exp_legacy.modules.gui.debug.event_view"), + require("modules/exp_scenario/gui/debug/redmew_global_view"), + require("modules/exp_scenario/gui/debug/expcore_datastore_view"), + require("modules/exp_scenario/gui/debug/expcore_gui_view"), + require("modules/exp_scenario/gui/debug/global_view"), + require("modules/exp_scenario/gui/debug/package_view"), + require("modules/exp_scenario/gui/debug/_g_view"), + require("modules/exp_scenario/gui/debug/event_view"), } local main_frame_name = Gui.uid_name() diff --git a/exp_legacy/module/modules/gui/debug/model.lua b/exp_scenario/module/gui/debug/model.lua similarity index 94% rename from exp_legacy/module/modules/gui/debug/model.lua rename to exp_scenario/module/gui/debug/model.lua index 27d23d86..52ab850f 100644 --- a/exp_legacy/module/modules/gui/debug/model.lua +++ b/exp_scenario/module/gui/debug/model.lua @@ -1,4 +1,4 @@ -local Gui = require("modules.exp_legacy.utils.gui") --- @dep utils.gui +local Gui = require("modules/exp_scenario/gui/debug/shim") --- @dep utils.gui local ExpUtil = require("modules/exp_util") local concat = table.concat diff --git a/exp_legacy/module/modules/gui/debug/package_view.lua b/exp_scenario/module/gui/debug/package_view.lua similarity index 96% rename from exp_legacy/module/modules/gui/debug/package_view.lua rename to exp_scenario/module/gui/debug/package_view.lua index 689462c8..126242f4 100644 --- a/exp_legacy/module/modules/gui/debug/package_view.lua +++ b/exp_scenario/module/gui/debug/package_view.lua @@ -1,6 +1,6 @@ -local Gui = require("modules.exp_legacy.utils.gui") --- @dep utils.gui +local Gui = require("modules/exp_scenario/gui/debug/shim") --- @dep utils.gui local Color = require("modules/exp_util/include/color") -local Model = require("modules.exp_legacy.modules.gui.debug.model") --- @dep modules.gui.debug.model +local Model = require("modules/exp_scenario/gui/debug/model") --- @dep modules.gui.debug.model local dump_function = Model.dump_function local loaded = _G.package.loaded diff --git a/exp_legacy/module/modules/gui/debug/redmew_global_view.lua b/exp_scenario/module/gui/debug/redmew_global_view.lua similarity index 95% rename from exp_legacy/module/modules/gui/debug/redmew_global_view.lua rename to exp_scenario/module/gui/debug/redmew_global_view.lua index da5417c4..8e387479 100644 --- a/exp_legacy/module/modules/gui/debug/redmew_global_view.lua +++ b/exp_scenario/module/gui/debug/redmew_global_view.lua @@ -1,7 +1,7 @@ -local Gui = require("modules.exp_legacy.utils.gui") --- @dep utils.gui +local Gui = require("modules/exp_scenario/gui/debug/shim") --- @dep utils.gui local Storage = require("modules/exp_util/storage") local Color = require("modules/exp_util/include/color") -local Model = require("modules.exp_legacy.modules.gui.debug.model") --- @dep modules.gui.debug.model +local Model = require("modules/exp_scenario/gui/debug/model") --- @dep modules.gui.debug.model local dump = Model.dump local dump_text = Model.dump_text diff --git a/exp_legacy/module/utils/gui.lua b/exp_scenario/module/gui/debug/shim.lua similarity index 97% rename from exp_legacy/module/utils/gui.lua rename to exp_scenario/module/gui/debug/shim.lua index c20da522..9c3fda14 100644 --- a/exp_legacy/module/utils/gui.lua +++ b/exp_scenario/module/gui/debug/shim.lua @@ -1,5 +1,4 @@ local Storage = require("modules/exp_util/storage") -local Event = require("modules/exp_legacy/utils/event") local mod_gui = require "mod-gui" local Gui = {} @@ -70,14 +69,15 @@ function Gui.clear(element) element.clear() end +Gui.events = {} local function handler_factory(event_name) return function(element_name, handler) - Event.add(defines.events[event_name], function(event) + Gui.events[defines.events[event_name]] = function(event) if event.element and event.element.valid and event.element.name == element_name then event.player = game.players[event.player_index] handler(event) end - end) + end end end From 7890540a3ee0a2a6f373b160e1c165f45687e58a Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Fri, 26 Jun 2026 15:40:53 +0100 Subject: [PATCH 28/33] Final lint fixes --- exp_scenario/module/commands/home.lua | 4 ++-- exp_scenario/module/gui/quick_actions.lua | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/exp_scenario/module/commands/home.lua b/exp_scenario/module/commands/home.lua index fb201fa0..256f41b9 100644 --- a/exp_scenario/module/commands/home.lua +++ b/exp_scenario/module/commands/home.lua @@ -54,7 +54,7 @@ commands.home = Commands.new("home", { "exp-commands_home.description-home" }) --- Teleports you to your previous location on the current surface --- @class ExpCommand_Home.commands._return: ExpCommand -commands._return = Commands.new("return", { "exp-commands_home.description-return" }) +commands.home_return = Commands.new("return", { "exp-commands_home.description-return" }) :add_flags{ "character_only" } :register(function(player) local surface = player.surface @@ -123,4 +123,4 @@ commands.get_home = Commands.new("get-home", { "exp-commands_home.description-ge return { commands = commands, -} \ No newline at end of file +} diff --git a/exp_scenario/module/gui/quick_actions.lua b/exp_scenario/module/gui/quick_actions.lua index 056a213a..0eacba2c 100644 --- a/exp_scenario/module/gui/quick_actions.lua +++ b/exp_scenario/module/gui/quick_actions.lua @@ -67,7 +67,7 @@ new_quick_action(addon_surface.commands.clear_ground_items) new_quick_action(addon_surface.commands.clear_blueprints_surface) new_quick_action(addon_surface.commands.clear_blueprints) new_quick_action(addon_home.commands.home) -new_quick_action(addon_home.commands._return) +new_quick_action(addon_home.commands.home_return) new_quick_action(addon_home.commands.set_home) new_quick_action(addon_home.commands.get_home) new_quick_action(addon_vlayer.commands.vlayer) From 1f3689a0acddd06c718ff0adbe91a984dd28b16f Mon Sep 17 00:00:00 2001 From: PHIDIAS Date: Fri, 26 Jun 2026 23:45:46 +0900 Subject: [PATCH 29/33] entity api change (#442) * Update warps.lua * . * . --- exp_legacy/module/config/expcore/roles.lua | 2 +- exp_legacy/module/modules/control/warps.lua | 2 +- exp_scenario/module/control/mine_depletion.lua | 2 +- exp_scenario/module/control/spawn_area.lua | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/exp_legacy/module/config/expcore/roles.lua b/exp_legacy/module/config/expcore/roles.lua index df346afb..8ffbb53f 100644 --- a/exp_legacy/module/config/expcore/roles.lua +++ b/exp_legacy/module/config/expcore/roles.lua @@ -14,7 +14,7 @@ Roles.define_flag_trigger("is_spectator", function(player, state) end) Roles.define_flag_trigger("is_jail", function(player, state) if player.character then - player.character.active = not state + player.character.disabled_by_script = not state end end) diff --git a/exp_legacy/module/modules/control/warps.lua b/exp_legacy/module/modules/control/warps.lua index b6cad0a9..fdca5b62 100644 --- a/exp_legacy/module/modules/control/warps.lua +++ b/exp_legacy/module/modules/control/warps.lua @@ -182,7 +182,7 @@ function Warps.make_warp_area(warp_id) } entity.destructible = false entity.health = 0 - entity.minable = false + entity.minable_flag = false entity.rotatable = false -- Save reference of the last power pole diff --git a/exp_scenario/module/control/mine_depletion.lua b/exp_scenario/module/control/mine_depletion.lua index 799d0649..6bd8d24e 100644 --- a/exp_scenario/module/control/mine_depletion.lua +++ b/exp_scenario/module/control/mine_depletion.lua @@ -43,7 +43,7 @@ local function prevent_deconstruction(entity) end -- Not minable, selectable, or deconstructive - if not entity.minable or not entity.prototype.selectable_in_game or entity.has_flag("not-deconstructable") then + if not entity.minable_flag or not entity.prototype.selectable_in_game or entity.has_flag("not-deconstructable") then return true end diff --git a/exp_scenario/module/control/spawn_area.lua b/exp_scenario/module/control/spawn_area.lua index 8c1626b7..e4c7213e 100644 --- a/exp_scenario/module/control/spawn_area.lua +++ b/exp_scenario/module/control/spawn_area.lua @@ -56,7 +56,7 @@ end local function protect_entity(entity) if entity and entity.valid then entity.destructible = false - entity.minable = false + entity.minable_flag = false entity.rotatable = false entity.operable = false end From 4fe0e9c0a652cb74f29f9c74d3494745b59d41f6 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Sat, 27 Jun 2026 14:02:49 +0100 Subject: [PATCH 30/33] Add `AABB.size` (#445) --- exp_scenario/module/commands/lawnmower.lua | 2 +- exp_scenario/module/commands/surface.lua | 2 +- exp_scenario/module/commands/waterfill.lua | 3 +-- exp_util/module/aabb.lua | 7 +++++++ 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/exp_scenario/module/commands/lawnmower.lua b/exp_scenario/module/commands/lawnmower.lua index f75f2fdc..b74c39cb 100644 --- a/exp_scenario/module/commands/lawnmower.lua +++ b/exp_scenario/module/commands/lawnmower.lua @@ -28,8 +28,8 @@ commands.lawnmower = Commands.new("lawnmower", { "exp-commands_lawnmower.descrip SelectArea:on_selection(function(event) local player = assert(game.get_player(event.player_index)) local area = AABB.expand(event.area) + local area_size = AABB.size(area) local surface = event.surface - local area_size = (area.right_bottom.x - area.left_top.x) * (area.right_bottom.y - area.left_top.y) if area_size > 1000 then player.print({ "exp-commands_lawnmower.area-too-large", 1000, area_size }, Commands.print_settings.error) diff --git a/exp_scenario/module/commands/surface.lua b/exp_scenario/module/commands/surface.lua index 1d98e96a..afe630f7 100644 --- a/exp_scenario/module/commands/surface.lua +++ b/exp_scenario/module/commands/surface.lua @@ -75,8 +75,8 @@ commands.clear_blueprints = Commands.new("clear-blueprints", { "exp-commands_sur SelectArea:on_selection(function(event) local player = assert(game.get_player(event.player_index)) local area = AABB.expand(event.area) + local area_size = AABB.size(area) local surface = event.surface - local area_size = (area.right_bottom.x - area.left_top.x) * (area.right_bottom.y - area.left_top.y) if area_size > 1000 then player.print({ "exp-commands_surface.area-too-large", 1000, area_size }, Commands.print_settings.error) diff --git a/exp_scenario/module/commands/waterfill.lua b/exp_scenario/module/commands/waterfill.lua index 4ca95ec9..6e1a8e24 100644 --- a/exp_scenario/module/commands/waterfill.lua +++ b/exp_scenario/module/commands/waterfill.lua @@ -41,6 +41,7 @@ commands.waterfill = Commands.new("waterfill", { "exp-commands_waterfill.descrip --- When an area is selected to be converted to water SelectArea:on_selection(function(event) local area = AABB.expand(event.area) + local area_size = AABB.size(area) local player = game.players[event.player_index] local surface = event.surface @@ -51,8 +52,6 @@ SelectArea:on_selection(function(event) end ]] - local area_size = (area.right_bottom.x - area.left_top.x) * (area.right_bottom.y - area.left_top.y) - if area_size > 1000 then player.print({ "exp-commands_waterfill.area-too-large", 1000, area_size }, Commands.print_settings.error) return diff --git a/exp_util/module/aabb.lua b/exp_util/module/aabb.lua index dda22d75..64c5719d 100644 --- a/exp_util/module/aabb.lua +++ b/exp_util/module/aabb.lua @@ -17,6 +17,13 @@ function AABB.valid(aabb) return aabb.left_top.x < aabb.right_bottom.x and aabb.left_top.y < aabb.right_bottom.y end +--- Returns the size of the area contained within an AABB +--- @param aabb BoundingBox +--- @return number +function AABB.size(aabb) + return (aabb.right_bottom.x - aabb.left_top.x) * (aabb.right_bottom.y - aabb.left_top.y) +end + --- Clone an area, allows for safe mutation of an input value --- @param aabb BoundingBox --- @return BoundingBox From c4f9c971300e896148976a29da20b21a344ee380 Mon Sep 17 00:00:00 2001 From: PHIDIAS Date: Mon, 29 Jun 2026 05:53:43 +0900 Subject: [PATCH 31/33] remove limit (#446) * Update surface.lua * . * . --- exp_scenario/module/commands/lawnmower.lua | 6 ------ exp_scenario/module/commands/surface.lua | 6 ------ exp_scenario/module/locale/en.cfg | 2 -- 3 files changed, 14 deletions(-) diff --git a/exp_scenario/module/commands/lawnmower.lua b/exp_scenario/module/commands/lawnmower.lua index b74c39cb..5ff1d2e1 100644 --- a/exp_scenario/module/commands/lawnmower.lua +++ b/exp_scenario/module/commands/lawnmower.lua @@ -28,14 +28,8 @@ commands.lawnmower = Commands.new("lawnmower", { "exp-commands_lawnmower.descrip SelectArea:on_selection(function(event) local player = assert(game.get_player(event.player_index)) local area = AABB.expand(event.area) - local area_size = AABB.size(area) local surface = event.surface - if area_size > 1000 then - player.print({ "exp-commands_lawnmower.area-too-large", 1000, area_size }, Commands.print_settings.error) - return - end - local entities = surface.find_entities_filtered{ area = area, type = "corpse" } for _, entity in pairs(entities) do if (entity.name ~= "transport-caution-corpse" and entity.name ~= "invisible-transport-caution-corpse") then diff --git a/exp_scenario/module/commands/surface.lua b/exp_scenario/module/commands/surface.lua index afe630f7..18504724 100644 --- a/exp_scenario/module/commands/surface.lua +++ b/exp_scenario/module/commands/surface.lua @@ -75,14 +75,8 @@ commands.clear_blueprints = Commands.new("clear-blueprints", { "exp-commands_sur SelectArea:on_selection(function(event) local player = assert(game.get_player(event.player_index)) local area = AABB.expand(event.area) - local area_size = AABB.size(area) local surface = event.surface - if area_size > 1000 then - player.print({ "exp-commands_surface.area-too-large", 1000, area_size }, Commands.print_settings.error) - return - end - local entities = surface.find_entities_filtered{ type = "entity-ghost", area = area } for _, entity in ipairs(entities) do entity.destroy() diff --git a/exp_scenario/module/locale/en.cfg b/exp_scenario/module/locale/en.cfg index ce446356..085052f9 100644 --- a/exp_scenario/module/locale/en.cfg +++ b/exp_scenario/module/locale/en.cfg @@ -105,7 +105,6 @@ description=Clean up biter corpse, decoratives and nuclear hole. arg-range=Range to clean up. enter=Entered selection mode, select the area to fill with water. exit=Exited selection mode. -area-too-large=Selected area is too large, must be less than __1__ tiles, selected __2__. complete=__1__ tiles were handled. [exp-commands_locate] @@ -226,7 +225,6 @@ items=__1__ cleared all items on the ground of __2__. blueprints=__1__ cleared all blueprints on __2__. enter=Entered selection mode, select the area. exit=Exited selection mode. -area-too-large=Selected area is too large, must be less than __1__, selected __2__. complete=__1__ blueprint ghost were removed. [exp-commands_trains] From 50daa81a4a79a501100f469c623ed6d332829541 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Mon, 29 Jun 2026 01:14:39 +0100 Subject: [PATCH 32/33] Fix require for `/debug` --- exp_scenario/module/commands/debug.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exp_scenario/module/commands/debug.lua b/exp_scenario/module/commands/debug.lua index a30d202e..0545e156 100644 --- a/exp_scenario/module/commands/debug.lua +++ b/exp_scenario/module/commands/debug.lua @@ -2,7 +2,7 @@ Adds a command that opens the debug frame ]] -local DebugView = require("modules.exp_legacy.modules.gui.debug.main_view") --- @dep modules.gui.debug.main_view +local DebugView = require("modules/exp_scenario/gui/debug/main_view") local Commands = require("modules/exp_commands") --- Opens the debug gui. From a07be7b7ed3ec59ebe30914355900d1589fd9e50 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Tue, 14 Jul 2026 00:50:00 +0100 Subject: [PATCH 33/33] Bump version in package jsons --- exp_commands/package.json | 2 +- exp_groups/package.json | 2 +- exp_gui/package.json | 2 +- exp_legacy/package.json | 3 +- exp_scenario/package.json | 3 +- exp_server_ups/package.json | 6 +- exp_util/package.json | 2 +- scripts/github_api.mts | 141 +++++++++++++++++++ scripts/pr_to_changelog.mts | 260 ++++++++++++++++++++++++++++++++++++ 9 files changed, 412 insertions(+), 9 deletions(-) create mode 100644 scripts/github_api.mts create mode 100644 scripts/pr_to_changelog.mts diff --git a/exp_commands/package.json b/exp_commands/package.json index bd7e0a20..8c9f8da9 100644 --- a/exp_commands/package.json +++ b/exp_commands/package.json @@ -1,6 +1,6 @@ { "name": "@expcluster/lib_commands", - "version": "0.1.0", + "version": "6.5.0", "description": "Clusterio plugin providing a Lua command processing library.", "author": "Cooldude2606 ", "license": "MIT", diff --git a/exp_groups/package.json b/exp_groups/package.json index 05bd9579..d54c67a8 100644 --- a/exp_groups/package.json +++ b/exp_groups/package.json @@ -1,6 +1,6 @@ { "name": "@expcluster/permission-groups", - "version": "0.1.0", + "version": "6.5.0", "description": "Clusterio plugin providing syncing of permission groups", "author": "Cooldude2606 ", "license": "MIT", diff --git a/exp_gui/package.json b/exp_gui/package.json index 441f7dff..7c1988c9 100644 --- a/exp_gui/package.json +++ b/exp_gui/package.json @@ -1,6 +1,6 @@ { "name": "@expcluster/lib_gui", - "version": "0.1.0", + "version": "6.5.0", "description": "Clusterio plugin providing a Lua GUI definition library.", "author": "Cooldude2606 ", "license": "MIT", diff --git a/exp_legacy/package.json b/exp_legacy/package.json index 70d90b2e..77a7f964 100644 --- a/exp_legacy/package.json +++ b/exp_legacy/package.json @@ -1,6 +1,6 @@ { "name": "@expcluster/legacy", - "version": "0.1.0", + "version": "6.5.0", "description": "Clusterio plugin implementing the legacy v6 scenario updated for factorio 2.0", "author": "Cooldude2606 ", "license": "MIT", @@ -26,6 +26,7 @@ "dependencies": { "@expcluster/lib_commands": "workspace:^", "@expcluster/lib_util": "workspace:^", + "@expcluster/lib_gui": "workspace:^", "@sinclair/typebox": "catalog:" }, "publishConfig": { diff --git a/exp_scenario/package.json b/exp_scenario/package.json index a7abd8c2..cd8b8b26 100644 --- a/exp_scenario/package.json +++ b/exp_scenario/package.json @@ -1,6 +1,6 @@ { "name": "@expcluster/scenario", - "version": "0.1.0", + "version": "6.5.0", "description": "Clusterio plugin implementing the Explosive Gaming scenario.", "author": "Cooldude2606 ", "license": "MIT", @@ -31,6 +31,7 @@ "dependencies": { "@expcluster/lib_commands": "workspace:^", "@expcluster/lib_util": "workspace:^", + "@expcluster/lib_gui": "workspace:^", "@sinclair/typebox": "catalog:" }, "publishConfig": { diff --git a/exp_server_ups/package.json b/exp_server_ups/package.json index 3cd9a76a..f48b165c 100644 --- a/exp_server_ups/package.json +++ b/exp_server_ups/package.json @@ -1,6 +1,6 @@ { "name": "@expcluster/server-ups", - "version": "0.1.0", + "version": "6.5.0", "description": "Clusterio plugin providing in game server ups counter", "author": "Cooldude2606 ", "license": "MIT", @@ -25,9 +25,9 @@ }, "dependencies": { "@sinclair/typebox": "catalog:", + "@expcluster/lib_commands": "workspace:^", "@expcluster/lib_util": "workspace:^", - "@expcluster/lib_gui": "workspace:^", - "@expcluster/lib_commands": "workspace:^" + "@expcluster/lib_gui": "workspace:^" }, "publishConfig": { "access": "public" diff --git a/exp_util/package.json b/exp_util/package.json index ac95386f..483b3a84 100644 --- a/exp_util/package.json +++ b/exp_util/package.json @@ -1,6 +1,6 @@ { "name": "@expcluster/lib_util", - "version": "0.1.0", + "version": "6.5.0", "description": "Clusterio plugin providing Lua libraries and other utilities.", "author": "Cooldude2606 ", "license": "MIT", diff --git a/scripts/github_api.mts b/scripts/github_api.mts new file mode 100644 index 00000000..1f03a51c --- /dev/null +++ b/scripts/github_api.mts @@ -0,0 +1,141 @@ +export async function githubFetch( + path: string, + query: Record= {}, + init: RequestInit & { headers?: Record } = {}, +) { + const url = new URL("https://api.github.com/"); + url.pathname = path; + for (const [name, value] of Object.entries(query)) { + url.searchParams.set(name, value); + } + init = { + ...init, + headers: { + ...init.headers as Record ?? {}, + "Accept": "application/vnd.github+json", + } + }; + if (process.env.GH_TOKEN) { + init.headers!["Authorization"] = `Bearer ${process.env.GH_TOKEN}`; + } + console.log("Fetching", url.href); + const response = await fetch(url, init); + if (!response.ok) { + console.log(response.headers); + throw new Error(`GitHub replied with ${response.status} ${response.statusText}: ${await response.text()}`); + } + if (!response.headers.get("Content-Type")?.startsWith("application/json")) { + throw new Error( + `GitHub replied with ${response.status} ${response.statusText}: ${response.headers.get("Content-Type")}` + ); + } + return response; +} + +export async function githubFetchJson( + path: string, + query: Record = {}, + init: RequestInit & { headers?: Record } = {}, +): Promise { + const response = await githubFetch(path, query, init); + return await response.json(); +} + +export async function githubFetchJsonPaginated( + path: string, + query: Record = {}, + init: RequestInit & { headers?: Record } = {}, + shouldContinue: (result: T[]) => boolean = () => true, +): Promise { + const data: T[] = []; + let pagesRemaining = true; + query = { + ...query, + per_page: "100", + }; + while (pagesRemaining) { + const response = await githubFetch(path, query, init); + const responseItems = await response.json(); + data.push(...responseItems); + const linkHeader = response.headers.get("Link"); + pagesRemaining = linkHeader?.includes(`rel="next"`) ?? false; + if (pagesRemaining) { + pagesRemaining = shouldContinue(responseItems); + } + if (pagesRemaining) { + query = { + ...query, + page: String((query.page ? (Number(query.page)) : 1) + 1), + } + } + } + return data; +} + +// Minimal types that include only what is used and useful for our scripts. See +// https://docs.github.com/en/rest for complete reference. +export interface PullRequest { + number: number, + url: string, + html_url: string, + state: string, + title: string, + body: string | null, + created_at: string, + updated_at: string, + closed_at: string | null, + merged_at: string | null, + merge_commit_sha: string | null, +} + +export interface Issue { + number: number, + title: string, + url: string, + html_url: string, + state: string, + body?: string | null, + user: { + login: string, + url: string, + html_url: string, + } + pull_request?: { + url: string, + html_url: string, + merged_at?: string | null, + }, +} + +export interface Release { + tag_name: string, + target_commitish: string, + name: string | null, + body: string | null, + created_at: string, + published_at: string | null, + updated_at?: string | null, +} + +export interface Commit { + sha: string, + commit: { + message: string, + }, + author: null | {} | { + name?: null | string, + login: string, + type: string, + }, + parents: { + sha: string, + }[], +} + +export interface Reference { + ref: string, + object: { + type: string, + sha: string, + }, +} diff --git a/scripts/pr_to_changelog.mts b/scripts/pr_to_changelog.mts new file mode 100644 index 00000000..7b80b7dc --- /dev/null +++ b/scripts/pr_to_changelog.mts @@ -0,0 +1,260 @@ +import { + type Release, + type Issue, + githubFetchJson, + githubFetchJsonPaginated, +} from "./github_api.mts"; + +type Changelog = Record; + +const repository = "explosivegaming/ExpCluster"; +const branch = "main"; + +const changelogSections = [ + "Major Features", + "Features", + "Fixes", + "Changes", + "Breaking Changes", + "Meta", +]; + +// Generic result codes +const success = 0; +const failure = 1; + +type ParserState = { + pos: number, + lines: string[], +}; + +function createParser(lines: string[]): ParserState { + return { pos: 0, lines }; +} + +function atEnd(parser: ParserState) { + return parser.pos >= parser.lines.length; +} + +function currentLine(parser: ParserState) { + if (atEnd(parser)) { + throw new Error("Attempt to access line past end"); + } + return parser.lines[parser.pos]; +} + +function skipToLine( + parser: ParserState, + pattern: RegExp +) { + for (; parser.pos < parser.lines.length; parser.pos++) { + if (pattern.test(currentLine(parser))) { + return success; + } + } + return failure; +} + +function skipEmptyLines(parser: ParserState) { + while (parser.pos < parser.lines.length && /^ *$/.test(currentLine(parser))) { + parser.pos += 1; + } +} + +function extractPullRequestChangelog( + parser: ParserState, +): + | [typeof success, string[]] + | [typeof failure, undefined, string] +{ + const headerFail = skipToLine(parser, /^###? Changelog *$/); + if (headerFail) { + return [failure, , "Header not found"]; + } + parser.pos += 1; + skipEmptyLines(parser); + if (!atEnd(parser) && /^None/i.test(currentLine(parser))) { + parser.pos += 1; + return [success, []]; + } + const blockStartFail = skipToLine(parser, /```/); + if (blockStartFail) { + return [failure, , "Code block not found after header"]; + } + parser.pos += 1 + const blockStartPos = parser.pos; + const blockEndFail = skipToLine(parser, /```/); + if (blockEndFail) { + return [failure, , "End of code block not found after header"]; + } + return [success, parser.lines.slice(blockStartPos, parser.pos)]; +} + +function parseChangelog(parser: ParserState, pr: Issue, issues: Issue[]): [Changelog, string[]] { + let section: string | undefined; + let changelog: Changelog = Object.create(null); + let warnings: string[] = []; + while (!atEnd(parser)) { + const line = currentLine(parser); + parser.pos += 1; + if (line.trim() === "") { + continue; + } + if (line.startsWith("###")) { + section = line.slice(4).trim(); + if (!changelogSections.includes(section)) { + warnings.push( + `Changelog section ${section} on line ${parser.pos} is not one of ${changelogSections.join(", ")}` + ); + } + changelog[section] = changelog[section] ?? []; + continue; + } + if (!section) { + warnings.push(`Unexpected content "${line}" outside of a changelog section on line ${parser.pos}`); + continue; + } + if (line.startsWith("- ")) { + const entry = line + .slice(2) + .trim() + .replace(new RegExp(`\\[#(\\d+)\\]\\(https://github.com/${repository}/(issues|pull)/\\d+\\)`, "g"), "#$1"); + let hasRef = false + for (const ref of entry.matchAll(/#(\d+)/g)) { + hasRef = true; + const id = Number.parseInt(ref[1], 10); + const refIssue = issues.find(issue => issue.number === id); + if (!refIssue) { + warnings.push( + `Issue reference ${ref[0]} on line ${parser.pos} does not exist ` + + "or has not been updated since the last release" + ); + } else if (refIssue.pull_request && refIssue.number !== pr.number) { + warnings.push( + `Pull request reference ${ref[0]} on line ${parser.pos} is to a ` + + "different Pull request than this one." + ); + } + } + if (!hasRef) { + warnings.push(`Changelog entry on line ${parser.pos} does not reference an issue`); + } + changelog[section].push(entry); + continue; + } + warnings.push(`Unexpected content "${line}" on line ${parser.pos}`); + } + return [changelog, warnings]; +} + +function changelogFromPullRequests(pullRequests: Issue[], issues: Issue[]) { + const mergedChangelog: Changelog = Object.create(null); + for (const pr of pullRequests) { + if (!pr.body) { + console.error(`Failed to extract changelog in ${pr.html_url}: No body`) + console.log(pr.title) + continue; + } + const [ + changelogFail, + changelogLines, + changelogFailReason + ] = extractPullRequestChangelog(createParser(pr.body!.split(/\r?\n/))); + if (changelogFail) { + console.error(`Failed to extract changelog in ${pr.html_url}: ${changelogFailReason}`) + console.log(pr.title) + console.log(pr.body) + continue; + } + const [changelog, warnings] = parseChangelog(createParser(changelogLines), pr, issues); + for (const warning of warnings) { + console.warn(`Warning in changelog for ${pr.html_url}: ${warning}`) + } + for (const [section, items] of Object.entries(changelog)) { + mergedChangelog[section] = [...mergedChangelog[section] ?? [], ...items]; + } + } + return mergedChangelog; +} + +async function fetchLastRelease() { + // Assumes releases are ordered by their creation or tag time in descending order. + const releases = await githubFetchJson(`/repos/${repository}/releases`); + const latest = releases[0] + if (!latest) { + throw new Error(`Unable to find latest release on ${branch}`); + } + return latest; +} + +async function fetchIssuesUpdatedSince(since: string) { + return await githubFetchJsonPaginated( + `/repos/${repository}/issues`, + { state: "all", since, sort: "updated", direction: "asc" }, + ); +} + +function printMarkdown( + changelog: Changelog, + issues: Issue[], + users: Issue["user"][], + refText: (issue: Issue) => string, + userText: (user: Issue["user"]) => string, +) { + const sections = new Set([...changelogSections, ...Object.keys(changelog)]); + for (const section of sections) { + if (section in changelog) { + console.log(`### ${section}\n`); + const entries = changelog[section]; + console.log(entries.map(text => ( + `- ${text.replaceAll(/#(\d+)/g, (ref, idAsText) => { + const id = Number.parseInt(idAsText, 10); + if (!Number.isFinite(id)) { + return ref; + } + const issue = issues.find(issue => issue.number === id); + if (!issue) { + return ref; + } + return refText(issue); + })}` + )).join("\n")); + console.log(); + } + } + + console.log(`Many thanks to the following for contributing to this release:`); + console.log(users.map(userText).join("\n")) +} + +async function main() { + const lastRelease = await fetchLastRelease(); + const issues = await fetchIssuesUpdatedSince(lastRelease.created_at); + + const pullRequests = issues.filter(issue => ( + issue.pull_request + && issue.pull_request.merged_at + && issue.pull_request.merged_at > lastRelease.created_at + )); + + const changelog = changelogFromPullRequests(pullRequests, issues); + const users = [...new Map(pullRequests.map(issue => [issue.user.login, issue.user])).values()] + + console.log(); + console.log("=== Github Release Markdown ==="); + printMarkdown(changelog, issues, users, + issue => `${repository}#${issue.number}`, + user => `@${user.login}`, + ); + + console.log(); + console.log("=== Discord / Changelog Markdown ==="); + printMarkdown(changelog, issues, users, + issue => `[#${issue.number}](<${issue.html_url}>)`, + user => `[@${user.login}](<${user.html_url}>)` + ); +} + +if (import.meta.main) { + main().catch(console.error); +}