Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ClientCore/Settings/UserINISettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class UserINISettings
public const string CLIENT_LOGS = "ClientLogs";
public const string GAME_LOGS = "GameLogs";
public const string SAVED_GAMES = "SavedGames";
public const string LOCAL_GAME_OPTIONS = "LocalGameOptions";
private const string FAVORITE_MAPS = "FavoriteMaps";

private const bool DEFAULT_SHOW_FRIENDS_ONLY_GAMES = false;
Expand Down
2 changes: 2 additions & 0 deletions DXMainClient/DXGUI/GameClass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,9 @@ private IServiceProvider BuildServiceProvider(WindowManager windowManager)
.AddTransientXnaControl<XNAChatTextBox>()
.AddTransientXnaControl<ChatListBox>()
.AddTransientXnaControl<GameLobbyCheckBox>()
.AddTransientXnaControl<LocalGameLobbyCheckBox>()
.AddTransientXnaControl<GameLobbyDropDown>()
.AddTransientXnaControl<LocalGameLobbyDropDown>()
.AddTransientXnaControl<CampaignCheckBox>()
.AddTransientXnaControl<CampaignDropDown>()
.AddTransientXnaControl<SettingCheckBox>()
Expand Down
61 changes: 56 additions & 5 deletions DXMainClient/DXGUI/Generic/GameSessionCheckBox.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;

using ClientCore;

using ClientGUI;

using DTAClient.Domain.Multiplayer;
Expand Down Expand Up @@ -36,7 +38,7 @@ public class GameSessionCheckBox : XNAClientCheckBox, IGameSessionSetting
{
private const int DEFAULT_SORT_ORDER = 0;

public GameSessionCheckBox(WindowManager windowManager) : base (windowManager) { }
public GameSessionCheckBox(WindowManager windowManager) : base(windowManager) { }

public bool AllowChanges { get; set; } = true;

Expand Down Expand Up @@ -113,10 +115,60 @@ public bool AllowScoring
/// </summary>
public int SortOrder { get; private set; } = DEFAULT_SORT_ORDER;

/// <summary>Whether this checkbox's value is remembered per user in [LocalGameOptions], keyed by its INI section name (<see cref="XNAControl.Name"/>).</summary>
public bool Persistent { get; private set; }

public override void Initialize()
{
base.Initialize();

LoadPersistedValue();
CheckedChanged += (_, _) => PersistValue();
}

public override void GetAttributes(IniFile iniFile)
{
// Campaign windows load attributes after Initialize; lobbies load them before it.
// Restore only after the whole section is read, so Persistent can appear anywhere.
try
{
restoringValue = true;
base.GetAttributes(iniFile);
LoadPersistedValue();
}
finally
{
restoringValue = false;
}
}

/// <summary>Suppresses persistence while INI defaults or stored preferences are being loaded.</summary>
private bool restoringValue;

private void PersistValue()
{
if (restoringValue || !Persistent)
return;

UserINISettings.Instance.SetValue(UserINISettings.LOCAL_GAME_OPTIONS, Name, Checked);
UserINISettings.Instance.SaveSettings();
}

private void LoadPersistedValue()
{
if (!Persistent)
return;

Checked = UserINISettings.Instance.GetValue(UserINISettings.LOCAL_GAME_OPTIONS, Name, Checked);
}

protected override void ParseControlINIAttribute(IniFile iniFile, string key, string value)
{
switch (key)
{
case "Persistent":
Persistent = Conversions.BooleanFromString(value, false);
return;
case "SpawnIniOption":
spawnIniOption = value;
return;
Expand All @@ -133,8 +185,7 @@ protected override void ParseControlINIAttribute(IniFile iniFile, string key, st
reversed = Conversions.BooleanFromString(value, false);
return;
case "Checked":
bool checkedValue = Conversions.BooleanFromString(value, false);
DefaultChecked = Checked = checkedValue;
DefaultChecked = Checked = Conversions.BooleanFromString(value, false);
return;
case "MapScoringMode":
mapScoringMode = (CheckBoxMapScoringMode)Enum.Parse(typeof(CheckBoxMapScoringMode), value);
Expand Down Expand Up @@ -193,7 +244,7 @@ public void ApplySpawnIniCode(IniFile spawnIni)

spawnIni.SetStringValue("Settings", spawnIniOption, value);
}

public void ApplyMapCode(IniFile mapIni, GameMode gameMode)
{
if (!AffectsMapCode || Checked == reversed)
Expand All @@ -207,7 +258,7 @@ public override void OnLeftClick(InputEventArgs inputEventArgs)
// FIXME there's a discrepancy with how base XNAUI handles this
// it doesn't set handled if changing the setting is not allowed
inputEventArgs.Handled = true;

if (!AllowChanges)
return;

Expand Down
57 changes: 56 additions & 1 deletion DXMainClient/DXGUI/Generic/GameSessionDropDown.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;

using ClientCore;
using ClientCore.Extensions;
using ClientCore.I18N;

Expand Down Expand Up @@ -79,6 +80,57 @@ public GameSessionDropDown(WindowManager windowManager) : base(windowManager) {
/// </summary>
public int SortOrder { get; private set; } = DEFAULT_SORT_ORDER;

/// <summary>Whether this dropdown's value is remembered per user in [LocalGameOptions], keyed by its INI section name (<see cref="XNAControl.Name"/>).</summary>
public bool Persistent { get; private set; }

public override void Initialize()
{
base.Initialize();

LoadPersistedValue();
SelectedIndexChanged += (_, _) => PersistValue();
}

public override void GetAttributes(IniFile iniFile)
{
// Campaign windows load attributes after Initialize; lobbies load them before it.
// Restore only after the whole section is read, so Persistent can appear anywhere.
try
{
restoringValue = true;
base.GetAttributes(iniFile);
LoadPersistedValue();
}
finally
{
restoringValue = false;
}
}

/// <summary>Suppresses persistence while INI defaults or stored preferences are being loaded.</summary>
private bool restoringValue;

private void PersistValue()
{
if (restoringValue || !Persistent)
return;

UserINISettings.Instance.SetValue(UserINISettings.LOCAL_GAME_OPTIONS, Name, SelectedIndex);
UserINISettings.Instance.SaveSettings();
}

private void LoadPersistedValue()
{
if (!Persistent)
return;

int storedIndex = UserINISettings.Instance.GetValue(UserINISettings.LOCAL_GAME_OPTIONS, Name, SelectedIndex);

// A package update may have removed the remembered item. Keep the INI default in that case.
if (storedIndex >= 0 && storedIndex < Items.Count)
SelectedIndex = storedIndex;
}

protected override void ParseControlINIAttribute(IniFile iniFile, string key, string value)
{
// shorthand for localization function
Expand Down Expand Up @@ -149,6 +201,9 @@ static string Localize(XNAControl control, string attributeName, string defaultV
case "SortOrder":
SortOrder = int.Parse(value);
return;
case "Persistent":
Persistent = Conversions.BooleanFromString(value, false);
return;
}

base.ParseControlINIAttribute(iniFile, key, value);
Expand Down Expand Up @@ -201,7 +256,7 @@ public override void OnLeftClick(InputEventArgs inputEventArgs)
// FIXME there's a discrepancy with how base XNAUI handles this
// it doesn't set handled if changing the setting is not allowed
inputEventArgs.Handled = true;

if (!AllowDropDown)
return;

Expand Down
12 changes: 12 additions & 0 deletions DXMainClient/DXGUI/Multiplayer/GameLobby/GameLobbyBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@
public List<GameLobbyCheckBox> CheckBoxes { get; } = new();
public List<GameLobbyDropDown> DropDowns { get; } = new();

/// <summary>Lobby options that are not broadcast to other players.</summary>
public List<LocalGameLobbyCheckBox> LocalCheckBoxes { get; } = new();

/// <summary>Lobby options that are not broadcast to other players.</summary>
public List<LocalGameLobbyDropDown> LocalDropDowns { get; } = new();

public List<IGameSessionSetting> GetBroadcastableSettings()
{
var result = new List<IGameSessionSetting>();
Expand Down Expand Up @@ -1056,7 +1062,7 @@
ddGameModeMapFilter.SelectedIndex = gameModeMapFilterIndex;
}

protected void AddSideToDropDown(XNADropDown dd, string name, string? uiName = null, Texture2D? texture = null)

Check warning on line 1065 in DXMainClient/DXGUI/Multiplayer/GameLobby/GameLobbyBase.cs

View workflow job for this annotation

GitHub Actions / build-clients

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 1065 in DXMainClient/DXGUI/Multiplayer/GameLobby/GameLobbyBase.cs

View workflow job for this annotation

GitHub Actions / build-clients

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 1065 in DXMainClient/DXGUI/Multiplayer/GameLobby/GameLobbyBase.cs

View workflow job for this annotation

GitHub Actions / build-clients

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
XNADropDownItem item = new()
{
Expand Down Expand Up @@ -1717,9 +1723,15 @@
foreach (GameLobbyCheckBox chkBox in CheckBoxes)
chkBox.ApplySpawnIniCode(spawnIni);

foreach (LocalGameLobbyCheckBox chkBox in LocalCheckBoxes)
chkBox.ApplySpawnIniCode(spawnIni);

foreach (GameLobbyDropDown dd in DropDowns)
dd.ApplySpawnIniCode(spawnIni);

foreach (LocalGameLobbyDropDown dd in LocalDropDowns)
dd.ApplySpawnIniCode(spawnIni);

// Apply forced options from GameOptions.ini

List<string> forcedKeys = GameOptionsIni.GetSectionKeys("ForcedSpawnIniOptions");
Expand Down
32 changes: 32 additions & 0 deletions DXMainClient/DXGUI/Multiplayer/GameLobby/LocalGameLobbyCheckBox.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#nullable enable

using DTAClient.DXGUI.Generic;

using Rampastring.XNAUI;
using Rampastring.XNAUI.XNAControls;

namespace DTAClient.DXGUI.Multiplayer.GameLobby;

/// <summary>A persisted lobby option that is not broadcast to other players.</summary>
public class LocalGameLobbyCheckBox : GameSessionCheckBox
{
public LocalGameLobbyCheckBox(WindowManager windowManager) : base(windowManager) { }

public override void Initialize()
{
// Register separately from broadcast game options.
XNAControl parent = Parent;
while (parent != null)
{
if (parent is GameLobbyBase gameLobby)
{
gameLobby.LocalCheckBoxes.Add(this);
break;
}

parent = parent.Parent;
}

base.Initialize();
}
}
32 changes: 32 additions & 0 deletions DXMainClient/DXGUI/Multiplayer/GameLobby/LocalGameLobbyDropDown.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#nullable enable

using DTAClient.DXGUI.Generic;

using Rampastring.XNAUI;
using Rampastring.XNAUI.XNAControls;

namespace DTAClient.DXGUI.Multiplayer.GameLobby;

/// <summary>A persisted lobby option that is not broadcast to other players.</summary>
public class LocalGameLobbyDropDown : GameSessionDropDown
{
public LocalGameLobbyDropDown(WindowManager windowManager) : base(windowManager) { }

public override void Initialize()
{
// Register separately from broadcast game options.
XNAControl parent = Parent;
while (parent != null)
{
if (parent is GameLobbyBase gameLobby)
{
gameLobby.LocalDropDowns.Add(this);
break;
}

parent = parent.Parent;
}

base.Initialize();
}
}
31 changes: 31 additions & 0 deletions Docs/INISystem.md
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,10 @@ EnabledIcon= ; string, texture name for the icon
DisabledIcon= ; string, texture name for the icon when setting is disabled.
SortOrder=0 ; integer, display order for icons in GameInformationPanel and GameListBox.
; Lower values appear first.
Persistent=false ; boolean, remember this checkbox's value per user in the `[LocalGameOptions]`
; section of the user's settings INI, keyed by the checkbox's own
; section name (`SOMEGAMESESSIONCHECKBOX` above). Without it, `Checked`
; applies on every client start.
```

##### [CampaignCheckBox](https://github.com/CnCNet/xna-cncnet-client/blob/develop/DXMainClient/DXGUI/Campaign/CampaignCheckBox.cs)
Expand All @@ -418,6 +422,23 @@ _(inherits [GameSessionCheckBox](#GameSessionCheckBox))_

Use this control type for game lobby checkboxes in `GameLobbyBase.ini`. Inherits all properties from `GameSessionCheckBox`.

##### [LocalGameLobbyCheckBox](https://github.com/CnCNet/xna-cncnet-client/blob/develop/DXMainClient/DXGUI/Multiplayer/GameLobby/LocalGameLobbyCheckBox.cs)

_(inherits [GameSessionCheckBox](#GameSessionCheckBox))_

Use this control type for game lobby checkboxes in `GameLobbyBase.ini` that only affect the local player, such as replay recording. Unlike `GameLobbyCheckBox` it isn't sent in game option messages, so each player (including non-hosts) sets it for themselves and `BroadcastToLobby`/the game list properties don't apply. Still written to `spawn.ini` via `SpawnIniOption`, usually paired with `Persistent=true` to remember the player's choice.

For example, a "Record replay" checkbox that every player sets for themselves and that is remembered between client sessions:

```ini
[chkRecordReplay] ; LocalGameLobbyCheckBox, in GameLobbyBase.ini
SpawnIniOption=EnableReplayRecording ; written to spawn.ini as EnableReplayRecording=True/False for the game engine to read
Persistent=true ; remembered under [LocalGameOptions] -> chkRecordReplay=True/False in the user's settings INI
Checked=true ; default the first time the client runs
```

`Persistent` has no separate key name to configure - it always uses the control's own INI section name (`chkRecordReplay` here) as the storage key in `[LocalGameOptions]`. That keeps the key unique automatically (section names are already unique within an INI file) and avoids modders having to invent and keep a second name in sync.

##### [GameSessionDropDown](https://github.com/CnCNet/xna-cncnet-client/blob/develop/DXMainClient/DXGUI/Generic/GameSessionDropDown.cs)

_(inherits [XNAClientDropDown](#XNAClientDropDown))_
Expand Down Expand Up @@ -449,6 +470,10 @@ Icons= ; comma-separated strings,
; number of items.
SortOrder=0 ; integer, display order for icons in GameInformationPanel and GameListBox.
; Lower values appear first.
Persistent=false ; boolean, remember this dropdown's selected item per user in the
; `[LocalGameOptions]` section of the user's settings INI, keyed by
; the dropdown's own section name. Without it, `DefaultIndex` applies
; on every client start.
```

##### [CampaignDropDown](https://github.com/CnCNet/xna-cncnet-client/blob/develop/DXMainClient/DXGUI/Campaign/CampaignDropDown.cs)
Expand All @@ -463,6 +488,12 @@ _(inherits [GameSessionDropDown](#GameSessionDropDown))_

Use this control type for game lobby dropdowns in `GameLobbyBase.ini`. Inherits all properties from `GameSessionDropDown`.

##### [LocalGameLobbyDropDown](https://github.com/CnCNet/xna-cncnet-client/blob/develop/DXMainClient/DXGUI/Multiplayer/GameLobby/LocalGameLobbyDropDown.cs)

_(inherits [GameSessionDropDown](#GameSessionDropDown))_

Dropdown counterpart of [LocalGameLobbyCheckBox](#LocalGameLobbyCheckBox); the same rules apply.

#### XNAOptionsPanel Controls

Following controls are only available as children of `XNAOptionsPanel` and derived controls. These currently use basic control properties only.
Expand Down
Loading