Skip to content

Commit 2079ea8

Browse files
committed
Implement proper difficulty dropdown for co-op gamemodes
1 parent 3cf078b commit 2079ea8

14 files changed

Lines changed: 224 additions & 20 deletions

ClientCore/ClientConfiguration.cs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ public void RefreshSettings()
264264

265265
public string[] TrustedDomains => clientDefinitionsIni.GetStringListValue(SETTINGS, "TrustedDomains", string.Empty);
266266

267-
public string[] AlwaysTrustedDomains = {"cncnet.org", "gamesurge.net", "dronebl.org", "discord.com", "discord.gg", "youtube.com", "youtu.be"};
267+
public string[] AlwaysTrustedDomains = { "cncnet.org", "gamesurge.net", "dronebl.org", "discord.com", "discord.gg", "youtube.com", "youtu.be" };
268268

269269
public (string Name, string Path) GetThemeInfoFromIndex(int themeIndex) => clientDefinitionsIni.GetStringValue("Themes", themeIndex.ToString(), ",").Split(',').AsTuple2();
270270

@@ -287,6 +287,30 @@ public string GetThemePath(string themeName)
287287
return null;
288288
}
289289

290+
private string[] GetCoopDifficultyINIPaths()
291+
{
292+
List<string> paths = new List<string>();
293+
var pathString = clientDefinitionsIni.GetStringValue(SETTINGS, nameof(CoopDifficultyINIPaths), string.Empty);
294+
295+
if (string.IsNullOrEmpty(pathString))
296+
{
297+
paths.Add(SafePath.CombineFilePath("INI", "Map Code", "Coop Difficulty Easy.ini"));
298+
paths.Add(SafePath.CombineFilePath("INI", "Map Code", "Coop Difficulty Medium.ini"));
299+
paths.Add(SafePath.CombineFilePath("INI", "Map Code", "Coop Difficulty Hard.ini"));
300+
}
301+
else
302+
{
303+
var pathStrings = pathString.Split(',', StringSplitOptions.RemoveEmptyEntries);
304+
305+
foreach (var path in pathStrings)
306+
{
307+
paths.Add(SafePath.CombineFilePath(path));
308+
}
309+
}
310+
311+
return paths.ToArray();
312+
}
313+
290314
public string SettingsIniName => clientDefinitionsIni.GetStringValue(SETTINGS, "SettingsFile", "Settings.ini");
291315

292316
public string TranslationIniName => clientDefinitionsIni.GetStringValue(TRANSLATIONS, nameof(TranslationIniName), "Translation.ini");
@@ -348,6 +372,8 @@ private List<TranslationGameFile> ParseTranslationGameFiles()
348372

349373
public string MPMapsIniPath => SafePath.CombineFilePath(clientDefinitionsIni.GetStringValue(SETTINGS, "MPMapsPath", SafePath.CombineFilePath("INI", "MPMaps.ini")));
350374

375+
public string[] CoopDifficultyINIPaths => GetCoopDifficultyINIPaths();
376+
351377
public string KeyboardINI => clientDefinitionsIni.GetStringValue(SETTINGS, "KeyboardINI", "Keyboard.ini");
352378

353379
public int MinimumIngameWidth => clientDefinitionsIni.GetIntValue(SETTINGS, "MinimumIngameWidth", 640);

ClientCore/ProgramConstants.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ public static class ProgramConstants
2626

2727
public const string QRES_EXECUTABLE = "qres.dat";
2828

29-
public const string CNCNET_PROTOCOL_REVISION = "R13";
30-
public const string LAN_PROTOCOL_REVISION = "RL8";
29+
public const string CNCNET_PROTOCOL_REVISION = "R14";
30+
public const string LAN_PROTOCOL_REVISION = "RL9";
3131
public const int LAN_PORT = 1234;
3232
public const int LAN_INGAME_PORT = 1234;
3333
public const int LAN_LOBBY_PORT = 1232;
@@ -112,6 +112,7 @@ public static string GetAILevelName(int aiLevel)
112112

113113
// Static fields might be initialized before the translation file is loaded. Change to readonly properties here.
114114
public static List<string> AI_PLAYER_NAMES => new List<string> { "Easy AI".L10N("Client:Main:EasyAIName"), "Medium AI".L10N("Client:Main:MediumAIName"), "Hard AI".L10N("Client:Main:HardAIName") };
115+
public static List<string> DIFFICULTY_NAMES => new List<string> { "Easy".L10N("Client:Main:DifficultyEasy"), "Medium".L10N("Client:Main:DifficultyMedium"), "Hard".L10N("Client:Main:DifficultyHard") };
115116

116117
public static string LogFileName { get; set; }
117118

DXMainClient/DXGUI/Campaign/CampaignSelector.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ public class CampaignSelector : XNAWindow
2828

2929
private const string SETTINGS_PATH = "Client/CampaignSettings.ini";
3030

31-
private static string[] DifficultyNames = new string[] { "Easy", "Medium", "Hard" };
32-
3331
private static string[] DifficultyIniPaths = new string[]
3432
{
3533
"INI/Map Code/Difficulty Easy.ini",
@@ -364,7 +362,7 @@ private void LaunchMission(Mission mission)
364362
spawnIni.WriteIniFile();
365363

366364
var difficultyIni = new IniFile(SafePath.CombineFilePath(ProgramConstants.GamePath, DifficultyIniPaths[trbDifficultySelector.Value]));
367-
string difficultyName = DifficultyNames[trbDifficultySelector.Value];
365+
string difficultyName = ProgramConstants.DIFFICULTY_NAMES[trbDifficultySelector.Value];
368366

369367
if (copyMapsToSpawnmapINI)
370368
{

DXMainClient/DXGUI/Multiplayer/CnCNet/CnCNetLobby.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1504,7 +1504,7 @@ private void GameBroadcastChannel_CTCPReceived(object sender, ChannelCTCPEventAr
15041504
string msg = e.Message.Substring(5); // Cut out GAME part
15051505
string[] splitMessage = msg.Split(new char[] { ';' });
15061506

1507-
if (splitMessage.Length != 13)
1507+
if (splitMessage.Length != 14)
15081508
{
15091509
Logger.Log("Ignoring CTCP game message because of an invalid amount of parameters.");
15101510

@@ -1550,6 +1550,8 @@ private void GameBroadcastChannel_CTCPReceived(object sender, ChannelCTCPEventAr
15501550
string loadedGameId = splitMessage[10];
15511551
int skillLevel = int.Parse(splitMessage[11]);
15521552
string mapHash = splitMessage[12];
1553+
int difficulty = int.Parse(splitMessage[13]);
1554+
15531555

15541556
CnCNetGame cncnetGame = gameCollection.GameList.Find(g => g.GameBroadcastChannel == channel.ChannelName);
15551557

@@ -1611,6 +1613,7 @@ private void GameBroadcastChannel_CTCPReceived(object sender, ChannelCTCPEventAr
16111613
game.Incompatible = cncnetGame == localGame && game.GameVersion != ProgramConstants.GAME_VERSION;
16121614
game.TunnelServer = tunnel;
16131615
game.SkillLevel = skillLevel;
1616+
game.Difficulty = difficulty;
16141617

16151618
if (isClosed)
16161619
{

DXMainClient/DXGUI/Multiplayer/GameInformationPanel.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,9 @@ public void SetInfo(GenericHostedGame game)
164164
string translatedGameModeName = string.IsNullOrEmpty(game.GameMode)
165165
? "Unknown".L10N("Client:Main:Unknown") : game.GameMode.L10N($"INI:GameModes:{game.GameMode}:UIName", notify: false);
166166

167+
if (game.Difficulty >= 0 && game.Difficulty < ProgramConstants.DIFFICULTY_NAMES.Count)
168+
translatedGameModeName += $" ({ProgramConstants.DIFFICULTY_NAMES[game.Difficulty]})";
169+
167170
lblGameMode.Text = Renderer.GetStringWithLimitedWidth("Game mode:".L10N("Client:Main:GameInfoGameMode") + " " + Renderer.GetSafeString(translatedGameModeName, lblGameMode.FontIndex),
168171
lblGameMode.FontIndex, Width - lblGameMode.X);
169172
lblGameMode.Visible = true;

DXMainClient/DXGUI/Multiplayer/GameLobby/CnCNetGameLobby.cs

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,29 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Xml.Linq;
7+
18
using ClientCore;
9+
using ClientCore.Extensions;
10+
211
using ClientGUI;
3-
using DTAClient.Domain.Multiplayer;
12+
413
using DTAClient.Domain;
14+
using DTAClient.Domain.Multiplayer;
15+
using DTAClient.Domain.Multiplayer.CnCNet;
516
using DTAClient.DXGUI.Generic;
617
using DTAClient.DXGUI.Multiplayer.CnCNet;
718
using DTAClient.DXGUI.Multiplayer.GameLobby.CommandHandlers;
819
using DTAClient.Online;
920
using DTAClient.Online.EventArguments;
21+
1022
using Microsoft.Xna.Framework;
23+
1124
using Rampastring.Tools;
1225
using Rampastring.XNAUI;
1326
using Rampastring.XNAUI.XNAControls;
14-
using System;
15-
using System.Collections.Generic;
16-
using System.IO;
17-
using System.Linq;
18-
using System.Text;
19-
using DTAClient.Domain.Multiplayer.CnCNet;
20-
using ClientCore.Extensions;
2127

2228
namespace DTAClient.DXGUI.Multiplayer.GameLobby
2329
{
@@ -1049,6 +1055,7 @@ protected override void OnGameOptionChanged()
10491055
sb.Append(RandomSeed);
10501056
sb.Append(Convert.ToInt32(RemoveStartingLocations));
10511057
sb.Append(Map?.UntranslatedName ?? string.Empty);
1058+
sb.Append(ddDifficulty?.SelectedIndex ?? -1);
10521059

10531060
channel.SendCTCPMessage(sb.ToString(), QueuedMessageType.GAME_SETTINGS_MESSAGE, 11);
10541061
}
@@ -1223,6 +1230,15 @@ private void ApplyGameOptions(string sender, string message)
12231230
Convert.ToInt32(RemoveStartingLocations)));
12241231
SetRandomStartingLocations(removeStartingLocations);
12251232

1233+
if (GameMode != null && GameMode.UseDifficultyDropDown && ddDifficulty != null)
1234+
{
1235+
int newIndex = Conversions.IntFromString(parts[partIndex + 9], ddDifficulty.SelectedIndex);
1236+
ddDifficulty.SelectedIndex = newIndex;
1237+
1238+
if (ddDifficulty.SelectedIndex >= 0)
1239+
AddNotice(string.Format("The game host has set {0} to {1}".L10N("Client:Main:HostSetOption"), "Difficulty".L10N("Client:Main:DifficultyOptionName"), ddDifficulty.SelectedItem.Text));
1240+
}
1241+
12261242
RandomSeed = randomSeed;
12271243
}
12281244

@@ -2086,10 +2102,12 @@ private void BroadcastGame()
20862102
sb.Append(skillLevel); // SkillLevel
20872103
sb.Append(";");
20882104
sb.Append(Map?.SHA1);
2105+
sb.Append(ddDifficulty?.SelectedIndex ?? -1);
20892106

20902107
broadcastChannel.SendCTCPMessage(sb.ToString(), QueuedMessageType.SYSTEM_MESSAGE, 20);
20912108
}
20922109

2110+
20932111
#endregion
20942112

20952113
public override string GetSwitchName() => "Game Lobby".L10N("Client:Main:GameLobby");

DXMainClient/DXGUI/Multiplayer/GameLobby/GameLobbyBase.cs

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,8 @@ protected GameModeMap GameModeMap
146146
protected ToolTip mapListTooltip;
147147
protected XNAClientDropDown ddGameModeMapFilter;
148148
protected XNALabel lblGameModeSelect;
149+
protected XNAClientDropDown ddDifficulty;
150+
protected XNALabel lblDifficulty;
149151
protected XNAContextMenu mapContextMenu;
150152
private XNAContextMenuItem toggleFavoriteItem;
151153

@@ -173,6 +175,7 @@ protected GameModeMap GameModeMap
173175
protected int UniqueGameID { get; set; }
174176
protected int SideCount { get; private set; }
175177
protected int RandomSelectorCount { get; private set; } = 1;
178+
protected int PreviousSelectedDifficulty { get; set; }
176179

177180
protected List<int[]> RandomSelectors = new List<int[]>();
178181

@@ -299,6 +302,21 @@ public override void Initialize()
299302

300303
lblGameModeSelect = FindChild<XNALabel>(nameof(lblGameModeSelect));
301304

305+
ddDifficulty = FindChild<XNAClientDropDown>(nameof(ddDifficulty), true);
306+
lblDifficulty = FindChild<XNALabel>(nameof(lblDifficulty), true);
307+
308+
if (ddDifficulty != null)
309+
{
310+
// Hardcode difficulty items just in case someone tries something stupid.
311+
ddDifficulty.Items.Clear();
312+
313+
foreach (string difficultyName in ProgramConstants.DIFFICULTY_NAMES)
314+
ddDifficulty.AddItem(difficultyName);
315+
316+
ddDifficulty.SelectedIndex = -1;
317+
ddDifficulty.SelectedIndexChanged += DdDifficulty_SelectedIndexChanged;
318+
}
319+
302320
InitBtnMapSort();
303321

304322
tbMapSearch = FindChild<XNASuggestionTextBox>(nameof(tbMapSearch));
@@ -568,9 +586,23 @@ protected void DdGameModeMapFilter_SelectedIndexChanged(object sender, EventArgs
568586
ListMaps();
569587

570588
if (lbGameModeMapList.SelectedIndex == -1)
589+
{
571590
lbGameModeMapList.SelectedIndex = 0; // Select default GameModeMap
591+
UpdateDifficultyDropdown();
592+
}
572593
else
594+
{
573595
ChangeMap(GameModeMap);
596+
}
597+
}
598+
599+
protected void DdDifficulty_SelectedIndexChanged(object sender, EventArgs e)
600+
{
601+
if (ddDifficulty.SelectedIndex >= 0)
602+
PreviousSelectedDifficulty = ddDifficulty.SelectedIndex;
603+
604+
OnGameOptionChanged();
605+
SetMapLabels();
574606
}
575607

576608
protected void BtnPlayerExtraOptions_LeftClick(object sender, EventArgs e)
@@ -694,7 +726,7 @@ protected void ListMaps()
694726
{
695727
// Note: StatisticsManager.Statistics must be initialized to call `HasBeatCoOpMap()`. This means StatisticsWindow must be initialized before any lobbies extending GameLobbyBase.
696728
if (StatisticsManager.Instance.HasBeatCoOpMap(gameModeMap.Map.UntranslatedName, gameModeMap.GameMode.UntranslatedUIName))
697-
rankItem.Texture = RankTextures[Math.Abs(2 - gameModeMap.GameMode.CoopDifficultyLevel) + 1];
729+
rankItem.Texture = RankTextures[Math.Abs(2 - GetCoopDifficultyLevel()) + 1];
698730
else
699731
rankItem.Texture = RankTextures[0];
700732
}
@@ -747,6 +779,45 @@ protected void ListMaps()
747779
LbGameModeMapList_SelectedIndexChanged();
748780
}
749781

782+
protected int GetCoopDifficultyLevel()
783+
{
784+
var gameMode = GameModeMap?.GameMode;
785+
786+
if (gameMode != null)
787+
{
788+
if (gameMode.UseDifficultyDropDown && ddDifficulty != null && ddDifficulty.SelectedIndex >= 0)
789+
{
790+
return ddDifficulty.Items.Count - ddDifficulty.SelectedIndex - 1;
791+
}
792+
else
793+
{
794+
return gameMode.CoopDifficultyLevel;
795+
}
796+
}
797+
798+
return 0;
799+
}
800+
801+
protected virtual void UpdateDifficultyDropdown()
802+
{
803+
bool enabled = GameModeMap != null && GameModeMap.GameMode != null && GameModeMap.GameMode.UseDifficultyDropDown;
804+
805+
if (ddDifficulty != null)
806+
{
807+
ddDifficulty.AllowDropDown = enabled;
808+
809+
if (enabled && ddDifficulty.SelectedIndex < 0)
810+
ddDifficulty.SelectedIndex = PreviousSelectedDifficulty;
811+
else if (!enabled)
812+
ddDifficulty.SelectedIndex = -1;
813+
}
814+
815+
if (lblDifficulty != null)
816+
{
817+
lblDifficulty.TextColor = enabled ? UISettings.ActiveSettings.TextColor : UISettings.ActiveSettings.DisabledItemColor;
818+
}
819+
}
820+
750821
protected abstract int GetDefaultMapRankIndex(GameModeMap gameModeMap);
751822

752823
private void LbGameModeMapList_RightClick(object sender, EventArgs e)
@@ -1587,7 +1658,7 @@ private PlayerHouseInfo[] WriteSpawnIni()
15871658

15881659
GameMode.ApplySpawnIniCode(spawnIni); // Forced options from the game mode
15891660
Map.ApplySpawnIniCode(spawnIni, Players.Count + AIPlayers.Count,
1590-
AIPlayers.Count, GameMode.CoopDifficultyLevel); // Forced options from the map
1661+
AIPlayers.Count, GetCoopDifficultyLevel()); // Forced options from the map
15911662

15921663
// Player options
15931664

@@ -1814,6 +1885,23 @@ private void WriteMap(PlayerHouseInfo[] houseInfos)
18141885
mapIni.SetStringValue("Basic", "OriginalFilename", mapIniFileName);
18151886
}
18161887

1888+
if (GameMode.UseDifficultyDropDown && ddDifficulty != null && ddDifficulty.SelectedIndex >= 0)
1889+
{
1890+
var filePath = string.Empty;
1891+
1892+
try
1893+
{
1894+
filePath = ClientConfiguration.Instance.CoopDifficultyINIPaths[ddDifficulty.SelectedIndex];
1895+
}
1896+
catch (IndexOutOfRangeException)
1897+
{
1898+
Logger.Log($"CoopDifficultyINIPaths does not contain file path for difficulty index {ddDifficulty.SelectedIndex}");
1899+
}
1900+
1901+
IniFile difficultyIni = new IniFile(filePath);
1902+
MapCodeHelper.ApplyMapCode(mapIni, difficultyIni);
1903+
}
1904+
18171905
foreach (GameLobbyCheckBox checkBox in CheckBoxes)
18181906
checkBox.ApplyMapCode(mapIni, GameMode);
18191907

@@ -2315,6 +2403,11 @@ protected virtual void SetMapLabels()
23152403
lblMapAuthor.Text = "By".L10N("Client:Main:AuthorBy") + " " + Renderer.GetSafeString(Map.Author, lblMapAuthor.FontIndex);
23162404
lblGameMode.Text = "Game mode:".L10N("Client:Main:GameModeLabel") + " " + GameMode.UIName;
23172405
lblMapSize.Text = "Size:".L10N("Client:Main:MapSize") + " " + Map.GetSizeString();
2406+
2407+
if (GameMode.UseDifficultyDropDown && ddDifficulty != null && ddDifficulty.SelectedIndex >= 0)
2408+
{
2409+
lblGameMode.Text += " (" + "Difficulty: ".L10N("Client:Main:DifficultyLabel") + ddDifficulty.SelectedItem.Text + ")";
2410+
}
23182411
}
23192412

23202413
/// <summary>
@@ -2333,6 +2426,7 @@ protected virtual void ChangeMap(GameModeMap gameModeMap)
23332426
{
23342427
MapPreviewBox.GameModeMap = null;
23352428
OnGameOptionChanged();
2429+
UpdateDifficultyDropdown();
23362430
return;
23372431
}
23382432

@@ -2345,6 +2439,8 @@ protected virtual void ChangeMap(GameModeMap gameModeMap)
23452439
foreach (var checkBox in CheckBoxes)
23462440
checkBox.AllowChecking = true;
23472441

2442+
UpdateDifficultyDropdown();
2443+
23482444
// We could either pass the CheckBoxes and DropDowns of this class
23492445
// to the Map and GameMode instances and let them apply their forced
23502446
// options, or we could do it in this class with helper functions.

0 commit comments

Comments
 (0)