Skip to content

Conversation

@Marc-Spector
Copy link
Collaborator

@Marc-Spector Marc-Spector commented Nov 3, 2025

Closes #3425

paint

Summary by CodeRabbit

  • New Features
    • Added "Skip Statistics" setting to prevent recording game statistics after matches.
    • Added "Disable Game Sound" setting to launch games without audio output.
    • Both toggles are now available in the preferences interface and take effect on game launch.

@coderabbitai
Copy link

coderabbitai bot commented Nov 3, 2025

Walkthrough

A new feature adds UI checkboxes in game settings to automatically include command-line parameters for statistics reporting and sound control. A new parameter resolver dynamically tracks enabled parameters based on preferences and integrates them into the game launch command.

Changes

Cohort / File(s) Summary
Preference Model
src/main/java/com/faforever/client/preferences/ForgedAlliancePrefs.java
Introduces two new BooleanProperty fields (skipStatistic, noGameSound) with property accessors, setters, and convenience getters to manage game launch preferences.
Parameter Management
src/main/java/com/faforever/client/fa/GameLaunchLocalParameter.java, src/main/java/com/faforever/client/fa/GameLaunchLocalParametersResolver.java
Creates a new enum defining available launch parameters (INIT_FILE, NO_BUG_REPORT, GAME_OVER, NO_SOUND) with command generation; introduces a Spring component resolver that subscribes to preference changes and maintains a dynamic set of active parameters.
Command Building
src/main/java/com/faforever/client/fa/LaunchCommandBuilder.java, src/main/java/com/faforever/client/fa/ForgedAllianceLaunchService.java
Extends LaunchCommandBuilder with a setter for custom game launch parameters; wires the resolver into ForgedAllianceLaunchService to propagate resolved parameters into the default launch command.
UI Layer
src/main/java/com/faforever/client/preferences/ui/SettingsController.java, src/main/resources/theme/settings/settings.fxml
Adds two new checkboxes (skipStatistic, noGameSoundCheckBox) bound bidirectionally to preferences; introduces a "Game settings" section in the settings UI with titles and descriptions.
Localization
src/main/resources/i18n/messages.properties
Adds four new keys for UI labels and descriptions: settings.fa.skipStatistic, settings.fa.skipStatistic.description, settings.fa.noGameSound, settings.fa.noGameSound.description.
Tests
src/test/java/com/faforever/client/fa/ForgedAllianceLaunchServiceTest.java
Introduces mock for GameLaunchLocalParametersResolver to enable testing resolver interactions.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant SettingsUI as Settings UI
    participant Prefs as ForgedAlliancePrefs
    participant Resolver as GameLaunchLocalParametersResolver
    participant Builder as LaunchCommandBuilder
    participant Launcher as ForgedAllianceLaunchService

    User->>SettingsUI: Toggle skipStatistic/noGameSound
    SettingsUI->>Prefs: Update property (bidirectional binding)
    Prefs->>Resolver: Property change listener triggered
    Resolver->>Resolver: Add/remove parameter from set
    Launcher->>Builder: Build launch command
    Builder->>Resolver: getParameters()
    Resolver-->>Builder: Return active parameters
    Builder->>Builder: Append parameter commands to base command
    Builder-->>Launcher: Return complete command
    Launcher-->>User: Launch game with parameters
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Multiple new files and classes introduce new architectural patterns (enum-based parameters, property-driven resolver component)
  • Bidirectional binding between UI and preferences requires verification of consistency
  • Parameter resolution chain (Prefs → Resolver → Builder → Launcher) should be traced for correctness
  • Ensure lazy initialization of properties and thread-safety in resolver's subscription model if applicable

Suggested reviewers

  • Sheikah45

Poem

🐰 Hoppity-hop, the switches align,
Command-line rabbits now softly opine,
No sound shall echo, stats skip away,
Configured with checkboxes—hip-hip-hooray!

Pre-merge checks and finishing touches

✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding checkboxes for command line switches, matching the PR's core functionality of introducing UI toggles for game launch parameters.
Linked Issues check ✅ Passed The PR implements the requested feature from issue #3425: it adds checkboxes for command line switches. Specifically, it implements /exitongameover (skipStatistic) and /nosound (noGameSound) as requested, plus foundational infrastructure for extensibility.
Out of Scope Changes check ✅ Passed All changes align with the feature scope. The PR adds new UI preferences, binds them to game launch parameters, and integrates them into the launch command builder—all required for the checkbox functionality requested in issue #3425.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/game-settings

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

@codecov
Copy link

codecov bot commented Nov 3, 2025

Codecov Report

❌ Patch coverage is 87.50000% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 58.38%. Comparing base (0a79802) to head (1217d38).
⚠️ Report is 21 commits behind head on develop.

Files with missing lines Patch % Lines
...forever/client/fa/ForgedAllianceLaunchService.java 42.85% 2 Missing and 2 partials ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##             develop    #3431      +/-   ##
=============================================
+ Coverage      58.26%   58.38%   +0.11%     
- Complexity      3924     3947      +23     
=============================================
  Files            581      579       -2     
  Lines          19313    19349      +36     
  Branches        1032     1036       +4     
=============================================
+ Hits           11253    11297      +44     
  Misses          7553     7553              
+ Partials         507      499       -8     
Files with missing lines Coverage Δ
.../faforever/client/fa/GameLaunchLocalParameter.java 100.00% <100.00%> (ø)
.../com/faforever/client/fa/LaunchCommandBuilder.java 94.96% <100.00%> (+0.14%) ⬆️
...orever/client/preferences/ForgedAlliancePrefs.java 80.30% <100.00%> (+1.96%) ⬆️
...ever/client/preferences/ui/SettingsController.java 76.06% <100.00%> (+0.14%) ⬆️
...forever/client/fa/ForgedAllianceLaunchService.java 79.16% <42.85%> (-4.17%) ⬇️

... and 27 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update da6d6df...1217d38. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Marc-Spector
Copy link
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Nov 3, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@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: 5

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d44f97d and 08d99c0.

📒 Files selected for processing (9)
  • src/main/java/com/faforever/client/fa/ForgedAllianceLaunchService.java (3 hunks)
  • src/main/java/com/faforever/client/fa/GameLaunchLocalParameter.java (1 hunks)
  • src/main/java/com/faforever/client/fa/GameLaunchLocalParametersResolver.java (1 hunks)
  • src/main/java/com/faforever/client/fa/LaunchCommandBuilder.java (4 hunks)
  • src/main/java/com/faforever/client/preferences/ForgedAlliancePrefs.java (2 hunks)
  • src/main/java/com/faforever/client/preferences/ui/SettingsController.java (2 hunks)
  • src/main/resources/i18n/messages.properties (1 hunks)
  • src/main/resources/theme/settings/settings.fxml (1 hunks)
  • src/test/java/com/faforever/client/fa/ForgedAllianceLaunchServiceTest.java (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (11)
src/main/resources/i18n/messages.properties (1)

872-875: LGTM! Localization keys follow existing conventions.

The new i18n keys for the game settings are properly formatted and align with the existing pattern for Forged Alliance settings.

src/test/java/com/faforever/client/fa/ForgedAllianceLaunchServiceTest.java (1)

32-33: LGTM! Test infrastructure updated appropriately.

The mock field addition properly supports testing the new dependency introduced in ForgedAllianceLaunchService.

src/main/java/com/faforever/client/preferences/ui/SettingsController.java (2)

149-150: LGTM! CheckBox fields properly declared.

The new checkbox fields follow the existing pattern in this controller.


337-338: LGTM! Bidirectional bindings correctly implemented.

The bindings properly connect the UI checkboxes to the underlying preference properties, ensuring changes persist and are reflected in the UI.

src/main/java/com/faforever/client/fa/ForgedAllianceLaunchService.java (1)

46-46: LGTM! Resolver integration is correct.

The GameLaunchLocalParametersResolver dependency is properly injected and used to augment the launch command with dynamic local parameters.

Also applies to: 130-130

src/main/java/com/faforever/client/fa/LaunchCommandBuilder.java (2)

53-53: LGTM! Field and setter follow builder pattern.

The new field and fluent setter method properly extend the builder to support dynamic local parameters.

Also applies to: 198-201


217-219: LGTM! Dynamic parameter handling implemented correctly.

The refactoring from hardcoded parameters to a dynamic list is well-executed:

  • Maintains existing behavior (INIT_FILE, NO_BUG_REPORT always included)
  • Safely handles null gameLaunchLocalParameters with List.of()
  • Delegates command construction to each parameter's getCommand() method

This provides flexibility while preserving backward compatibility.

src/main/java/com/faforever/client/fa/GameLaunchLocalParameter.java (1)

8-30: LGTM! Enum design is clean and correct.

The GameLaunchLocalParameter enum provides a type-safe way to represent launch parameters:

  • Clear constructor overloading for parameters with/without values
  • getCommand() correctly returns either [name, value] or [name] based on whether a value is present
  • The four constants map to the command-line switches mentioned in the linked issue (#3425)

This is a good abstraction that encapsulates the command construction logic.

src/main/java/com/faforever/client/preferences/ForgedAlliancePrefs.java (3)

189-195: LGTM - property accessors follow standard JavaFX pattern.

The property accessor methods correctly return the underlying BooleanProperty fields, following the established pattern in this class. However, the effectiveness of these methods depends on resolving the naming issue flagged for line 42.


197-203: LGTM - setter methods implemented correctly.

The setter methods follow the standard JavaFX pattern and are consistent with other boolean property setters in this class. Note that renaming the skipStatistic property will require updating the setter method name as well.


205-211: LGTM - getter methods follow JavaBean conventions.

The boolean getter methods correctly use the is prefix and return the property values, consistent with JavaBean conventions and the existing code style in this class.

import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify if this import is needed.

The ArrayList import was added but may be unused. The getParameters() method returns a List, and no explicit ArrayList instantiation appears in this file.


🏁 Script executed:

#!/bin/bash
# Check if ArrayList is used in ForgedAllianceLaunchService
rg -n '\bArrayList\b' --type java src/main/java/com/faforever/client/fa/ForgedAllianceLaunchService.java

Length of output: 30


Remove the unused ArrayList import at line 22.

The ArrayList class is imported but never used in this file. Since getParameters() returns a List interface and there's no explicit ArrayList instantiation, this import should be removed.

🤖 Prompt for AI Agents
In src/main/java/com/faforever/client/fa/ForgedAllianceLaunchService.java around
line 22, the import statement for java.util.ArrayList is unused; remove that
import so only required imports remain (keep java.util.List if used) and then
recompile to ensure there are no compile errors from the removal.

@Component
public class GameLaunchLocalParametersResolver {

private final Set<GameLaunchLocalParameter> parameters = new HashSet<>();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Consider thread safety for the parameters set.

The HashSet at line 16 is not thread-safe. While JavaFX properties typically fire on the JavaFX Application Thread, there's no explicit guarantee here, and getParameters() can be called from any thread (e.g., during game launch).

Use a thread-safe collection:

+  import java.util.concurrent.ConcurrentHashMap;
+  import java.util.Collections;

-  private final Set<GameLaunchLocalParameter> parameters = new HashSet<>();
+  private final Set<GameLaunchLocalParameter> parameters = ConcurrentHashMap.newKeySet();

This ensures safe concurrent access without explicit synchronization.

Also applies to: 29-35, 37-39

🤖 Prompt for AI Agents
In src/main/java/com/faforever/client/fa/GameLaunchLocalParametersResolver.java
around line 16 (also applies to lines 29-35 and 37-39), the field initialization
uses a non-thread-safe HashSet which can be accessed from multiple threads;
replace it with a thread-safe set implementation (for example
Collections.synchronizedSet(new HashSet<>()) or ConcurrentHashMap.newKeySet() or
CopyOnWriteArraySet) and update any other similar set fields/usages listed to
the same thread-safe implementation; when exposing the set via getters, return
either an unmodifiable view or a defensive copy to avoid exposing internal
mutable state across threads.

Comment on lines +18 to +23
public GameLaunchLocalParametersResolver(Preferences preferences) {
ForgedAlliancePrefs faPrefs = preferences.getForgedAlliance();

subscribe(faPrefs.skipStatisticProperty(), GameLaunchLocalParameter.GAME_OVER);
subscribe(faPrefs.noGameSoundProperty(), GameLaunchLocalParameter.NO_SOUND);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Initial preference values are not captured.

The subscribe() method only listens for changes to the property, not the current value. If a preference is already enabled when the application starts (e.g., user previously checked "skip statistics"), the corresponding parameter won't be added to the set, and the launch command won't include it.

Fix by using addListener() with immediate triggering:

   public GameLaunchLocalParametersResolver(Preferences preferences) {
     ForgedAlliancePrefs faPrefs = preferences.getForgedAlliance();
 
-    subscribe(faPrefs.skipStatisticProperty(), GameLaunchLocalParameter.GAME_OVER);
-    subscribe(faPrefs.noGameSoundProperty(), GameLaunchLocalParameter.NO_SOUND);
+    subscribeAndInitialize(faPrefs.skipStatisticProperty(), GameLaunchLocalParameter.GAME_OVER);
+    subscribeAndInitialize(faPrefs.noGameSoundProperty(), GameLaunchLocalParameter.NO_SOUND);
   }
 
-  private void subscribe(BooleanProperty property, GameLaunchLocalParameter parameter) {
-    property.subscribe(newValue -> resolveParameter(parameter, newValue));
+  private void subscribeAndInitialize(BooleanProperty property, GameLaunchLocalParameter parameter) {
+    property.addListener((observable, oldValue, newValue) -> resolveParameter(parameter, newValue));
+    resolveParameter(parameter, property.get());  // Initialize with current value
   }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/main/java/com/faforever/client/fa/GameLaunchLocalParametersResolver.java
around lines 18 to 23, the current code only calls subscribe(...) which reacts
to future changes and therefore misses applying preference values that are
already true at startup; replace the subscribe(...) calls with adding a
ChangeListener (or equivalent addListener) that updates the
GameLaunchLocalParameter set and immediately invoke that listener (or manually
check the property.get() and apply the parameter) so the current preference
value is captured on initialization as well as on subsequent changes.

Comment on lines +42 to +43
private final BooleanProperty skipStatistic = new SimpleBooleanProperty(false);
private final BooleanProperty noGameSound = new SimpleBooleanProperty(false);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Misleading property name: skipStatistic does not convey "exit on game over" functionality.

Based on the linked issue #3425 and the AI summary indicating this maps to the GAME_OVER parameter (corresponding to the /exitongameover command-line switch), the name skipStatistic is misleading. The current name suggests disabling statistics collection rather than automatically exiting when the game ends.

Additionally, these properties lack JavaDoc comments explaining their purpose and which command-line flags they correspond to.

Apply this diff to rename the property and add documentation:

+  /**
+   * When enabled, the game will automatically exit when the match ends.
+   * Corresponds to the /exitongameover command-line switch.
+   */
-  private final BooleanProperty skipStatistic = new SimpleBooleanProperty(false);
+  private final BooleanProperty exitOnGameOver = new SimpleBooleanProperty(false);
+  /**
+   * When enabled, the game launches without sound.
+   * Corresponds to the /nosound command-line switch.
+   * Useful for working around sound issues.
+   */
   private final BooleanProperty noGameSound = new SimpleBooleanProperty(false);

Note: If you rename this property, ensure all references in GameLaunchLocalParametersResolver, SettingsController, and related files are updated accordingly.

Run the following script to find all references to skipStatistic:


🏁 Script executed:

#!/bin/bash
# Find all references to skipStatistic that need to be renamed
rg -n --type=java 'skipStatistic' -C2

Length of output: 4928


Misleading property name: skipStatistic does not convey "exit on game over" functionality.

The property name is misleading. While it maps to GameLaunchLocalParameter.GAME_OVER and the i18n label correctly states "Immediately exit the game when it is over", the property name skipStatistic suggests disabling statistics collection rather than exiting after game completion. Additionally, the i18n description ("Game statistic will be skipped") contradicts the actual functionality.

Required changes across 4 files:

  • Rename property field and all accessor methods: skipStatisticexitOnGameOver in ForgedAlliancePrefs.java (lines 42, 189-191, 197-199, 205-207)
  • Update UI binding in SettingsController.java (lines 149, 337)
  • Update GameLaunchLocalParametersResolver.java (line 21)
  • Fix i18n keys and descriptions in messages.properties (lines 872-873)
  • Add JavaDoc explaining the property's purpose and command-line switch correspondence
🤖 Prompt for AI Agents
In src/main/java/com/faforever/client/preferences/ForgedAlliancePrefs.java
around lines 42 and 189-191,197-199,205-207 rename the BooleanProperty field and
all its accessors from skipStatistic to exitOnGameOver, add JavaDoc to the
property explaining it controls "Immediately exit the game when it is over" and
that it maps to the GameLaunchLocalParameter.GAME_OVER / corresponding
command-line switch; in
src/main/java/com/faforever/client/ui/SettingsController.java update the UI
binding and references at lines ~149 and ~337 to use exitOnGameOver; in
src/main/java/com/faforever/client/launcher/GameLaunchLocalParametersResolver.java
update the parameter mapping at line 21 to use exitOnGameOver/GAME_OVER; and in
src/main/resources/i18n/messages.properties update the key and description lines
(~872-873) so the label and description reflect "Immediately exit the game when
it is over" (remove/replace "Game statistic will be skipped"). Ensure method
names (getter/setter/property) and usages are consistently renamed project-wide
and update any imports or tests that reference the old name.

</rowConstraints>
<children>
<Label contentDisplay="RIGHT" maxWidth="1.7976931348623157E308"
styleClass="setting-title" text="Game settings" GridPane.rowIndex="0"/>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Replace hardcoded text with i18n key.

Line 703 contains hardcoded text "Game settings" instead of an i18n key. This breaks localization consistency—all other labels in the file use keys like %settings.fa.title.

Add an i18n key to messages.properties:

+settings.fa.gameSettings = Game settings

Then update the FXML:

-                                                       styleClass="setting-title" text="Game settings" GridPane.rowIndex="0"/>
+                                                       styleClass="setting-title" text="%settings.fa.gameSettings" GridPane.rowIndex="0"/>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
styleClass="setting-title" text="Game settings" GridPane.rowIndex="0"/>
styleClass="setting-title" text="%settings.fa.gameSettings" GridPane.rowIndex="0"/>
🤖 Prompt for AI Agents
In src/main/resources/theme/settings/settings.fxml around line 703 the label
uses hardcoded text "Game settings" which breaks localization; replace it with
an i18n key (for example %settings.game.title) by adding that key and its
translation to src/main/resources/messages.properties, then update the FXML to
use the i18n key (replace text="Game settings" with text="%settings.game.title")
so it follows the existing localization pattern.

Copy link
Member

Choose a reason for hiding this comment

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

I don't quite understand the purpose of the game resolver and actively subscribing to the prefs object.

We only need to care what the preferences are when we launch the game right? I would think more that the prefs object would just be an observable set of the enums. Then we can just get that.

Then in the settings controller we can have the add remove logic since it is really more a UI translation rather than embedding that into the preferences.

It also gives a little more flexibility and less proliferation of preferences fields.

You can also inject the ForgedAlliancePrefs directly

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We only need to care what the preferences are when we launch the game right?

That is right, you are right! I wrote the code, which was the first thing that came to mind. My implementation of the code is crap. I will redo it.

Copy link
Member

Choose a reason for hiding this comment

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

I don't think these enums need parameters. In the launcher they could just be in a switch statement and should only be used in one place.

Comment on lines -210 to +219
command.addAll(Arrays.asList(
"/init", ForgedAlliancePrefs.INIT_FILE_NAME,
"/nobugreport"
));

List<GameLaunchLocalParameter> defaultGameParameters = Lists.newArrayList(INIT_FILE, NO_BUG_REPORT);
defaultGameParameters.addAll(gameLaunchLocalParameters != null ? gameLaunchLocalParameters : List.of());
defaultGameParameters.forEach(parameter -> command.addAll(parameter.getCommand()));
Copy link
Member

Choose a reason for hiding this comment

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

Also I wouldn't add always present options to the enums. It should just be optional options.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Add checkboxes to include various command line switches

3 participants