Skip to content

Remove game options of unused mods from game launch info#7057

Open
lL1l1 wants to merge 8 commits intoFAForever:developfrom
lL1l1:Fix/long-game-options
Open

Remove game options of unused mods from game launch info#7057
lL1l1 wants to merge 8 commits intoFAForever:developfrom
lL1l1:Fix/long-game-options

Conversation

@lL1l1
Copy link
Copy Markdown
Contributor

@lL1l1 lL1l1 commented Mar 1, 2026

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:

  1. Records all the default lobby options.
  2. During mod option import, record what option keys are used by what mods.

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).

ConExecute([[SimLua reprsl(ScenarioInfo.Options)]])

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

  • Game options belonging to disabled mods are now properly excluded during game launch, fixing live replay compatibility issues.

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.
@lL1l1 lL1l1 requested a review from speed2CZ March 1, 2026 08:21
@lL1l1 lL1l1 added area: ui Anything to do with the User Interface of the Game area: lobby related to lobby: options, chat, etc labels Mar 1, 2026
@lL1l1 lL1l1 marked this pull request as ready for review March 1, 2026 08:22
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 1, 2026

📝 Walkthrough

Walkthrough

A 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

Cohort / File(s) Summary
Changelog Entry
changelog/snippets/fix.7057.md
New changelog entry documenting the removal of unused mod options from game launch info.
Mod-Aware Option Filtering
lua/ui/lobby/lobby.lua
Introduces ModOptionMapping and DefaultOptionKeys data structures to track which mods use which options. Adds helper functions (initOptionKeys, IsDefaultOption, IsOptionUsedByGivenMods) and integrates option filtering logic into ImportModAIOptions and TryLaunch to remove options from disabled mods before game launch. Includes conditional logging via DebugComponent.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • KionX

Poem

🐰 A whisker-twitch of options cleared,
Mod-mapping magic now appeared,
When disabled mods fade away,
Their orphaned options cannot stay,
Replays shine bright, forever here!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: removing game options belonging to unused mods from game launch info.
Description check ✅ Passed The description comprehensively covers proposed changes, testing methodology, all checklist items, and provides sufficient context about the issue and implementation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 name for ModOptionMapping and enabledModNames can 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
         end

Also 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.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9868135 and bd1d0ab.

📒 Files selected for processing (2)
  • changelog/snippets/fix.7057.md
  • lua/ui/lobby/lobby.lua

Comment on lines +2353 to +2389
--#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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: lobby related to lobby: options, chat, etc area: ui Anything to do with the User Interface of the Game

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant