Skip to content

Commit 0e6d197

Browse files
authored
feat: engine version indicator (AscensionGameDev#2537)
- add `IsDeveloper` to the application context, this is true when the client host is non-public or failed to be resolved and false in all other cases - Version indicator is shown in bottom left corner of main menu - Version indicator is shown in bottom left corner of in-game escape menu (not including the simplified menu) - make version label copy version string to clipboard on click
1 parent f6c09cb commit 0e6d197

File tree

21 files changed

+367
-33
lines changed

21 files changed

+367
-33
lines changed

Framework/Intersect.Framework.Core/Color.cs

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
using System.Diagnostics.CodeAnalysis;
2+
using System.Globalization;
3+
using Intersect.Framework;
14
using Intersect.Localization;
25
using MessagePack;
36

@@ -232,7 +235,61 @@ public static Color FromRgba(int rgba)
232235

233236
public static string ToString(Color clr) => clr?.ToString() ?? string.Empty;
234237

235-
public static Color FromString(string val, Color defaultColor = null)
238+
[return: NotNullIfNotNull(nameof(defaultColor))]
239+
public static Color? FromHex(string hexString, Color? defaultColor = default)
240+
{
241+
const NumberStyles hexStyles = NumberStyles.HexNumber | NumberStyles.AllowHexSpecifier;
242+
243+
if (string.IsNullOrWhiteSpace(hexString))
244+
{
245+
return defaultColor;
246+
}
247+
248+
hexString = hexString.Trim();
249+
250+
if (hexString.StartsWith('#'))
251+
{
252+
hexString = hexString[1..];
253+
}
254+
255+
string reversedHexString = hexString.Reverse();
256+
257+
if (!uint.TryParse(reversedHexString, hexStyles, CultureInfo.InvariantCulture, out var abgr))
258+
{
259+
return defaultColor;
260+
}
261+
262+
int a, g, b, r;
263+
264+
if (reversedHexString.Length <= 4)
265+
{
266+
a = (int)(abgr >> 12 & 0xf);
267+
b = (int)(abgr >> 8 & 0xf);
268+
g = (int)(abgr >> 4 & 0xf);
269+
r = (int)(abgr & 0xf);
270+
a |= a << 4;
271+
b |= b << 4;
272+
g |= g << 4;
273+
r |= r << 4;
274+
}
275+
else
276+
{
277+
a = (int)(abgr >> 28 & 0xf | (abgr >> 24 & 0xf) << 4);
278+
b = (int)(abgr >> 20 & 0xf | (abgr >> 16 & 0xf) << 4);
279+
g = (int)(abgr >> 12 & 0xf | (abgr >> 8 & 0xf) << 4);
280+
r = (int)(abgr >> 4 & 0xf | (abgr & 0xf) << 4);
281+
}
282+
283+
a = reversedHexString.Length switch
284+
{
285+
< 4 or > 4 and < 8 => 0xff,
286+
_ => a,
287+
};
288+
289+
return new Color(a: a, r: r, g: g, b: b);
290+
}
291+
292+
public static Color? FromString(string val, Color? defaultColor = default)
236293
{
237294
if (string.IsNullOrEmpty(val))
238295
{

Framework/Intersect.Framework.Core/Core/IApplicationContext.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11

2+
using Intersect.Framework.Reflection;
23
using Intersect.Plugins.Interfaces;
34
using Intersect.Threading;
45
using Microsoft.Extensions.Logging;
@@ -15,6 +16,26 @@ public interface IApplicationContext : IDisposable
1516
/// </summary>
1617
bool HasErrors { get; }
1718

19+
/// <summary>
20+
/// If the application is a debug build.
21+
/// </summary>
22+
bool IsDebug
23+
{
24+
get
25+
{
26+
#if !DEBUG
27+
return true;
28+
#else
29+
return false;
30+
#endif
31+
}
32+
}
33+
34+
/// <summary>
35+
/// If the application is being run in developer mode.
36+
/// </summary>
37+
bool IsDeveloper => false;
38+
1839
/// <summary>
1940
/// If the application has been disposed.
2041
/// </summary>
@@ -50,6 +71,11 @@ public interface IApplicationContext : IDisposable
5071
/// </summary>
5172
List<IApplicationService> Services { get; }
5273

74+
/// <summary>
75+
/// The human-friendly version string.
76+
/// </summary>
77+
string VersionName => GetType().Assembly.GetMetadataVersionName();
78+
5379
/// <summary>
5480
/// Gets a service of type <typeparamref name="TApplicationService"/> if one has been registered.
5581
/// </summary>

Framework/Intersect.Framework/Reflection/AssemblyExtensions.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ public static string GetMetadataVersion(this Assembly assembly, bool excludeComm
5656
return GetMetadataVersionFrom(assembly, assemblyName, excludeCommitSha);
5757
}
5858

59+
public static string GetMetadataVersionName(this Assembly assembly, bool excludeCommitSha = false) =>
60+
$"v{GetMetadataVersion(assembly: assembly, excludeCommitSha: excludeCommitSha)}";
61+
5962
private static string GetMetadataVersionFrom(Assembly assembly, AssemblyName assemblyName, bool excludeCommitSha)
6063
{
6164
var assemblyVersion = assemblyName.Version ?? new Version(0, 0, 0, 0);
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System.Text;
2+
3+
namespace Intersect.Framework;
4+
5+
public static class StringExtensions
6+
{
7+
public static string Reverse(this string @string)
8+
{
9+
var codepoints = new int[@string.Length];
10+
var numCodepoints = 0;
11+
for (var index = 0; index < @string.Length; ++index)
12+
{
13+
++numCodepoints;
14+
15+
codepoints[index] = char.ConvertToUtf32(@string, index);
16+
if (char.IsSurrogatePair(@string, index))
17+
{
18+
++index;
19+
}
20+
}
21+
22+
codepoints = codepoints[..numCodepoints];
23+
Array.Reverse(codepoints);
24+
25+
return codepoints.Aggregate(string.Empty, (current, codepoint) => current + char.ConvertFromUtf32(codepoint));
26+
}
27+
}

Intersect.Client.Core/Core/Bootstrapper.cs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,28 @@ public static void Start(params string[] args)
7878
var clientConfiguration = ClientConfiguration.LoadAndSave();
7979
loggingLevelSwitch.MinimumLevel = LevelConvert.ToSerilogLevel(clientConfiguration.LogLevel);
8080

81-
var context = new ClientContext(commandLineOptions, logger, packetHelper);
81+
if (commandLineOptions.Server is { } server)
82+
{
83+
var serverParts = server.Split(':', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
84+
var host = serverParts.First();
85+
86+
var portPart = serverParts.Skip(1).FirstOrDefault();
87+
if (string.IsNullOrEmpty(portPart))
88+
{
89+
portPart = "5400";
90+
}
91+
var port = ushort.Parse(portPart);
92+
93+
clientConfiguration.Host = host;
94+
clientConfiguration.Port = port;
95+
}
96+
97+
commandLineOptions = commandLineOptions with
98+
{
99+
Server = $"{clientConfiguration.Host}:{clientConfiguration.Port}",
100+
};
101+
102+
ClientContext context = new(commandLineOptions, clientConfiguration, logger, packetHelper);
82103
context.Start();
83104
}
84105

Intersect.Client.Core/Core/ClientCommandLineOptions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ namespace Intersect.Client.Core;
66

77
internal readonly partial struct ClientCommandLineOptions : ICommandLineOptions
88
{
9+
// ReSharper disable once ConvertToPrimaryConstructor
910
public ClientCommandLineOptions(
1011
bool borderlessWindow,
1112
int screenWidth,
@@ -33,7 +34,7 @@ IEnumerable<string> pluginDirectories
3334
public int ScreenHeight { get; }
3435

3536
[Option('S', "server", Default = null, Required = false)]
36-
public string Server { get; }
37+
public string Server { get; init; }
3738

3839
[Option("working-directory", Default = null, Required = false)]
3940
public string WorkingDirectory { get; }

Intersect.Client.Core/Core/ClientContext.cs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
using System.Net;
2+
using System.Net.Sockets;
13
using Intersect.Client.Networking;
24
using Intersect.Client.Plugins.Contexts;
5+
using Intersect.Configuration;
36
using Intersect.Core;
47
using Intersect.Factories;
8+
using Intersect.Framework.Net;
9+
using Intersect.Network;
510
using Intersect.Plugins;
611
using Intersect.Plugins.Interfaces;
712
using Intersect.Reflection;
@@ -18,13 +23,33 @@ internal sealed partial class ClientContext : ApplicationContext<ClientContext,
1823

1924
private IPlatformRunner? mPlatformRunner;
2025

21-
internal ClientContext(ClientCommandLineOptions startupOptions, ILogger logger, IPacketHelper packetHelper) : base(
26+
internal ClientContext(ClientCommandLineOptions startupOptions, ClientConfiguration clientConfiguration, ILogger logger, IPacketHelper packetHelper) : base(
2227
startupOptions, logger, packetHelper
2328
)
2429
{
30+
var hostNameOrAddress = clientConfiguration.Host;
31+
try
32+
{
33+
var address = Dns.GetHostAddresses(hostNameOrAddress).FirstOrDefault();
34+
IsDeveloper = !(address?.IsPublic() ?? false);
35+
}
36+
catch (SocketException socketException)
37+
{
38+
if (socketException.SocketErrorCode != SocketError.HostNotFound)
39+
{
40+
throw;
41+
}
42+
43+
ClientNetwork.UnresolvableHostNames.Add(startupOptions.Server);
44+
ApplicationContext.Context.Value?.Logger.LogError(socketException, $"Failed to resolve host: '{hostNameOrAddress}'");
45+
IsDeveloper = true;
46+
}
47+
2548
_ = FactoryRegistry<IPluginContext>.RegisterFactory(new ClientPluginContext.Factory());
2649
}
2750

51+
public bool IsDeveloper { get; }
52+
2853
protected override bool UsesMainThread => true;
2954

3055
public IPlatformRunner PlatformRunner

Intersect.Client.Core/Interface/Game/EscapeMenu.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public partial class EscapeMenu : ImagePanel
1313
{
1414
private readonly SettingsWindow _settingsWindow;
1515
private readonly Button _buttonCharacterSelect;
16+
private readonly Panel _versionPanel;
1617

1718
public EscapeMenu(Canvas gameCanvas) : base(gameCanvas, nameof(EscapeMenu))
1819
{
@@ -76,6 +77,8 @@ public EscapeMenu(Canvas gameCanvas) : base(gameCanvas, nameof(EscapeMenu))
7677
{
7778
_buttonCharacterSelect.IsDisabled = true;
7879
}
80+
81+
_versionPanel = new VersionPanel(this, name: nameof(_versionPanel));
7982
}
8083

8184
public override void Invalidate()

Intersect.Client.Core/Interface/Menu/MenuGuiBase.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
using Intersect.Client.Core;
22
using Intersect.Client.Framework.File_Management;
3+
using Intersect.Client.Framework.Gwen;
34
using Intersect.Client.Framework.Gwen.Control;
5+
using Intersect.Client.Interface.Shared;
46
using Intersect.Client.Localization;
57
using Intersect.Client.Networking;
8+
using Intersect.Core;
69

710
namespace Intersect.Client.Interface.Menu;
811

@@ -11,6 +14,7 @@ public partial class MenuGuiBase : IMutableInterface
1114
private readonly Canvas _menuCanvas;
1215
private readonly ImagePanel _serverStatusArea;
1316
private readonly Label _serverStatusLabel;
17+
private readonly VersionPanel _versionPanel;
1418

1519
public MainMenu MainMenu { get; }
1620

@@ -21,6 +25,9 @@ public MenuGuiBase(Canvas myCanvas)
2125
_menuCanvas = myCanvas;
2226

2327
MainMenu = new MainMenu(_menuCanvas);
28+
29+
_versionPanel = new VersionPanel(_menuCanvas, name: nameof(_versionPanel));
30+
2431
_serverStatusArea = new ImagePanel(_menuCanvas, "ServerStatusArea")
2532
{
2633
IsHidden = ClientContext.IsSinglePlayer,
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using Intersect.Client.Framework.Gwen;
2+
using Intersect.Client.Framework.Gwen.Control;
3+
using Intersect.Client.Framework.Gwen.Platform;
4+
using Intersect.Client.Framework.Input;
5+
6+
namespace Intersect.Client.Interface.Shared;
7+
8+
public partial class VersionLabel : Label
9+
{
10+
public VersionLabel(Base parent, string name = nameof(VersionLabel)) : base(parent: parent, name: name)
11+
{
12+
AutoSizeToContents = true;
13+
Dock = Pos.Top;
14+
MouseInputEnabled = true;
15+
TextPadding = new Padding(2, 0);
16+
}
17+
18+
protected override void Layout(Framework.Gwen.Skin.Base skin)
19+
{
20+
base.Layout(skin);
21+
22+
SizeToContents();
23+
}
24+
25+
protected override void OnMouseClicked(MouseButton mouseButton, Point mousePosition, bool userAction = true)
26+
{
27+
base.OnMouseClicked(mouseButton, mousePosition, userAction);
28+
29+
if (Text is { } text && !string.IsNullOrWhiteSpace(text))
30+
{
31+
Neutral.SetClipboardText(text);
32+
}
33+
}
34+
}

0 commit comments

Comments
 (0)