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