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
This commit is contained in:
@@ -4,10 +4,11 @@ using MegaCrit.Sts2.Core.Nodes.GodotExtensions;
|
|||||||
|
|
||||||
namespace SlayTheSpire2.LAN.Multiplayer.Components
|
namespace SlayTheSpire2.LAN.Multiplayer.Components
|
||||||
{
|
{
|
||||||
internal class AddressLineEdit : NMegaLineEdit
|
internal partial class AddressLineEdit : NMegaLineEdit
|
||||||
{
|
{
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
|
base._Ready();
|
||||||
TextChanged += OnTextChanged;
|
TextChanged += OnTextChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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<Font>("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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<CompressedTexture2D>("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(
|
||||||
|
"<svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 -960 960 960\" width=\"24px\" fill=\"#FFFFFF\"><path d=\"M120-240v-80h720v80H120Zm0-200v-80h720v80H120Zm0-200v-80h720v80H120Z\"/></svg>");
|
||||||
|
|
||||||
|
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<CompressedTexture2D>("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<Control>("Content");
|
||||||
|
_menu = GetNode<Control>("Content/Menu");
|
||||||
|
_box = GetNode<Control>("Content/Box");
|
||||||
|
|
||||||
|
_loading = GetNode<Control>("Content/Box/Container/Loading");
|
||||||
|
|
||||||
|
_ipAddress = GetNode<Control>("Content/Box/Container/IPAddress");
|
||||||
|
_ipAddressTitleLabel = _ipAddress.GetNode<IPAddressLabel>("TitleLabel");
|
||||||
|
_ipAddressLabel = _ipAddress.GetNode<IPAddressLabel>("Label");
|
||||||
|
|
||||||
|
_copiedLabel = GetNode<CopiedLabel>("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<Control>("Content/Box/Container/IPV6IPAddress");
|
||||||
|
_ipv6AddressTitleLabel = _ipv6Address.GetNode<IPAddressLabel>("TitleLabel");
|
||||||
|
_ipv6AddressLabel = _ipv6Address.GetNode<IPAddressLabel>("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<Control>("Content/Box/Container/LocalIPAddress");
|
||||||
|
_localIPAddressTitleLabel = _localIPAddress.GetNode<IPAddressLabel>("TitleLabel");
|
||||||
|
_localIPAddressContainer = _localIPAddress.GetNode<Control>("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<string> GetLocalIPAddressList()
|
||||||
|
{
|
||||||
|
var list = new List<string>();
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Font>("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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<CompressedTexture2D>("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<TextureRect>("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<MegaLabel>("Label");
|
||||||
|
node.SetTextAutoSize(new LocString("main_menu_ui", "JOIN.title").GetFormattedText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ using SlayTheSpire2.LAN.Multiplayer.Services;
|
|||||||
|
|
||||||
namespace SlayTheSpire2.LAN.Multiplayer.Components
|
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");
|
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()
|
public new static NMultiplayerHostSubmenu? Create()
|
||||||
{
|
{
|
||||||
if (Instance != null)
|
if (IsInstanceValid(Instance))
|
||||||
return Instance;
|
return Instance;
|
||||||
|
|
||||||
if (TestMode.IsOn)
|
if (TestMode.IsOn)
|
||||||
|
|||||||
@@ -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(
|
||||||
|
"<svg xmlns=\"http://www.w3.org/2000/svg\" height=\"64px\" viewBox=\"0 -960 960 960\" width=\"64px\" fill=\"#FFFFFF\"><path d=\"M323-111q-73-31-127-85t-85-127q-31-73-31-157t31-157q31-73 85-127t127-85q73-31 157-31 12 0 21 9t9 21q0 12-9 21t-21 9q-141 0-240.5 99.5T140-480q0 141 99.5 240.5T480-140q141 0 240.5-99.5T820-480q0-12 9-21t21-9q12 0 21 9t9 21q0 84-31 157t-85 127q-54 54-127 85T480-80q-84 0-157-31Z\"/></svg>");
|
||||||
|
|
||||||
|
Texture = ImageTexture.CreateFromImage(svgImage);
|
||||||
|
|
||||||
|
PivotOffset = Size / 2;
|
||||||
|
|
||||||
|
_tween = CreateTween().SetLoops();
|
||||||
|
|
||||||
|
_tween.TweenProperty(this, "rotation", Mathf.DegToRad(360), 2f).AsRelative();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,8 +13,9 @@ namespace SlayTheSpire2.LAN.Multiplayer.Components
|
|||||||
|
|
||||||
public bool IsEmpty => string.IsNullOrEmpty(Text);
|
public bool IsEmpty => string.IsNullOrEmpty(Text);
|
||||||
|
|
||||||
public PlayerNameLineEdit()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
|
base._Ready();
|
||||||
TextChanged += OnTextChanged;
|
TextChanged += OnTextChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ namespace SlayTheSpire2.LAN.Multiplayer.Helpers
|
|||||||
{
|
{
|
||||||
var netService = new NetHostGameService();
|
var netService = new NetHostGameService();
|
||||||
NetErrorInfo? netErrorInfo = null;
|
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}");
|
Log.Info($"HostGame open on port:{port}");
|
||||||
if (!netErrorInfo.HasValue)
|
if (!netErrorInfo.HasValue)
|
||||||
{
|
{
|
||||||
@@ -94,7 +95,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Helpers
|
|||||||
{
|
{
|
||||||
var netService = new NetHostGameService();
|
var netService = new NetHostGameService();
|
||||||
NetErrorInfo? netErrorInfo = null;
|
NetErrorInfo? netErrorInfo = null;
|
||||||
netService.StartENetHost(port, maxPlayers);
|
netService.StartENetHost(port, maxPlayers + 1);
|
||||||
Log.Info($"HostGame open on port:{port}");
|
Log.Info($"HostGame open on port:{port}");
|
||||||
if (!netErrorInfo.HasValue)
|
if (!netErrorInfo.HasValue)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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<PacketWriter, int> SetPacketWriterBitPosition;
|
||||||
|
|
||||||
|
private static readonly Action<PacketReader, int> SetPacketReaderBitPosition;
|
||||||
|
|
||||||
|
private static readonly AccessTools.FieldRef<PacketWriter, byte[]> RefPacketWriterTempBuffer;
|
||||||
|
|
||||||
|
private static readonly AccessTools.FieldRef<PacketReader, byte[]> RefPacketReaderTempBuffer;
|
||||||
|
|
||||||
|
static PacketHelper()
|
||||||
|
{
|
||||||
|
const BindingFlags flags = BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public;
|
||||||
|
|
||||||
|
SetPacketWriterBitPosition =
|
||||||
|
AccessTools.MethodDelegate<Action<PacketWriter, int>>(typeof(PacketWriter)
|
||||||
|
.GetProperty("BitPosition", flags)
|
||||||
|
?.SetMethod!);
|
||||||
|
SetPacketReaderBitPosition =
|
||||||
|
AccessTools.MethodDelegate<Action<PacketReader, int>>(typeof(PacketReader)
|
||||||
|
.GetProperty("BitPosition", flags)
|
||||||
|
?.SetMethod!);
|
||||||
|
|
||||||
|
RefPacketWriterTempBuffer =
|
||||||
|
AccessTools.FieldRefAccess<PacketWriter, byte[]>("_tempBuffer");
|
||||||
|
RefPacketReaderTempBuffer =
|
||||||
|
AccessTools.FieldRefAccess<PacketReader, byte[]>("_tempBuffer");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void WriteList<T>(PacketWriter instance, IReadOnlyList<T> 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<T> ReadList<T>(PacketReader reader) where T : IPacketSerializable, new()
|
||||||
|
{
|
||||||
|
var list = new List<T>();
|
||||||
|
var num = ReadVarInt(reader);
|
||||||
|
for (var i = 0; i < num; i++)
|
||||||
|
{
|
||||||
|
list.Add(reader.Read<T>());
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,13 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.ENet
|
|||||||
[HarmonyPatch(typeof(ENetClient), "ConnectToHost")]
|
[HarmonyPatch(typeof(ENetClient), "ConnectToHost")]
|
||||||
internal class ENetClientConnectToHostPatch
|
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,
|
private static bool Prefix(ENetClient __instance, ulong netId, string ip, ushort port,
|
||||||
CancellationToken cancelToken, Logger ____logger, INetClientHandler ____handler, ref Task __result)
|
CancellationToken cancelToken, Logger ____logger, INetClientHandler ____handler, ref Task __result)
|
||||||
{
|
{
|
||||||
@@ -82,7 +89,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.ENet
|
|||||||
handler.OnConnectedToHost();
|
handler.OnConnectedToHost();
|
||||||
foreach (var item in bufferedPackets)
|
foreach (var item in bufferedPackets)
|
||||||
{
|
{
|
||||||
Traverse.Create(eNetClient).Method("HandleMessageReceived", item).GetValue();
|
HandleMessageReceived(eNetClient, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using MegaCrit.Sts2.Core.Entities.Multiplayer;
|
|||||||
using MegaCrit.Sts2.Core.Multiplayer.Connection;
|
using MegaCrit.Sts2.Core.Multiplayer.Connection;
|
||||||
using MegaCrit.Sts2.Core.Multiplayer.Game;
|
using MegaCrit.Sts2.Core.Multiplayer.Game;
|
||||||
using MegaCrit.Sts2.Core.Multiplayer.Messages.Lobby;
|
using MegaCrit.Sts2.Core.Multiplayer.Messages.Lobby;
|
||||||
|
using MegaCrit.Sts2.Core.Platform;
|
||||||
using SlayTheSpire2.LAN.Multiplayer.Models;
|
using SlayTheSpire2.LAN.Multiplayer.Models;
|
||||||
using SlayTheSpire2.LAN.Multiplayer.Services;
|
using SlayTheSpire2.LAN.Multiplayer.Services;
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs
|
|||||||
{
|
{
|
||||||
var result = await clientLobbyJoinResponseMessage;
|
var result = await clientLobbyJoinResponseMessage;
|
||||||
|
|
||||||
if (joinFlow.NetService == null)
|
if (joinFlow.NetService is not { Platform: PlatformType.None })
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
var lanPlayerNameService = LanPlayerNameService.Instance;
|
var lanPlayerNameService = LanPlayerNameService.Instance;
|
||||||
@@ -51,7 +52,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs
|
|||||||
{
|
{
|
||||||
var result = await clientLoadJoinResponseMessage;
|
var result = await clientLoadJoinResponseMessage;
|
||||||
|
|
||||||
if (joinFlow.NetService == null)
|
if (joinFlow.NetService is not { Platform: PlatformType.None })
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
var lanPlayerNameService = LanPlayerNameService.Instance;
|
var lanPlayerNameService = LanPlayerNameService.Instance;
|
||||||
@@ -78,7 +79,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs
|
|||||||
{
|
{
|
||||||
var result = await clientRejoinResponseMessage;
|
var result = await clientRejoinResponseMessage;
|
||||||
|
|
||||||
if (joinFlow.NetService == null)
|
if (joinFlow.NetService is not { Platform: PlatformType.None })
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
var lanPlayerNameService = LanPlayerNameService.Instance;
|
var lanPlayerNameService = LanPlayerNameService.Instance;
|
||||||
@@ -95,25 +96,30 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs
|
|||||||
[HarmonyPatch(typeof(JoinFlow), "OnDisconnected")]
|
[HarmonyPatch(typeof(JoinFlow), "OnDisconnected")]
|
||||||
internal class JoinFlowOnDisconnectedPatch
|
internal class JoinFlowOnDisconnectedPatch
|
||||||
{
|
{
|
||||||
private static void Postfix(NetErrorInfo info)
|
private static void Postfix(JoinFlow __instance, NetErrorInfo info)
|
||||||
|
{
|
||||||
|
if (__instance.NetService is { Platform: PlatformType.None })
|
||||||
|
{
|
||||||
|
var lanPlayerNameCompletion = LanPlayerNameService.Instance.LanPlayerNameCompletion;
|
||||||
|
|
||||||
|
if (lanPlayerNameCompletion?.Task is { IsCompleted: false })
|
||||||
{
|
{
|
||||||
var exception =
|
var exception =
|
||||||
new ClientConnectionFailedException(
|
new ClientConnectionFailedException(
|
||||||
$"Unexpectedly disconnected from host while joining. Reason: {info.GetReason()}", info);
|
$"Unexpectedly disconnected from host while joining. Reason: {info.GetReason()}", info);
|
||||||
|
|
||||||
var lanPlayerNameCompletion = LanPlayerNameService.Instance.LanPlayerNameCompletion;
|
|
||||||
|
|
||||||
if (lanPlayerNameCompletion?.Task is { IsCompleted: false })
|
|
||||||
{
|
|
||||||
lanPlayerNameCompletion.SetException(exception);
|
lanPlayerNameCompletion.SetException(exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[HarmonyPatch(typeof(JoinFlow), "Cancel")]
|
[HarmonyPatch(typeof(JoinFlow), "Cancel")]
|
||||||
internal class JoinFlowCancelPatch
|
internal class JoinFlowCancelPatch
|
||||||
{
|
{
|
||||||
private static void Postfix()
|
private static void Postfix(JoinFlow __instance)
|
||||||
|
{
|
||||||
|
if (__instance.NetService is { Platform: PlatformType.None })
|
||||||
{
|
{
|
||||||
var lanPlayerNameCompletion = LanPlayerNameService.Instance.LanPlayerNameCompletion;
|
var lanPlayerNameCompletion = LanPlayerNameService.Instance.LanPlayerNameCompletion;
|
||||||
|
|
||||||
@@ -124,3 +130,4 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -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<LobbyPlayer>(reader);
|
||||||
|
if (reader.ReadBool())
|
||||||
|
{
|
||||||
|
__instance.dailyTime = reader.Read<TimeServerResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reader.ReadBool())
|
||||||
|
{
|
||||||
|
__instance.seed = reader.ReadString();
|
||||||
|
}
|
||||||
|
|
||||||
|
__instance.ascension = reader.ReadInt(5);
|
||||||
|
__instance.modifiers = reader.ReadList<SerializableModifier>();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<LobbyPlayer>(reader);
|
||||||
|
__instance.seed = reader.ReadString();
|
||||||
|
__instance.modifiers = reader.ReadList<SerializableModifier>();
|
||||||
|
__instance.act1 = reader.ReadString();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<CharacterModel>();
|
||||||
|
__instance.unlockState = reader.Read<SerializableUnlockState>();
|
||||||
|
__instance.maxMultiplayerAscensionUnlocked = reader.ReadInt();
|
||||||
|
__instance.isReady = reader.ReadBool();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
using MegaCrit.Sts2.addons.mega_text;
|
using MegaCrit.Sts2.addons.mega_text;
|
||||||
using MegaCrit.Sts2.Core.Helpers;
|
using MegaCrit.Sts2.Core.Helpers;
|
||||||
using MegaCrit.Sts2.Core.Localization;
|
|
||||||
using MegaCrit.Sts2.Core.Multiplayer.Connection;
|
using MegaCrit.Sts2.Core.Multiplayer.Connection;
|
||||||
using MegaCrit.Sts2.Core.Nodes.GodotExtensions;
|
using MegaCrit.Sts2.Core.Nodes.GodotExtensions;
|
||||||
using MegaCrit.Sts2.Core.Nodes.Screens.MainMenu;
|
using MegaCrit.Sts2.Core.Nodes.Screens.MainMenu;
|
||||||
@@ -20,7 +19,6 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
|
|||||||
private static void Prefix(NJoinFriendScreen __instance)
|
private static void Prefix(NJoinFriendScreen __instance)
|
||||||
{
|
{
|
||||||
var lanPanel = new NinePatchRect { Name = "LANPanel" };
|
var lanPanel = new NinePatchRect { Name = "LANPanel" };
|
||||||
|
|
||||||
__instance.AddChild(lanPanel);
|
__instance.AddChild(lanPanel);
|
||||||
|
|
||||||
lanPanel.PatchMarginTop = 12;
|
lanPanel.PatchMarginTop = 12;
|
||||||
@@ -66,16 +64,12 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
|
|||||||
addressLineEdit.CustomMinimumSize = new Vector2(300, 50);
|
addressLineEdit.CustomMinimumSize = new Vector2(300, 50);
|
||||||
addressLineEdit.SizeFlagsHorizontal = Control.SizeFlags.ShrinkCenter;
|
addressLineEdit.SizeFlagsHorizontal = Control.SizeFlags.ShrinkCenter;
|
||||||
|
|
||||||
if (__instance.GetNode<NJoinFriendRefreshButton>("RefreshButton").Duplicate() is NJoinFriendRefreshButton
|
var joinButton = JoinButton.Create(__instance.GetNode<NJoinFriendRefreshButton>("RefreshButton"));
|
||||||
joinButton)
|
|
||||||
{
|
|
||||||
joinButton.Name = "JointButton";
|
joinButton.Name = "JointButton";
|
||||||
|
|
||||||
vBoxContainer.AddChild(joinButton);
|
vBoxContainer.AddChild(joinButton);
|
||||||
|
|
||||||
joinButton.CustomMinimumSize = new Vector2(150, 50);
|
|
||||||
joinButton.SizeFlagsHorizontal = Control.SizeFlags.ShrinkCenter;
|
|
||||||
|
|
||||||
joinButton.Connect(NClickableControl.SignalName.Released, Callable.From<NClickableControl>(_ =>
|
joinButton.Connect(NClickableControl.SignalName.Released, Callable.From<NClickableControl>(_ =>
|
||||||
{
|
{
|
||||||
var addressInfo = addressLineEdit.GetAddressInfo();
|
var addressInfo = addressLineEdit.GetAddressInfo();
|
||||||
@@ -101,14 +95,6 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
|
|||||||
SettingsService.Instance.SettingsModel.NetId, addressInfo.Address, port)));
|
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<MegaLabel>("Label");
|
|
||||||
|
|
||||||
joinButtonLabel.SetTextAutoSize(new LocString("main_menu_ui", "JOIN.title").GetFormattedText());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using HarmonyLib;
|
using Godot;
|
||||||
|
using HarmonyLib;
|
||||||
using MegaCrit.Sts2.Core.Helpers;
|
using MegaCrit.Sts2.Core.Helpers;
|
||||||
using MegaCrit.Sts2.Core.Nodes.Screens.MainMenu;
|
using MegaCrit.Sts2.Core.Nodes.Screens.MainMenu;
|
||||||
using SlayTheSpire2.LAN.Multiplayer.Components;
|
using SlayTheSpire2.LAN.Multiplayer.Components;
|
||||||
@@ -15,7 +16,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
|
|||||||
{
|
{
|
||||||
if (type == typeof(LanMultiplayerHostSubmenu))
|
if (type == typeof(LanMultiplayerHostSubmenu))
|
||||||
{
|
{
|
||||||
if (LanMultiplayerHostSubmenu.Instance == null)
|
if (!GodotObject.IsInstanceValid(LanMultiplayerHostSubmenu.Instance))
|
||||||
{
|
{
|
||||||
var lanMultiplayerHostSubmenu = LanMultiplayerHostSubmenu.Create();
|
var lanMultiplayerHostSubmenu = LanMultiplayerHostSubmenu.Create();
|
||||||
|
|
||||||
|
|||||||
+12
-2
@@ -17,6 +17,13 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
|
|||||||
[HarmonyPatch(typeof(NMultiplayerPlayerExpandedState), "_Ready")]
|
[HarmonyPatch(typeof(NMultiplayerPlayerExpandedState), "_Ready")]
|
||||||
internal class NMultiplayerPlayerExpandedStateReadyPatch
|
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)
|
private static void Prefix(NMultiplayerPlayerExpandedState __instance, Player ____player)
|
||||||
{
|
{
|
||||||
if (____player.NetId != RunManager.Instance.NetService.NetId)
|
if (____player.NetId != RunManager.Instance.NetService.NetId)
|
||||||
@@ -48,8 +55,11 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
|
|||||||
|
|
||||||
cardLibraryTickBox.Toggled += tickBox =>
|
cardLibraryTickBox.Toggled += tickBox =>
|
||||||
{
|
{
|
||||||
var drawingState = Traverse.Create(NMapScreen.Instance?.Drawings)
|
if (NMapScreen.Instance == null)
|
||||||
.Method("GetDrawingStateForPlayer", ____player.NetId).GetValue();
|
return;
|
||||||
|
|
||||||
|
var drawingState = GetDrawingStateForPlayer(NMapScreen.Instance.Drawings, ____player.NetId);
|
||||||
|
|
||||||
var drawViewport = Traverse.Create(drawingState).Field("drawViewport").GetValue<SubViewport>();
|
var drawViewport = Traverse.Create(drawingState).Field("drawViewport").GetValue<SubViewport>();
|
||||||
|
|
||||||
if (tickBox.IsTicked)
|
if (tickBox.IsTicked)
|
||||||
|
|||||||
@@ -18,12 +18,20 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
|
|||||||
[HarmonyPatch(typeof(NMultiplayerSubmenu), "_Ready")]
|
[HarmonyPatch(typeof(NMultiplayerSubmenu), "_Ready")]
|
||||||
internal class NMultiplayerSubmenuReadyPatch
|
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)
|
private static void Prefix(NMultiplayerSubmenu __instance)
|
||||||
{
|
{
|
||||||
var buttonContainerNode = __instance.GetNode("ButtonContainer");
|
var buttonContainerNode = __instance.GetNode("ButtonContainer");
|
||||||
|
|
||||||
if (buttonContainerNode.GetNode("HostButton").Duplicate() is not NSubmenuButton lanHostButton)
|
if (buttonContainerNode.GetNode("HostButton").Duplicate() is NSubmenuButton lanHostButton)
|
||||||
return;
|
{
|
||||||
|
NSubmenuButtonDuplicateMaterial(lanHostButton);
|
||||||
|
|
||||||
buttonContainerNode.AddChild(lanHostButton);
|
buttonContainerNode.AddChild(lanHostButton);
|
||||||
buttonContainerNode.MoveChild(lanHostButton, 1);
|
buttonContainerNode.MoveChild(lanHostButton, 1);
|
||||||
@@ -52,14 +60,12 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
|
|||||||
var lanHostTitle = Traverse.Create(lanHostButton).Field("_title").GetValue<MegaLabel>();
|
var lanHostTitle = Traverse.Create(lanHostButton).Field("_title").GetValue<MegaLabel>();
|
||||||
lanHostTitle.Text = $"LAN {lanHostTitle.Text}";
|
lanHostTitle.Text = $"LAN {lanHostTitle.Text}";
|
||||||
|
|
||||||
NSubmenuButtonDuplicateMaterial(lanHostButton);
|
LanMultiplayerSubmenuButtonService.Instance.LanHostButton = lanHostButton;
|
||||||
|
}
|
||||||
|
|
||||||
var lanMultiplayerSubmenuButtonService = LanMultiplayerSubmenuButtonService.Instance;
|
if (buttonContainerNode.GetNode("LoadButton").Duplicate() is NSubmenuButton lanLoadButton)
|
||||||
|
{
|
||||||
lanMultiplayerSubmenuButtonService.LanHostButton = lanHostButton;
|
NSubmenuButtonDuplicateMaterial(lanLoadButton);
|
||||||
|
|
||||||
if (buttonContainerNode.GetNode("LoadButton").Duplicate() is not NSubmenuButton lanLoadButton)
|
|
||||||
return;
|
|
||||||
|
|
||||||
buttonContainerNode.AddChild(lanLoadButton);
|
buttonContainerNode.AddChild(lanLoadButton);
|
||||||
buttonContainerNode.MoveChild(lanLoadButton, 2);
|
buttonContainerNode.MoveChild(lanLoadButton, 2);
|
||||||
@@ -79,12 +85,12 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
|
|||||||
var lanLoadButtonTitle = Traverse.Create(lanLoadButton).Field("_title").GetValue<MegaLabel>();
|
var lanLoadButtonTitle = Traverse.Create(lanLoadButton).Field("_title").GetValue<MegaLabel>();
|
||||||
lanLoadButtonTitle.Text = $"LAN {lanLoadButtonTitle.Text}";
|
lanLoadButtonTitle.Text = $"LAN {lanLoadButtonTitle.Text}";
|
||||||
|
|
||||||
NSubmenuButtonDuplicateMaterial(lanLoadButton);
|
LanMultiplayerSubmenuButtonService.Instance.LanLoadButton = lanLoadButton;
|
||||||
|
}
|
||||||
|
|
||||||
lanMultiplayerSubmenuButtonService.LanLoadButton = lanLoadButton;
|
if (buttonContainerNode.GetNode("AbandonButton").Duplicate() is NSubmenuButton lanAbandonButton)
|
||||||
|
{
|
||||||
if (buttonContainerNode.GetNode("AbandonButton").Duplicate() is not NSubmenuButton lanAbandonButton)
|
NSubmenuButtonDuplicateMaterial(lanAbandonButton);
|
||||||
return;
|
|
||||||
|
|
||||||
buttonContainerNode.AddChild(lanAbandonButton);
|
buttonContainerNode.AddChild(lanAbandonButton);
|
||||||
buttonContainerNode.MoveChild(lanAbandonButton, 3);
|
buttonContainerNode.MoveChild(lanAbandonButton, 3);
|
||||||
@@ -92,28 +98,21 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
|
|||||||
lanAbandonButton.Connect(NClickableControl.SignalName.Released,
|
lanAbandonButton.Connect(NClickableControl.SignalName.Released,
|
||||||
Callable.From<NButton>(_ =>
|
Callable.From<NButton>(_ =>
|
||||||
{
|
{
|
||||||
var traverse = Traverse.Create(__instance);
|
|
||||||
|
|
||||||
TaskHelper.RunSafely(
|
TaskHelper.RunSafely(
|
||||||
LanHostHelper.TryAbandonMultiplayerRun(() => traverse.Method("UpdateButtons").GetValue()));
|
LanHostHelper.TryAbandonMultiplayerRun(() => UpdateButtons(__instance)));
|
||||||
}));
|
}));
|
||||||
lanAbandonButton.SetIconAndLocalization("MP_ABANDON");
|
lanAbandonButton.SetIconAndLocalization("MP_ABANDON");
|
||||||
var lanAbandonButtonTitle = Traverse.Create(lanAbandonButton).Field("_title").GetValue<MegaLabel>();
|
var lanAbandonButtonTitle = Traverse.Create(lanAbandonButton).Field("_title").GetValue<MegaLabel>();
|
||||||
lanAbandonButtonTitle.Text = $"LAN {lanAbandonButtonTitle.Text}";
|
lanAbandonButtonTitle.Text = $"LAN {lanAbandonButtonTitle.Text}";
|
||||||
|
|
||||||
NSubmenuButtonDuplicateMaterial(lanAbandonButton);
|
LanMultiplayerSubmenuButtonService.Instance.LanAbandonButton = lanAbandonButton;
|
||||||
|
}
|
||||||
lanMultiplayerSubmenuButtonService.LanAbandonButton = lanAbandonButton;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void NSubmenuButtonDuplicateMaterial(NSubmenuButton nSubmenuButton)
|
private static void NSubmenuButtonDuplicateMaterial(NSubmenuButton nSubmenuButton)
|
||||||
{
|
{
|
||||||
var traverse = Traverse.Create(nSubmenuButton);
|
var bgPanel = nSubmenuButton.GetNode<Control>("BgPanel");
|
||||||
|
|
||||||
var bgPanel = traverse.Field("_bgPanel").GetValue<Control>();
|
|
||||||
bgPanel.Material = bgPanel.Material.Duplicate() as Material;
|
bgPanel.Material = bgPanel.Material.Duplicate() as Material;
|
||||||
|
|
||||||
traverse.Field("_hsv").SetValue(bgPanel.Material);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using Godot;
|
using Godot;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
|
using MegaCrit.Sts2.addons.mega_text;
|
||||||
|
using MegaCrit.Sts2.Core.Localization;
|
||||||
using MegaCrit.Sts2.Core.Nodes.Screens.Settings;
|
using MegaCrit.Sts2.Core.Nodes.Screens.Settings;
|
||||||
using SlayTheSpire2.LAN.Multiplayer.Components;
|
using SlayTheSpire2.LAN.Multiplayer.Components;
|
||||||
using SlayTheSpire2.LAN.Multiplayer.Services;
|
using SlayTheSpire2.LAN.Multiplayer.Services;
|
||||||
@@ -12,6 +14,13 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
|
|||||||
[HarmonyPatch(typeof(NSettingsScreen), "_Ready")]
|
[HarmonyPatch(typeof(NSettingsScreen), "_Ready")]
|
||||||
internal class NSettingsScreenReadyPatch
|
internal class NSettingsScreenReadyPatch
|
||||||
{
|
{
|
||||||
|
[HarmonyReversePatch]
|
||||||
|
[HarmonyPatch(typeof(NSettingsPanel), "RefreshSize")]
|
||||||
|
private static void RefreshSize(NSettingsPanel instance)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
private static void Prefix(NSettingsScreen __instance)
|
private static void Prefix(NSettingsScreen __instance)
|
||||||
{
|
{
|
||||||
var moddingNode = __instance.GetNode("%Modding");
|
var moddingNode = __instance.GetNode("%Modding");
|
||||||
@@ -21,7 +30,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
|
|||||||
|
|
||||||
if (__instance.GetNode("%ModdingDivider").Duplicate() is ColorRect hostPortDivider &&
|
if (__instance.GetNode("%ModdingDivider").Duplicate() is ColorRect hostPortDivider &&
|
||||||
moddingNode.Duplicate() is MarginContainer hostPort &&
|
moddingNode.Duplicate() is MarginContainer hostPort &&
|
||||||
hostPort.GetNode("Label") is RichTextLabel hostPortLabel)
|
hostPort.GetNode("Label") is MegaRichTextLabel hostPortLabel)
|
||||||
{
|
{
|
||||||
hostPortDivider.Name = "HostPortDivider";
|
hostPortDivider.Name = "HostPortDivider";
|
||||||
|
|
||||||
@@ -40,7 +49,6 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
|
|||||||
hostPort.Show();
|
hostPort.Show();
|
||||||
|
|
||||||
var hostPortLineEdit = new SpinBox { Name = "HostPortInput" };
|
var hostPortLineEdit = new SpinBox { Name = "HostPortInput" };
|
||||||
|
|
||||||
hostPort.AddChild(hostPortLineEdit);
|
hostPort.AddChild(hostPortLineEdit);
|
||||||
|
|
||||||
hostPortLineEdit.CustomMinimumSize = new Vector2(324, 64);
|
hostPortLineEdit.CustomMinimumSize = new Vector2(324, 64);
|
||||||
@@ -62,7 +70,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
|
|||||||
|
|
||||||
if (__instance.GetNode("%ModdingDivider").Duplicate() is ColorRect hostMaxPlayersDivider &&
|
if (__instance.GetNode("%ModdingDivider").Duplicate() is ColorRect hostMaxPlayersDivider &&
|
||||||
moddingNode.Duplicate() is MarginContainer hostMaxPlayers &&
|
moddingNode.Duplicate() is MarginContainer hostMaxPlayers &&
|
||||||
hostMaxPlayers.GetNode("Label") is RichTextLabel hostMaxPlayersLabel)
|
hostMaxPlayers.GetNode("Label") is MegaRichTextLabel hostMaxPlayersLabel)
|
||||||
{
|
{
|
||||||
hostMaxPlayersDivider.Name = "HostMaxPlayersDivider";
|
hostMaxPlayersDivider.Name = "HostMaxPlayersDivider";
|
||||||
|
|
||||||
@@ -81,7 +89,6 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
|
|||||||
hostMaxPlayers.Show();
|
hostMaxPlayers.Show();
|
||||||
|
|
||||||
var hostMaxPlayersInput = new SpinBox { Name = "HostMaxPlayersInput" };
|
var hostMaxPlayersInput = new SpinBox { Name = "HostMaxPlayersInput" };
|
||||||
|
|
||||||
hostMaxPlayers.AddChild(hostMaxPlayersInput);
|
hostMaxPlayers.AddChild(hostMaxPlayersInput);
|
||||||
|
|
||||||
hostMaxPlayersInput.CustomMinimumSize = new Vector2(324, 64);
|
hostMaxPlayersInput.CustomMinimumSize = new Vector2(324, 64);
|
||||||
@@ -102,7 +109,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
|
|||||||
|
|
||||||
if (__instance.GetNode("%ModdingDivider").Duplicate() is ColorRect playerNameDivider &&
|
if (__instance.GetNode("%ModdingDivider").Duplicate() is ColorRect playerNameDivider &&
|
||||||
moddingNode.Duplicate() is MarginContainer playerName &&
|
moddingNode.Duplicate() is MarginContainer playerName &&
|
||||||
playerName.GetNode("Label") is RichTextLabel playerNameLabel)
|
playerName.GetNode("Label") is MegaRichTextLabel playerNameLabel)
|
||||||
{
|
{
|
||||||
playerNameDivider.Name = "PlayerNameDivider";
|
playerNameDivider.Name = "PlayerNameDivider";
|
||||||
|
|
||||||
@@ -120,11 +127,15 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
|
|||||||
|
|
||||||
playerName.Show();
|
playerName.Show();
|
||||||
|
|
||||||
|
var marginContainer = new MarginContainer();
|
||||||
|
playerName.AddChild(marginContainer);
|
||||||
|
|
||||||
|
marginContainer.AddThemeConstantOverride("margin_right", 18);
|
||||||
|
|
||||||
var playerNameInput = new PlayerNameLineEdit { Name = "PlayerNameInput" };
|
var playerNameInput = new PlayerNameLineEdit { Name = "PlayerNameInput" };
|
||||||
|
marginContainer.AddChild(playerNameInput);
|
||||||
|
|
||||||
playerName.AddChild(playerNameInput);
|
playerNameInput.CustomMinimumSize = new Vector2(308, 64);
|
||||||
|
|
||||||
playerNameInput.CustomMinimumSize = new Vector2(324, 64);
|
|
||||||
playerNameInput.SizeFlagsHorizontal = Control.SizeFlags.ShrinkEnd;
|
playerNameInput.SizeFlagsHorizontal = Control.SizeFlags.ShrinkEnd;
|
||||||
playerNameInput.Alignment = HorizontalAlignment.Center;
|
playerNameInput.Alignment = HorizontalAlignment.Center;
|
||||||
|
|
||||||
@@ -145,7 +156,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
|
|||||||
|
|
||||||
if (__instance.GetNode("%ModdingDivider").Duplicate() is ColorRect netIdDivider &&
|
if (__instance.GetNode("%ModdingDivider").Duplicate() is ColorRect netIdDivider &&
|
||||||
moddingNode.Duplicate() is MarginContainer netId &&
|
moddingNode.Duplicate() is MarginContainer netId &&
|
||||||
netId.GetNode("Label") is RichTextLabel netIdLabel)
|
netId.GetNode("Label") is MegaRichTextLabel netIdLabel)
|
||||||
{
|
{
|
||||||
netIdDivider.Name = "NetIDDivider";
|
netIdDivider.Name = "NetIDDivider";
|
||||||
|
|
||||||
@@ -164,7 +175,6 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
|
|||||||
netId.Show();
|
netId.Show();
|
||||||
|
|
||||||
var netIdInput = new SpinBox { Name = "NetIDInput" };
|
var netIdInput = new SpinBox { Name = "NetIDInput" };
|
||||||
|
|
||||||
netId.AddChild(netIdInput);
|
netId.AddChild(netIdInput);
|
||||||
|
|
||||||
netIdInput.CustomMinimumSize = new Vector2(324, 64);
|
netIdInput.CustomMinimumSize = new Vector2(324, 64);
|
||||||
@@ -186,8 +196,33 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
|
|||||||
|
|
||||||
if (generalSettings is NSettingsPanel nSettingsPanel)
|
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<NSettingsPanel>("%GeneralSettings").Content;
|
||||||
|
|
||||||
|
LocHelper(content.GetNode<Node>("HostPort"),
|
||||||
|
new LocString("settings_ui", "SlayTheSpire2.LAN.Multiplayer.HOST_PORT"));
|
||||||
|
LocHelper(content.GetNode<Node>("HostMaxPlayers"),
|
||||||
|
new LocString("settings_ui", "SlayTheSpire2.LAN.Multiplayer.HOST_MAX_PLAYERS"));
|
||||||
|
LocHelper(content.GetNode<Node>("PlayerName"),
|
||||||
|
new LocString("settings_ui", "SlayTheSpire2.LAN.Multiplayer.PLAYER_NAME"));
|
||||||
|
LocHelper(content.GetNode<Node>("NetID"),
|
||||||
|
new LocString("settings_ui", "SlayTheSpire2.LAN.Multiplayer.NET_ID"));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@ using MegaCrit.Sts2.Core.Multiplayer.Game.Lobby;
|
|||||||
using MegaCrit.Sts2.Core.Nodes.Screens.CharacterSelect;
|
using MegaCrit.Sts2.Core.Nodes.Screens.CharacterSelect;
|
||||||
using MegaCrit.Sts2.Core.Nodes.Screens.CustomRun;
|
using MegaCrit.Sts2.Core.Nodes.Screens.CustomRun;
|
||||||
using MegaCrit.Sts2.Core.Nodes.Screens.DailyRun;
|
using MegaCrit.Sts2.Core.Nodes.Screens.DailyRun;
|
||||||
|
using MegaCrit.Sts2.Core.Nodes.Screens.MainMenu;
|
||||||
using MegaCrit.Sts2.Core.Platform;
|
using MegaCrit.Sts2.Core.Platform;
|
||||||
using SlayTheSpire2.LAN.Multiplayer.Services;
|
using SlayTheSpire2.LAN.Multiplayer.Services;
|
||||||
|
|
||||||
@@ -14,21 +15,52 @@ using SlayTheSpire2.LAN.Multiplayer.Services;
|
|||||||
namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
|
namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
|
||||||
{
|
{
|
||||||
[HarmonyPatch]
|
[HarmonyPatch]
|
||||||
internal class RunLoadScreenInitializePatchs
|
internal class RunLoadScreenInitializeAsHostPatchs
|
||||||
{
|
{
|
||||||
private static IEnumerable<MethodInfo> TargetMethods()
|
private static IEnumerable<MethodInfo> TargetMethods()
|
||||||
{
|
{
|
||||||
const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public;
|
const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public;
|
||||||
|
|
||||||
yield return typeof(NMultiplayerLoadGameScreen).GetMethod("InitializeAsHost", flags)!;
|
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("InitializeAsHost", flags)!;
|
||||||
yield return typeof(NDailyRunLoadScreen).GetMethod("InitializeAsClient", flags)!;
|
|
||||||
yield return typeof(NCustomRunLoadScreen).GetMethod("InitializeAsHost", 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<MethodInfo> 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)!;
|
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)
|
if (gameService.Platform == PlatformType.None)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ using MegaCrit.Sts2.Core.Multiplayer.Game;
|
|||||||
using MegaCrit.Sts2.Core.Nodes.Screens.CharacterSelect;
|
using MegaCrit.Sts2.Core.Nodes.Screens.CharacterSelect;
|
||||||
using MegaCrit.Sts2.Core.Nodes.Screens.CustomRun;
|
using MegaCrit.Sts2.Core.Nodes.Screens.CustomRun;
|
||||||
using MegaCrit.Sts2.Core.Nodes.Screens.DailyRun;
|
using MegaCrit.Sts2.Core.Nodes.Screens.DailyRun;
|
||||||
|
using MegaCrit.Sts2.Core.Nodes.Screens.MainMenu;
|
||||||
using MegaCrit.Sts2.Core.Platform;
|
using MegaCrit.Sts2.Core.Platform;
|
||||||
|
using SlayTheSpire2.LAN.Multiplayer.Components;
|
||||||
using SlayTheSpire2.LAN.Multiplayer.Services;
|
using SlayTheSpire2.LAN.Multiplayer.Services;
|
||||||
|
|
||||||
// ReSharper disable UnusedMember.Global
|
// ReSharper disable UnusedMember.Global
|
||||||
@@ -13,21 +15,41 @@ using SlayTheSpire2.LAN.Multiplayer.Services;
|
|||||||
namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
|
namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
|
||||||
{
|
{
|
||||||
[HarmonyPatch]
|
[HarmonyPatch]
|
||||||
internal class RunScreenInitializePatchs
|
internal class RunScreenReadyPatchs
|
||||||
|
{
|
||||||
|
private static IEnumerable<MethodInfo> 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<MethodInfo> TargetMethods()
|
private static IEnumerable<MethodInfo> TargetMethods()
|
||||||
{
|
{
|
||||||
const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public;
|
const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public;
|
||||||
|
|
||||||
yield return typeof(NCharacterSelectScreen).GetMethod("InitializeMultiplayerAsClient", flags)!;
|
|
||||||
yield return typeof(NCharacterSelectScreen).GetMethod("InitializeMultiplayerAsHost", 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(NDailyRunScreen).GetMethod("InitializeMultiplayerAsHost", flags)!;
|
||||||
yield return typeof(NCustomRunScreen).GetMethod("InitializeMultiplayerAsClient", flags)!;
|
|
||||||
yield return typeof(NCustomRunScreen).GetMethod("InitializeMultiplayerAsHost", 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)
|
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<MethodInfo> 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<MethodInfo> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,7 +55,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Services
|
|||||||
|
|
||||||
nVerticalPopup.YesButton.Visible = false;
|
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.header"),
|
||||||
new LocString("gameplay_ui", "CONFIRM_LOAD_SAVE.cancel"),
|
new LocString("gameplay_ui", "CONFIRM_LOAD_SAVE.cancel"),
|
||||||
new LocString("gameplay_ui", "CONFIRM_LOAD_SAVE.confirm"));
|
new LocString("gameplay_ui", "CONFIRM_LOAD_SAVE.confirm"));
|
||||||
|
|||||||
@@ -8,6 +8,10 @@
|
|||||||
<FileVersion>1.5.0.0</FileVersion>
|
<FileVersion>1.5.0.0</FileVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Godot.SourceGenerators" Version="4.5.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="0Harmony">
|
<Reference Include="0Harmony">
|
||||||
<HintPath>V:\Slay.the.Spire.2\data_sts2_windows_x86_64\0Harmony.dll</HintPath>
|
<HintPath>V:\Slay.the.Spire.2\data_sts2_windows_x86_64\0Harmony.dll</HintPath>
|
||||||
|
|||||||
@@ -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:"
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
@@ -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:"
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
@@ -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:"
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
@@ -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:"
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
@@ -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:"
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
@@ -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アドレス:"
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
@@ -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주소:"
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
@@ -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:"
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
@@ -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:"
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
@@ -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 адрес:"
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
@@ -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:"
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
@@ -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:"
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
@@ -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:"
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
@@ -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地址:"
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user