Remove game options of unused mods from game launch info#7057
Remove game options of unused mods from game launch info#7057lL1l1 wants to merge 8 commits intoFAForever:developfrom
Conversation
Mostly generated by ai
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.
📝 WalkthroughWalkthroughA changelog entry is added, and the lobby launcher is enhanced to track which game options belong to which mods, then automatically filter out options from disabled mods during game launch to prevent live replay issues. Changes
Sequence DiagramsequenceDiagram
participant ModLoad as Mod Loading Phase
participant OptionMap as ModOptionMapping
participant GameLaunch as Game Launch (TryLaunch)
participant Filter as Option Filter
participant GameInfo as GameInfo.GameOptions
ModLoad->>OptionMap: ImportModAIOptions: Record mod name for each option key
OptionMap->>OptionMap: Build mapping: optionKey → {modName → true}
GameLaunch->>GameLaunch: Compute enabledModNames from GameMods
GameLaunch->>Filter: For each option in GameOptions
Filter->>OptionMap: Check if option is default or used by enabled mods
Filter-->>Filter: Keep option if: IsDefaultOption OR IsOptionUsedByGivenMods
Filter->>GameInfo: Remove option if belongs to disabled mod only
GameLaunch->>GameLaunch: Log removal count and details
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
lua/ui/lobby/lobby.lua (1)
154-154: Prefer stable mod identifiers over display names for matching.Using
nameforModOptionMappingandenabledModNamescan misclassify options when different mods share a display name. Use a stable identifier (uid/UID) when available.💡 Proposed hardening
- ModOptionMapping[t.key][ModData.name] = true + local modIdentifier = ModData.uid or ModData.UID or ModData.name + ModOptionMapping[t.key][modIdentifier] = true- for _, modInfo in gameInfo.GameMods do - enabledModNames[modInfo.name] = true + for _, modInfo in gameInfo.GameMods do + local modIdentifier = modInfo.uid or modInfo.UID or modInfo.name + enabledModNames[modIdentifier] = true endAlso applies to: 2355-2358
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lua/ui/lobby/lobby.lua` at line 154, The code uses ModData.name when keying ModOptionMapping and enabledModNames which can collide across mods; change the keys to use a stable identifier (e.g., ModData.uid or ModData.UID) wherever ModOptionMapping[t.key][ModData.name] and enabledModNames are set or checked (also update the similar occurrences around the enabledModNames block at the other location), and add a safe fallback to ModData.name only if uid/UID is nil to preserve behavior for older mods; ensure all lookups and assignments that previously used ModData.name are updated to the chosen stable identifier to keep mapping consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@lua/ui/lobby/lobby.lua`:
- Around line 2353-2389: The GameOptions filtering runs too late; move the whole
filtering block (the code that builds enabledModNames, iterates
gameInfo.GameOptions, uses IsDefaultOption, ModOptionMapping and
IsOptionUsedByGivenMods, collects keysToRemove and nils them from
gameInfo.GameOptions) so it executes before the launch data broadcast is
created/sent (i.e. before the function that prepares/broadcasts the launch
payload to clients). Also ensure any mod resolution this filtering depends on
(the population of gameInfo.GameMods and ModOptionMapping) is performed
beforehand so enabledModNames is accurate; after moving, confirm the broadcast
uses the now-filtered gameInfo.GameOptions.
---
Nitpick comments:
In `@lua/ui/lobby/lobby.lua`:
- Line 154: The code uses ModData.name when keying ModOptionMapping and
enabledModNames which can collide across mods; change the keys to use a stable
identifier (e.g., ModData.uid or ModData.UID) wherever
ModOptionMapping[t.key][ModData.name] and enabledModNames are set or checked
(also update the similar occurrences around the enabledModNames block at the
other location), and add a safe fallback to ModData.name only if uid/UID is nil
to preserve behavior for older mods; ensure all lookups and assignments that
previously used ModData.name are updated to the chosen stable identifier to keep
mapping consistent.
| --#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 |
There was a problem hiding this comment.
Filter is executed too late to affect clients’ launch payload.
At Line 2348, launch data is already broadcast before this filtering runs, so clients can still receive unfiltered GameOptions. Move the filtering (and mod resolution it depends on) before the broadcast.
💡 Proposed fix
- lobbyComm:BroadcastData({ Type = 'Launch', GameInfo = gameInfo })
-
- -- set the mods
- gameInfo.GameMods = Mods.GetGameMods(gameInfo.GameMods)
+ -- 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
@@
for _, key in keysToRemove do
gameInfo.GameOptions[key] = nil
end
--#endregion
+
+ -- Broadcast the already-filtered launch payload
+ lobbyComm:BroadcastData({ Type = 'Launch', GameInfo = gameInfo })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lua/ui/lobby/lobby.lua` around lines 2353 - 2389, The GameOptions filtering
runs too late; move the whole filtering block (the code that builds
enabledModNames, iterates gameInfo.GameOptions, uses IsDefaultOption,
ModOptionMapping and IsOptionUsedByGivenMods, collects keysToRemove and nils
them from gameInfo.GameOptions) so it executes before the launch data broadcast
is created/sent (i.e. before the function that prepares/broadcasts the launch
payload to clients). Also ensure any mod resolution this filtering depends on
(the population of gameInfo.GameMods and ModOptionMapping) is performed
beforehand so enabledModNames is accurate; after moving, confirm the broadcast
uses the now-filtered gameInfo.GameOptions.
Description of the proposed changes
Removes unused mod options from scenario info that was causing live replays to not work according to this message on zulip #general > Live replay @ 💬
On initialization:
On game launch, remove all options that are not a default option and are not used by any enabled mods.
Testing done on the proposed changes
Playing the game without any mods gives a small ScenarioInfo.Options table (1320 bytes).
Playing the game with a mod that has lots of options such as "Swarm Survival Vanilla" produces no errors in the log from the mod and printing the scenario info shows the expected options. The debugging also does not show that any removed options that were used by the mod.
M28AI adds an option with the key "UnitCap" which is the same as the base game unit cap option's key. M28AI's option is not removed and its value of 10000 works as expected as shown by the scoreboard.
Ending the game and entering a new skirmish with adjusted mods and without restarting the application does not cause any issues as long as the lobby file was not dirtied in the session.
Checklist
Summary by CodeRabbit
Bug Fixes