Skip to content

Commit

Permalink
Merge pull request #562 from CnCNet/develop
Browse files Browse the repository at this point in the history
Release 2.11.1.0
  • Loading branch information
Metadorius authored Sep 25, 2024
2 parents fb64c5d + b77fbb4 commit e62a850
Show file tree
Hide file tree
Showing 20 changed files with 382 additions and 242 deletions.
3 changes: 2 additions & 1 deletion ClientCore/I18N/Translation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ public static string GetLanguageName(string localeCode)
/// <returns>Locale code -> display name pairs.</returns>
public static Dictionary<string, string> GetTranslations()
{
var translations = new Dictionary<string, string>
var translations = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase)
{
// Add default localization so that we always have it in the list even if the localization does not exist
[ProgramConstants.HARDCODED_LOCALE_CODE] = GetLanguageName(ProgramConstants.HARDCODED_LOCALE_CODE)
Expand Down Expand Up @@ -224,6 +224,7 @@ public static string GetDefaultTranslationLocaleCode()
{
string translation = culture.Name;

// the keys in 'translations' are case-insensitive
if (translations.ContainsKey(translation))
return translation;
}
Expand Down
5 changes: 3 additions & 2 deletions ClientCore/ProcessLauncher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ namespace ClientCore
{
public static class ProcessLauncher
{
public static void StartShellProcess(string commandLine)
public static void StartShellProcess(string commandLine, string arguments = null)
{
using var _ = Process.Start(new ProcessStartInfo
{
FileName = commandLine,
Arguments = arguments,
UseShellExecute = true
});
}
}
}
}
13 changes: 10 additions & 3 deletions ClientGUI/XNALinkButton.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public XNALinkButton(WindowManager windowManager) : base(windowManager) { }

public string URL { get; set; }
public string UnixURL { get; set; }
public string Arguments { get; set; }

protected override void ParseControlINIAttribute(IniFile iniFile, string key, string value)
{
Expand All @@ -26,6 +27,12 @@ protected override void ParseControlINIAttribute(IniFile iniFile, string key, st
return;
}

if (key == "Arguments")
{
Arguments = value;
return;
}

base.ParseControlINIAttribute(iniFile, key, value);
}

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

if (osVersion == OSVersion.UNIX && !string.IsNullOrEmpty(UnixURL))
ProcessLauncher.StartShellProcess(UnixURL);
ProcessLauncher.StartShellProcess(UnixURL, Arguments);
else if (!string.IsNullOrEmpty(URL))
ProcessLauncher.StartShellProcess(URL);
ProcessLauncher.StartShellProcess(URL, Arguments);

base.OnLeftClick();
}
}
}
}
240 changes: 70 additions & 170 deletions DTAConfig/OptionPanels/DisplayOptionsPanel.cs

Large diffs are not rendered by default.

158 changes: 158 additions & 0 deletions DTAConfig/ScreenResolution.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
using System;
using System.Collections.Generic;
using System.Linq;

using Microsoft.Xna.Framework.Graphics;

namespace DTAConfig
{
/// <summary>
/// A single screen resolution.
/// </summary>
public sealed record ScreenResolution : IComparable<ScreenResolution>
{

/// <summary>
/// The width of the resolution in pixels.
/// </summary>
public int Width { get; }

/// <summary>
/// The height of the resolution in pixels.
/// </summary>
public int Height { get; }

public ScreenResolution(int width, int height)
{
Width = width;
Height = height;
}

public ScreenResolution(string resolution)
{
List<int> resolutionList = resolution.Trim().Split('x').Take(2).Select(int.Parse).ToList();
Width = resolutionList[0];
Height = resolutionList[1];
}

public static implicit operator ScreenResolution(string resolution) => new(resolution);

public sealed override string ToString() => Width + "x" + Height;

public static implicit operator string(ScreenResolution resolution) => resolution.ToString();

public void Deconstruct(out int width, out int height)
{
width = this.Width;
height = this.Height;
}

public static implicit operator ScreenResolution((int Width, int Height) resolutionTuple) => new(resolutionTuple.Width, resolutionTuple.Height);

public static implicit operator (int Width, int Height)(ScreenResolution resolution) => new(resolution.Width, resolution.Height);

public bool Fit(ScreenResolution child) => this.Width >= child.Width && this.Height >= child.Height;

public int CompareTo(ScreenResolution other) => (this.Width, this.Height).CompareTo(other);

// Accessing GraphicsAdapter.DefaultAdapter requiring DXMainClient.GameClass has been constructed. Lazy loading prevents possible null reference issues for now.
private static ScreenResolution _desktopResolution = null;
public static ScreenResolution DesktopResolution =>
_desktopResolution ??= new(GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width, GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height);

// The default graphic profile supports resolution up to 4096x4096. The number gets even smaller in practice. Therefore, we select 3840 as the limit.
public static ScreenResolution HiDefLimitResolution { get; } = "3840x3840";

private static ScreenResolution _safeMaximumResolution = null;
public static ScreenResolution SafeMaximumResolution
{
get
{
#if XNA
return _safeMaximumResolution ??= HiDefLimitResolution.Fit(DesktopResolution) ? DesktopResolution : HiDefLimitResolution;
#else
return _safeMaximumResolution ??= DesktopResolution;
#endif
}
}

private static ScreenResolution _safeFullScreenResolution = null;
public static ScreenResolution SafeFullScreenResolution => _safeFullScreenResolution ??= GetFullScreenResolutions(minWidth: 800, minHeight: 600).Max();

public static SortedSet<ScreenResolution> GetFullScreenResolutions(int minWidth, int minHeight) =>
GetFullScreenResolutions(minWidth, minHeight, SafeMaximumResolution.Width, SafeMaximumResolution.Height);
public static SortedSet<ScreenResolution> GetFullScreenResolutions(int minWidth, int minHeight, int maxWidth, int maxHeight)
{
SortedSet<ScreenResolution> screenResolutions = [];

foreach (DisplayMode dm in GraphicsAdapter.DefaultAdapter.SupportedDisplayModes)
{
if (dm.Width < minWidth || dm.Height < minHeight || dm.Width > maxWidth || dm.Height > maxHeight)
continue;

var resolution = new ScreenResolution(dm.Width, dm.Height);

// SupportedDisplayModes can include the same resolution multiple times
// because it takes the refresh rate into consideration.
// Which will be filtered out by HashSet

screenResolutions.Add(resolution);
}

return screenResolutions;
}

public static readonly IReadOnlyList<ScreenResolution> OptimalWindowedResolutions =
[
"1024x600",
"1024x720",
"1280x600",
"1280x720",
"1280x768",
"1280x800",
];

public const int MAX_INT_SCALE = 9;

public SortedSet<ScreenResolution> GetIntegerScaledResolutions() =>
GetIntegerScaledResolutions(SafeMaximumResolution);
public SortedSet<ScreenResolution> GetIntegerScaledResolutions(ScreenResolution maxResolution)
{
SortedSet<ScreenResolution> resolutions = [];
for (int i = 1; i <= MAX_INT_SCALE; i++)
{
ScreenResolution scaledResolution = (this.Width * i, this.Height * i);

if (maxResolution.Fit(scaledResolution))
resolutions.Add(scaledResolution);
else
break;
}

return resolutions;
}

public static SortedSet<ScreenResolution> GetWindowedResolutions(int minWidth, int minHeight) =>
GetWindowedResolutions(minWidth, minHeight, SafeMaximumResolution.Width, SafeMaximumResolution.Height);
public static SortedSet<ScreenResolution> GetWindowedResolutions(IEnumerable<ScreenResolution> optimalResolutions, int minWidth, int minHeight) =>
GetWindowedResolutions(OptimalWindowedResolutions, minWidth, minHeight, SafeMaximumResolution.Width, SafeMaximumResolution.Height);
public static SortedSet<ScreenResolution> GetWindowedResolutions(int minWidth, int minHeight, int maxWidth, int maxHeight) =>
GetWindowedResolutions(OptimalWindowedResolutions, minWidth, minHeight, maxWidth, maxHeight);
public static SortedSet<ScreenResolution> GetWindowedResolutions(IEnumerable<ScreenResolution> optimalResolutions, int minWidth, int minHeight, int maxWidth, int maxHeight)
{
ScreenResolution maxResolution = (maxWidth, maxHeight);

SortedSet<ScreenResolution> windowedResolutions = [];

foreach (ScreenResolution optimalResolution in optimalResolutions)
{
if (optimalResolution.Width < minWidth || optimalResolution.Height < minHeight)
continue;

windowedResolutions.Add(optimalResolution);
}

return windowedResolutions;
}
}
}
32 changes: 26 additions & 6 deletions DXMainClient/DXGUI/GameClass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ public GameClass()
graphics.SynchronizeWithVerticalRetrace = false;
#if !XNA
graphics.HardwareModeSwitch = false;

// Enable HiDef on a large monitor.
if (!ScreenResolution.HiDefLimitResolution.Fit(ScreenResolution.DesktopResolution))
{
// Enabling HiDef profile drops legacy GPUs not supporting DirectX 10.
// In practice, it's recommended to have a DirectX 11 capable GPU.
graphics.GraphicsProfile = GraphicsProfile.HiDef;
}
#endif
content = new ContentManager(Services);
}
Expand Down Expand Up @@ -311,16 +319,18 @@ public static void SetGraphicsMode(WindowManager wm)
int windowHeight = UserINISettings.Instance.ClientResolutionY;

bool borderlessWindowedClient = UserINISettings.Instance.BorderlessWindowedClient;
int currentWidth = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
int currentHeight = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;

if (currentWidth >= windowWidth && currentHeight >= windowHeight)
(int desktopWidth, int desktopHeight) = ScreenResolution.SafeMaximumResolution;

if (desktopWidth >= windowWidth && desktopHeight >= windowHeight)
{
if (!wm.InitGraphicsMode(windowWidth, windowHeight, false))
throw new GraphicsModeInitializationException("Setting graphics mode failed!".L10N("Client:Main:SettingGraphicModeFailed") + " " + windowWidth + "x" + windowHeight);
}
else
{
// fallback to the minimum supported resolution when the desktop is not sufficient to contain the client
// e.g., when users set a lower desktop resolution but the client resolution in the settings file remains high
if (!wm.InitGraphicsMode(1024, 600, false))
throw new GraphicsModeInitializationException("Setting default graphics mode failed!".L10N("Client:Main:SettingDefaultGraphicModeFailed"));
}
Expand Down Expand Up @@ -348,7 +358,7 @@ public static void SetGraphicsMode(WindowManager wm)
if (ratio > 1.0)
{
// Check whether we could sharp-scale our client window
for (int i = 2; i < 10; i++)
for (int i = 2; i <= ScreenResolution.MAX_INT_SCALE; i++)
{
int sharpScaleRenderResX = windowWidth / i;
int sharpScaleRenderResY = windowHeight / i;
Expand Down Expand Up @@ -379,8 +389,18 @@ public static void SetGraphicsMode(WindowManager wm)

if (borderlessWindowedClient)
{
graphics.IsFullScreen = true;
graphics.ApplyChanges();
// Note: on fullscreen mode, the client resolution must exactly match the desktop resolution. Otherwise buttons outside of client resolution are unclickable.
ScreenResolution clientResolution = (windowWidth, windowHeight);
if (ScreenResolution.DesktopResolution == clientResolution)
{
Logger.Log($"Entering fullscreen mode with resolution {ScreenResolution.DesktopResolution}.");
graphics.IsFullScreen = true;
graphics.ApplyChanges();
}
else
{
Logger.Log($"Not entering fullscreen mode due to resolution mismatch. Desktop: {ScreenResolution.DesktopResolution}, Client: {clientResolution}.");
}
}

#endif
Expand Down
12 changes: 7 additions & 5 deletions DXMainClient/DXGUI/Multiplayer/CnCNet/CnCNetLobby.cs
Original file line number Diff line number Diff line change
Expand Up @@ -438,11 +438,13 @@ private bool HostedGameMatches(GenericHostedGame hg)

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

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

string translatedMapName = mapLoader.TranslatedMapNames.ContainsKey(hg.Map)
? mapLoader.TranslatedMapNames[hg.Map]
: null;
string translatedMapName = string.IsNullOrEmpty(hg.Map)
? "Unknown".L10N("Client:Main:Unknown") : mapLoader.TranslatedMapNames.ContainsKey(hg.Map)
? mapLoader.TranslatedMapNames[hg.Map] : null;

return
string.IsNullOrWhiteSpace(tbGameSearch?.Text) ||
Expand Down Expand Up @@ -1463,7 +1465,7 @@ private void GameBroadcastChannel_CTCPReceived(object sender, ChannelCTCPEventAr
return;

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

if (splitMessage.Length != 11)
{
Expand Down
17 changes: 9 additions & 8 deletions DXMainClient/DXGUI/Multiplayer/GameInformationPanel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,18 +94,19 @@ public override void Initialize()

public void SetInfo(GenericHostedGame game)
{
string gameModeName = game.GameMode.L10N($"INI:GameModes:{game.GameMode}:UIName", notify: false);
// we don't have the ID of a map here
string translatedMapName = string.IsNullOrEmpty(game.Map)
? "Unknown".L10N("Client:Main:Unknown") : mapLoader.TranslatedMapNames.ContainsKey(game.Map)
? mapLoader.TranslatedMapNames[game.Map] : game.Map;

string translatedGameModeName = string.IsNullOrEmpty(game.GameMode)
? "Unknown".L10N("Client:Main:Unknown") : game.GameMode.L10N($"INI:GameModes:{game.GameMode}:UIName", notify: false);

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

// we don't have the ID of a map here
string mapName = mapLoader.TranslatedMapNames.ContainsKey(game.Map)
? mapLoader.TranslatedMapNames[game.Map]
: game.Map;

lblMap.Text = Renderer.GetStringWithLimitedWidth("Map:".L10N("Client:Main:GameInfoMap") + " " + Renderer.GetSafeString(mapName, lblMap.FontIndex),
lblMap.Text = Renderer.GetStringWithLimitedWidth("Map:".L10N("Client:Main:GameInfoMap") + " " + Renderer.GetSafeString(translatedMapName, lblMap.FontIndex),
lblMap.FontIndex, Width - lblMap.X * 2);
lblMap.Visible = true;

Expand Down
2 changes: 1 addition & 1 deletion DXMainClient/DXGUI/Multiplayer/GameListBox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ private IEnumerable<GenericHostedGame> GetSortedGames()
var sortedGames =
HostedGames
.OrderBy(hg => hg.Locked)
.ThenBy(hg => string.Equals(hg.Game.InternalName, localGameIdentifier, StringComparison.CurrentCultureIgnoreCase))
.ThenBy(hg => string.Equals(hg.Game.InternalName, localGameIdentifier, StringComparison.InvariantCultureIgnoreCase))
.ThenBy(hg => hg.GameVersion != ProgramConstants.GAME_VERSION)
.ThenBy(hg => hg.Passworded);

Expand Down
Loading

0 comments on commit e62a850

Please sign in to comment.