Skip to content

Commit d658c28

Browse files
authored
Merge pull request #663 from CnCNet/develop
2.11.7.0
2 parents 95d43c2 + e5034eb commit d658c28

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+740
-719
lines changed

ClientCore/ClientConfiguration.cs

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public class ClientConfiguration
2121
private const string CLIENT_SETTINGS = "DTACnCNetClient.ini";
2222
private const string GAME_OPTIONS = "GameOptions.ini";
2323
private const string CLIENT_DEFS = "ClientDefinitions.ini";
24+
private const string NETWORK_DEFS_LOCAL = "NetworkDefinitions.local.ini";
2425
private const string NETWORK_DEFS = "NetworkDefinitions.ini";
2526

2627
private static ClientConfiguration _instance;
@@ -48,7 +49,17 @@ protected ClientConfiguration()
4849

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

51-
networkDefinitionsIni = new IniFile(SafePath.CombineFilePath(ProgramConstants.GetResourcePath(), NETWORK_DEFS));
52+
string networkDefsPathLocal = SafePath.CombineFilePath(ProgramConstants.GetResourcePath(), NETWORK_DEFS_LOCAL);
53+
if (File.Exists(networkDefsPathLocal))
54+
{
55+
networkDefinitionsIni = new IniFile(networkDefsPathLocal);
56+
Logger.Log("Loaded network definitions from NetworkDefinitions.local.ini (user override)");
57+
}
58+
else
59+
{
60+
string networkDefsPath = SafePath.CombineFilePath(ProgramConstants.GetResourcePath(), NETWORK_DEFS);
61+
networkDefinitionsIni = new IniFile(networkDefsPath);
62+
}
5263
}
5364

5465
/// <summary>
@@ -214,13 +225,13 @@ public void RefreshSettings()
214225

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

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

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

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

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

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

@@ -400,13 +411,13 @@ public IEnumerable<string> SupplementalMapFileExtensions
400411

401412
#region Network definitions
402413

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

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

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

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

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

ClientGUI/ClientGUI.csproj

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,15 @@
55
<ItemGroup>
66
<ProjectReference Include="..\ClientCore\ClientCore.csproj" />
77
</ItemGroup>
8+
<ItemGroup Condition="$(Configuration.Contains('GL'))">
9+
<!--Remove WinForm-->
10+
<Compile Remove="IME\WinFormsIMEHandler.cs" />
11+
<None Include="IME\WinFormsIMEHandler.cs" />
12+
</ItemGroup>
13+
<ItemGroup Condition="!$(Configuration.Contains('GL'))">
14+
<!--Remove SDL-->
15+
<Compile Remove="IME\SdlIMEHandler.cs" />
16+
<None Include="IME\SdlIMEHandler.cs" />
17+
<PackageReference Include="ImeSharp" />
18+
</ItemGroup>
819
</Project>

ClientGUI/GameProcessLogic.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public static void StartGameProcess(WindowManager windowManager)
3737
int waitTimes = 0;
3838
while (PreprocessorBackgroundTask.Instance.IsRunning)
3939
{
40+
Logger.Log("The preprocessor background task is still running. Wait for it...");
4041
Thread.Sleep(1000);
4142
waitTimes++;
4243
if (waitTimes > 10)

ClientGUI/IME/DummyIMEHandler.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#nullable enable
2+
using Microsoft.Xna.Framework;
3+
4+
namespace ClientGUI.IME
5+
{
6+
internal class DummyIMEHandler : IMEHandler
7+
{
8+
public DummyIMEHandler() { }
9+
10+
public override bool TextCompositionEnabled { get => false; protected set { } }
11+
12+
public override void SetTextInputRectangle(Rectangle rectangle) { }
13+
public override void StartTextComposition() { }
14+
public override void StopTextComposition() { }
15+
}
16+
}

ClientGUI/IME/IMEHandler.cs

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
#nullable enable
2+
using System;
3+
using System.Collections.Concurrent;
4+
using System.Diagnostics;
5+
6+
using Microsoft.Xna.Framework;
7+
8+
using Rampastring.XNAUI;
9+
using Rampastring.XNAUI.Input;
10+
using Rampastring.XNAUI.XNAControls;
11+
12+
namespace ClientGUI.IME;
13+
public abstract class IMEHandler : IIMEHandler
14+
{
15+
bool IIMEHandler.TextCompositionEnabled => TextCompositionEnabled;
16+
public abstract bool TextCompositionEnabled { get; protected set; }
17+
18+
private XNATextBox? _IMEFocus = null;
19+
public XNATextBox? IMEFocus
20+
{
21+
get => _IMEFocus;
22+
protected set
23+
{
24+
_IMEFocus = value;
25+
Debug.Assert(!_IMEFocus?.IMEDisabled ?? true, "IME focus should not be assigned from a textbox with IME disabled");
26+
}
27+
}
28+
29+
private string _composition = string.Empty;
30+
31+
public string Composition
32+
{
33+
get => _composition;
34+
protected set
35+
{
36+
string old = _composition;
37+
_composition = value;
38+
OnCompositionChanged(old, value);
39+
}
40+
}
41+
42+
public bool CompositionEmpty => string.IsNullOrEmpty(_composition);
43+
44+
protected bool IMEEventReceived = false;
45+
protected bool LastActionIMEChatInput = true;
46+
47+
private void OnCompositionChanged(string oldValue, string newValue)
48+
{
49+
//Debug.WriteLine($"IME: OnCompositionChanged: {newValue.Length - oldValue.Length}");
50+
51+
IMEEventReceived = true;
52+
// It seems that OnIMETextInput() is always triggered after OnCompositionChanged(). We expect such a behavior.
53+
LastActionIMEChatInput = false;
54+
}
55+
56+
protected ConcurrentDictionary<XNATextBox, Action<char>?> TextBoxHandleChatInputCallbacks = [];
57+
58+
public virtual int CompositionCursorPosition { get; set; }
59+
60+
public static IMEHandler Create(Game game)
61+
{
62+
#if DX
63+
return new WinFormsIMEHandler(game);
64+
#elif XNA
65+
// Warning: Think carefully before enabling WinFormsIMEHandler for XNA builds!
66+
// It *might* occasionally crash due to an unknown stack overflow issue.
67+
// This *might* be caused by both ImeSharp and XNAUI hooking into WndProc.
68+
// ImeSharp: https://github.com/ryancheung/ImeSharp/blob/dc2243beff9ef48eb37e398c506c905c965f8e68/ImeSharp/InputMethod.cs#L170
69+
// XNAUI: https://github.com/Rampastring/Rampastring.XNAUI/blob/9a7d5bb3e47ea50286ee05073d0a6723bc6d764d/Input/KeyboardEventInput.cs#L79
70+
//
71+
// That said, you can try returning a WinFormsIMEHandler and test if it is stable enough now. Who knows?
72+
return new DummyIMEHandler();
73+
#elif GL
74+
return new SdlIMEHandler(game);
75+
#else
76+
#error Unknown variant
77+
#endif
78+
}
79+
80+
public abstract void SetTextInputRectangle(Rectangle rectangle);
81+
82+
public abstract void StartTextComposition();
83+
84+
public abstract void StopTextComposition();
85+
86+
protected virtual void OnIMETextInput(char character)
87+
{
88+
//Debug.WriteLine($"IME: OnIMETextInput: {character} {(short)character}; IMEFocus is null? {IMEFocus == null}");
89+
90+
IMEEventReceived = true;
91+
LastActionIMEChatInput = true;
92+
93+
if (IMEFocus != null)
94+
{
95+
TextBoxHandleChatInputCallbacks.TryGetValue(IMEFocus, out var handleChatInput);
96+
handleChatInput?.Invoke(character);
97+
}
98+
}
99+
100+
public void SetIMETextInputRectangle(WindowManager manager)
101+
{
102+
// When the client window resizes, we should call SetIMETextInputRectangle()
103+
if (manager.SelectedControl is XNATextBox textBox)
104+
SetIMETextInputRectangle(textBox);
105+
}
106+
107+
private void SetIMETextInputRectangle(XNATextBox sender)
108+
{
109+
WindowManager windowManager = sender.WindowManager;
110+
111+
Rectangle textBoxRect = sender.RenderRectangle();
112+
double scaleRatio = windowManager.ScaleRatio;
113+
114+
Rectangle rect = new()
115+
{
116+
X = (int)(textBoxRect.X * scaleRatio + windowManager.SceneXPosition),
117+
Y = (int)(textBoxRect.Y * scaleRatio + windowManager.SceneYPosition),
118+
Width = (int)(textBoxRect.Width * scaleRatio),
119+
Height = (int)(textBoxRect.Height * scaleRatio)
120+
};
121+
122+
// The following code returns a more accurate location based on the current InputPosition.
123+
// However, as SetIMETextInputRectangle() does not automatically update with changes in InputPosition
124+
// (e.g., due to scrolling or mouse clicks altering the textbox's input position without shifting focus),
125+
// accuracy becomes inconsistent. Sometimes it's precise, other times it's off,
126+
// which is arguably worse than a consistent but manageable inaccuracy.
127+
// This inconsistency could lead to a confusing user experience,
128+
// as the input rectangle's position may not reliably reflect the current input position.
129+
// Therefore, unless whenever InputPosition is changed, SetIMETextInputRectangle() is raised
130+
// -- which requires more time to investigate and test, it's commented out for now.
131+
//var vec = Renderer.GetTextDimensions(
132+
// sender.Text.Substring(sender.TextStartPosition, sender.InputPosition),
133+
// sender.FontIndex);
134+
//rect.X += (int)(vec.X * scaleRatio);
135+
136+
SetTextInputRectangle(rect);
137+
}
138+
139+
void IIMEHandler.OnSelectedChanged(XNATextBox sender)
140+
{
141+
if (sender.WindowManager.SelectedControl == sender)
142+
{
143+
StopTextComposition();
144+
145+
if (!sender.IMEDisabled && sender.Enabled && sender.Visible)
146+
{
147+
IMEFocus = sender;
148+
149+
// Update the location of IME based on the textbox
150+
SetIMETextInputRectangle(sender);
151+
152+
StartTextComposition();
153+
}
154+
else
155+
{
156+
IMEFocus = null;
157+
}
158+
}
159+
else if (sender.WindowManager.SelectedControl is not XNATextBox)
160+
{
161+
// Disable IME since the current selected control is not XNATextBox
162+
IMEFocus = null;
163+
StopTextComposition();
164+
}
165+
166+
// Note: if sender.WindowManager.SelectedControl != sender and is XNATextBox,
167+
// another OnSelectedChanged() will be triggered,
168+
// so we do not need to handle this case
169+
}
170+
171+
void IIMEHandler.RegisterXNATextBox(XNATextBox sender, Action<char>? handleCharInput)
172+
=> TextBoxHandleChatInputCallbacks[sender] = handleCharInput;
173+
174+
void IIMEHandler.KillXNATextBox(XNATextBox sender)
175+
=> TextBoxHandleChatInputCallbacks.TryRemove(sender, out _);
176+
177+
bool IIMEHandler.HandleScrollLeftKey(XNATextBox sender)
178+
=> !CompositionEmpty;
179+
180+
bool IIMEHandler.HandleScrollRightKey(XNATextBox sender)
181+
=> !CompositionEmpty;
182+
183+
bool IIMEHandler.HandleBackspaceKey(XNATextBox sender)
184+
{
185+
bool handled = !LastActionIMEChatInput;
186+
LastActionIMEChatInput = true;
187+
//Debug.WriteLine($"IME: HandleBackspaceKey: handled: {handled}");
188+
return handled;
189+
}
190+
191+
bool IIMEHandler.HandleDeleteKey(XNATextBox sender)
192+
{
193+
bool handled = !LastActionIMEChatInput;
194+
LastActionIMEChatInput = true;
195+
//Debug.WriteLine($"IME: HandleDeleteKey: handled: {handled}");
196+
return handled;
197+
}
198+
199+
bool IIMEHandler.GetDrawCompositionText(XNATextBox sender, out string composition, out int compositionCursorPosition)
200+
{
201+
if (IMEFocus != sender || CompositionEmpty)
202+
{
203+
composition = string.Empty;
204+
compositionCursorPosition = 0;
205+
return false;
206+
}
207+
208+
composition = Composition;
209+
compositionCursorPosition = CompositionCursorPosition;
210+
return true;
211+
}
212+
213+
bool IIMEHandler.HandleCharInput(XNATextBox sender, char input)
214+
=> TextCompositionEnabled;
215+
216+
bool IIMEHandler.HandleEnterKey(XNATextBox sender)
217+
=> false;
218+
219+
bool IIMEHandler.HandleEscapeKey(XNATextBox sender)
220+
{
221+
//Debug.WriteLine($"IME: HandleEscapeKey: handled: {IMEEventReceived}");
222+
return IMEEventReceived;
223+
}
224+
225+
void IIMEHandler.OnTextChanged(XNATextBox sender) { }
226+
}

ClientGUI/IME/SdlIMEHandler.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#nullable enable
2+
using Microsoft.Xna.Framework;
3+
4+
namespace ClientGUI.IME;
5+
6+
/// <summary>
7+
/// Integrate IME to DesktopGL(SDL2) platform.
8+
/// </summary>
9+
/// <remarks>
10+
/// Note: We were unable to provide reliable input method support for
11+
/// SDL2 due to the lack of a way to be able to stabilize hooks for
12+
/// the SDL2 main loop.<br/>
13+
/// Perhaps this requires some changes in Monogame.
14+
/// </remarks>
15+
internal sealed class SdlIMEHandler(Game game) : DummyIMEHandler
16+
{
17+
}

0 commit comments

Comments
 (0)