Skip to content

Commit

Permalink
Merge pull request #663 from CnCNet/develop
Browse files Browse the repository at this point in the history
2.11.7.0
  • Loading branch information
SadPencil authored Feb 11, 2025
2 parents 95d43c2 + e5034eb commit d658c28
Show file tree
Hide file tree
Showing 45 changed files with 740 additions and 719 deletions.
27 changes: 19 additions & 8 deletions ClientCore/ClientConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class ClientConfiguration
private const string CLIENT_SETTINGS = "DTACnCNetClient.ini";
private const string GAME_OPTIONS = "GameOptions.ini";
private const string CLIENT_DEFS = "ClientDefinitions.ini";
private const string NETWORK_DEFS_LOCAL = "NetworkDefinitions.local.ini";
private const string NETWORK_DEFS = "NetworkDefinitions.ini";

private static ClientConfiguration _instance;
Expand Down Expand Up @@ -48,7 +49,17 @@ protected ClientConfiguration()

gameOptions_ini = new IniFile(SafePath.CombineFilePath(baseResourceDirectory.FullName, GAME_OPTIONS));

networkDefinitionsIni = new IniFile(SafePath.CombineFilePath(ProgramConstants.GetResourcePath(), NETWORK_DEFS));
string networkDefsPathLocal = SafePath.CombineFilePath(ProgramConstants.GetResourcePath(), NETWORK_DEFS_LOCAL);
if (File.Exists(networkDefsPathLocal))
{
networkDefinitionsIni = new IniFile(networkDefsPathLocal);
Logger.Log("Loaded network definitions from NetworkDefinitions.local.ini (user override)");
}
else
{
string networkDefsPath = SafePath.CombineFilePath(ProgramConstants.GetResourcePath(), NETWORK_DEFS);
networkDefinitionsIni = new IniFile(networkDefsPath);
}
}

/// <summary>
Expand Down Expand Up @@ -214,13 +225,13 @@ public void RefreshSettings()

public string LongGameName => clientDefinitionsIni.GetStringValue(SETTINGS, "LongGameName", "Tiberian Sun");

public string LongSupportURL => clientDefinitionsIni.GetStringValue(SETTINGS, "LongSupportURL", "http://www.moddb.com/members/rampastring");
public string LongSupportURL => clientDefinitionsIni.GetStringValue(SETTINGS, "LongSupportURL", "https://www.moddb.com/members/rampastring");

public string ShortSupportURL => clientDefinitionsIni.GetStringValue(SETTINGS, "ShortSupportURL", "www.moddb.com/members/rampastring");

public string ChangelogURL => clientDefinitionsIni.GetStringValue(SETTINGS, "ChangelogURL", "http://www.moddb.com/mods/the-dawn-of-the-tiberium-age/tutorials/change-log");
public string ChangelogURL => clientDefinitionsIni.GetStringValue(SETTINGS, "ChangelogURL", "https://www.moddb.com/mods/the-dawn-of-the-tiberium-age/tutorials/change-log");

public string CreditsURL => clientDefinitionsIni.GetStringValue(SETTINGS, "CreditsURL", "http://www.moddb.com/mods/the-dawn-of-the-tiberium-age/tutorials/credits#Rampastring");
public string CreditsURL => clientDefinitionsIni.GetStringValue(SETTINGS, "CreditsURL", "https://www.moddb.com/mods/the-dawn-of-the-tiberium-age/tutorials/credits#Rampastring");

public string ManualDownloadURL => clientDefinitionsIni.GetStringValue(SETTINGS, "ManualDownloadURL", string.Empty);

Expand Down Expand Up @@ -400,13 +411,13 @@ public IEnumerable<string> SupplementalMapFileExtensions

#region Network definitions

public string CnCNetTunnelListURL => networkDefinitionsIni.GetStringValue(SETTINGS, "CnCNetTunnelListURL", "http://cncnet.org/master-list");
public string CnCNetTunnelListURL => networkDefinitionsIni.GetStringValue(SETTINGS, "CnCNetTunnelListURL", "https://cncnet.org/master-list");

public string CnCNetPlayerCountURL => networkDefinitionsIni.GetStringValue(SETTINGS, "CnCNetPlayerCountURL", "http://api.cncnet.org/status");
public string CnCNetPlayerCountURL => networkDefinitionsIni.GetStringValue(SETTINGS, "CnCNetPlayerCountURL", "https://api.cncnet.org/status");

public string CnCNetMapDBDownloadURL => networkDefinitionsIni.GetStringValue(SETTINGS, "CnCNetMapDBDownloadURL", "http://mapdb.cncnet.org");
public string CnCNetMapDBDownloadURL => networkDefinitionsIni.GetStringValue(SETTINGS, "CnCNetMapDBDownloadURL", "https://mapdb.cncnet.org");

public string CnCNetMapDBUploadURL => networkDefinitionsIni.GetStringValue(SETTINGS, "CnCNetMapDBUploadURL", "http://mapdb.cncnet.org/upload");
public string CnCNetMapDBUploadURL => networkDefinitionsIni.GetStringValue(SETTINGS, "CnCNetMapDBUploadURL", "https://mapdb.cncnet.org/upload");

public bool DisableDiscordIntegration => networkDefinitionsIni.GetBooleanValue(SETTINGS, "DisableDiscordIntegration", false);

Expand Down
11 changes: 11 additions & 0 deletions ClientGUI/ClientGUI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,15 @@
<ItemGroup>
<ProjectReference Include="..\ClientCore\ClientCore.csproj" />
</ItemGroup>
<ItemGroup Condition="$(Configuration.Contains('GL'))">
<!--Remove WinForm-->
<Compile Remove="IME\WinFormsIMEHandler.cs" />
<None Include="IME\WinFormsIMEHandler.cs" />
</ItemGroup>
<ItemGroup Condition="!$(Configuration.Contains('GL'))">
<!--Remove SDL-->
<Compile Remove="IME\SdlIMEHandler.cs" />
<None Include="IME\SdlIMEHandler.cs" />
<PackageReference Include="ImeSharp" />
</ItemGroup>
</Project>
1 change: 1 addition & 0 deletions ClientGUI/GameProcessLogic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public static void StartGameProcess(WindowManager windowManager)
int waitTimes = 0;
while (PreprocessorBackgroundTask.Instance.IsRunning)
{
Logger.Log("The preprocessor background task is still running. Wait for it...");
Thread.Sleep(1000);
waitTimes++;
if (waitTimes > 10)
Expand Down
16 changes: 16 additions & 0 deletions ClientGUI/IME/DummyIMEHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#nullable enable
using Microsoft.Xna.Framework;

namespace ClientGUI.IME
{
internal class DummyIMEHandler : IMEHandler
{
public DummyIMEHandler() { }

public override bool TextCompositionEnabled { get => false; protected set { } }

public override void SetTextInputRectangle(Rectangle rectangle) { }
public override void StartTextComposition() { }
public override void StopTextComposition() { }
}
}
226 changes: 226 additions & 0 deletions ClientGUI/IME/IMEHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
#nullable enable
using System;
using System.Collections.Concurrent;
using System.Diagnostics;

using Microsoft.Xna.Framework;

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

namespace ClientGUI.IME;
public abstract class IMEHandler : IIMEHandler
{
bool IIMEHandler.TextCompositionEnabled => TextCompositionEnabled;
public abstract bool TextCompositionEnabled { get; protected set; }

private XNATextBox? _IMEFocus = null;
public XNATextBox? IMEFocus
{
get => _IMEFocus;
protected set
{
_IMEFocus = value;
Debug.Assert(!_IMEFocus?.IMEDisabled ?? true, "IME focus should not be assigned from a textbox with IME disabled");
}
}

private string _composition = string.Empty;

public string Composition
{
get => _composition;
protected set
{
string old = _composition;
_composition = value;
OnCompositionChanged(old, value);
}
}

public bool CompositionEmpty => string.IsNullOrEmpty(_composition);

protected bool IMEEventReceived = false;
protected bool LastActionIMEChatInput = true;

private void OnCompositionChanged(string oldValue, string newValue)
{
//Debug.WriteLine($"IME: OnCompositionChanged: {newValue.Length - oldValue.Length}");

IMEEventReceived = true;
// It seems that OnIMETextInput() is always triggered after OnCompositionChanged(). We expect such a behavior.
LastActionIMEChatInput = false;
}

protected ConcurrentDictionary<XNATextBox, Action<char>?> TextBoxHandleChatInputCallbacks = [];

public virtual int CompositionCursorPosition { get; set; }

public static IMEHandler Create(Game game)
{
#if DX
return new WinFormsIMEHandler(game);
#elif XNA
// Warning: Think carefully before enabling WinFormsIMEHandler for XNA builds!
// It *might* occasionally crash due to an unknown stack overflow issue.
// This *might* be caused by both ImeSharp and XNAUI hooking into WndProc.
// ImeSharp: https://github.com/ryancheung/ImeSharp/blob/dc2243beff9ef48eb37e398c506c905c965f8e68/ImeSharp/InputMethod.cs#L170
// XNAUI: https://github.com/Rampastring/Rampastring.XNAUI/blob/9a7d5bb3e47ea50286ee05073d0a6723bc6d764d/Input/KeyboardEventInput.cs#L79
//
// That said, you can try returning a WinFormsIMEHandler and test if it is stable enough now. Who knows?
return new DummyIMEHandler();
#elif GL
return new SdlIMEHandler(game);
#else
#error Unknown variant
#endif
}

public abstract void SetTextInputRectangle(Rectangle rectangle);

public abstract void StartTextComposition();

public abstract void StopTextComposition();

protected virtual void OnIMETextInput(char character)
{
//Debug.WriteLine($"IME: OnIMETextInput: {character} {(short)character}; IMEFocus is null? {IMEFocus == null}");

IMEEventReceived = true;
LastActionIMEChatInput = true;

if (IMEFocus != null)
{
TextBoxHandleChatInputCallbacks.TryGetValue(IMEFocus, out var handleChatInput);
handleChatInput?.Invoke(character);
}
}

public void SetIMETextInputRectangle(WindowManager manager)
{
// When the client window resizes, we should call SetIMETextInputRectangle()
if (manager.SelectedControl is XNATextBox textBox)
SetIMETextInputRectangle(textBox);
}

private void SetIMETextInputRectangle(XNATextBox sender)
{
WindowManager windowManager = sender.WindowManager;

Rectangle textBoxRect = sender.RenderRectangle();
double scaleRatio = windowManager.ScaleRatio;

Rectangle rect = new()
{
X = (int)(textBoxRect.X * scaleRatio + windowManager.SceneXPosition),
Y = (int)(textBoxRect.Y * scaleRatio + windowManager.SceneYPosition),
Width = (int)(textBoxRect.Width * scaleRatio),
Height = (int)(textBoxRect.Height * scaleRatio)
};

// The following code returns a more accurate location based on the current InputPosition.
// However, as SetIMETextInputRectangle() does not automatically update with changes in InputPosition
// (e.g., due to scrolling or mouse clicks altering the textbox's input position without shifting focus),
// accuracy becomes inconsistent. Sometimes it's precise, other times it's off,
// which is arguably worse than a consistent but manageable inaccuracy.
// This inconsistency could lead to a confusing user experience,
// as the input rectangle's position may not reliably reflect the current input position.
// Therefore, unless whenever InputPosition is changed, SetIMETextInputRectangle() is raised
// -- which requires more time to investigate and test, it's commented out for now.
//var vec = Renderer.GetTextDimensions(
// sender.Text.Substring(sender.TextStartPosition, sender.InputPosition),
// sender.FontIndex);
//rect.X += (int)(vec.X * scaleRatio);

SetTextInputRectangle(rect);
}

void IIMEHandler.OnSelectedChanged(XNATextBox sender)
{
if (sender.WindowManager.SelectedControl == sender)
{
StopTextComposition();

if (!sender.IMEDisabled && sender.Enabled && sender.Visible)
{
IMEFocus = sender;

// Update the location of IME based on the textbox
SetIMETextInputRectangle(sender);

StartTextComposition();
}
else
{
IMEFocus = null;
}
}
else if (sender.WindowManager.SelectedControl is not XNATextBox)
{
// Disable IME since the current selected control is not XNATextBox
IMEFocus = null;
StopTextComposition();
}

// Note: if sender.WindowManager.SelectedControl != sender and is XNATextBox,
// another OnSelectedChanged() will be triggered,
// so we do not need to handle this case
}

void IIMEHandler.RegisterXNATextBox(XNATextBox sender, Action<char>? handleCharInput)
=> TextBoxHandleChatInputCallbacks[sender] = handleCharInput;

void IIMEHandler.KillXNATextBox(XNATextBox sender)
=> TextBoxHandleChatInputCallbacks.TryRemove(sender, out _);

bool IIMEHandler.HandleScrollLeftKey(XNATextBox sender)
=> !CompositionEmpty;

bool IIMEHandler.HandleScrollRightKey(XNATextBox sender)
=> !CompositionEmpty;

bool IIMEHandler.HandleBackspaceKey(XNATextBox sender)
{
bool handled = !LastActionIMEChatInput;
LastActionIMEChatInput = true;
//Debug.WriteLine($"IME: HandleBackspaceKey: handled: {handled}");
return handled;
}

bool IIMEHandler.HandleDeleteKey(XNATextBox sender)
{
bool handled = !LastActionIMEChatInput;
LastActionIMEChatInput = true;
//Debug.WriteLine($"IME: HandleDeleteKey: handled: {handled}");
return handled;
}

bool IIMEHandler.GetDrawCompositionText(XNATextBox sender, out string composition, out int compositionCursorPosition)
{
if (IMEFocus != sender || CompositionEmpty)
{
composition = string.Empty;
compositionCursorPosition = 0;
return false;
}

composition = Composition;
compositionCursorPosition = CompositionCursorPosition;
return true;
}

bool IIMEHandler.HandleCharInput(XNATextBox sender, char input)
=> TextCompositionEnabled;

bool IIMEHandler.HandleEnterKey(XNATextBox sender)
=> false;

bool IIMEHandler.HandleEscapeKey(XNATextBox sender)
{
//Debug.WriteLine($"IME: HandleEscapeKey: handled: {IMEEventReceived}");
return IMEEventReceived;
}

void IIMEHandler.OnTextChanged(XNATextBox sender) { }
}
17 changes: 17 additions & 0 deletions ClientGUI/IME/SdlIMEHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#nullable enable
using Microsoft.Xna.Framework;

namespace ClientGUI.IME;

/// <summary>
/// Integrate IME to DesktopGL(SDL2) platform.
/// </summary>
/// <remarks>
/// Note: We were unable to provide reliable input method support for
/// SDL2 due to the lack of a way to be able to stabilize hooks for
/// the SDL2 main loop.<br/>
/// Perhaps this requires some changes in Monogame.
/// </remarks>
internal sealed class SdlIMEHandler(Game game) : DummyIMEHandler

Check warning on line 15 in ClientGUI/IME/SdlIMEHandler.cs

View workflow job for this annotation

GitHub Actions / build-clients (Ares)

Parameter 'game' is unread.

Check warning on line 15 in ClientGUI/IME/SdlIMEHandler.cs

View workflow job for this annotation

GitHub Actions / build-clients (TS)

Parameter 'game' is unread.

Check warning on line 15 in ClientGUI/IME/SdlIMEHandler.cs

View workflow job for this annotation

GitHub Actions / build-clients (YR)

Parameter 'game' is unread.
{
}
Loading

0 comments on commit d658c28

Please sign in to comment.