Skip to content

Commit e62a850

Browse files
authored
Merge pull request #562 from CnCNet/develop
Release 2.11.1.0
2 parents fb64c5d + b77fbb4 commit e62a850

File tree

20 files changed

+382
-242
lines changed

20 files changed

+382
-242
lines changed

ClientCore/I18N/Translation.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ public static string GetLanguageName(string localeCode)
191191
/// <returns>Locale code -> display name pairs.</returns>
192192
public static Dictionary<string, string> GetTranslations()
193193
{
194-
var translations = new Dictionary<string, string>
194+
var translations = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase)
195195
{
196196
// Add default localization so that we always have it in the list even if the localization does not exist
197197
[ProgramConstants.HARDCODED_LOCALE_CODE] = GetLanguageName(ProgramConstants.HARDCODED_LOCALE_CODE)
@@ -224,6 +224,7 @@ public static string GetDefaultTranslationLocaleCode()
224224
{
225225
string translation = culture.Name;
226226

227+
// the keys in 'translations' are case-insensitive
227228
if (translations.ContainsKey(translation))
228229
return translation;
229230
}

ClientCore/ProcessLauncher.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ namespace ClientCore
44
{
55
public static class ProcessLauncher
66
{
7-
public static void StartShellProcess(string commandLine)
7+
public static void StartShellProcess(string commandLine, string arguments = null)
88
{
99
using var _ = Process.Start(new ProcessStartInfo
1010
{
1111
FileName = commandLine,
12+
Arguments = arguments,
1213
UseShellExecute = true
1314
});
1415
}
1516
}
16-
}
17+
}

ClientGUI/XNALinkButton.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public XNALinkButton(WindowManager windowManager) : base(windowManager) { }
1111

1212
public string URL { get; set; }
1313
public string UnixURL { get; set; }
14+
public string Arguments { get; set; }
1415

1516
protected override void ParseControlINIAttribute(IniFile iniFile, string key, string value)
1617
{
@@ -26,6 +27,12 @@ protected override void ParseControlINIAttribute(IniFile iniFile, string key, st
2627
return;
2728
}
2829

30+
if (key == "Arguments")
31+
{
32+
Arguments = value;
33+
return;
34+
}
35+
2936
base.ParseControlINIAttribute(iniFile, key, value);
3037
}
3138

@@ -34,11 +41,11 @@ public override void OnLeftClick()
3441
OSVersion osVersion = ClientConfiguration.Instance.GetOperatingSystemVersion();
3542

3643
if (osVersion == OSVersion.UNIX && !string.IsNullOrEmpty(UnixURL))
37-
ProcessLauncher.StartShellProcess(UnixURL);
44+
ProcessLauncher.StartShellProcess(UnixURL, Arguments);
3845
else if (!string.IsNullOrEmpty(URL))
39-
ProcessLauncher.StartShellProcess(URL);
46+
ProcessLauncher.StartShellProcess(URL, Arguments);
4047

4148
base.OnLeftClick();
4249
}
4350
}
44-
}
51+
}

DTAConfig/OptionPanels/DisplayOptionsPanel.cs

Lines changed: 70 additions & 170 deletions
Large diffs are not rendered by default.

DTAConfig/ScreenResolution.cs

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
5+
using Microsoft.Xna.Framework.Graphics;
6+
7+
namespace DTAConfig
8+
{
9+
/// <summary>
10+
/// A single screen resolution.
11+
/// </summary>
12+
public sealed record ScreenResolution : IComparable<ScreenResolution>
13+
{
14+
15+
/// <summary>
16+
/// The width of the resolution in pixels.
17+
/// </summary>
18+
public int Width { get; }
19+
20+
/// <summary>
21+
/// The height of the resolution in pixels.
22+
/// </summary>
23+
public int Height { get; }
24+
25+
public ScreenResolution(int width, int height)
26+
{
27+
Width = width;
28+
Height = height;
29+
}
30+
31+
public ScreenResolution(string resolution)
32+
{
33+
List<int> resolutionList = resolution.Trim().Split('x').Take(2).Select(int.Parse).ToList();
34+
Width = resolutionList[0];
35+
Height = resolutionList[1];
36+
}
37+
38+
public static implicit operator ScreenResolution(string resolution) => new(resolution);
39+
40+
public sealed override string ToString() => Width + "x" + Height;
41+
42+
public static implicit operator string(ScreenResolution resolution) => resolution.ToString();
43+
44+
public void Deconstruct(out int width, out int height)
45+
{
46+
width = this.Width;
47+
height = this.Height;
48+
}
49+
50+
public static implicit operator ScreenResolution((int Width, int Height) resolutionTuple) => new(resolutionTuple.Width, resolutionTuple.Height);
51+
52+
public static implicit operator (int Width, int Height)(ScreenResolution resolution) => new(resolution.Width, resolution.Height);
53+
54+
public bool Fit(ScreenResolution child) => this.Width >= child.Width && this.Height >= child.Height;
55+
56+
public int CompareTo(ScreenResolution other) => (this.Width, this.Height).CompareTo(other);
57+
58+
// Accessing GraphicsAdapter.DefaultAdapter requiring DXMainClient.GameClass has been constructed. Lazy loading prevents possible null reference issues for now.
59+
private static ScreenResolution _desktopResolution = null;
60+
public static ScreenResolution DesktopResolution =>
61+
_desktopResolution ??= new(GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width, GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height);
62+
63+
// The default graphic profile supports resolution up to 4096x4096. The number gets even smaller in practice. Therefore, we select 3840 as the limit.
64+
public static ScreenResolution HiDefLimitResolution { get; } = "3840x3840";
65+
66+
private static ScreenResolution _safeMaximumResolution = null;
67+
public static ScreenResolution SafeMaximumResolution
68+
{
69+
get
70+
{
71+
#if XNA
72+
return _safeMaximumResolution ??= HiDefLimitResolution.Fit(DesktopResolution) ? DesktopResolution : HiDefLimitResolution;
73+
#else
74+
return _safeMaximumResolution ??= DesktopResolution;
75+
#endif
76+
}
77+
}
78+
79+
private static ScreenResolution _safeFullScreenResolution = null;
80+
public static ScreenResolution SafeFullScreenResolution => _safeFullScreenResolution ??= GetFullScreenResolutions(minWidth: 800, minHeight: 600).Max();
81+
82+
public static SortedSet<ScreenResolution> GetFullScreenResolutions(int minWidth, int minHeight) =>
83+
GetFullScreenResolutions(minWidth, minHeight, SafeMaximumResolution.Width, SafeMaximumResolution.Height);
84+
public static SortedSet<ScreenResolution> GetFullScreenResolutions(int minWidth, int minHeight, int maxWidth, int maxHeight)
85+
{
86+
SortedSet<ScreenResolution> screenResolutions = [];
87+
88+
foreach (DisplayMode dm in GraphicsAdapter.DefaultAdapter.SupportedDisplayModes)
89+
{
90+
if (dm.Width < minWidth || dm.Height < minHeight || dm.Width > maxWidth || dm.Height > maxHeight)
91+
continue;
92+
93+
var resolution = new ScreenResolution(dm.Width, dm.Height);
94+
95+
// SupportedDisplayModes can include the same resolution multiple times
96+
// because it takes the refresh rate into consideration.
97+
// Which will be filtered out by HashSet
98+
99+
screenResolutions.Add(resolution);
100+
}
101+
102+
return screenResolutions;
103+
}
104+
105+
public static readonly IReadOnlyList<ScreenResolution> OptimalWindowedResolutions =
106+
[
107+
"1024x600",
108+
"1024x720",
109+
"1280x600",
110+
"1280x720",
111+
"1280x768",
112+
"1280x800",
113+
];
114+
115+
public const int MAX_INT_SCALE = 9;
116+
117+
public SortedSet<ScreenResolution> GetIntegerScaledResolutions() =>
118+
GetIntegerScaledResolutions(SafeMaximumResolution);
119+
public SortedSet<ScreenResolution> GetIntegerScaledResolutions(ScreenResolution maxResolution)
120+
{
121+
SortedSet<ScreenResolution> resolutions = [];
122+
for (int i = 1; i <= MAX_INT_SCALE; i++)
123+
{
124+
ScreenResolution scaledResolution = (this.Width * i, this.Height * i);
125+
126+
if (maxResolution.Fit(scaledResolution))
127+
resolutions.Add(scaledResolution);
128+
else
129+
break;
130+
}
131+
132+
return resolutions;
133+
}
134+
135+
public static SortedSet<ScreenResolution> GetWindowedResolutions(int minWidth, int minHeight) =>
136+
GetWindowedResolutions(minWidth, minHeight, SafeMaximumResolution.Width, SafeMaximumResolution.Height);
137+
public static SortedSet<ScreenResolution> GetWindowedResolutions(IEnumerable<ScreenResolution> optimalResolutions, int minWidth, int minHeight) =>
138+
GetWindowedResolutions(OptimalWindowedResolutions, minWidth, minHeight, SafeMaximumResolution.Width, SafeMaximumResolution.Height);
139+
public static SortedSet<ScreenResolution> GetWindowedResolutions(int minWidth, int minHeight, int maxWidth, int maxHeight) =>
140+
GetWindowedResolutions(OptimalWindowedResolutions, minWidth, minHeight, maxWidth, maxHeight);
141+
public static SortedSet<ScreenResolution> GetWindowedResolutions(IEnumerable<ScreenResolution> optimalResolutions, int minWidth, int minHeight, int maxWidth, int maxHeight)
142+
{
143+
ScreenResolution maxResolution = (maxWidth, maxHeight);
144+
145+
SortedSet<ScreenResolution> windowedResolutions = [];
146+
147+
foreach (ScreenResolution optimalResolution in optimalResolutions)
148+
{
149+
if (optimalResolution.Width < minWidth || optimalResolution.Height < minHeight)
150+
continue;
151+
152+
windowedResolutions.Add(optimalResolution);
153+
}
154+
155+
return windowedResolutions;
156+
}
157+
}
158+
}

DXMainClient/DXGUI/GameClass.cs

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ public GameClass()
4646
graphics.SynchronizeWithVerticalRetrace = false;
4747
#if !XNA
4848
graphics.HardwareModeSwitch = false;
49+
50+
// Enable HiDef on a large monitor.
51+
if (!ScreenResolution.HiDefLimitResolution.Fit(ScreenResolution.DesktopResolution))
52+
{
53+
// Enabling HiDef profile drops legacy GPUs not supporting DirectX 10.
54+
// In practice, it's recommended to have a DirectX 11 capable GPU.
55+
graphics.GraphicsProfile = GraphicsProfile.HiDef;
56+
}
4957
#endif
5058
content = new ContentManager(Services);
5159
}
@@ -311,16 +319,18 @@ public static void SetGraphicsMode(WindowManager wm)
311319
int windowHeight = UserINISettings.Instance.ClientResolutionY;
312320

313321
bool borderlessWindowedClient = UserINISettings.Instance.BorderlessWindowedClient;
314-
int currentWidth = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
315-
int currentHeight = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
316322

317-
if (currentWidth >= windowWidth && currentHeight >= windowHeight)
323+
(int desktopWidth, int desktopHeight) = ScreenResolution.SafeMaximumResolution;
324+
325+
if (desktopWidth >= windowWidth && desktopHeight >= windowHeight)
318326
{
319327
if (!wm.InitGraphicsMode(windowWidth, windowHeight, false))
320328
throw new GraphicsModeInitializationException("Setting graphics mode failed!".L10N("Client:Main:SettingGraphicModeFailed") + " " + windowWidth + "x" + windowHeight);
321329
}
322330
else
323331
{
332+
// fallback to the minimum supported resolution when the desktop is not sufficient to contain the client
333+
// e.g., when users set a lower desktop resolution but the client resolution in the settings file remains high
324334
if (!wm.InitGraphicsMode(1024, 600, false))
325335
throw new GraphicsModeInitializationException("Setting default graphics mode failed!".L10N("Client:Main:SettingDefaultGraphicModeFailed"));
326336
}
@@ -348,7 +358,7 @@ public static void SetGraphicsMode(WindowManager wm)
348358
if (ratio > 1.0)
349359
{
350360
// Check whether we could sharp-scale our client window
351-
for (int i = 2; i < 10; i++)
361+
for (int i = 2; i <= ScreenResolution.MAX_INT_SCALE; i++)
352362
{
353363
int sharpScaleRenderResX = windowWidth / i;
354364
int sharpScaleRenderResY = windowHeight / i;
@@ -379,8 +389,18 @@ public static void SetGraphicsMode(WindowManager wm)
379389

380390
if (borderlessWindowedClient)
381391
{
382-
graphics.IsFullScreen = true;
383-
graphics.ApplyChanges();
392+
// Note: on fullscreen mode, the client resolution must exactly match the desktop resolution. Otherwise buttons outside of client resolution are unclickable.
393+
ScreenResolution clientResolution = (windowWidth, windowHeight);
394+
if (ScreenResolution.DesktopResolution == clientResolution)
395+
{
396+
Logger.Log($"Entering fullscreen mode with resolution {ScreenResolution.DesktopResolution}.");
397+
graphics.IsFullScreen = true;
398+
graphics.ApplyChanges();
399+
}
400+
else
401+
{
402+
Logger.Log($"Not entering fullscreen mode due to resolution mismatch. Desktop: {ScreenResolution.DesktopResolution}, Client: {clientResolution}.");
403+
}
384404
}
385405

386406
#endif

DXMainClient/DXGUI/Multiplayer/CnCNet/CnCNetLobby.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -438,11 +438,13 @@ private bool HostedGameMatches(GenericHostedGame hg)
438438

439439
string textUpper = tbGameSearch?.Text?.ToUpperInvariant();
440440

441-
string translatedGameMode = hg.GameMode.L10N($"INI:GameModes:{hg.GameMode}:UIName", notify: false);
441+
string translatedGameMode = string.IsNullOrEmpty(hg.GameMode)
442+
? "Unknown".L10N("Client:Main:Unknown")
443+
: hg.GameMode.L10N($"INI:GameModes:{hg.GameMode}:UIName", notify: false);
442444

443-
string translatedMapName = mapLoader.TranslatedMapNames.ContainsKey(hg.Map)
444-
? mapLoader.TranslatedMapNames[hg.Map]
445-
: null;
445+
string translatedMapName = string.IsNullOrEmpty(hg.Map)
446+
? "Unknown".L10N("Client:Main:Unknown") : mapLoader.TranslatedMapNames.ContainsKey(hg.Map)
447+
? mapLoader.TranslatedMapNames[hg.Map] : null;
446448

447449
return
448450
string.IsNullOrWhiteSpace(tbGameSearch?.Text) ||
@@ -1463,7 +1465,7 @@ private void GameBroadcastChannel_CTCPReceived(object sender, ChannelCTCPEventAr
14631465
return;
14641466

14651467
string msg = e.Message.Substring(5); // Cut out GAME part
1466-
string[] splitMessage = msg.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
1468+
string[] splitMessage = msg.Split(new char[] { ';' });
14671469

14681470
if (splitMessage.Length != 11)
14691471
{

DXMainClient/DXGUI/Multiplayer/GameInformationPanel.cs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,18 +94,19 @@ public override void Initialize()
9494

9595
public void SetInfo(GenericHostedGame game)
9696
{
97-
string gameModeName = game.GameMode.L10N($"INI:GameModes:{game.GameMode}:UIName", notify: false);
97+
// we don't have the ID of a map here
98+
string translatedMapName = string.IsNullOrEmpty(game.Map)
99+
? "Unknown".L10N("Client:Main:Unknown") : mapLoader.TranslatedMapNames.ContainsKey(game.Map)
100+
? mapLoader.TranslatedMapNames[game.Map] : game.Map;
101+
102+
string translatedGameModeName = string.IsNullOrEmpty(game.GameMode)
103+
? "Unknown".L10N("Client:Main:Unknown") : game.GameMode.L10N($"INI:GameModes:{game.GameMode}:UIName", notify: false);
98104

99-
lblGameMode.Text = Renderer.GetStringWithLimitedWidth("Game mode:".L10N("Client:Main:GameInfoGameMode") + " " + Renderer.GetSafeString(gameModeName, lblGameMode.FontIndex),
105+
lblGameMode.Text = Renderer.GetStringWithLimitedWidth("Game mode:".L10N("Client:Main:GameInfoGameMode") + " " + Renderer.GetSafeString(translatedGameModeName, lblGameMode.FontIndex),
100106
lblGameMode.FontIndex, Width - lblGameMode.X * 2);
101107
lblGameMode.Visible = true;
102108

103-
// we don't have the ID of a map here
104-
string mapName = mapLoader.TranslatedMapNames.ContainsKey(game.Map)
105-
? mapLoader.TranslatedMapNames[game.Map]
106-
: game.Map;
107-
108-
lblMap.Text = Renderer.GetStringWithLimitedWidth("Map:".L10N("Client:Main:GameInfoMap") + " " + Renderer.GetSafeString(mapName, lblMap.FontIndex),
109+
lblMap.Text = Renderer.GetStringWithLimitedWidth("Map:".L10N("Client:Main:GameInfoMap") + " " + Renderer.GetSafeString(translatedMapName, lblMap.FontIndex),
109110
lblMap.FontIndex, Width - lblMap.X * 2);
110111
lblMap.Visible = true;
111112

DXMainClient/DXGUI/Multiplayer/GameListBox.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ private IEnumerable<GenericHostedGame> GetSortedGames()
128128
var sortedGames =
129129
HostedGames
130130
.OrderBy(hg => hg.Locked)
131-
.ThenBy(hg => string.Equals(hg.Game.InternalName, localGameIdentifier, StringComparison.CurrentCultureIgnoreCase))
131+
.ThenBy(hg => string.Equals(hg.Game.InternalName, localGameIdentifier, StringComparison.InvariantCultureIgnoreCase))
132132
.ThenBy(hg => hg.GameVersion != ProgramConstants.GAME_VERSION)
133133
.ThenBy(hg => hg.Passworded);
134134

0 commit comments

Comments
 (0)