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. diff --git a/lua/ui/lobby/lobby.lua b/lua/ui/lobby/lobby.lua index ae045f8bf40..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") @@ -58,6 +66,8 @@ if versionType != "FAF" then ConExecute("net_DebugLevel 10") end +local DebugComponent = import("/lua/shared/components/DebugComponent.lua").DebugComponent + function GetAITypes() AIKeys = {} AIStrings = {} @@ -82,11 +92,32 @@ 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 option keys to mods that use them +-- Format: { [optionKey] = { modName1 = true, modName2 = true, ... } } +---@type table> +local ModOptionMapping = {} + +-- Set of option keys from the original/default lobbyOptions.lua +-- Used to distinguish default options from mod-added options +---@type table +local DefaultOptionKeys = {} + +-- 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 -- Add lobby options from AI mods @@ -102,6 +133,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 @@ -109,6 +147,11 @@ function ImportModAIOptions() if not alreadyStored then table.insert(AIOpts, t) end + + -- 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 @@ -118,6 +161,28 @@ 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 +---@param optionKey string +---@return boolean +local function IsDefaultOption(optionKey) + return DefaultOptionKeys[optionKey] ~= nil +end + +--- Helper function: Returns true if the given option is used by any of the given mods +---@param optionKey string +---@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 false +end + local rehostPlayerOptions = {} -- Player options loaded from preset, used for rehosting local formattedOptions = {} @@ -2285,6 +2350,44 @@ 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 IsDefaultOption(optionKey) and ModOptionMapping[optionKey] then + -- Check if this option is used by an enabled mod + local isUsed = IsOptionUsedByGivenMods(optionKey, enabledModNames) + + -- If NOT used, mark for removal + if not isUsed then + table.insert(keysToRemove, optionKey) + if DebugComponent.EnabledSpewing then + SPEW(string.format('Option "%s" marked for removal because none of these mods are enabled: "%s"' + , optionKey + , table.concatkeys(ModOptionMapping[optionKey], '", "') + )) + 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 + gameInfo.GameOptions[key] = nil + end + --#endregion + SetWindowedLobby(false) Presets.SaveLastGamePreset()