From 9e7c3ea567bea8edaeb724b86c7b5baaa31c469a Mon Sep 17 00:00:00 2001 From: kmyuhkyuk <72241714+kmyuhkyuk@users.noreply.github.com> Date: Sun, 22 Mar 2026 16:04:20 +0800 Subject: [PATCH] Added Multilingual Localization Added Host IP Information Panel Used 7-bit variant to expand the playersInLobby list and slotId of LobbyMessage Fixed LAN UI was not displayed after switch save Fixed where last max player is very difficult to connect Fixed Join button on the controller is the same as the refresh button --- .../Components/AddressLineEdit.cs | 3 +- .../Components/CopiedLabel.cs | 70 +++ .../Components/IPAddressInfoPanel.cs | 474 ++++++++++++++++++ .../Components/IPAddressLabel.cs | 55 ++ .../Components/JoinButton.cs | 62 +++ .../Components/LanMultiplayerHostSubmenu.cs | 4 +- .../Components/LoadingIcon.cs | 25 + .../Components/PlayerNameLineEdit.cs | 3 +- .../Helpers/LanHostHelper.cs | 5 +- .../Helpers/PacketHelper.cs | 114 +++++ .../Patchs/BitSerializationUtilPatch.cs | 43 ++ .../Patchs/ENet/ENetClientPatch.cs | 9 +- .../Patchs/JoinFlowPatch.cs | 41 +- .../ClientLobbyJoinResponseMessagePatch.cs | 65 +++ .../Messages/LobbyBeginRunMessagePatch.cs | 45 ++ .../Patchs/Messages/LobbyPlayerPatch.cs | 44 ++ .../Patchs/Screens/NJoinFriendScreenPatch.cs | 64 +-- .../Screens/NMainMenuSubmenuStackPatch.cs | 5 +- .../NMultiplayerPlayerExpandedStatePatch.cs | 14 +- .../Screens/NMultiplayerSubmenuPatch.cs | 151 +++--- .../Patchs/Screens/NSettingsScreenPatch.cs | 57 ++- .../Patchs/Screens/RunLoadScreenPatchs.cs | 40 +- .../Patchs/Screens/RunScreenPatchs.cs | 111 +++- .../Services/RunScreenService.cs | 2 +- .../SlayTheSpire2.LAN.Multiplayer.csproj | 4 + localization/deu/main_menu_ui.json | 6 + localization/deu/settings_ui.json | 6 + localization/eng/main_menu_ui.json | 6 + localization/eng/settings_ui.json | 6 + localization/esp/main_menu_ui.json | 6 + localization/esp/settings_ui.json | 6 + localization/fra/main_menu_ui.json | 6 + localization/fra/settings_ui.json | 6 + localization/ita/main_menu_ui.json | 6 + localization/ita/settings_ui.json | 6 + localization/jpn/main_menu_ui.json | 6 + localization/jpn/settings_ui.json | 6 + localization/kor/main_menu_ui.json | 6 + localization/kor/settings_ui.json | 6 + localization/pol/main_menu_ui.json | 6 + localization/pol/settings_ui.json | 6 + localization/ptb/main_menu_ui.json | 6 + localization/ptb/settings_ui.json | 6 + localization/rus/main_menu_ui.json | 6 + localization/rus/settings_ui.json | 6 + localization/spa/main_menu_ui.json | 6 + localization/spa/settings_ui.json | 6 + localization/tha/main_menu_ui.json | 6 + localization/tha/settings_ui.json | 6 + localization/tur/main_menu_ui.json | 6 + localization/tur/settings_ui.json | 6 + localization/zhs/main_menu_ui.json | 6 + localization/zhs/settings_ui.json | 6 + 53 files changed, 1514 insertions(+), 164 deletions(-) create mode 100644 SlayTheSpire2.LAN.Multiplayer/Components/CopiedLabel.cs create mode 100644 SlayTheSpire2.LAN.Multiplayer/Components/IPAddressInfoPanel.cs create mode 100644 SlayTheSpire2.LAN.Multiplayer/Components/IPAddressLabel.cs create mode 100644 SlayTheSpire2.LAN.Multiplayer/Components/JoinButton.cs create mode 100644 SlayTheSpire2.LAN.Multiplayer/Components/LoadingIcon.cs create mode 100644 SlayTheSpire2.LAN.Multiplayer/Helpers/PacketHelper.cs create mode 100644 SlayTheSpire2.LAN.Multiplayer/Patchs/BitSerializationUtilPatch.cs create mode 100644 SlayTheSpire2.LAN.Multiplayer/Patchs/Messages/ClientLobbyJoinResponseMessagePatch.cs create mode 100644 SlayTheSpire2.LAN.Multiplayer/Patchs/Messages/LobbyBeginRunMessagePatch.cs create mode 100644 SlayTheSpire2.LAN.Multiplayer/Patchs/Messages/LobbyPlayerPatch.cs create mode 100644 localization/deu/main_menu_ui.json create mode 100644 localization/deu/settings_ui.json create mode 100644 localization/eng/main_menu_ui.json create mode 100644 localization/eng/settings_ui.json create mode 100644 localization/esp/main_menu_ui.json create mode 100644 localization/esp/settings_ui.json create mode 100644 localization/fra/main_menu_ui.json create mode 100644 localization/fra/settings_ui.json create mode 100644 localization/ita/main_menu_ui.json create mode 100644 localization/ita/settings_ui.json create mode 100644 localization/jpn/main_menu_ui.json create mode 100644 localization/jpn/settings_ui.json create mode 100644 localization/kor/main_menu_ui.json create mode 100644 localization/kor/settings_ui.json create mode 100644 localization/pol/main_menu_ui.json create mode 100644 localization/pol/settings_ui.json create mode 100644 localization/ptb/main_menu_ui.json create mode 100644 localization/ptb/settings_ui.json create mode 100644 localization/rus/main_menu_ui.json create mode 100644 localization/rus/settings_ui.json create mode 100644 localization/spa/main_menu_ui.json create mode 100644 localization/spa/settings_ui.json create mode 100644 localization/tha/main_menu_ui.json create mode 100644 localization/tha/settings_ui.json create mode 100644 localization/tur/main_menu_ui.json create mode 100644 localization/tur/settings_ui.json create mode 100644 localization/zhs/main_menu_ui.json create mode 100644 localization/zhs/settings_ui.json diff --git a/SlayTheSpire2.LAN.Multiplayer/Components/AddressLineEdit.cs b/SlayTheSpire2.LAN.Multiplayer/Components/AddressLineEdit.cs index 70fb56f..adcb37f 100644 --- a/SlayTheSpire2.LAN.Multiplayer/Components/AddressLineEdit.cs +++ b/SlayTheSpire2.LAN.Multiplayer/Components/AddressLineEdit.cs @@ -4,10 +4,11 @@ using MegaCrit.Sts2.Core.Nodes.GodotExtensions; namespace SlayTheSpire2.LAN.Multiplayer.Components { - internal class AddressLineEdit : NMegaLineEdit + internal partial class AddressLineEdit : NMegaLineEdit { public override void _Ready() { + base._Ready(); TextChanged += OnTextChanged; } diff --git a/SlayTheSpire2.LAN.Multiplayer/Components/CopiedLabel.cs b/SlayTheSpire2.LAN.Multiplayer/Components/CopiedLabel.cs new file mode 100644 index 0000000..a172f91 --- /dev/null +++ b/SlayTheSpire2.LAN.Multiplayer/Components/CopiedLabel.cs @@ -0,0 +1,70 @@ +using Godot; +using MegaCrit.Sts2.addons.mega_text; +using MegaCrit.Sts2.Core.Localization; +using MegaCrit.Sts2.Core.Localization.Fonts; + +namespace SlayTheSpire2.LAN.Multiplayer.Components +{ + internal partial class CopiedLabel : MegaLabel + { + private Tween? _tween; + + private const string LocKeyPrefix = "SlayTheSpire2.LAN.Multiplayer.COPIED"; + + public override void _Ready() + { + Modulate = new Color(Colors.White, 0); + + AutoSizeEnabled = false; + + MinFontSize = 24; + + AddThemeColorOverride("font_color", new Color(1.0f, 0.922f, 0.761f)); + AddThemeColorOverride("font_shadow_color", new Color(Colors.Black, 0.251f)); + + var font = GD.Load("res://themes/kreon_bold_glyph_space_one.tres"); + + AddThemeFontOverride("font", font); + AddThemeFontSizeOverride("font_size", 23); + + RefreshLabel(); + + base._Ready(); + } + + public void ShowWithPosition(Vector2 position) + { + _tween?.Kill(); + + Modulate = new Color(Colors.White); + + GlobalPosition = position; + + _tween = GetTree().CreateTween(); + + _tween.SetParallel(); + + _tween.TweenProperty(this, "position:y", Position.Y - 30, 0.3f).SetTrans(Tween.TransitionType.Cubic) + .SetEase(Tween.EaseType.Out); + + _tween.Chain().TweenProperty(this, "modulate:a", 0, 0.4f).SetDelay(0.8f); + } + + public override void _Notification(int what) + { + if ((long)what == 2010 && IsNodeReady()) + { + RefreshLabel(); + } + + base._Notification(what); + } + + private void RefreshLabel() + { + var locString = new LocString("main_menu_ui", LocKeyPrefix); + SetTextAutoSize(locString.GetFormattedText()); + this.ApplyLocaleFontSubstitution(FontType.Regular, ThemeConstants.Label.font); + } + } +} \ No newline at end of file diff --git a/SlayTheSpire2.LAN.Multiplayer/Components/IPAddressInfoPanel.cs b/SlayTheSpire2.LAN.Multiplayer/Components/IPAddressInfoPanel.cs new file mode 100644 index 0000000..1bcaf14 --- /dev/null +++ b/SlayTheSpire2.LAN.Multiplayer/Components/IPAddressInfoPanel.cs @@ -0,0 +1,474 @@ +using System.Net.NetworkInformation; +using System.Net.Sockets; +using Godot; +using MegaCrit.Sts2.Core.Helpers; +using MegaCrit.Sts2.Core.Logging; +using MegaCrit.Sts2.Core.Nodes.CommonUi; +using SlayTheSpire2.LAN.Multiplayer.Services; +using BoxContainer = Godot.BoxContainer; +using Control = Godot.Control; +using HttpClient = System.Net.Http.HttpClient; + +namespace SlayTheSpire2.LAN.Multiplayer.Components +{ + internal partial class IPAddressInfoPanel : Control + { + private Control? _content; + + private Control? _menu; + + private Control? _box; + + private Control? _loading; + + private CopiedLabel? _copiedLabel; + + private Control? _ipAddress; + + private IPAddressLabel? _ipAddressTitleLabel; + + private IPAddressLabel? _ipAddressLabel; + + private Control? _ipv6Address; + + private IPAddressLabel? _ipv6AddressTitleLabel; + + private IPAddressLabel? _ipv6AddressLabel; + + private Control? _localIPAddress; + + private IPAddressLabel? _localIPAddressTitleLabel; + + private Control? _localIPAddressContainer; + + private CancellationTokenSource? _cancellationTokenSource; + + private static readonly HttpClient HttpClient = new(); + + public static IPAddressInfoPanel Create() + { + var ipAddressInfoPanel = new IPAddressInfoPanel(); + + ipAddressInfoPanel.MouseFilter = MouseFilterEnum.Ignore; + + ipAddressInfoPanel.SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect); + + var content = new VBoxContainer { Name = "Content" }; + ipAddressInfoPanel.AddChild(content); + + content.MouseFilter = MouseFilterEnum.Stop; + + var menu = new Control { Name = "Menu" }; + content.AddChild(menu); + + menu.CustomMinimumSize = new Vector2(300, 24); + + menu.MouseFilter = MouseFilterEnum.Pass; + + var background = new NinePatchRect { Name = "Background" }; + menu.AddChild(background); + + background.MouseFilter = MouseFilterEnum.Ignore; + + background.SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect); + + background.Texture = GD.Load("res://images/ui/tiny_nine_patch.png"); + background.PatchMarginLeft = 12; + background.PatchMarginTop = 12; + background.PatchMarginRight = 12; + background.PatchMarginBottom = 12; + + background.Modulate = new Color(Colors.Black, 0.471f); + + var menuIcon = new TextureRect { Name = "Icon" }; + menu.AddChild(menuIcon); + + menuIcon.MouseFilter = MouseFilterEnum.Ignore; + + menuIcon.SetAnchorsPreset(LayoutPreset.Center); + menuIcon.OffsetLeft = -12; + menuIcon.OffsetTop = -12; + menuIcon.OffsetRight = 12; + menuIcon.OffsetBottom = 12; + + var menuSvgImage = new Image(); + + menuSvgImage.LoadSvgFromString( + ""); + + menuIcon.Texture = ImageTexture.CreateFromImage(menuSvgImage); + + var box = new PanelContainer { Name = "Box" }; + content.AddChild(box); + + box.MouseFilter = MouseFilterEnum.Ignore; + + box.Modulate = new Color(Colors.White, 0); + + var styleBox = new StyleBoxTexture(); + + styleBox.Texture = GD.Load("res://images/ui/tiny_nine_patch.png"); + styleBox.TextureMarginLeft = 12; + styleBox.TextureMarginTop = 12; + styleBox.TextureMarginRight = 12; + styleBox.TextureMarginBottom = 12; + + styleBox.ContentMarginLeft = 12; + styleBox.ContentMarginTop = 12; + styleBox.ContentMarginRight = 12; + styleBox.ContentMarginBottom = 12; + + styleBox.ModulateColor = new Color(Colors.Black, 0.471f); + + box.AddThemeStyleboxOverride("panel", styleBox); + + var container = new VBoxContainer { Name = "Container" }; + box.AddChild(container); + + container.MouseFilter = MouseFilterEnum.Ignore; + + container.SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect); + + container.Alignment = BoxContainer.AlignmentMode.Center; + + container.AddThemeConstantOverride("separation", 8); + + var loading = new Control { Name = "Loading" }; + container.AddChild(loading); + + loading.CustomMinimumSize = new Vector2(64, 64); + + loading.MouseFilter = MouseFilterEnum.Ignore; + + var loadingIcon = new LoadingIcon(); + loading.AddChild(loadingIcon); + + loadingIcon.MouseFilter = MouseFilterEnum.Ignore; + + loadingIcon.SetAnchorsPreset(LayoutPreset.Center); + loadingIcon.OffsetLeft = -32; + loadingIcon.OffsetTop = -32; + loadingIcon.OffsetRight = 32; + loadingIcon.OffsetBottom = 32; + + AddAddressElement(container, "IPAddress", "SlayTheSpire2.LAN.Multiplayer.IP_ADDRESS_TITLE", false); + AddAddressElement(container, "IPV6IPAddress", "SlayTheSpire2.LAN.Multiplayer.IPV6_ADDRESS_TITLE", true); + AddAddressContainer(container, "LocalIPAddress", "SlayTheSpire2.LAN.Multiplayer.LOCAL_IP_ADDRESS_TITLE"); + + content.SetAnchorsAndOffsetsPreset(LayoutPreset.CenterTop); + + var copiedLabel = new CopiedLabel { Name = "CopiedLabel" }; + ipAddressInfoPanel.AddChild(copiedLabel); + + copiedLabel.MouseFilter = MouseFilterEnum.Ignore; + + return ipAddressInfoPanel; + } + + private static void AddAddressElement(Node container, string name, string locKeyPrefix, bool isTrim) + { + var addressElement = new HBoxContainer { Name = name }; + container.AddChild(addressElement); + + addressElement.CustomMinimumSize = new Vector2(0, 24); + + addressElement.Alignment = BoxContainer.AlignmentMode.Center; + + addressElement.MouseFilter = MouseFilterEnum.Ignore; + + var ipAddressTitleLabel = new IPAddressLabel { Name = "TitleLabel" }; + addressElement.AddChild(ipAddressTitleLabel); + + ipAddressTitleLabel.MouseFilter = MouseFilterEnum.Ignore; + + ipAddressTitleLabel.SetLocalization(locKeyPrefix); + + var ipAddressLabel = new IPAddressLabel { Name = "Label" }; + addressElement.AddChild(ipAddressLabel); + + ipAddressLabel.MouseFilter = MouseFilterEnum.Ignore; + + if (isTrim) + { + ipAddressLabel.SizeFlagsHorizontal = SizeFlags.ExpandFill; + ipAddressLabel.TextOverrunBehavior = TextServer.OverrunBehavior.TrimEllipsis; + } + } + + private static void AddAddressContainer(Node container, string name, string locKeyPrefix) + { + var addressElement = new VBoxContainer { Name = name }; + container.AddChild(addressElement); + + addressElement.CustomMinimumSize = new Vector2(0, 24); + + addressElement.MouseFilter = MouseFilterEnum.Ignore; + + var ipAddressTitleLabel = new IPAddressLabel { Name = "TitleLabel" }; + addressElement.AddChild(ipAddressTitleLabel); + + ipAddressTitleLabel.HorizontalAlignment = HorizontalAlignment.Center; + + ipAddressTitleLabel.SetLocalization(locKeyPrefix); + + var vBoxContainer = new VBoxContainer { Name = "Container" }; + addressElement.AddChild(vBoxContainer); + + vBoxContainer.MouseFilter = MouseFilterEnum.Ignore; + + vBoxContainer.Alignment = BoxContainer.AlignmentMode.Center; + } + + public override void _Ready() + { + _content = GetNode("Content"); + _menu = GetNode("Content/Menu"); + _box = GetNode("Content/Box"); + + _loading = GetNode("Content/Box/Container/Loading"); + + _ipAddress = GetNode("Content/Box/Container/IPAddress"); + _ipAddressTitleLabel = _ipAddress.GetNode("TitleLabel"); + _ipAddressLabel = _ipAddress.GetNode("Label"); + + _copiedLabel = GetNode("CopiedLabel"); + + _ipAddress.GuiInput += inputEvent => + { + if (inputEvent is InputEventMouseButton + { + ButtonIndex: MouseButton.Left, Pressed: true + } inputEventMouseButton && + !string.IsNullOrEmpty(_ipAddressLabel.Text)) + { + DisplayServer.ClipboardSet(_ipAddressLabel.Text); + _copiedLabel.ShowWithPosition(inputEventMouseButton.GlobalPosition); + } + }; + + _ipv6Address = GetNode("Content/Box/Container/IPV6IPAddress"); + _ipv6AddressTitleLabel = _ipv6Address.GetNode("TitleLabel"); + _ipv6AddressLabel = _ipv6Address.GetNode("Label"); + + _ipv6Address.GuiInput += inputEvent => + { + if (inputEvent is InputEventMouseButton + { + ButtonIndex: MouseButton.Left, Pressed: true + } inputEventMouseButton && + !string.IsNullOrEmpty(_ipv6AddressLabel.Text)) + { + DisplayServer.ClipboardSet(_ipv6AddressLabel.Text); + _copiedLabel.ShowWithPosition(inputEventMouseButton.GlobalPosition); + } + }; + + _localIPAddress = GetNode("Content/Box/Container/LocalIPAddress"); + _localIPAddressTitleLabel = _localIPAddress.GetNode("TitleLabel"); + _localIPAddressContainer = _localIPAddress.GetNode("Container"); + + UpdateController(); + + _menu.MouseEntered += OnMouseEntered; + _content.MouseExited += OnMouseExited; + + NControllerManager.Instance?.Connect(NControllerManager.SignalName.MouseDetected, + Callable.From(UpdateController)); + NControllerManager.Instance?.Connect(NControllerManager.SignalName.ControllerDetected, + Callable.From(UpdateController)); + NInputManager.Instance?.Connect(NInputManager.SignalName.InputRebound, Callable.From(UpdateController)); + } + + private void UpdateController() + { + if (NControllerManager.Instance?.IsUsingController ?? false) + { + ShowBox(); + } + } + + public void Initialize() + { + _cancellationTokenSource?.Cancel(); + _cancellationTokenSource?.Dispose(); + + _cancellationTokenSource = new CancellationTokenSource(); + + TaskHelper.RunSafely(InitializeAsync(_cancellationTokenSource.Token)); + } + + private async Task InitializeAsync(CancellationToken cancellationToken) + { + if (_ipAddress == null || _localIPAddress == null || _ipv6Address == null || _loading == null || + _content == null || _localIPAddressContainer == null) + { + Log.Error($"{nameof(IPAddressInfoPanel)} has null element"); + return; + } + + _ipAddress.Visible = false; + _localIPAddress.Visible = false; + _ipv6Address.Visible = false; + + foreach (var child in _localIPAddressContainer.GetChildren()) + { + child.QueueFree(); + } + + _loading.Visible = true; + + _content.Size = _content.CustomMinimumSize; + + var ipAddress = string.Empty; + var port = SettingsService.Instance.SettingsModel.HostPort; + + var hasIPAddress = false; + + try + { + ipAddress = await HttpClient.GetStringAsync("https://api-ipv4.ip.sb/ip", cancellationToken); + if (!string.IsNullOrEmpty(ipAddress)) + { + _ipAddressLabel?.SetTextAutoSize($"{ipAddress.Replace("\n", string.Empty)}:{port}"); + hasIPAddress = true; + } + } + catch (OperationCanceledException ex) + { + Log.Debug(ex.Message); + } + catch (Exception ex) + { + Log.Warn(ex.Message); + } + + var hasLocalIPAddress = false; + + var localIPAddressList = GetLocalIPAddressList(); + + foreach (var localIPAddress in localIPAddressList) + { + if (localIPAddress != ipAddress) + { + var ipAddressLabel = new IPAddressLabel(); + _localIPAddressContainer.AddChild(ipAddressLabel); + + ipAddressLabel.MouseFilter = MouseFilterEnum.Pass; + + ipAddressLabel.HorizontalAlignment = HorizontalAlignment.Center; + + ipAddressLabel.Text = $"{localIPAddress}:{port}"; + + ipAddressLabel.GuiInput += inputEvent => + { + if (inputEvent is InputEventMouseButton + { + ButtonIndex: MouseButton.Left, Pressed: true + } inputEventMouseButton && + !string.IsNullOrEmpty(ipAddressLabel.Text)) + { + DisplayServer.ClipboardSet(ipAddressLabel.Text); + _copiedLabel?.ShowWithPosition(inputEventMouseButton.GlobalPosition); + } + }; + + hasLocalIPAddress = true; + } + } + + var hasIPV6Address = false; + + try + { + var ipv6Address = await HttpClient.GetStringAsync("https://api-ipv6.ip.sb/ip", cancellationToken); + _ipv6AddressLabel?.SetTextAutoSize($"[{ipv6Address.Replace("\n", string.Empty)}]:{port}"); + hasIPV6Address = true; + } + catch (OperationCanceledException ex) + { + Log.Debug(ex.Message); + } + catch (Exception ex) + { + Log.Warn(ex.Message); + } + + _loading.Visible = false; + + _ipAddress.Visible = hasIPAddress; + _localIPAddress.Visible = hasLocalIPAddress; + _ipv6Address.Visible = hasIPV6Address; + + _content.SetAnchorsAndOffsetsPreset(LayoutPreset.CenterTop); + } + + private static List GetLocalIPAddressList() + { + var list = new List(); + + foreach (var networkInterface in NetworkInterface.GetAllNetworkInterfaces()) + { + if (networkInterface.OperationalStatus == OperationalStatus.Up && + networkInterface.NetworkInterfaceType != NetworkInterfaceType.Loopback) + { + if (networkInterface.Name.Contains("vEthernet")) + continue; + + foreach (var ip in networkInterface.GetIPProperties().UnicastAddresses) + { + if (ip.Address.AddressFamily == AddressFamily.InterNetwork) + { + list.Add(ip.Address.ToString()); + } + } + } + } + + return list; + } + + private void OnMouseEntered() + { + if (NControllerManager.Instance?.IsUsingController ?? false) + return; + + ShowBox(); + } + + private void OnMouseExited() + { + if (NControllerManager.Instance?.IsUsingController ?? false) + return; + + HideBox(); + } + + private void ShowBox() + { + if (_box == null || _ipAddress == null || _localIPAddress == null || _ipv6Address == null) + return; + + _box.Modulate = new Color(Colors.White); + _ipAddress.MouseFilter = MouseFilterEnum.Pass; + _localIPAddress.MouseFilter = MouseFilterEnum.Pass; + _ipv6Address.MouseFilter = MouseFilterEnum.Pass; + } + + private void HideBox() + { + if (_box == null || _ipAddress == null || _localIPAddress == null || _ipv6Address == null) + return; + + _box.Modulate = new Color(Colors.White, 0); + _ipAddress.MouseFilter = MouseFilterEnum.Ignore; + _localIPAddress.MouseFilter = MouseFilterEnum.Ignore; + _ipv6Address.MouseFilter = MouseFilterEnum.Ignore; + } + + public override void _ExitTree() + { + _cancellationTokenSource?.Cancel(); + _cancellationTokenSource?.Dispose(); + } + } +} \ No newline at end of file diff --git a/SlayTheSpire2.LAN.Multiplayer/Components/IPAddressLabel.cs b/SlayTheSpire2.LAN.Multiplayer/Components/IPAddressLabel.cs new file mode 100644 index 0000000..0a922ae --- /dev/null +++ b/SlayTheSpire2.LAN.Multiplayer/Components/IPAddressLabel.cs @@ -0,0 +1,55 @@ +using Godot; +using MegaCrit.Sts2.addons.mega_text; +using MegaCrit.Sts2.Core.Localization; +using MegaCrit.Sts2.Core.Localization.Fonts; + +namespace SlayTheSpire2.LAN.Multiplayer.Components +{ + internal partial class IPAddressLabel : MegaLabel + { + private string? _locKeyPrefix; + + public override void _Ready() + { + AutoSizeEnabled = false; + + MinFontSize = 24; + + AddThemeColorOverride("font_color", new Color(1.0f, 0.922f, 0.761f)); + AddThemeColorOverride("font_shadow_color", new Color(Colors.Black, 0.251f)); + + var font = GD.Load("res://themes/kreon_bold_glyph_space_one.tres"); + + AddThemeFontOverride("font", font); + AddThemeFontSizeOverride("font_size", 23); + + base._Ready(); + } + + public override void _Notification(int what) + { + if ((long)what == 2010 && IsNodeReady()) + { + RefreshLabel(); + } + + base._Notification(what); + } + + public void SetLocalization(string locKeyPrefix) + { + _locKeyPrefix = locKeyPrefix; + RefreshLabel(); + } + + private void RefreshLabel() + { + if (_locKeyPrefix != null) + { + var locString = new LocString("main_menu_ui", _locKeyPrefix); + SetTextAutoSize(locString.GetFormattedText()); + this.ApplyLocaleFontSubstitution(FontType.Regular, ThemeConstants.Label.font); + } + } + } +} \ No newline at end of file diff --git a/SlayTheSpire2.LAN.Multiplayer/Components/JoinButton.cs b/SlayTheSpire2.LAN.Multiplayer/Components/JoinButton.cs new file mode 100644 index 0000000..64b4147 --- /dev/null +++ b/SlayTheSpire2.LAN.Multiplayer/Components/JoinButton.cs @@ -0,0 +1,62 @@ +using Godot; +using MegaCrit.Sts2.addons.mega_text; +using MegaCrit.Sts2.Core.ControllerInput; +using MegaCrit.Sts2.Core.Localization; +using MegaCrit.Sts2.Core.Nodes.Screens.MainMenu; + +namespace SlayTheSpire2.LAN.Multiplayer.Components +{ + internal partial class JoinButton : NJoinFriendRefreshButton + { + protected override string[] Hotkeys => [MegaInput.viewMap]; + + public static JoinButton Create(NJoinFriendRefreshButton joinFriendRefreshButton) + { + var joinButton = new JoinButton(); + + joinButton.CustomMinimumSize = new Vector2(150, 50); + joinButton.SizeFlagsHorizontal = SizeFlags.ShrinkCenter; + + joinButton.MouseFilter = MouseFilterEnum.Stop; + + joinButton.Material = joinFriendRefreshButton.Material.Duplicate() as Material; + + var background = new NinePatchRect { Name = "Background" }; + joinButton.AddChild(background); + + background.MouseFilter = MouseFilterEnum.Ignore; + + background.SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect); + + background.Texture = GD.Load("res://images/ui/tiny_nine_patch.png"); + background.PatchMarginLeft = 12; + background.PatchMarginTop = 12; + background.PatchMarginRight = 12; + background.PatchMarginBottom = 12; + + background.Modulate = joinFriendRefreshButton.SelfModulate; + background.Material = joinButton.Material; + + foreach (var child in joinFriendRefreshButton.GetChildren()) + { + joinButton.AddChild(child.Duplicate()); + } + + var controllerIcon = joinButton.GetNode("ControllerIcon"); + + controllerIcon.Owner = joinButton; + + controllerIcon.Position = new Vector2(controllerIcon.Position.X - 12, controllerIcon.Position.Y); + + return joinButton; + } + + public override void _Ready() + { + base._Ready(); + + var node = GetNode("Label"); + node.SetTextAutoSize(new LocString("main_menu_ui", "JOIN.title").GetFormattedText()); + } + } +} \ No newline at end of file diff --git a/SlayTheSpire2.LAN.Multiplayer/Components/LanMultiplayerHostSubmenu.cs b/SlayTheSpire2.LAN.Multiplayer/Components/LanMultiplayerHostSubmenu.cs index 2d36934..5994b64 100644 --- a/SlayTheSpire2.LAN.Multiplayer/Components/LanMultiplayerHostSubmenu.cs +++ b/SlayTheSpire2.LAN.Multiplayer/Components/LanMultiplayerHostSubmenu.cs @@ -11,7 +11,7 @@ using SlayTheSpire2.LAN.Multiplayer.Services; namespace SlayTheSpire2.LAN.Multiplayer.Components { - internal class LanMultiplayerHostSubmenu : NMultiplayerHostSubmenu + internal partial class LanMultiplayerHostSubmenu : NMultiplayerHostSubmenu { private static readonly string ScenePath = SceneHelper.GetScenePath("screens/multiplayer_host_submenu"); @@ -21,7 +21,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Components public new static NMultiplayerHostSubmenu? Create() { - if (Instance != null) + if (IsInstanceValid(Instance)) return Instance; if (TestMode.IsOn) diff --git a/SlayTheSpire2.LAN.Multiplayer/Components/LoadingIcon.cs b/SlayTheSpire2.LAN.Multiplayer/Components/LoadingIcon.cs new file mode 100644 index 0000000..b09adcd --- /dev/null +++ b/SlayTheSpire2.LAN.Multiplayer/Components/LoadingIcon.cs @@ -0,0 +1,25 @@ +using Godot; + +namespace SlayTheSpire2.LAN.Multiplayer.Components +{ + internal partial class LoadingIcon : TextureRect + { + private Tween? _tween; + + public override void _Ready() + { + var svgImage = new Image(); + + svgImage.LoadSvgFromString( + ""); + + Texture = ImageTexture.CreateFromImage(svgImage); + + PivotOffset = Size / 2; + + _tween = CreateTween().SetLoops(); + + _tween.TweenProperty(this, "rotation", Mathf.DegToRad(360), 2f).AsRelative(); + } + } +} \ No newline at end of file diff --git a/SlayTheSpire2.LAN.Multiplayer/Components/PlayerNameLineEdit.cs b/SlayTheSpire2.LAN.Multiplayer/Components/PlayerNameLineEdit.cs index 13b1dd7..bd8c7e9 100644 --- a/SlayTheSpire2.LAN.Multiplayer/Components/PlayerNameLineEdit.cs +++ b/SlayTheSpire2.LAN.Multiplayer/Components/PlayerNameLineEdit.cs @@ -13,8 +13,9 @@ namespace SlayTheSpire2.LAN.Multiplayer.Components public bool IsEmpty => string.IsNullOrEmpty(Text); - public PlayerNameLineEdit() + public override void _Ready() { + base._Ready(); TextChanged += OnTextChanged; } diff --git a/SlayTheSpire2.LAN.Multiplayer/Helpers/LanHostHelper.cs b/SlayTheSpire2.LAN.Multiplayer/Helpers/LanHostHelper.cs index 11381ac..33eb855 100644 --- a/SlayTheSpire2.LAN.Multiplayer/Helpers/LanHostHelper.cs +++ b/SlayTheSpire2.LAN.Multiplayer/Helpers/LanHostHelper.cs @@ -32,7 +32,8 @@ namespace SlayTheSpire2.LAN.Multiplayer.Helpers { var netService = new NetHostGameService(); NetErrorInfo? netErrorInfo = null; - netService.StartENetHost(port, maxPlayers); + //Add one more max client to send the full lobby message + netService.StartENetHost(port, maxPlayers + 1); Log.Info($"HostGame open on port:{port}"); if (!netErrorInfo.HasValue) { @@ -94,7 +95,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Helpers { var netService = new NetHostGameService(); NetErrorInfo? netErrorInfo = null; - netService.StartENetHost(port, maxPlayers); + netService.StartENetHost(port, maxPlayers + 1); Log.Info($"HostGame open on port:{port}"); if (!netErrorInfo.HasValue) { diff --git a/SlayTheSpire2.LAN.Multiplayer/Helpers/PacketHelper.cs b/SlayTheSpire2.LAN.Multiplayer/Helpers/PacketHelper.cs new file mode 100644 index 0000000..9aa7db7 --- /dev/null +++ b/SlayTheSpire2.LAN.Multiplayer/Helpers/PacketHelper.cs @@ -0,0 +1,114 @@ +using System.Reflection; +using HarmonyLib; +using MegaCrit.Sts2.Core.Multiplayer.Serialization; +using SlayTheSpire2.LAN.Multiplayer.Patchs; + +// ReSharper disable ClassNeverInstantiated.Global + +namespace SlayTheSpire2.LAN.Multiplayer.Helpers +{ + internal class PacketHelper + { + private static readonly Action SetPacketWriterBitPosition; + + private static readonly Action SetPacketReaderBitPosition; + + private static readonly AccessTools.FieldRef RefPacketWriterTempBuffer; + + private static readonly AccessTools.FieldRef RefPacketReaderTempBuffer; + + static PacketHelper() + { + const BindingFlags flags = BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public; + + SetPacketWriterBitPosition = + AccessTools.MethodDelegate>(typeof(PacketWriter) + .GetProperty("BitPosition", flags) + ?.SetMethod!); + SetPacketReaderBitPosition = + AccessTools.MethodDelegate>(typeof(PacketReader) + .GetProperty("BitPosition", flags) + ?.SetMethod!); + + RefPacketWriterTempBuffer = + AccessTools.FieldRefAccess("_tempBuffer"); + RefPacketReaderTempBuffer = + AccessTools.FieldRefAccess("_tempBuffer"); + } + + public static void WriteList(PacketWriter instance, IReadOnlyList list) + where T : IPacketSerializable, new() + { + WriteVarInt(instance, (uint)list.Count); + foreach (var item in list) + { + instance.Write(item); + } + } + + public static void WriteVarInt(PacketWriter writer, uint val) + { + var tempBuffer = RefPacketWriterTempBuffer(writer); + + var span = tempBuffer.AsSpan(); + + var bytesWritten = 0; + + while (val >= 0x80) + { + span[bytesWritten++] = (byte)((val & 0x7F) | 0x80); + val >>= 7; + } + + span[bytesWritten++] = (byte)val; + + var totalBits = bytesWritten * 8; + BitSerializationUtilWriteBytesPatch.WriteBytes(tempBuffer, writer.Buffer, writer.BitPosition, totalBits); + SetPacketWriterBitPosition(writer, writer.BitPosition + totalBits); + } + + public static List ReadList(PacketReader reader) where T : IPacketSerializable, new() + { + var list = new List(); + var num = ReadVarInt(reader); + for (var i = 0; i < num; i++) + { + list.Add(reader.Read()); + } + + return list; + } + + public static uint ReadVarInt(PacketReader reader) + { + var tempBuffer = RefPacketReaderTempBuffer(reader); + + uint result = 0; + var shift = 0; + + while (true) + { + Array.Clear(tempBuffer); + + //7-bit VarInt and 1-bit Flag + BitSerializationUtilReadBitsPatch.ReadBits(reader.Buffer, reader.BitPosition, tempBuffer, 8); + + SetPacketReaderBitPosition(reader, reader.BitPosition + 8); + + result |= (uint)(tempBuffer[0] & 0x7F) << shift; + + if ((tempBuffer[0] & 0x80) == 0) + break; + + shift += 7; + + if (shift >= 35) + { + throw new Exception("VarInt Invalid"); + } + } + + return result; + } + } +} \ No newline at end of file diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/BitSerializationUtilPatch.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/BitSerializationUtilPatch.cs new file mode 100644 index 0000000..777bf4b --- /dev/null +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/BitSerializationUtilPatch.cs @@ -0,0 +1,43 @@ +using System.Reflection; +using HarmonyLib; + +// ReSharper disable ClassNeverInstantiated.Global +// ReSharper disable UnusedMember.Global +// ReSharper disable UnusedType.Global + +namespace SlayTheSpire2.LAN.Multiplayer.Patchs +{ + [HarmonyPatch] + internal class BitSerializationUtilWriteBytesPatch + { + private static MethodInfo TargetMethod() + { + return AccessTools.TypeByName("MegaCrit.Sts2.Core.Multiplayer.Serialization.BitSerializationUtil") + .GetMethod("WriteBytes", BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public)!; + } + + [HarmonyReversePatch] + public static void WriteBytes(byte[] originBuffer, byte[] destinationBuffer, int destinationBitPosition, + int totalBitsToWrite) + { + throw new NotImplementedException(); + } + } + + [HarmonyPatch] + internal class BitSerializationUtilReadBitsPatch + { + private static MethodInfo TargetMethod() + { + return AccessTools.TypeByName("MegaCrit.Sts2.Core.Multiplayer.Serialization.BitSerializationUtil") + .GetMethod("ReadBits", BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public)!; + } + + [HarmonyReversePatch] + public static void ReadBits(byte[] originBuffer, int originBitPosition, byte[] destinationBuffer, + int totalBitsToRead) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/ENet/ENetClientPatch.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/ENet/ENetClientPatch.cs index eb79b0a..a1c3072 100644 --- a/SlayTheSpire2.LAN.Multiplayer/Patchs/ENet/ENetClientPatch.cs +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/ENet/ENetClientPatch.cs @@ -15,6 +15,13 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.ENet [HarmonyPatch(typeof(ENetClient), "ConnectToHost")] internal class ENetClientConnectToHostPatch { + [HarmonyReversePatch] + [HarmonyPatch(typeof(ENetClient), "HandleMessageReceived")] + private static void HandleMessageReceived(ENetClient instance, ENetServiceData data) + { + throw new NotImplementedException(); + } + private static bool Prefix(ENetClient __instance, ulong netId, string ip, ushort port, CancellationToken cancelToken, Logger ____logger, INetClientHandler ____handler, ref Task __result) { @@ -82,7 +89,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.ENet handler.OnConnectedToHost(); foreach (var item in bufferedPackets) { - Traverse.Create(eNetClient).Method("HandleMessageReceived", item).GetValue(); + HandleMessageReceived(eNetClient, item); } return null; diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/JoinFlowPatch.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/JoinFlowPatch.cs index 2b35b09..32af667 100644 --- a/SlayTheSpire2.LAN.Multiplayer/Patchs/JoinFlowPatch.cs +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/JoinFlowPatch.cs @@ -3,6 +3,7 @@ using MegaCrit.Sts2.Core.Entities.Multiplayer; using MegaCrit.Sts2.Core.Multiplayer.Connection; using MegaCrit.Sts2.Core.Multiplayer.Game; using MegaCrit.Sts2.Core.Multiplayer.Messages.Lobby; +using MegaCrit.Sts2.Core.Platform; using SlayTheSpire2.LAN.Multiplayer.Models; using SlayTheSpire2.LAN.Multiplayer.Services; @@ -24,7 +25,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs { var result = await clientLobbyJoinResponseMessage; - if (joinFlow.NetService == null) + if (joinFlow.NetService is not { Platform: PlatformType.None }) return result; var lanPlayerNameService = LanPlayerNameService.Instance; @@ -51,7 +52,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs { var result = await clientLoadJoinResponseMessage; - if (joinFlow.NetService == null) + if (joinFlow.NetService is not { Platform: PlatformType.None }) return result; var lanPlayerNameService = LanPlayerNameService.Instance; @@ -78,7 +79,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs { var result = await clientRejoinResponseMessage; - if (joinFlow.NetService == null) + if (joinFlow.NetService is not { Platform: PlatformType.None }) return result; var lanPlayerNameService = LanPlayerNameService.Instance; @@ -95,17 +96,20 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs [HarmonyPatch(typeof(JoinFlow), "OnDisconnected")] internal class JoinFlowOnDisconnectedPatch { - private static void Postfix(NetErrorInfo info) + private static void Postfix(JoinFlow __instance, NetErrorInfo info) { - var exception = - new ClientConnectionFailedException( - $"Unexpectedly disconnected from host while joining. Reason: {info.GetReason()}", info); - - var lanPlayerNameCompletion = LanPlayerNameService.Instance.LanPlayerNameCompletion; - - if (lanPlayerNameCompletion?.Task is { IsCompleted: false }) + if (__instance.NetService is { Platform: PlatformType.None }) { - lanPlayerNameCompletion.SetException(exception); + var lanPlayerNameCompletion = LanPlayerNameService.Instance.LanPlayerNameCompletion; + + if (lanPlayerNameCompletion?.Task is { IsCompleted: false }) + { + var exception = + new ClientConnectionFailedException( + $"Unexpectedly disconnected from host while joining. Reason: {info.GetReason()}", info); + + lanPlayerNameCompletion.SetException(exception); + } } } } @@ -113,13 +117,16 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs [HarmonyPatch(typeof(JoinFlow), "Cancel")] internal class JoinFlowCancelPatch { - private static void Postfix() + private static void Postfix(JoinFlow __instance) { - var lanPlayerNameCompletion = LanPlayerNameService.Instance.LanPlayerNameCompletion; - - if (lanPlayerNameCompletion?.Task is { IsCompleted: false }) + if (__instance.NetService is { Platform: PlatformType.None }) { - lanPlayerNameCompletion.SetCanceled(); + var lanPlayerNameCompletion = LanPlayerNameService.Instance.LanPlayerNameCompletion; + + if (lanPlayerNameCompletion?.Task is { IsCompleted: false }) + { + lanPlayerNameCompletion.SetCanceled(); + } } } } diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/Messages/ClientLobbyJoinResponseMessagePatch.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/Messages/ClientLobbyJoinResponseMessagePatch.cs new file mode 100644 index 0000000..c12685a --- /dev/null +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/Messages/ClientLobbyJoinResponseMessagePatch.cs @@ -0,0 +1,65 @@ +using HarmonyLib; +using MegaCrit.Sts2.Core.Daily; +using MegaCrit.Sts2.Core.Entities.Multiplayer; +using MegaCrit.Sts2.Core.Multiplayer.Messages.Lobby; +using MegaCrit.Sts2.Core.Multiplayer.Serialization; +using MegaCrit.Sts2.Core.Saves.Runs; +using SlayTheSpire2.LAN.Multiplayer.Helpers; + +// ReSharper disable UnusedMember.Global +// ReSharper disable UnusedType.Global + +namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Messages +{ + [HarmonyPatch(typeof(ClientLobbyJoinResponseMessage), "Serialize")] + internal class ClientLobbyJoinResponseMessageSerializePatch + { + private static bool Prefix(ClientLobbyJoinResponseMessage __instance, PacketWriter writer) + { + if (__instance.playersInLobby == null) + { + throw new InvalidOperationException("Tried to serialize ClientSlotGrantedMessage with null list!"); + } + + PacketHelper.WriteList(writer, __instance.playersInLobby); + writer.WriteBool(__instance.dailyTime.HasValue); + if (__instance.dailyTime.HasValue) + { + writer.Write(__instance.dailyTime.Value); + } + + writer.WriteBool(__instance.seed != null); + if (__instance.seed != null) + { + writer.WriteString(__instance.seed); + } + + writer.WriteInt(__instance.ascension, 5); + writer.WriteList(__instance.modifiers); + + return false; + } + } + + [HarmonyPatch(typeof(ClientLobbyJoinResponseMessage), "Deserialize")] + internal class ClientLobbyJoinResponseMessageDeserializePatch + { + private static bool Prefix(ref ClientLobbyJoinResponseMessage __instance, PacketReader reader) + { + __instance.playersInLobby = PacketHelper.ReadList(reader); + if (reader.ReadBool()) + { + __instance.dailyTime = reader.Read(); + } + + if (reader.ReadBool()) + { + __instance.seed = reader.ReadString(); + } + + __instance.ascension = reader.ReadInt(5); + __instance.modifiers = reader.ReadList(); + return false; + } + } +} \ No newline at end of file diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/Messages/LobbyBeginRunMessagePatch.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/Messages/LobbyBeginRunMessagePatch.cs new file mode 100644 index 0000000..901ef00 --- /dev/null +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/Messages/LobbyBeginRunMessagePatch.cs @@ -0,0 +1,45 @@ +using HarmonyLib; +using MegaCrit.Sts2.Core.Entities.Multiplayer; +using MegaCrit.Sts2.Core.Multiplayer.Messages.Lobby; +using MegaCrit.Sts2.Core.Multiplayer.Serialization; +using MegaCrit.Sts2.Core.Saves.Runs; +using SlayTheSpire2.LAN.Multiplayer.Helpers; + +// ReSharper disable UnusedMember.Global +// ReSharper disable UnusedType.Global + +namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Messages +{ + [HarmonyPatch(typeof(LobbyBeginRunMessage), "Serialize")] + internal class LobbyBeginRunMessageSerializePatch + { + private static bool Prefix(LobbyBeginRunMessage __instance, PacketWriter writer) + { + if (__instance.playersInLobby == null) + { + throw new InvalidOperationException("Tried to serialize ClientSlotGrantedMessage with null list!"); + } + + PacketHelper.WriteList(writer, __instance.playersInLobby); + writer.WriteString(__instance.seed); + writer.WriteList(__instance.modifiers); + writer.WriteString(__instance.act1); + + return false; + } + } + + [HarmonyPatch(typeof(LobbyBeginRunMessage), "Deserialize")] + internal class LobbyBeginRunMessageDeserializePatch + { + private static bool Prefix(ref LobbyBeginRunMessage __instance, PacketReader reader) + { + __instance.playersInLobby = PacketHelper.ReadList(reader); + __instance.seed = reader.ReadString(); + __instance.modifiers = reader.ReadList(); + __instance.act1 = reader.ReadString(); + + return false; + } + } +} \ No newline at end of file diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/Messages/LobbyPlayerPatch.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/Messages/LobbyPlayerPatch.cs new file mode 100644 index 0000000..f8494aa --- /dev/null +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/Messages/LobbyPlayerPatch.cs @@ -0,0 +1,44 @@ +using HarmonyLib; +using MegaCrit.Sts2.Core.Entities.Multiplayer; +using MegaCrit.Sts2.Core.Models; +using MegaCrit.Sts2.Core.Multiplayer.Serialization; +using MegaCrit.Sts2.Core.Unlocks; +using SlayTheSpire2.LAN.Multiplayer.Helpers; + +// ReSharper disable UnusedMember.Global +// ReSharper disable UnusedType.Global + +namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Messages +{ + [HarmonyPatch(typeof(LobbyPlayer), "Serialize")] + internal class LobbyPlayerSerializePatch + { + private static bool Prefix(LobbyPlayer __instance, PacketWriter writer) + { + writer.WriteULong(__instance.id); + PacketHelper.WriteVarInt(writer, (uint)__instance.slotId); + writer.WriteModel(__instance.character); + writer.Write(__instance.unlockState); + writer.WriteInt(__instance.maxMultiplayerAscensionUnlocked); + writer.WriteBool(__instance.isReady); + + return false; + } + } + + [HarmonyPatch(typeof(LobbyPlayer), "Deserialize")] + internal class LobbyPlayerDeserializePatch + { + private static bool Prefix(ref LobbyPlayer __instance, PacketReader reader) + { + __instance.id = reader.ReadULong(); + __instance.slotId = (int)PacketHelper.ReadVarInt(reader); + __instance.character = reader.ReadModel(); + __instance.unlockState = reader.Read(); + __instance.maxMultiplayerAscensionUnlocked = reader.ReadInt(); + __instance.isReady = reader.ReadBool(); + + return false; + } + } +} \ No newline at end of file diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NJoinFriendScreenPatch.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NJoinFriendScreenPatch.cs index d5f9af7..28fb581 100644 --- a/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NJoinFriendScreenPatch.cs +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NJoinFriendScreenPatch.cs @@ -2,7 +2,6 @@ using HarmonyLib; using MegaCrit.Sts2.addons.mega_text; using MegaCrit.Sts2.Core.Helpers; -using MegaCrit.Sts2.Core.Localization; using MegaCrit.Sts2.Core.Multiplayer.Connection; using MegaCrit.Sts2.Core.Nodes.GodotExtensions; using MegaCrit.Sts2.Core.Nodes.Screens.MainMenu; @@ -20,7 +19,6 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens private static void Prefix(NJoinFriendScreen __instance) { var lanPanel = new NinePatchRect { Name = "LANPanel" }; - __instance.AddChild(lanPanel); lanPanel.PatchMarginTop = 12; @@ -66,49 +64,37 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens addressLineEdit.CustomMinimumSize = new Vector2(300, 50); addressLineEdit.SizeFlagsHorizontal = Control.SizeFlags.ShrinkCenter; - if (__instance.GetNode("RefreshButton").Duplicate() is NJoinFriendRefreshButton - joinButton) + var joinButton = JoinButton.Create(__instance.GetNode("RefreshButton")); + + joinButton.Name = "JointButton"; + + vBoxContainer.AddChild(joinButton); + + joinButton.Connect(NClickableControl.SignalName.Released, Callable.From(_ => { - joinButton.Name = "JointButton"; + var addressInfo = addressLineEdit.GetAddressInfo(); - vBoxContainer.AddChild(joinButton); + if (!addressInfo.IsValid) + return; - joinButton.CustomMinimumSize = new Vector2(150, 50); - joinButton.SizeFlagsHorizontal = Control.SizeFlags.ShrinkCenter; + SettingsService.Instance.SettingsModel.IPAddress = addressLineEdit.Text; + SettingsService.Instance.WriteSettings(); - joinButton.Connect(NClickableControl.SignalName.Released, Callable.From(_ => + ushort port = 33771; + + if (addressInfo.Port.HasValue) { - var addressInfo = addressLineEdit.GetAddressInfo(); + port = addressInfo.Port.Value; + } - if (!addressInfo.IsValid) - return; - - SettingsService.Instance.SettingsModel.IPAddress = addressLineEdit.Text; - SettingsService.Instance.WriteSettings(); - - ushort port = 33771; - - if (addressInfo.Port.HasValue) - { - port = addressInfo.Port.Value; - } - - DisplayServer.WindowSetTitle("Slay The Spire 2 (Client)"); - if (addressInfo.Address != null) - { - TaskHelper.RunSafely( - __instance.JoinGameAsync(new ENetClientConnectionInitializer( - SettingsService.Instance.SettingsModel.NetId, addressInfo.Address, port))); - } - })); - - joinButton.Material = joinButton.Material.Duplicate() as Material; - Traverse.Create(joinButton).Field("_hsv").SetValue(joinButton.Material); - - var joinButtonLabel = joinButton.GetNode("Label"); - - joinButtonLabel.SetTextAutoSize(new LocString("main_menu_ui", "JOIN.title").GetFormattedText()); - } + DisplayServer.WindowSetTitle("Slay The Spire 2 (Client)"); + if (addressInfo.Address != null) + { + TaskHelper.RunSafely( + __instance.JoinGameAsync(new ENetClientConnectionInitializer( + SettingsService.Instance.SettingsModel.NetId, addressInfo.Address, port))); + } + })); } } } \ No newline at end of file diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NMainMenuSubmenuStackPatch.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NMainMenuSubmenuStackPatch.cs index 3f2f01c..8431200 100644 --- a/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NMainMenuSubmenuStackPatch.cs +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NMainMenuSubmenuStackPatch.cs @@ -1,4 +1,5 @@ -using HarmonyLib; +using Godot; +using HarmonyLib; using MegaCrit.Sts2.Core.Helpers; using MegaCrit.Sts2.Core.Nodes.Screens.MainMenu; using SlayTheSpire2.LAN.Multiplayer.Components; @@ -15,7 +16,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens { if (type == typeof(LanMultiplayerHostSubmenu)) { - if (LanMultiplayerHostSubmenu.Instance == null) + if (!GodotObject.IsInstanceValid(LanMultiplayerHostSubmenu.Instance)) { var lanMultiplayerHostSubmenu = LanMultiplayerHostSubmenu.Create(); diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NMultiplayerPlayerExpandedStatePatch.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NMultiplayerPlayerExpandedStatePatch.cs index 6d99806..41984df 100644 --- a/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NMultiplayerPlayerExpandedStatePatch.cs +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NMultiplayerPlayerExpandedStatePatch.cs @@ -17,6 +17,13 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens [HarmonyPatch(typeof(NMultiplayerPlayerExpandedState), "_Ready")] internal class NMultiplayerPlayerExpandedStateReadyPatch { + [HarmonyReversePatch] + [HarmonyPatch(typeof(NMapDrawings), "GetDrawingStateForPlayer")] + private static object GetDrawingStateForPlayer(NMapDrawings instance, ulong playerId) + { + throw new NotImplementedException(); + } + private static void Prefix(NMultiplayerPlayerExpandedState __instance, Player ____player) { if (____player.NetId != RunManager.Instance.NetService.NetId) @@ -48,8 +55,11 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens cardLibraryTickBox.Toggled += tickBox => { - var drawingState = Traverse.Create(NMapScreen.Instance?.Drawings) - .Method("GetDrawingStateForPlayer", ____player.NetId).GetValue(); + if (NMapScreen.Instance == null) + return; + + var drawingState = GetDrawingStateForPlayer(NMapScreen.Instance.Drawings, ____player.NetId); + var drawViewport = Traverse.Create(drawingState).Field("drawViewport").GetValue(); if (tickBox.IsTicked) diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NMultiplayerSubmenuPatch.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NMultiplayerSubmenuPatch.cs index 0dcc613..bd07f6b 100644 --- a/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NMultiplayerSubmenuPatch.cs +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NMultiplayerSubmenuPatch.cs @@ -18,102 +18,101 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens [HarmonyPatch(typeof(NMultiplayerSubmenu), "_Ready")] internal class NMultiplayerSubmenuReadyPatch { + [HarmonyReversePatch(HarmonyReversePatchType.Snapshot)] + [HarmonyPatch(typeof(NMultiplayerSubmenu), "UpdateButtons")] + private static void UpdateButtons(NMultiplayerSubmenu instance) + { + throw new NotImplementedException(); + } + private static void Prefix(NMultiplayerSubmenu __instance) { var buttonContainerNode = __instance.GetNode("ButtonContainer"); - if (buttonContainerNode.GetNode("HostButton").Duplicate() is not NSubmenuButton lanHostButton) - return; + if (buttonContainerNode.GetNode("HostButton").Duplicate() is NSubmenuButton lanHostButton) + { + NSubmenuButtonDuplicateMaterial(lanHostButton); - buttonContainerNode.AddChild(lanHostButton); - buttonContainerNode.MoveChild(lanHostButton, 1); + buttonContainerNode.AddChild(lanHostButton); + buttonContainerNode.MoveChild(lanHostButton, 1); - lanHostButton.Connect(NClickableControl.SignalName.Released, - Callable.From(_ => - { - var traverse = Traverse.Create(__instance); - - var settingsModel = SettingsService.Instance.SettingsModel; - - var stack = traverse.Field("_stack").GetValue(); - - if (SaveManager.Instance.Progress.NumberOfRuns > 0) + lanHostButton.Connect(NClickableControl.SignalName.Released, + Callable.From(_ => { - stack.PushSubmenuType(); - } - else + var traverse = Traverse.Create(__instance); + + var settingsModel = SettingsService.Instance.SettingsModel; + + var stack = traverse.Field("_stack").GetValue(); + + if (SaveManager.Instance.Progress.NumberOfRuns > 0) + { + stack.PushSubmenuType(); + } + else + { + LanHostHelper.StartHost(GameMode.Standard, + traverse.Field("_loadingOverlay").GetValue(), stack, settingsModel.HostPort, + settingsModel.HostMaxPlayers); + } + })); + lanHostButton.SetIconAndLocalization("HOST"); + var lanHostTitle = Traverse.Create(lanHostButton).Field("_title").GetValue(); + lanHostTitle.Text = $"LAN {lanHostTitle.Text}"; + + LanMultiplayerSubmenuButtonService.Instance.LanHostButton = lanHostButton; + } + + if (buttonContainerNode.GetNode("LoadButton").Duplicate() is NSubmenuButton lanLoadButton) + { + NSubmenuButtonDuplicateMaterial(lanLoadButton); + + buttonContainerNode.AddChild(lanLoadButton); + buttonContainerNode.MoveChild(lanLoadButton, 2); + + lanLoadButton.Connect(NClickableControl.SignalName.Released, + Callable.From(_ => { - LanHostHelper.StartHost(GameMode.Standard, - traverse.Field("_loadingOverlay").GetValue(), stack, settingsModel.HostPort, - settingsModel.HostMaxPlayers); - } - })); - lanHostButton.SetIconAndLocalization("HOST"); - var lanHostTitle = Traverse.Create(lanHostButton).Field("_title").GetValue(); - lanHostTitle.Text = $"LAN {lanHostTitle.Text}"; + var traverse = Traverse.Create(__instance); - NSubmenuButtonDuplicateMaterial(lanHostButton); + var settingsModel = SettingsService.Instance.SettingsModel; - var lanMultiplayerSubmenuButtonService = LanMultiplayerSubmenuButtonService.Instance; + LanHostHelper.StartLoad(lanLoadButton, traverse.Field("_loadingOverlay").GetValue(), + traverse.Field("_stack").GetValue(), + settingsModel.HostPort, settingsModel.HostMaxPlayers); + })); + lanLoadButton.SetIconAndLocalization("MP_LOAD"); + var lanLoadButtonTitle = Traverse.Create(lanLoadButton).Field("_title").GetValue(); + lanLoadButtonTitle.Text = $"LAN {lanLoadButtonTitle.Text}"; - lanMultiplayerSubmenuButtonService.LanHostButton = lanHostButton; + LanMultiplayerSubmenuButtonService.Instance.LanLoadButton = lanLoadButton; + } - if (buttonContainerNode.GetNode("LoadButton").Duplicate() is not NSubmenuButton lanLoadButton) - return; + if (buttonContainerNode.GetNode("AbandonButton").Duplicate() is NSubmenuButton lanAbandonButton) + { + NSubmenuButtonDuplicateMaterial(lanAbandonButton); - buttonContainerNode.AddChild(lanLoadButton); - buttonContainerNode.MoveChild(lanLoadButton, 2); + buttonContainerNode.AddChild(lanAbandonButton); + buttonContainerNode.MoveChild(lanAbandonButton, 3); - lanLoadButton.Connect(NClickableControl.SignalName.Released, - Callable.From(_ => - { - var traverse = Traverse.Create(__instance); + lanAbandonButton.Connect(NClickableControl.SignalName.Released, + Callable.From(_ => + { + TaskHelper.RunSafely( + LanHostHelper.TryAbandonMultiplayerRun(() => UpdateButtons(__instance))); + })); + lanAbandonButton.SetIconAndLocalization("MP_ABANDON"); + var lanAbandonButtonTitle = Traverse.Create(lanAbandonButton).Field("_title").GetValue(); + lanAbandonButtonTitle.Text = $"LAN {lanAbandonButtonTitle.Text}"; - var settingsModel = SettingsService.Instance.SettingsModel; - - LanHostHelper.StartLoad(lanLoadButton, traverse.Field("_loadingOverlay").GetValue(), - traverse.Field("_stack").GetValue(), - settingsModel.HostPort, settingsModel.HostMaxPlayers); - })); - lanLoadButton.SetIconAndLocalization("MP_LOAD"); - var lanLoadButtonTitle = Traverse.Create(lanLoadButton).Field("_title").GetValue(); - lanLoadButtonTitle.Text = $"LAN {lanLoadButtonTitle.Text}"; - - NSubmenuButtonDuplicateMaterial(lanLoadButton); - - lanMultiplayerSubmenuButtonService.LanLoadButton = lanLoadButton; - - if (buttonContainerNode.GetNode("AbandonButton").Duplicate() is not NSubmenuButton lanAbandonButton) - return; - - buttonContainerNode.AddChild(lanAbandonButton); - buttonContainerNode.MoveChild(lanAbandonButton, 3); - - lanAbandonButton.Connect(NClickableControl.SignalName.Released, - Callable.From(_ => - { - var traverse = Traverse.Create(__instance); - - TaskHelper.RunSafely( - LanHostHelper.TryAbandonMultiplayerRun(() => traverse.Method("UpdateButtons").GetValue())); - })); - lanAbandonButton.SetIconAndLocalization("MP_ABANDON"); - var lanAbandonButtonTitle = Traverse.Create(lanAbandonButton).Field("_title").GetValue(); - lanAbandonButtonTitle.Text = $"LAN {lanAbandonButtonTitle.Text}"; - - NSubmenuButtonDuplicateMaterial(lanAbandonButton); - - lanMultiplayerSubmenuButtonService.LanAbandonButton = lanAbandonButton; + LanMultiplayerSubmenuButtonService.Instance.LanAbandonButton = lanAbandonButton; + } } private static void NSubmenuButtonDuplicateMaterial(NSubmenuButton nSubmenuButton) { - var traverse = Traverse.Create(nSubmenuButton); - - var bgPanel = traverse.Field("_bgPanel").GetValue(); + var bgPanel = nSubmenuButton.GetNode("BgPanel"); bgPanel.Material = bgPanel.Material.Duplicate() as Material; - - traverse.Field("_hsv").SetValue(bgPanel.Material); } } diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NSettingsScreenPatch.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NSettingsScreenPatch.cs index e541f4c..871de63 100644 --- a/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NSettingsScreenPatch.cs +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NSettingsScreenPatch.cs @@ -1,5 +1,7 @@ using Godot; using HarmonyLib; +using MegaCrit.Sts2.addons.mega_text; +using MegaCrit.Sts2.Core.Localization; using MegaCrit.Sts2.Core.Nodes.Screens.Settings; using SlayTheSpire2.LAN.Multiplayer.Components; using SlayTheSpire2.LAN.Multiplayer.Services; @@ -12,6 +14,13 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens [HarmonyPatch(typeof(NSettingsScreen), "_Ready")] internal class NSettingsScreenReadyPatch { + [HarmonyReversePatch] + [HarmonyPatch(typeof(NSettingsPanel), "RefreshSize")] + private static void RefreshSize(NSettingsPanel instance) + { + throw new NotImplementedException(); + } + private static void Prefix(NSettingsScreen __instance) { var moddingNode = __instance.GetNode("%Modding"); @@ -21,7 +30,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens if (__instance.GetNode("%ModdingDivider").Duplicate() is ColorRect hostPortDivider && moddingNode.Duplicate() is MarginContainer hostPort && - hostPort.GetNode("Label") is RichTextLabel hostPortLabel) + hostPort.GetNode("Label") is MegaRichTextLabel hostPortLabel) { hostPortDivider.Name = "HostPortDivider"; @@ -40,7 +49,6 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens hostPort.Show(); var hostPortLineEdit = new SpinBox { Name = "HostPortInput" }; - hostPort.AddChild(hostPortLineEdit); hostPortLineEdit.CustomMinimumSize = new Vector2(324, 64); @@ -62,7 +70,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens if (__instance.GetNode("%ModdingDivider").Duplicate() is ColorRect hostMaxPlayersDivider && moddingNode.Duplicate() is MarginContainer hostMaxPlayers && - hostMaxPlayers.GetNode("Label") is RichTextLabel hostMaxPlayersLabel) + hostMaxPlayers.GetNode("Label") is MegaRichTextLabel hostMaxPlayersLabel) { hostMaxPlayersDivider.Name = "HostMaxPlayersDivider"; @@ -81,7 +89,6 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens hostMaxPlayers.Show(); var hostMaxPlayersInput = new SpinBox { Name = "HostMaxPlayersInput" }; - hostMaxPlayers.AddChild(hostMaxPlayersInput); hostMaxPlayersInput.CustomMinimumSize = new Vector2(324, 64); @@ -102,7 +109,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens if (__instance.GetNode("%ModdingDivider").Duplicate() is ColorRect playerNameDivider && moddingNode.Duplicate() is MarginContainer playerName && - playerName.GetNode("Label") is RichTextLabel playerNameLabel) + playerName.GetNode("Label") is MegaRichTextLabel playerNameLabel) { playerNameDivider.Name = "PlayerNameDivider"; @@ -120,11 +127,15 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens playerName.Show(); + var marginContainer = new MarginContainer(); + playerName.AddChild(marginContainer); + + marginContainer.AddThemeConstantOverride("margin_right", 18); + var playerNameInput = new PlayerNameLineEdit { Name = "PlayerNameInput" }; + marginContainer.AddChild(playerNameInput); - playerName.AddChild(playerNameInput); - - playerNameInput.CustomMinimumSize = new Vector2(324, 64); + playerNameInput.CustomMinimumSize = new Vector2(308, 64); playerNameInput.SizeFlagsHorizontal = Control.SizeFlags.ShrinkEnd; playerNameInput.Alignment = HorizontalAlignment.Center; @@ -145,7 +156,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens if (__instance.GetNode("%ModdingDivider").Duplicate() is ColorRect netIdDivider && moddingNode.Duplicate() is MarginContainer netId && - netId.GetNode("Label") is RichTextLabel netIdLabel) + netId.GetNode("Label") is MegaRichTextLabel netIdLabel) { netIdDivider.Name = "NetIDDivider"; @@ -164,7 +175,6 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens netId.Show(); var netIdInput = new SpinBox { Name = "NetIDInput" }; - netId.AddChild(netIdInput); netIdInput.CustomMinimumSize = new Vector2(324, 64); @@ -186,8 +196,33 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens if (generalSettings is NSettingsPanel nSettingsPanel) { - Traverse.Create(nSettingsPanel).Method("RefreshSize").GetValue(); + RefreshSize(nSettingsPanel); } } } + + [HarmonyPatch(typeof(NSettingsScreen), "LocalizeLabels")] + internal class NSettingsScreenLocalizeLabelsPatch + { + [HarmonyReversePatch] + [HarmonyPatch(typeof(NSettingsScreen), "LocHelper")] + private static void LocHelper(Node settingsLineNode, LocString locString) + { + throw new NotImplementedException(); + } + + private static void Prefix(NSettingsScreen __instance) + { + var content = __instance.GetNode("%GeneralSettings").Content; + + LocHelper(content.GetNode("HostPort"), + new LocString("settings_ui", "SlayTheSpire2.LAN.Multiplayer.HOST_PORT")); + LocHelper(content.GetNode("HostMaxPlayers"), + new LocString("settings_ui", "SlayTheSpire2.LAN.Multiplayer.HOST_MAX_PLAYERS")); + LocHelper(content.GetNode("PlayerName"), + new LocString("settings_ui", "SlayTheSpire2.LAN.Multiplayer.PLAYER_NAME")); + LocHelper(content.GetNode("NetID"), + new LocString("settings_ui", "SlayTheSpire2.LAN.Multiplayer.NET_ID")); + } + } } \ No newline at end of file diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/RunLoadScreenPatchs.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/RunLoadScreenPatchs.cs index 38437a0..d02ca29 100644 --- a/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/RunLoadScreenPatchs.cs +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/RunLoadScreenPatchs.cs @@ -5,6 +5,7 @@ using MegaCrit.Sts2.Core.Multiplayer.Game.Lobby; using MegaCrit.Sts2.Core.Nodes.Screens.CharacterSelect; using MegaCrit.Sts2.Core.Nodes.Screens.CustomRun; using MegaCrit.Sts2.Core.Nodes.Screens.DailyRun; +using MegaCrit.Sts2.Core.Nodes.Screens.MainMenu; using MegaCrit.Sts2.Core.Platform; using SlayTheSpire2.LAN.Multiplayer.Services; @@ -14,21 +15,52 @@ using SlayTheSpire2.LAN.Multiplayer.Services; namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens { [HarmonyPatch] - internal class RunLoadScreenInitializePatchs + internal class RunLoadScreenInitializeAsHostPatchs { private static IEnumerable TargetMethods() { const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public; yield return typeof(NMultiplayerLoadGameScreen).GetMethod("InitializeAsHost", flags)!; - yield return typeof(NMultiplayerLoadGameScreen).GetMethod("InitializeAsClient", flags)!; yield return typeof(NDailyRunLoadScreen).GetMethod("InitializeAsHost", flags)!; - yield return typeof(NDailyRunLoadScreen).GetMethod("InitializeAsClient", flags)!; yield return typeof(NCustomRunLoadScreen).GetMethod("InitializeAsHost", flags)!; + } + + private static void Prefix(NSubmenu __instance, INetGameService gameService) + { + if (gameService.Platform == PlatformType.None) + { + var runScreenService = RunScreenService.Instance; + + switch (__instance) + { + case NMultiplayerLoadGameScreen multiplayerLoadGameScreen: + runScreenService.MultiplayerLoadGameScreen = multiplayerLoadGameScreen; + break; + case NDailyRunLoadScreen dailyRunLoadScreen: + runScreenService.DailyRunLoadScreen = dailyRunLoadScreen; + break; + case NCustomRunLoadScreen customRunLoadScreen: + runScreenService.CustomRunLoadScreen = customRunLoadScreen; + break; + } + } + } + } + + [HarmonyPatch] + internal class RunLoadScreenInitializeAsClientPatchs + { + private static IEnumerable TargetMethods() + { + const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public; + + yield return typeof(NMultiplayerLoadGameScreen).GetMethod("InitializeAsClient", flags)!; + yield return typeof(NDailyRunLoadScreen).GetMethod("InitializeAsClient", flags)!; yield return typeof(NCustomRunLoadScreen).GetMethod("InitializeAsClient", flags)!; } - private static void Prefix(object __instance, INetGameService gameService) + private static void Prefix(NSubmenu __instance, INetGameService gameService) { if (gameService.Platform == PlatformType.None) { diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/RunScreenPatchs.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/RunScreenPatchs.cs index cbb25a3..aa265c9 100644 --- a/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/RunScreenPatchs.cs +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/RunScreenPatchs.cs @@ -4,7 +4,9 @@ using MegaCrit.Sts2.Core.Multiplayer.Game; using MegaCrit.Sts2.Core.Nodes.Screens.CharacterSelect; using MegaCrit.Sts2.Core.Nodes.Screens.CustomRun; using MegaCrit.Sts2.Core.Nodes.Screens.DailyRun; +using MegaCrit.Sts2.Core.Nodes.Screens.MainMenu; using MegaCrit.Sts2.Core.Platform; +using SlayTheSpire2.LAN.Multiplayer.Components; using SlayTheSpire2.LAN.Multiplayer.Services; // ReSharper disable UnusedMember.Global @@ -13,21 +15,41 @@ using SlayTheSpire2.LAN.Multiplayer.Services; namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens { [HarmonyPatch] - internal class RunScreenInitializePatchs + internal class RunScreenReadyPatchs + { + private static IEnumerable TargetMethods() + { + const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public; + + yield return typeof(NCharacterSelectScreen).GetMethod("_Ready", flags)!; + yield return typeof(NDailyRunScreen).GetMethod("_Ready", flags)!; + yield return typeof(NCustomRunScreen).GetMethod("_Ready", flags)!; + } + + private static void Prefix(NSubmenu __instance) + { + var ipAddressInfoPanel = IPAddressInfoPanel.Create(); + ipAddressInfoPanel.Name = "IPAddressPanel"; + + __instance.AddChild(ipAddressInfoPanel); + + ipAddressInfoPanel.Visible = false; + } + } + + [HarmonyPatch] + internal class RunScreenInitializeAsHostPatchs { private static IEnumerable TargetMethods() { const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public; - yield return typeof(NCharacterSelectScreen).GetMethod("InitializeMultiplayerAsClient", flags)!; yield return typeof(NCharacterSelectScreen).GetMethod("InitializeMultiplayerAsHost", flags)!; - yield return typeof(NDailyRunScreen).GetMethod("InitializeMultiplayerAsClient", flags)!; yield return typeof(NDailyRunScreen).GetMethod("InitializeMultiplayerAsHost", flags)!; - yield return typeof(NCustomRunScreen).GetMethod("InitializeMultiplayerAsClient", flags)!; yield return typeof(NCustomRunScreen).GetMethod("InitializeMultiplayerAsHost", flags)!; } - private static void Prefix(object __instance, INetGameService gameService) + private static void Prefix(NSubmenu __instance, INetGameService gameService) { if (gameService.Platform == PlatformType.None) { @@ -47,5 +69,84 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens } } } + + private static void Postfix(NSubmenu __instance, INetGameService gameService) + { + if (__instance.GetNode("IPAddressPanel") is IPAddressInfoPanel ipAddressInfoPanel) + { + if (gameService.Platform == PlatformType.None) + { + ipAddressInfoPanel.Initialize(); + ipAddressInfoPanel.Visible = true; + } + else + { + ipAddressInfoPanel.Visible = false; + } + } + } + } + + [HarmonyPatch] + internal class RunScreenInitializeAsClientPatchs + { + private static IEnumerable TargetMethods() + { + const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public; + + yield return typeof(NCharacterSelectScreen).GetMethod("InitializeMultiplayerAsClient", flags)!; + yield return typeof(NDailyRunScreen).GetMethod("InitializeMultiplayerAsClient", flags)!; + yield return typeof(NCustomRunScreen).GetMethod("InitializeMultiplayerAsClient", flags)!; + } + + private static void Prefix(NSubmenu __instance, INetGameService gameService) + { + if (gameService.Platform == PlatformType.None) + { + var runScreenService = RunScreenService.Instance; + + switch (__instance) + { + case NCharacterSelectScreen characterSelectScreen: + runScreenService.CharacterSelectScreen = characterSelectScreen; + break; + case NDailyRunScreen dailyRunScreen: + runScreenService.DailyRunScreen = dailyRunScreen; + break; + case NCustomRunScreen customRunScreen: + runScreenService.CustomRunScreen = customRunScreen; + break; + } + } + } + + private static void Postfix(NSubmenu __instance) + { + if (__instance.GetNode("IPAddressPanel") is IPAddressInfoPanel ipAddressInfoPanel) + { + ipAddressInfoPanel.Visible = false; + } + } + } + + [HarmonyPatch] + internal class RunScreenInitializeSingleplayerPatchs + { + private static IEnumerable TargetMethods() + { + const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public; + + yield return typeof(NCharacterSelectScreen).GetMethod("InitializeSingleplayer", flags)!; + yield return typeof(NDailyRunScreen).GetMethod("InitializeSingleplayer", flags)!; + yield return typeof(NCustomRunScreen).GetMethod("InitializeSingleplayer", flags)!; + } + + private static void Postfix(NSubmenu __instance) + { + if (__instance.GetNode("IPAddressPanel") is IPAddressInfoPanel ipAddressInfoPanel) + { + ipAddressInfoPanel.Visible = false; + } + } } } \ No newline at end of file diff --git a/SlayTheSpire2.LAN.Multiplayer/Services/RunScreenService.cs b/SlayTheSpire2.LAN.Multiplayer/Services/RunScreenService.cs index 668a995..2af9c01 100644 --- a/SlayTheSpire2.LAN.Multiplayer/Services/RunScreenService.cs +++ b/SlayTheSpire2.LAN.Multiplayer/Services/RunScreenService.cs @@ -55,7 +55,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Services nVerticalPopup.YesButton.Visible = false; - return await nGenericPopup.WaitForConfirmation(locString, + await nGenericPopup.WaitForConfirmation(locString, new LocString("gameplay_ui", "CONFIRM_LOAD_SAVE.header"), new LocString("gameplay_ui", "CONFIRM_LOAD_SAVE.cancel"), new LocString("gameplay_ui", "CONFIRM_LOAD_SAVE.confirm")); diff --git a/SlayTheSpire2.LAN.Multiplayer/SlayTheSpire2.LAN.Multiplayer.csproj b/SlayTheSpire2.LAN.Multiplayer/SlayTheSpire2.LAN.Multiplayer.csproj index 3bc861d..af54d03 100644 --- a/SlayTheSpire2.LAN.Multiplayer/SlayTheSpire2.LAN.Multiplayer.csproj +++ b/SlayTheSpire2.LAN.Multiplayer/SlayTheSpire2.LAN.Multiplayer.csproj @@ -8,6 +8,10 @@ 1.5.0.0 + + + + V:\Slay.the.Spire.2\data_sts2_windows_x86_64\0Harmony.dll diff --git a/localization/deu/main_menu_ui.json b/localization/deu/main_menu_ui.json new file mode 100644 index 0000000..ae52e31 --- /dev/null +++ b/localization/deu/main_menu_ui.json @@ -0,0 +1,6 @@ +{ + "SlayTheSpire2.LAN.Multiplayer.COPIED": "Kopiert", + "SlayTheSpire2.LAN.Multiplayer.IP_ADDRESS_TITLE": "IP Adresse:", + "SlayTheSpire2.LAN.Multiplayer.LOCAL_IP_ADDRESS_TITLE": "Lokale IP Adresse:", + "SlayTheSpire2.LAN.Multiplayer.IPV6_ADDRESS_TITLE": "IPV6 Adresse:" +} \ No newline at end of file diff --git a/localization/deu/settings_ui.json b/localization/deu/settings_ui.json new file mode 100644 index 0000000..d23fdee --- /dev/null +++ b/localization/deu/settings_ui.json @@ -0,0 +1,6 @@ +{ + "SlayTheSpire2.LAN.Multiplayer.HOST_PORT": "Host Port", + "SlayTheSpire2.LAN.Multiplayer.HOST_MAX_PLAYERS": "Max Spieleranzahl", + "SlayTheSpire2.LAN.Multiplayer.PLAYER_NAME": "Spielername", + "SlayTheSpire2.LAN.Multiplayer.NET_ID": "NetID" +} \ No newline at end of file diff --git a/localization/eng/main_menu_ui.json b/localization/eng/main_menu_ui.json new file mode 100644 index 0000000..8ada1f4 --- /dev/null +++ b/localization/eng/main_menu_ui.json @@ -0,0 +1,6 @@ +{ + "SlayTheSpire2.LAN.Multiplayer.COPIED": "Copied", + "SlayTheSpire2.LAN.Multiplayer.IP_ADDRESS_TITLE": "IP Address:", + "SlayTheSpire2.LAN.Multiplayer.LOCAL_IP_ADDRESS_TITLE": "Local IP Address:", + "SlayTheSpire2.LAN.Multiplayer.IPV6_ADDRESS_TITLE": "IPV6 Address:" +} \ No newline at end of file diff --git a/localization/eng/settings_ui.json b/localization/eng/settings_ui.json new file mode 100644 index 0000000..8c9c815 --- /dev/null +++ b/localization/eng/settings_ui.json @@ -0,0 +1,6 @@ +{ + "SlayTheSpire2.LAN.Multiplayer.HOST_PORT": "Host Port", + "SlayTheSpire2.LAN.Multiplayer.HOST_MAX_PLAYERS": "Host Max Players", + "SlayTheSpire2.LAN.Multiplayer.PLAYER_NAME": "Player Name", + "SlayTheSpire2.LAN.Multiplayer.NET_ID": "NetID" +} \ No newline at end of file diff --git a/localization/esp/main_menu_ui.json b/localization/esp/main_menu_ui.json new file mode 100644 index 0000000..5b0e9e9 --- /dev/null +++ b/localization/esp/main_menu_ui.json @@ -0,0 +1,6 @@ +{ + "SlayTheSpire2.LAN.Multiplayer.COPIED": "Copiado", + "SlayTheSpire2.LAN.Multiplayer.IP_ADDRESS_TITLE": "Dirección IP:", + "SlayTheSpire2.LAN.Multiplayer.LOCAL_IP_ADDRESS_TITLE": "Dirección IP local:", + "SlayTheSpire2.LAN.Multiplayer.IPV6_ADDRESS_TITLE": "Dirección IPV6:" +} \ No newline at end of file diff --git a/localization/esp/settings_ui.json b/localization/esp/settings_ui.json new file mode 100644 index 0000000..d37b888 --- /dev/null +++ b/localization/esp/settings_ui.json @@ -0,0 +1,6 @@ +{ + "SlayTheSpire2.LAN.Multiplayer.HOST_PORT": "Puerto del host", + "SlayTheSpire2.LAN.Multiplayer.HOST_MAX_PLAYERS": "Máximo de jugadores", + "SlayTheSpire2.LAN.Multiplayer.PLAYER_NAME": "Nombre del jugador", + "SlayTheSpire2.LAN.Multiplayer.NET_ID": "NetID" +} \ No newline at end of file diff --git a/localization/fra/main_menu_ui.json b/localization/fra/main_menu_ui.json new file mode 100644 index 0000000..7a02019 --- /dev/null +++ b/localization/fra/main_menu_ui.json @@ -0,0 +1,6 @@ +{ + "SlayTheSpire2.LAN.Multiplayer.COPIED": "Copié", + "SlayTheSpire2.LAN.Multiplayer.IP_ADDRESS_TITLE": "Adresse IP:", + "SlayTheSpire2.LAN.Multiplayer.LOCAL_IP_ADDRESS_TITLE": "Adresse IP locale:", + "SlayTheSpire2.LAN.Multiplayer.IPV6_ADDRESS_TITLE": "Adresse IPV6:" +} \ No newline at end of file diff --git a/localization/fra/settings_ui.json b/localization/fra/settings_ui.json new file mode 100644 index 0000000..45fb244 --- /dev/null +++ b/localization/fra/settings_ui.json @@ -0,0 +1,6 @@ +{ + "SlayTheSpire2.LAN.Multiplayer.HOST_PORT": "Port de l'hôte", + "SlayTheSpire2.LAN.Multiplayer.HOST_MAX_PLAYERS": "Nombre max de joueurs", + "SlayTheSpire2.LAN.Multiplayer.PLAYER_NAME": "Nom du joueur", + "SlayTheSpire2.LAN.Multiplayer.NET_ID": "NetID" +} \ No newline at end of file diff --git a/localization/ita/main_menu_ui.json b/localization/ita/main_menu_ui.json new file mode 100644 index 0000000..b2ea6a2 --- /dev/null +++ b/localization/ita/main_menu_ui.json @@ -0,0 +1,6 @@ +{ + "SlayTheSpire2.LAN.Multiplayer.COPIED": "Copiato", + "SlayTheSpire2.LAN.Multiplayer.IP_ADDRESS_TITLE": "Indirizzo IP:", + "SlayTheSpire2.LAN.Multiplayer.LOCAL_IP_ADDRESS_TITLE": "Indirizzo IP locale:", + "SlayTheSpire2.LAN.Multiplayer.IPV6_ADDRESS_TITLE": "Indirizzo IPV6:" +} \ No newline at end of file diff --git a/localization/ita/settings_ui.json b/localization/ita/settings_ui.json new file mode 100644 index 0000000..ce5a22d --- /dev/null +++ b/localization/ita/settings_ui.json @@ -0,0 +1,6 @@ +{ + "SlayTheSpire2.LAN.Multiplayer.HOST_PORT": "Porta Host", + "SlayTheSpire2.LAN.Multiplayer.HOST_MAX_PLAYERS": "Numero massimo giocatori", + "SlayTheSpire2.LAN.Multiplayer.PLAYER_NAME": "Nome giocatore", + "SlayTheSpire2.LAN.Multiplayer.NET_ID": "NetID" +} \ No newline at end of file diff --git a/localization/jpn/main_menu_ui.json b/localization/jpn/main_menu_ui.json new file mode 100644 index 0000000..c7b7120 --- /dev/null +++ b/localization/jpn/main_menu_ui.json @@ -0,0 +1,6 @@ +{ + "SlayTheSpire2.LAN.Multiplayer.COPIED": "コピーしました", + "SlayTheSpire2.LAN.Multiplayer.IP_ADDRESS_TITLE": "IPアドレス:", + "SlayTheSpire2.LAN.Multiplayer.LOCAL_IP_ADDRESS_TITLE": "ローカルIPアドレス:", + "SlayTheSpire2.LAN.Multiplayer.IPV6_ADDRESS_TITLE": "IPV6アドレス:" +} \ No newline at end of file diff --git a/localization/jpn/settings_ui.json b/localization/jpn/settings_ui.json new file mode 100644 index 0000000..394147b --- /dev/null +++ b/localization/jpn/settings_ui.json @@ -0,0 +1,6 @@ +{ + "SlayTheSpire2.LAN.Multiplayer.HOST_PORT": "ホストポート", + "SlayTheSpire2.LAN.Multiplayer.HOST_MAX_PLAYERS": "最大プレイヤー数", + "SlayTheSpire2.LAN.Multiplayer.PLAYER_NAME": "プレイヤー名", + "SlayTheSpire2.LAN.Multiplayer.NET_ID": "NetID" +} \ No newline at end of file diff --git a/localization/kor/main_menu_ui.json b/localization/kor/main_menu_ui.json new file mode 100644 index 0000000..4614483 --- /dev/null +++ b/localization/kor/main_menu_ui.json @@ -0,0 +1,6 @@ +{ + "SlayTheSpire2.LAN.Multiplayer.COPIED": "복사됨", + "SlayTheSpire2.LAN.Multiplayer.IP_ADDRESS_TITLE": "IP주소:", + "SlayTheSpire2.LAN.Multiplayer.LOCAL_IP_ADDRESS_TITLE": "로컬IP주소:", + "SlayTheSpire2.LAN.Multiplayer.IPV6_ADDRESS_TITLE": "IPV6주소:" +} \ No newline at end of file diff --git a/localization/kor/settings_ui.json b/localization/kor/settings_ui.json new file mode 100644 index 0000000..5149244 --- /dev/null +++ b/localization/kor/settings_ui.json @@ -0,0 +1,6 @@ +{ + "SlayTheSpire2.LAN.Multiplayer.HOST_PORT": "호스트 포트", + "SlayTheSpire2.LAN.Multiplayer.HOST_MAX_PLAYERS": "호스트 최대 인원", + "SlayTheSpire2.LAN.Multiplayer.PLAYER_NAME": "플레이어 이름", + "SlayTheSpire2.LAN.Multiplayer.NET_ID": "NetID" +} \ No newline at end of file diff --git a/localization/pol/main_menu_ui.json b/localization/pol/main_menu_ui.json new file mode 100644 index 0000000..5c1b996 --- /dev/null +++ b/localization/pol/main_menu_ui.json @@ -0,0 +1,6 @@ +{ + "SlayTheSpire2.LAN.Multiplayer.COPIED": "Skopiowano", + "SlayTheSpire2.LAN.Multiplayer.IP_ADDRESS_TITLE": "Adres IP:", + "SlayTheSpire2.LAN.Multiplayer.LOCAL_IP_ADDRESS_TITLE": "Lokalny adres IP:", + "SlayTheSpire2.LAN.Multiplayer.IPV6_ADDRESS_TITLE": "Adres IPV6:" +} \ No newline at end of file diff --git a/localization/pol/settings_ui.json b/localization/pol/settings_ui.json new file mode 100644 index 0000000..3927375 --- /dev/null +++ b/localization/pol/settings_ui.json @@ -0,0 +1,6 @@ +{ + "SlayTheSpire2.LAN.Multiplayer.HOST_PORT": "Port hosta", + "SlayTheSpire2.LAN.Multiplayer.HOST_MAX_PLAYERS": "Maks. liczba graczy", + "SlayTheSpire2.LAN.Multiplayer.PLAYER_NAME": "Nazwa gracza", + "SlayTheSpire2.LAN.Multiplayer.NET_ID": "NetID" +} \ No newline at end of file diff --git a/localization/ptb/main_menu_ui.json b/localization/ptb/main_menu_ui.json new file mode 100644 index 0000000..45eb047 --- /dev/null +++ b/localization/ptb/main_menu_ui.json @@ -0,0 +1,6 @@ +{ + "SlayTheSpire2.LAN.Multiplayer.COPIED": "Copiado", + "SlayTheSpire2.LAN.Multiplayer.IP_ADDRESS_TITLE": "Endereço IP:", + "SlayTheSpire2.LAN.Multiplayer.LOCAL_IP_ADDRESS_TITLE": "Endereço IP Local:", + "SlayTheSpire2.LAN.Multiplayer.IPV6_ADDRESS_TITLE": "Endereço IPV6:" +} \ No newline at end of file diff --git a/localization/ptb/settings_ui.json b/localization/ptb/settings_ui.json new file mode 100644 index 0000000..546653b --- /dev/null +++ b/localization/ptb/settings_ui.json @@ -0,0 +1,6 @@ +{ + "SlayTheSpire2.LAN.Multiplayer.HOST_PORT": "Porta do Host", + "SlayTheSpire2.LAN.Multiplayer.HOST_MAX_PLAYERS": "Máximo de Jogadores", + "SlayTheSpire2.LAN.Multiplayer.PLAYER_NAME": "Nome do Jogador", + "SlayTheSpire2.LAN.Multiplayer.NET_ID": "NetID" +} \ No newline at end of file diff --git a/localization/rus/main_menu_ui.json b/localization/rus/main_menu_ui.json new file mode 100644 index 0000000..aad74eb --- /dev/null +++ b/localization/rus/main_menu_ui.json @@ -0,0 +1,6 @@ +{ + "SlayTheSpire2.LAN.Multiplayer.COPIED": "Скопировано", + "SlayTheSpire2.LAN.Multiplayer.IP_ADDRESS_TITLE": "IP адрес:", + "SlayTheSpire2.LAN.Multiplayer.LOCAL_IP_ADDRESS_TITLE": "Локальный IP адрес:", + "SlayTheSpire2.LAN.Multiplayer.IPV6_ADDRESS_TITLE": "IPV6 адрес:" +} \ No newline at end of file diff --git a/localization/rus/settings_ui.json b/localization/rus/settings_ui.json new file mode 100644 index 0000000..4fa5d89 --- /dev/null +++ b/localization/rus/settings_ui.json @@ -0,0 +1,6 @@ +{ + "SlayTheSpire2.LAN.Multiplayer.HOST_PORT": "Порт хоста", + "SlayTheSpire2.LAN.Multiplayer.HOST_MAX_PLAYERS": "Макс. игроков", + "SlayTheSpire2.LAN.Multiplayer.PLAYER_NAME": "Имя игрока", + "SlayTheSpire2.LAN.Multiplayer.NET_ID": "NetID" +} \ No newline at end of file diff --git a/localization/spa/main_menu_ui.json b/localization/spa/main_menu_ui.json new file mode 100644 index 0000000..5b0e9e9 --- /dev/null +++ b/localization/spa/main_menu_ui.json @@ -0,0 +1,6 @@ +{ + "SlayTheSpire2.LAN.Multiplayer.COPIED": "Copiado", + "SlayTheSpire2.LAN.Multiplayer.IP_ADDRESS_TITLE": "Dirección IP:", + "SlayTheSpire2.LAN.Multiplayer.LOCAL_IP_ADDRESS_TITLE": "Dirección IP local:", + "SlayTheSpire2.LAN.Multiplayer.IPV6_ADDRESS_TITLE": "Dirección IPV6:" +} \ No newline at end of file diff --git a/localization/spa/settings_ui.json b/localization/spa/settings_ui.json new file mode 100644 index 0000000..ce963ee --- /dev/null +++ b/localization/spa/settings_ui.json @@ -0,0 +1,6 @@ +{ + "SlayTheSpire2.LAN.Multiplayer.HOST_PORT": "Puerto del anfitrión", + "SlayTheSpire2.LAN.Multiplayer.HOST_MAX_PLAYERS": "Máximo de jugadores", + "SlayTheSpire2.LAN.Multiplayer.PLAYER_NAME": "Nombre del jugador", + "SlayTheSpire2.LAN.Multiplayer.NET_ID": "NetID" +} \ No newline at end of file diff --git a/localization/tha/main_menu_ui.json b/localization/tha/main_menu_ui.json new file mode 100644 index 0000000..ef8b829 --- /dev/null +++ b/localization/tha/main_menu_ui.json @@ -0,0 +1,6 @@ +{ + "SlayTheSpire2.LAN.Multiplayer.COPIED": "คัดลอกแล้ว", + "SlayTheSpire2.LAN.Multiplayer.IP_ADDRESS_TITLE": "ที่อยู่ IP:", + "SlayTheSpire2.LAN.Multiplayer.LOCAL_IP_ADDRESS_TITLE": "ที่อยู่ IP ภายใน:", + "SlayTheSpire2.LAN.Multiplayer.IPV6_ADDRESS_TITLE": "ที่อยู่ IPV6:" +} \ No newline at end of file diff --git a/localization/tha/settings_ui.json b/localization/tha/settings_ui.json new file mode 100644 index 0000000..519d180 --- /dev/null +++ b/localization/tha/settings_ui.json @@ -0,0 +1,6 @@ +{ + "SlayTheSpire2.LAN.Multiplayer.HOST_PORT": "พอร์ตโฮสต์", + "SlayTheSpire2.LAN.Multiplayer.HOST_MAX_PLAYERS": "จำนวนผู้เล่นสูงสุด", + "SlayTheSpire2.LAN.Multiplayer.PLAYER_NAME": "ชื่อผู้เล่น", + "SlayTheSpire2.LAN.Multiplayer.NET_ID": "NetID" +} \ No newline at end of file diff --git a/localization/tur/main_menu_ui.json b/localization/tur/main_menu_ui.json new file mode 100644 index 0000000..4d449e9 --- /dev/null +++ b/localization/tur/main_menu_ui.json @@ -0,0 +1,6 @@ +{ + "SlayTheSpire2.LAN.Multiplayer.COPIED": "Kopyalandı", + "SlayTheSpire2.LAN.Multiplayer.IP_ADDRESS_TITLE": "IP Adresi:", + "SlayTheSpire2.LAN.Multiplayer.LOCAL_IP_ADDRESS_TITLE": "Yerel IP Adresi:", + "SlayTheSpire2.LAN.Multiplayer.IPV6_ADDRESS_TITLE": "IPv6 Adresi:" +} \ No newline at end of file diff --git a/localization/tur/settings_ui.json b/localization/tur/settings_ui.json new file mode 100644 index 0000000..255d240 --- /dev/null +++ b/localization/tur/settings_ui.json @@ -0,0 +1,6 @@ +{ + "SlayTheSpire2.LAN.Multiplayer.HOST_PORT": "Sunucu Portu", + "SlayTheSpire2.LAN.Multiplayer.HOST_MAX_PLAYERS": "Maksimum Oyuncu", + "SlayTheSpire2.LAN.Multiplayer.PLAYER_NAME": "Oyuncu Adı", + "SlayTheSpire2.LAN.Multiplayer.NET_ID": "NetID" +} \ No newline at end of file diff --git a/localization/zhs/main_menu_ui.json b/localization/zhs/main_menu_ui.json new file mode 100644 index 0000000..c555ed7 --- /dev/null +++ b/localization/zhs/main_menu_ui.json @@ -0,0 +1,6 @@ +{ + "SlayTheSpire2.LAN.Multiplayer.COPIED": "已复制", + "SlayTheSpire2.LAN.Multiplayer.IP_ADDRESS_TITLE": "IP地址:", + "SlayTheSpire2.LAN.Multiplayer.LOCAL_IP_ADDRESS_TITLE": "本地IP地址:", + "SlayTheSpire2.LAN.Multiplayer.IPV6_ADDRESS_TITLE": "IPV6地址:" +} \ No newline at end of file diff --git a/localization/zhs/settings_ui.json b/localization/zhs/settings_ui.json new file mode 100644 index 0000000..8ca27f9 --- /dev/null +++ b/localization/zhs/settings_ui.json @@ -0,0 +1,6 @@ +{ + "SlayTheSpire2.LAN.Multiplayer.HOST_PORT": "主机端口", + "SlayTheSpire2.LAN.Multiplayer.HOST_MAX_PLAYERS": "主机最大玩家数", + "SlayTheSpire2.LAN.Multiplayer.PLAYER_NAME": "玩家名称", + "SlayTheSpire2.LAN.Multiplayer.NET_ID": "NetID" +} \ No newline at end of file