From e3322ee7a4d98c3869155b44cd4305fe106053be Mon Sep 17 00:00:00 2001 From: lL1l1 <82986251+lL1l1@users.noreply.github.com> Date: Sat, 28 Feb 2026 21:05:53 -0800 Subject: [PATCH 1/7] Remove unused modded options before launching game Mostly generated by ai --- lua/ui/lobby/lobby.lua | 67 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/lua/ui/lobby/lobby.lua b/lua/ui/lobby/lobby.lua index ae045f8bf40..a5b244c27df 100644 --- a/lua/ui/lobby/lobby.lua +++ b/lua/ui/lobby/lobby.lua @@ -87,6 +87,21 @@ local teamOpts = import("/lua/ui/lobby/lobbyoptions.lua").teamOptions local AIOpts = import("/lua/ui/lobby/lobbyoptions.lua").AIOpts local gameColors = import("/lua/gamecolors.lua").GameColors +-- Table mapping mod names to option keys they provide +-- Format: { [modName] = { optionKey1 = true, optionKey2 = true, ... } } +---@type table> +local ModOptionMapping = {} + +-- Set of option keys from the original/default lobbyOptions.lua AIOpts +-- Used to distinguish default options from mod-added options +---@type table +local DefaultAIOptionKeys = {} + +-- Initialize DefaultOptionKeys with the original lobbyOptions.lua AIOpts options +for _, option in AIOpts do + DefaultAIOptionKeys[option.key] = true +end + local numOpenSlots = LobbyComm.maxPlayerSlots -- Add lobby options from AI mods @@ -97,6 +112,8 @@ function ImportModAIOptions() for Index, ModData in simMods do if exists(ModData.location..'/lua/AI/LobbyOptions/lobbyoptions.lua') then OptionData = import(ModData.location..'/lua/AI/LobbyOptions/lobbyoptions.lua').AIOpts + -- Initialize the mod's option set if it doesn't exist + ModOptionMapping[ModData.name] = ModOptionMapping[ModData.name] or {} for s, t in OptionData do -- check, if we have this option already stored alreadyStored = false @@ -108,6 +125,8 @@ function ImportModAIOptions() end if not alreadyStored then table.insert(AIOpts, t) + -- Track that this option came from this mod + ModOptionMapping[ModData.name][t.key] = true end end end @@ -118,6 +137,25 @@ ImportModAIOptions() -- Maps faction identifiers to their names. local FACTION_NAMES = {[1] = "uef", [2] = "aeon", [3] = "cybran", [4] = "seraphim", [5] = "random" } +--- Helper function: Returns true if the given option key exists in the default `lobbyOptions.lua` `AIOpts` +---@param optionKey string +---@return boolean +local function IsDefaultAIOption(optionKey) + return DefaultAIOptionKeys[optionKey] ~= nil +end + +--- Helper function: Returns the mod name that provides the given option key, or nil if not from a mod +---@param optionKey string +---@return string? +local function GetModSourceForAIOption(optionKey) + for modName, optionKeys in ModOptionMapping do + if optionKeys[optionKey] then + return modName + end + end + return nil +end + local rehostPlayerOptions = {} -- Player options loaded from preset, used for rehosting local formattedOptions = {} @@ -2285,6 +2323,35 @@ local function TryLaunch(skipNoObserversCheck) -- set the mods gameInfo.GameMods = Mods.GetGameMods(gameInfo.GameMods) + --#region Filter GameOptions to remove options from disabled mods + -- Build set of enabled mod names for quick lookup + local enabledModNames = {} + for _, modInfo in gameInfo.GameMods do + enabledModNames[modInfo.name] = true + end + + -- Remove options from disabled mods + -- Only remove options that are not in the default lobbyOptions.lua + local keysToRemove = {} + for optionKey, _ in gameInfo.GameOptions do + -- Skip if this is a default option (always keep default options) + if not IsDefaultAIOption(optionKey) then + -- Check if this option came from a mod + local modSource = GetModSourceForAIOption(optionKey) + + -- If from a mod and that mod is NOT enabled, mark for removal + if modSource and not enabledModNames[modSource] then + table.insert(keysToRemove, optionKey) + end + end + end + + -- Remove the marked keys from GameOptions + for _, key in keysToRemove do + gameInfo.GameOptions[key] = nil + end + --#endregion + SetWindowedLobby(false) Presets.SaveLastGamePreset() From 5995bee79700d2dac211e1e8777665ef000e7d67 Mon Sep 17 00:00:00 2001 From: lL1l1 <82986251+lL1l1@users.noreply.github.com> Date: Sat, 28 Feb 2026 22:26:17 -0800 Subject: [PATCH 2/7] Debug logging --- lua/ui/lobby/lobby.lua | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lua/ui/lobby/lobby.lua b/lua/ui/lobby/lobby.lua index a5b244c27df..16b9a38033f 100644 --- a/lua/ui/lobby/lobby.lua +++ b/lua/ui/lobby/lobby.lua @@ -58,6 +58,8 @@ if versionType != "FAF" then ConExecute("net_DebugLevel 10") end +local DebugComponent = import("/lua/shared/components/DebugComponent.lua").DebugComponent + function GetAITypes() AIKeys = {} AIStrings = {} @@ -119,6 +121,13 @@ function ImportModAIOptions() alreadyStored = false for k, v in AIOpts do if v.key == t.key then + if DebugComponent.EnabledLogging then + LOG(string.format( + 'Found duplicate mod option "%s" in mod "%s"' + , t.key + , ModData.name + )) + end alreadyStored = true break end @@ -2342,9 +2351,18 @@ local function TryLaunch(skipNoObserversCheck) -- If from a mod and that mod is NOT enabled, mark for removal if modSource and not enabledModNames[modSource] then table.insert(keysToRemove, optionKey) + if DebugComponent.EnabledSpewing then + SPEW(string.format('Option "%s" from disabled mod "%s" marked for removal' + , optionKey + , modSource + )) + end end end end + if DebugComponent.EnabledLogging then + LOG(string.format("%d options marked for removal", table.getsize(keysToRemove))) + end -- Remove the marked keys from GameOptions for _, key in keysToRemove do From 6e3f560c33b282a1a3d21c9209e1090508088aba Mon Sep 17 00:00:00 2001 From: lL1l1 <82986251+lL1l1@users.noreply.github.com> Date: Sat, 28 Feb 2026 22:19:12 -0800 Subject: [PATCH 3/7] Fix not tracking options used by multiple mods --- lua/ui/lobby/lobby.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/ui/lobby/lobby.lua b/lua/ui/lobby/lobby.lua index 16b9a38033f..7a0f471967d 100644 --- a/lua/ui/lobby/lobby.lua +++ b/lua/ui/lobby/lobby.lua @@ -134,9 +134,9 @@ function ImportModAIOptions() end if not alreadyStored then table.insert(AIOpts, t) - -- Track that this option came from this mod - ModOptionMapping[ModData.name][t.key] = true end + -- Track that this option came from this mod + ModOptionMapping[ModData.name][t.key] = true end end end From f145048b857359587a15fcf9ee2dba41d188b296 Mon Sep 17 00:00:00 2001 From: lL1l1 <82986251+lL1l1@users.noreply.github.com> Date: Sat, 28 Feb 2026 22:25:05 -0800 Subject: [PATCH 4/7] Fix not all default options being accounted for This fixes the removal logic incorrectly removing options with the same key as a default option that is not from the AIOpts table. This fixes M28AI's UnitCap option. --- lua/ui/lobby/lobby.lua | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/lua/ui/lobby/lobby.lua b/lua/ui/lobby/lobby.lua index 7a0f471967d..0ec8a7d888a 100644 --- a/lua/ui/lobby/lobby.lua +++ b/lua/ui/lobby/lobby.lua @@ -84,9 +84,10 @@ if HasCommandLineArg("/syncreplay") and HasCommandLineArg("/gpgnet") then IsSyncReplayServer = true end -local globalOpts = import("/lua/ui/lobby/lobbyoptions.lua").globalOpts -local teamOpts = import("/lua/ui/lobby/lobbyoptions.lua").teamOptions -local AIOpts = import("/lua/ui/lobby/lobbyoptions.lua").AIOpts +local lobbyOptions = import("/lua/ui/lobby/lobbyoptions.lua") +local globalOpts = lobbyOptions.globalOpts +local teamOpts = lobbyOptions.teamOptions +local AIOpts = lobbyOptions.AIOpts local gameColors = import("/lua/gamecolors.lua").GameColors -- Table mapping mod names to option keys they provide @@ -94,15 +95,20 @@ local gameColors = import("/lua/gamecolors.lua").GameColors ---@type table> local ModOptionMapping = {} --- Set of option keys from the original/default lobbyOptions.lua AIOpts +-- Set of option keys from the original/default lobbyOptions.lua -- Used to distinguish default options from mod-added options ---@type table -local DefaultAIOptionKeys = {} +local DefaultOptionKeys = {} --- Initialize DefaultOptionKeys with the original lobbyOptions.lua AIOpts options -for _, option in AIOpts do - DefaultAIOptionKeys[option.key] = true +-- Initialize DefaultOptionKeys with the original lobbyOptions.lua options +local function initOptionKeys(...) + for _, optionTable in ipairs(arg) do + for _, option in optionTable do + DefaultOptionKeys[option.key] = true + end + end end +initOptionKeys(globalOpts, teamOpts, AIOpts) local numOpenSlots = LobbyComm.maxPlayerSlots @@ -146,17 +152,17 @@ ImportModAIOptions() -- Maps faction identifiers to their names. local FACTION_NAMES = {[1] = "uef", [2] = "aeon", [3] = "cybran", [4] = "seraphim", [5] = "random" } ---- Helper function: Returns true if the given option key exists in the default `lobbyOptions.lua` `AIOpts` +--- Helper function: Returns true if the given option key exists in the default lobbyOptions.lua ---@param optionKey string ---@return boolean -local function IsDefaultAIOption(optionKey) - return DefaultAIOptionKeys[optionKey] ~= nil +local function IsDefaultOption(optionKey) + return DefaultOptionKeys[optionKey] ~= nil end --- Helper function: Returns the mod name that provides the given option key, or nil if not from a mod ---@param optionKey string ---@return string? -local function GetModSourceForAIOption(optionKey) +local function GetModSourceForOption(optionKey) for modName, optionKeys in ModOptionMapping do if optionKeys[optionKey] then return modName @@ -2344,9 +2350,9 @@ local function TryLaunch(skipNoObserversCheck) local keysToRemove = {} for optionKey, _ in gameInfo.GameOptions do -- Skip if this is a default option (always keep default options) - if not IsDefaultAIOption(optionKey) then + if not IsDefaultOption(optionKey) then -- Check if this option came from a mod - local modSource = GetModSourceForAIOption(optionKey) + local modSource = GetModSourceForOption(optionKey) -- If from a mod and that mod is NOT enabled, mark for removal if modSource and not enabledModNames[modSource] then From 853a2a7fab31ad200631da2231ec852ba9d9dda6 Mon Sep 17 00:00:00 2001 From: lL1l1 <82986251+lL1l1@users.noreply.github.com> Date: Sat, 28 Feb 2026 22:58:59 -0800 Subject: [PATCH 5/7] Key ModOptionMapping by option instead of mod --- lua/ui/lobby/lobby.lua | 44 +++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/lua/ui/lobby/lobby.lua b/lua/ui/lobby/lobby.lua index 0ec8a7d888a..010895993ea 100644 --- a/lua/ui/lobby/lobby.lua +++ b/lua/ui/lobby/lobby.lua @@ -90,8 +90,8 @@ local teamOpts = lobbyOptions.teamOptions local AIOpts = lobbyOptions.AIOpts local gameColors = import("/lua/gamecolors.lua").GameColors --- Table mapping mod names to option keys they provide --- Format: { [modName] = { optionKey1 = true, optionKey2 = true, ... } } +-- Table mapping option keys to mods that use them +-- Format: { [optionKey] = { modName1 = true, modName2 = true, ... } } ---@type table> local ModOptionMapping = {} @@ -120,8 +120,6 @@ function ImportModAIOptions() for Index, ModData in simMods do if exists(ModData.location..'/lua/AI/LobbyOptions/lobbyoptions.lua') then OptionData = import(ModData.location..'/lua/AI/LobbyOptions/lobbyoptions.lua').AIOpts - -- Initialize the mod's option set if it doesn't exist - ModOptionMapping[ModData.name] = ModOptionMapping[ModData.name] or {} for s, t in OptionData do -- check, if we have this option already stored alreadyStored = false @@ -141,8 +139,11 @@ function ImportModAIOptions() if not alreadyStored then table.insert(AIOpts, t) end - -- Track that this option came from this mod - ModOptionMapping[ModData.name][t.key] = true + + -- Initialize the option's mod set + ModOptionMapping[t.key] = ModOptionMapping[t.key] or {} + -- Track that this option is used by this mod + ModOptionMapping[t.key][ModData.name] = true end end end @@ -159,16 +160,19 @@ local function IsDefaultOption(optionKey) return DefaultOptionKeys[optionKey] ~= nil end ---- Helper function: Returns the mod name that provides the given option key, or nil if not from a mod +--- Helper function: Returns true if the given option is used by any of the given mods ---@param optionKey string ----@return string? -local function GetModSourceForOption(optionKey) - for modName, optionKeys in ModOptionMapping do - if optionKeys[optionKey] then - return modName +---@param enabledModNames table +---@return boolean +local function IsOptionUsedByGivenMods(optionKey, enabledModNames) + local modNamesUsingOpt = ModOptionMapping[optionKey] + if not modNamesUsingOpt then return false end + for modName, _ in modNamesUsingOpt do + if enabledModNames[modName] then + return true end end - return nil + return false end local rehostPlayerOptions = {} -- Player options loaded from preset, used for rehosting @@ -2350,17 +2354,17 @@ local function TryLaunch(skipNoObserversCheck) local keysToRemove = {} for optionKey, _ in gameInfo.GameOptions do -- Skip if this is a default option (always keep default options) - if not IsDefaultOption(optionKey) then - -- Check if this option came from a mod - local modSource = GetModSourceForOption(optionKey) + if not IsDefaultOption(optionKey) and ModOptionMapping[optionKey] then + -- Check if this option is used by an enabled mod + local isUsed = IsOptionUsedByGivenMods(optionKey, enabledModNames) - -- If from a mod and that mod is NOT enabled, mark for removal - if modSource and not enabledModNames[modSource] then + -- If NOT used, mark for removal + if not isUsed then table.insert(keysToRemove, optionKey) if DebugComponent.EnabledSpewing then - SPEW(string.format('Option "%s" from disabled mod "%s" marked for removal' + SPEW(string.format('Option "%s" marked for removal because none of these mods are enabled: "%s"' , optionKey - , modSource + , table.concatkeys(ModOptionMapping[optionKey], '", "') )) end end From 09b204f4113fe9107475c16014f0693f0ece695f Mon Sep 17 00:00:00 2001 From: lL1l1 <82986251+lL1l1@users.noreply.github.com> Date: Sat, 28 Feb 2026 23:19:01 -0800 Subject: [PATCH 6/7] Comment about lobby file structure --- lua/ui/lobby/lobby.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lua/ui/lobby/lobby.lua b/lua/ui/lobby/lobby.lua index 010895993ea..db0530e854c 100644 --- a/lua/ui/lobby/lobby.lua +++ b/lua/ui/lobby/lobby.lua @@ -5,6 +5,14 @@ --* --* Copyright © 2005 Gas Powered Games, Inc. All rights reserved. --***************************************************************************** + +-- This file implements the main lobby screen +-- To see the options/map selection screen, see mapselect.lua +-- For mods, modsmanager.lua +-- For unit restrictions, unitsmanager.lua +-- For "patchnotes" screen, changelog.lua +-- For load game screen, saveload.lua CreateLoadDialog + local GameVersion = import("/lua/version.lua").GetVersion local UIUtil = import("/lua/ui/uiutil.lua") local MenuCommon = import("/lua/ui/menus/menucommon.lua") From bd1d0ab9059d7d4ff90ae92f184962bde2959e9a Mon Sep 17 00:00:00 2001 From: lL1l1 <82986251+lL1l1@users.noreply.github.com> Date: Sun, 1 Mar 2026 00:22:27 -0800 Subject: [PATCH 7/7] Create fix.7057.md --- changelog/snippets/fix.7057.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/snippets/fix.7057.md diff --git a/changelog/snippets/fix.7057.md b/changelog/snippets/fix.7057.md new file mode 100644 index 00000000000..333cd7332f8 --- /dev/null +++ b/changelog/snippets/fix.7057.md @@ -0,0 +1 @@ +- (#7057) Remove game options of unused mods from game launch info to try to get live replays to work.