From 6ff7a9dc2bd49e63e03301cfaf5a71df5795fac7 Mon Sep 17 00:00:00 2001 From: kmyuhkyuk <72241714+kmyuhkyuk@users.noreply.github.com> Date: Sun, 15 Mar 2026 08:06:26 +0800 Subject: [PATCH] Fixed inability to connect via port Added Scrollbar to choose Relic with more than 4 players Added custom player name feature, Now host will synchronize client names --- .../Components/AddressLineEdit.cs | 5 +- .../Components/PlayerNameLineEdit.cs | 37 ++++++ .../Helpers/LanPlayerNameHelper.cs | 119 +++++++++++++++++ .../Helpers/LanRunSaveManagerHelper.cs | 16 ++- .../Models/LanPlayerNameRequestMessage.cs | 25 ++++ .../Models/LanPlayerNameResponseMessage.cs | 35 +++++ .../Models/LanPlayerNames.cs | 9 ++ .../Models/SettingsModel.cs | 2 + .../Patchs/{ => ENet}/ENetClientPatch.cs | 4 +- .../Patchs/{ => ENet}/ENetHostPatch.cs | 2 +- .../Patchs/JoinFlowPatch.cs | 120 +++++++++++++++++ .../NTreasureRoomRelicCollectionPatch.cs | 67 ---------- .../Patchs/NullPlatformUtilStrategyPatch.cs | 45 +++++++ .../Patchs/RunLobbyPatchs.cs | 82 ++++++++++++ .../Patchs/RunManagerPatch.cs | 3 +- .../Patchs/RunSaveManagerPatch.cs | 28 +++- .../Screens/NCharacterSelectScreenPatchs.cs | 32 +++++ .../{ => Screens}/NJoinFriendScreenPatch.cs | 13 +- .../Patchs/{ => Screens}/NMapDrawingsPatch.cs | 4 +- .../NMultiplayerLoadGameScreenPatch.cs | 32 +++++ .../NMultiplayerPlayerExpandedStatePatch.cs | 4 +- .../{ => Screens}/NMultiplayerSubmenuPatch.cs | 2 +- .../{ => Screens}/NRestSiteRoomPatch.cs | 4 +- .../{ => Screens}/NSettingsScreenPatch.cs | 80 +++++++++--- .../NTreasureRoomRelicCollectionPatch.cs | 122 ++++++++++++++++++ 25 files changed, 785 insertions(+), 107 deletions(-) create mode 100644 SlayTheSpire2.LAN.Multiplayer/Components/PlayerNameLineEdit.cs create mode 100644 SlayTheSpire2.LAN.Multiplayer/Helpers/LanPlayerNameHelper.cs create mode 100644 SlayTheSpire2.LAN.Multiplayer/Models/LanPlayerNameRequestMessage.cs create mode 100644 SlayTheSpire2.LAN.Multiplayer/Models/LanPlayerNameResponseMessage.cs create mode 100644 SlayTheSpire2.LAN.Multiplayer/Models/LanPlayerNames.cs rename SlayTheSpire2.LAN.Multiplayer/Patchs/{ => ENet}/ENetClientPatch.cs (98%) rename SlayTheSpire2.LAN.Multiplayer/Patchs/{ => ENet}/ENetHostPatch.cs (98%) create mode 100644 SlayTheSpire2.LAN.Multiplayer/Patchs/JoinFlowPatch.cs delete mode 100644 SlayTheSpire2.LAN.Multiplayer/Patchs/NTreasureRoomRelicCollectionPatch.cs create mode 100644 SlayTheSpire2.LAN.Multiplayer/Patchs/NullPlatformUtilStrategyPatch.cs create mode 100644 SlayTheSpire2.LAN.Multiplayer/Patchs/RunLobbyPatchs.cs create mode 100644 SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NCharacterSelectScreenPatchs.cs rename SlayTheSpire2.LAN.Multiplayer/Patchs/{ => Screens}/NJoinFriendScreenPatch.cs (91%) rename SlayTheSpire2.LAN.Multiplayer/Patchs/{ => Screens}/NMapDrawingsPatch.cs (83%) create mode 100644 SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NMultiplayerLoadGameScreenPatch.cs rename SlayTheSpire2.LAN.Multiplayer/Patchs/{ => Screens}/NMultiplayerPlayerExpandedStatePatch.cs (96%) rename SlayTheSpire2.LAN.Multiplayer/Patchs/{ => Screens}/NMultiplayerSubmenuPatch.cs (98%) rename SlayTheSpire2.LAN.Multiplayer/Patchs/{ => Screens}/NRestSiteRoomPatch.cs (97%) rename SlayTheSpire2.LAN.Multiplayer/Patchs/{ => Screens}/NSettingsScreenPatch.cs (59%) create mode 100644 SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NTreasureRoomRelicCollectionPatch.cs diff --git a/SlayTheSpire2.LAN.Multiplayer/Components/AddressLineEdit.cs b/SlayTheSpire2.LAN.Multiplayer/Components/AddressLineEdit.cs index dcce3ca..70fb56f 100644 --- a/SlayTheSpire2.LAN.Multiplayer/Components/AddressLineEdit.cs +++ b/SlayTheSpire2.LAN.Multiplayer/Components/AddressLineEdit.cs @@ -8,7 +8,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Components { public override void _Ready() { - TextSubmitted += OnTextSubmitted; + TextChanged += OnTextChanged; } public struct AddressInfo(bool isValid, string? address, ushort? port) @@ -50,13 +50,14 @@ namespace SlayTheSpire2.LAN.Multiplayer.Components else if (IPEndPoint.TryParse(Text, out var ipEndPoint)) { addressInfo.IsValid = true; + addressInfo.Address = ipEndPoint.Address.ToString(); addressInfo.Port = (ushort)ipEndPoint.Port; } return addressInfo; } - private void OnTextSubmitted(string newText) + private void OnTextChanged(string newText) { Modulate = Colors.White; diff --git a/SlayTheSpire2.LAN.Multiplayer/Components/PlayerNameLineEdit.cs b/SlayTheSpire2.LAN.Multiplayer/Components/PlayerNameLineEdit.cs new file mode 100644 index 0000000..d79555a --- /dev/null +++ b/SlayTheSpire2.LAN.Multiplayer/Components/PlayerNameLineEdit.cs @@ -0,0 +1,37 @@ +using System.Text.RegularExpressions; +using Godot; +using MegaCrit.Sts2.Core.Nodes.GodotExtensions; + +namespace SlayTheSpire2.LAN.Multiplayer.Components +{ + internal partial class PlayerNameLineEdit : NMegaLineEdit + { + [GeneratedRegex(@"[\x00-\x1F\x7F<>:""/\\|?*\x0B\x0C\x0D&;`#$%^+={}]")] + private static partial Regex InvalidCharsRegex(); + + public bool IsInvalid => !GetPlayerNameIsInvalid(Text); + + public bool IsEmpty => string.IsNullOrEmpty(Text); + + public PlayerNameLineEdit() + { + TextChanged += OnTextChanged; + } + + private void OnTextChanged(string newText) + { + Modulate = Colors.White; + + if (string.IsNullOrEmpty(newText) || !GetPlayerNameIsInvalid(newText)) + return; + + Modulate = Colors.Red; + } + + public static bool GetPlayerNameIsInvalid(string playerName) + { + return string.IsNullOrWhiteSpace(playerName) || playerName.StartsWith(' ') || + InvalidCharsRegex().IsMatch(playerName); + } + } +} \ No newline at end of file diff --git a/SlayTheSpire2.LAN.Multiplayer/Helpers/LanPlayerNameHelper.cs b/SlayTheSpire2.LAN.Multiplayer/Helpers/LanPlayerNameHelper.cs new file mode 100644 index 0000000..43f0049 --- /dev/null +++ b/SlayTheSpire2.LAN.Multiplayer/Helpers/LanPlayerNameHelper.cs @@ -0,0 +1,119 @@ +using Godot; +using HarmonyLib; +using MegaCrit.Sts2.addons.mega_text; +using MegaCrit.Sts2.Core.Multiplayer; +using MegaCrit.Sts2.Core.Multiplayer.Game; +using MegaCrit.Sts2.Core.Nodes.Multiplayer; +using MegaCrit.Sts2.Core.Nodes.Screens.CharacterSelect; +using MegaCrit.Sts2.Core.Platform; +using SlayTheSpire2.LAN.Multiplayer.Components; +using SlayTheSpire2.LAN.Multiplayer.Models; + +// ReSharper disable ClassNeverInstantiated.Global + +namespace SlayTheSpire2.LAN.Multiplayer.Helpers +{ + internal class LanPlayerNameHelper + { + public static LanPlayerNames PlayerNameDictionary = DefaultPlayerNameDictionary; + + public static INetGameService? NetService; + + public static NCharacterSelectScreen? CharacterSelectScreen; + + public static NMultiplayerLoadGameScreen? MultiplayerLoadGameScreen; + + public static TaskCompletionSource? LanPlayerNameCompletion; + + private static LanPlayerNames DefaultPlayerNameDictionary => + !PlayerNameLineEdit.GetPlayerNameIsInvalid(SettingsHelper.Instance.SettingsModel.PlayerName) + ? new LanPlayerNames + { + { 1u, SettingsHelper.Instance.SettingsModel.PlayerName } + } + : new LanPlayerNames(); + + public static void SetHostPlayerName() + { + if (!PlayerNameLineEdit.GetPlayerNameIsInvalid(SettingsHelper.Instance.SettingsModel.PlayerName)) + { + PlayerNameDictionary[1u] = SettingsHelper.Instance.SettingsModel.PlayerName; + } + else + { + PlayerNameDictionary.Remove(1u); + } + } + + public static void SetDefaultPlayerNameDictionary() + { + PlayerNameDictionary = DefaultPlayerNameDictionary; + } + + public static void HandleLanPlayerNameResponseMessage(LanPlayerNameResponseMessage lanPlayerNameResponseMessage, + ulong senderId) + { + PlayerNameDictionary = lanPlayerNameResponseMessage.playerNameDictionary; + + UpdatePlayerName(); + + LanPlayerNameCompletion?.SetResult(lanPlayerNameResponseMessage); + } + + public static void HandleLanPlayerNameRequestMessage(LanPlayerNameRequestMessage lanPlayerNameRequestMessage, + ulong senderId) + { + if (PlayerNameLineEdit.GetPlayerNameIsInvalid(lanPlayerNameRequestMessage.playerName)) + { + NetService?.SendMessage( + new LanPlayerNameResponseMessage { playerNameDictionary = PlayerNameDictionary }, senderId); + return; + } + + PlayerNameDictionary[senderId] = lanPlayerNameRequestMessage.playerName; + + UpdatePlayerName(); + + NetService?.SendMessage(new LanPlayerNameResponseMessage { playerNameDictionary = PlayerNameDictionary }); + } + + public static async Task AttemptPlayerName(NetClientGameService gameService) + { + LanPlayerNameCompletion = new TaskCompletionSource(); + var message = new LanPlayerNameRequestMessage + { playerName = SettingsHelper.Instance.SettingsModel.PlayerName }; + gameService.SendMessage(message); + await LanPlayerNameCompletion.Task; + LanPlayerNameCompletion = null; + } + + private static void UpdatePlayerName() + { + if (CharacterSelectScreen != null) + { + UpdateNameplateLabel(CharacterSelectScreen); + } + + if (MultiplayerLoadGameScreen != null) + { + UpdateNameplateLabel(MultiplayerLoadGameScreen); + } + } + + private static void UpdateNameplateLabel(Control container) + { + var nodes = Traverse.Create(container).Field("_remotePlayerContainer") + .Field("_nodes").GetValue>(); + + foreach (var node in nodes) + { + var megaLabel = Traverse.Create(node).Field("_nameplateLabel").GetValue(); + + if (!GodotObject.IsInstanceValid(megaLabel)) + continue; + + megaLabel.SetTextAutoSize(PlatformUtil.GetPlayerName(PlatformType.None, node.PlayerId)); + } + } + } +} \ No newline at end of file diff --git a/SlayTheSpire2.LAN.Multiplayer/Helpers/LanRunSaveManagerHelper.cs b/SlayTheSpire2.LAN.Multiplayer/Helpers/LanRunSaveManagerHelper.cs index 458b646..87d9c74 100644 --- a/SlayTheSpire2.LAN.Multiplayer/Helpers/LanRunSaveManagerHelper.cs +++ b/SlayTheSpire2.LAN.Multiplayer/Helpers/LanRunSaveManagerHelper.cs @@ -1,9 +1,11 @@ -using HarmonyLib; +using System.Text.Json; +using HarmonyLib; using MegaCrit.Sts2.Core.Logging; using MegaCrit.Sts2.Core.Runs; using MegaCrit.Sts2.Core.Saves; using MegaCrit.Sts2.Core.Saves.Managers; using MegaCrit.Sts2.Core.Saves.Migrations; +using SlayTheSpire2.LAN.Multiplayer.Models; // ReSharper disable MemberCanBePrivate.Global // ReSharper disable ClassNeverInstantiated.Global @@ -23,6 +25,9 @@ namespace SlayTheSpire2.LAN.Multiplayer.Helpers public static string CurrentMultiplayerRunSavePath => RunSaveManager.GetRunSavePath(ProfileIdProvider.CurrentProfileId, "current_lan_run_mp.save"); + public static string CurrentMultiplayerRunPlayerNamesPath => + RunSaveManager.GetRunSavePath(ProfileIdProvider.CurrentProfileId, "current_lan_run_mp_player_names.json"); + public static bool HasMultiplayerRunSave => SaveStore.FileExists(CurrentMultiplayerRunSavePath); public static ReadSaveResult LoadAndCanonicalizeMultiplayerRunSave(ulong localPlayerId) @@ -33,6 +38,14 @@ namespace SlayTheSpire2.LAN.Multiplayer.Helpers try { var data = RunManager.CanonicalizeSave(readSaveResult.SaveData, localPlayerId); + var playerNamesJson = SaveStore.ReadFile(CurrentMultiplayerRunPlayerNamesPath); + if (!string.IsNullOrEmpty(playerNamesJson)) + { + LanPlayerNameHelper.PlayerNameDictionary = + JsonSerializer.Deserialize(playerNamesJson) ?? new LanPlayerNames(); + } + + LanPlayerNameHelper.SetHostPlayerName(); return new ReadSaveResult(data, ReadSaveStatus.Success); } catch (Exception value) @@ -76,6 +89,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Helpers public static void DeleteCurrentMultiplayerRun() { SaveStore.DeleteFile(CurrentMultiplayerRunSavePath); + SaveStore.DeleteFile(CurrentMultiplayerRunPlayerNamesPath); } public static void RenameBrokenMultiplayerRunSave(ReadSaveStatus status) diff --git a/SlayTheSpire2.LAN.Multiplayer/Models/LanPlayerNameRequestMessage.cs b/SlayTheSpire2.LAN.Multiplayer/Models/LanPlayerNameRequestMessage.cs new file mode 100644 index 0000000..86ad7c0 --- /dev/null +++ b/SlayTheSpire2.LAN.Multiplayer/Models/LanPlayerNameRequestMessage.cs @@ -0,0 +1,25 @@ +using MegaCrit.Sts2.Core.Logging; +using MegaCrit.Sts2.Core.Multiplayer.Serialization; +using MegaCrit.Sts2.Core.Multiplayer.Transport; + +namespace SlayTheSpire2.LAN.Multiplayer.Models +{ + public struct LanPlayerNameRequestMessage : INetMessage + { + public string playerName; + + public bool ShouldBroadcast => false; + public NetTransferMode Mode => NetTransferMode.Reliable; + public LogLevel LogLevel => LogLevel.Info; + + public void Serialize(PacketWriter writer) + { + writer.WriteString(playerName); + } + + public void Deserialize(PacketReader reader) + { + playerName = reader.ReadString(); + } + } +} \ No newline at end of file diff --git a/SlayTheSpire2.LAN.Multiplayer/Models/LanPlayerNameResponseMessage.cs b/SlayTheSpire2.LAN.Multiplayer/Models/LanPlayerNameResponseMessage.cs new file mode 100644 index 0000000..51c1c07 --- /dev/null +++ b/SlayTheSpire2.LAN.Multiplayer/Models/LanPlayerNameResponseMessage.cs @@ -0,0 +1,35 @@ +using MegaCrit.Sts2.Core.Logging; +using MegaCrit.Sts2.Core.Multiplayer.Serialization; +using MegaCrit.Sts2.Core.Multiplayer.Transport; + +namespace SlayTheSpire2.LAN.Multiplayer.Models +{ + public struct LanPlayerNameResponseMessage : INetMessage + { + public LanPlayerNames playerNameDictionary; + + public bool ShouldBroadcast => false; + public NetTransferMode Mode => NetTransferMode.Reliable; + public LogLevel LogLevel => LogLevel.Info; + + public void Serialize(PacketWriter writer) + { + writer.WriteInt(playerNameDictionary.Count); + foreach (var keyValue in playerNameDictionary) + { + writer.WriteULong(keyValue.Key); + writer.WriteString(keyValue.Value); + } + } + + public void Deserialize(PacketReader reader) + { + var count = reader.ReadInt(); + playerNameDictionary = new LanPlayerNames(); + for (var i = 0; i < count; i++) + { + playerNameDictionary.Add(reader.ReadULong(), reader.ReadString()); + } + } + } +} \ No newline at end of file diff --git a/SlayTheSpire2.LAN.Multiplayer/Models/LanPlayerNames.cs b/SlayTheSpire2.LAN.Multiplayer/Models/LanPlayerNames.cs new file mode 100644 index 0000000..c4070fb --- /dev/null +++ b/SlayTheSpire2.LAN.Multiplayer/Models/LanPlayerNames.cs @@ -0,0 +1,9 @@ +using System.Text.Json.Serialization; + +namespace SlayTheSpire2.LAN.Multiplayer.Models +{ + [JsonSerializable(typeof(LanPlayerNames))] + public partial class LanPlayerNamesContext : JsonSerializerContext; + + public class LanPlayerNames : Dictionary; +} \ No newline at end of file diff --git a/SlayTheSpire2.LAN.Multiplayer/Models/SettingsModel.cs b/SlayTheSpire2.LAN.Multiplayer/Models/SettingsModel.cs index 6fd8671..a5999e4 100644 --- a/SlayTheSpire2.LAN.Multiplayer/Models/SettingsModel.cs +++ b/SlayTheSpire2.LAN.Multiplayer/Models/SettingsModel.cs @@ -1,5 +1,6 @@ using System.Text.Json.Serialization; using MegaCrit.Sts2.Core.Saves; +using Steamworks; namespace SlayTheSpire2.LAN.Multiplayer.Models { @@ -12,6 +13,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Models [JsonPropertyName("host_max_players")] public int HostMaxPlayers { get; set; } = 4; [JsonPropertyName("ip_address")] public string IPAddress { get; set; } = "127.0.0.1"; [JsonPropertyName("net_id")] public ulong NetId { get; set; } = 1000u; + [JsonPropertyName("player_name")] public string PlayerName { get; set; } = SteamFriends.GetPersonaName(); public int SchemaVersion { get; set; } } diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/ENetClientPatch.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/ENet/ENetClientPatch.cs similarity index 98% rename from SlayTheSpire2.LAN.Multiplayer/Patchs/ENetClientPatch.cs rename to SlayTheSpire2.LAN.Multiplayer/Patchs/ENet/ENetClientPatch.cs index 37b757e..eb79b0a 100644 --- a/SlayTheSpire2.LAN.Multiplayer/Patchs/ENetClientPatch.cs +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/ENet/ENetClientPatch.cs @@ -10,7 +10,7 @@ using Logger = MegaCrit.Sts2.Core.Logging.Logger; // ReSharper disable UnusedMember.Global // ReSharper disable UnusedType.Global -namespace SlayTheSpire2.LAN.Multiplayer.Patchs +namespace SlayTheSpire2.LAN.Multiplayer.Patchs.ENet { [HarmonyPatch(typeof(ENetClient), "ConnectToHost")] internal class ENetClientConnectToHostPatch @@ -132,7 +132,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs if (eNetHandshakeResponse.status == ENetHandshakeStatus.IdCollision) { logger.Warn( - $"NetID:{netId} already occupied, Next try server send new NetID:{eNetHandshakeResponse.newNetId}"); + $"NetID:{netId} already occupied, Next try host send new NetID:{eNetHandshakeResponse.newNetId}"); return (new NetErrorInfo(NetError.Kicked, selfInitiated: false), eNetHandshakeResponse.newNetId); } diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/ENetHostPatch.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/ENet/ENetHostPatch.cs similarity index 98% rename from SlayTheSpire2.LAN.Multiplayer/Patchs/ENetHostPatch.cs rename to SlayTheSpire2.LAN.Multiplayer/Patchs/ENet/ENetHostPatch.cs index cf8254e..4494bbe 100644 --- a/SlayTheSpire2.LAN.Multiplayer/Patchs/ENetHostPatch.cs +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/ENet/ENetHostPatch.cs @@ -12,7 +12,7 @@ using Logger = MegaCrit.Sts2.Core.Logging.Logger; // ReSharper disable UnusedMember.Global // ReSharper disable UnusedType.Global -namespace SlayTheSpire2.LAN.Multiplayer.Patchs +namespace SlayTheSpire2.LAN.Multiplayer.Patchs.ENet { [HarmonyPatch(typeof(ENetHost), "DoClientHandshake")] internal class ENetHostDoClientHandshakePatch diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/JoinFlowPatch.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/JoinFlowPatch.cs new file mode 100644 index 0000000..e8327d3 --- /dev/null +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/JoinFlowPatch.cs @@ -0,0 +1,120 @@ +using HarmonyLib; +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 SlayTheSpire2.LAN.Multiplayer.Helpers; +using SlayTheSpire2.LAN.Multiplayer.Models; + +// ReSharper disable UnusedMember.Global +// ReSharper disable UnusedType.Global + +namespace SlayTheSpire2.LAN.Multiplayer.Patchs +{ + [HarmonyPatch(typeof(JoinFlow), "AttemptJoin")] + internal class JoinFlowAttemptJoinPatch + { + private static void Postfix(JoinFlow __instance, ref Task __result) + { + __result = AttemptJoin(__instance, __result); + } + + private static async Task AttemptJoin(JoinFlow joinFlow, + Task clientLobbyJoinResponseMessage) + { + var result = await clientLobbyJoinResponseMessage; + + if (joinFlow.NetService == null) + return result; + + joinFlow.NetService.RegisterMessageHandler(LanPlayerNameHelper + .HandleLanPlayerNameResponseMessage); + + await LanPlayerNameHelper.AttemptPlayerName(joinFlow.NetService); + + return result; + } + } + + [HarmonyPatch(typeof(JoinFlow), "AttemptLoadJoin")] + internal class JoinFlowAttemptLoadJoinPatch + { + private static void Postfix(JoinFlow __instance, ref Task __result) + { + __result = AttemptLoadJoin(__instance, __result); + } + + private static async Task AttemptLoadJoin(JoinFlow joinFlow, + Task clientLoadJoinResponseMessage) + { + var result = await clientLoadJoinResponseMessage; + + if (joinFlow.NetService == null) + return result; + + joinFlow.NetService.RegisterMessageHandler(LanPlayerNameHelper + .HandleLanPlayerNameResponseMessage); + + await LanPlayerNameHelper.AttemptPlayerName(joinFlow.NetService); + + return result; + } + } + + [HarmonyPatch(typeof(JoinFlow), "AttemptRejoin")] + internal class JoinFlowAttemptRejoinPatch + { + private static void Postfix(JoinFlow __instance, ref Task __result) + { + __result = AttemptRejoin(__instance, __result); + } + + private static async Task AttemptRejoin(JoinFlow joinFlow, + Task clientRejoinResponseMessage) + { + var result = await clientRejoinResponseMessage; + + if (joinFlow.NetService == null) + return result; + + joinFlow.NetService.RegisterMessageHandler(LanPlayerNameHelper + .HandleLanPlayerNameResponseMessage); + + await LanPlayerNameHelper.AttemptPlayerName(joinFlow.NetService); + + return result; + } + } + + [HarmonyPatch(typeof(JoinFlow), "OnDisconnected")] + internal class JoinFlowOnDisconnectedPatch + { + private static void Postfix(NetErrorInfo info) + { + var exception = + new ClientConnectionFailedException( + $"Unexpectedly disconnected from host while joining. Reason: {info.GetReason()}", info); + + var lanPlayerNameCompletion = LanPlayerNameHelper.LanPlayerNameCompletion; + + if (lanPlayerNameCompletion?.Task is { IsCompleted: false }) + { + lanPlayerNameCompletion.SetException(exception); + } + } + } + + [HarmonyPatch(typeof(JoinFlow), "Cancel")] + internal class JoinFlowCancelPatch + { + private static void Postfix() + { + var lanPlayerNameCompletion = LanPlayerNameHelper.LanPlayerNameCompletion; + + if (lanPlayerNameCompletion?.Task is { IsCompleted: false }) + { + lanPlayerNameCompletion.SetCanceled(); + } + } + } +} \ No newline at end of file diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/NTreasureRoomRelicCollectionPatch.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/NTreasureRoomRelicCollectionPatch.cs deleted file mode 100644 index 4804ccf..0000000 --- a/SlayTheSpire2.LAN.Multiplayer/Patchs/NTreasureRoomRelicCollectionPatch.cs +++ /dev/null @@ -1,67 +0,0 @@ -using Godot; -using HarmonyLib; -using MegaCrit.Sts2.Core.Nodes.Screens.TreasureRoomRelic; -using MegaCrit.Sts2.Core.Runs; - -// ReSharper disable UnusedMember.Global -// ReSharper disable UnusedType.Global - -namespace SlayTheSpire2.LAN.Multiplayer.Patchs -{ - [HarmonyPatch(typeof(NTreasureRoomRelicCollection), "_Ready")] - internal class NTreasureRoomRelicCollectionPatch - { - private static void Prefix(NTreasureRoomRelicCollection __instance) - { - var runState = Traverse.Create(RunManager.Instance).Property("State").GetValue(); - - if (runState is { Players.Count: > 4 }) - { - var container = __instance.GetNode("Container"); - var multiplayerRelicHolder = container.GetNode("MultiplayerRelicHolder4"); - - for (var i = 4; i < runState.Players.Count; i++) - { - var newMultiplayerRelicHolder = multiplayerRelicHolder.Duplicate(); - container.AddChild(newMultiplayerRelicHolder); - - if (newMultiplayerRelicHolder is NTreasureRoomRelicHolder nTreasureRoomRelicHolder) - { - nTreasureRoomRelicHolder.Name = $"MultiplayerRelicHolder{i + 1}"; - } - } - } - } - - private static void Postfix(List ____multiplayerHolders) - { - if (____multiplayerHolders.Count > 4) - { - var more16People = ____multiplayerHolders.Count > 16; - - var position = new Vector2(more16People ? 30 : 62, 110); - - for (var i = 0; i < ____multiplayerHolders.Count; i++) - { - if (i > 0) - { - if (more16People) - { - position = i % 6 == 0 - ? new Vector2(30, position.Y + 140) - : new Vector2(position.X + 140, position.Y); - } - else - { - position = i % 4 == 0 - ? new Vector2(62, position.Y + 140) - : new Vector2(position.X + 240, position.Y); - } - } - - ____multiplayerHolders[i].Position = position; - } - } - } - } -} \ No newline at end of file diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/NullPlatformUtilStrategyPatch.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/NullPlatformUtilStrategyPatch.cs new file mode 100644 index 0000000..b26fb93 --- /dev/null +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/NullPlatformUtilStrategyPatch.cs @@ -0,0 +1,45 @@ +using HarmonyLib; +using MegaCrit.Sts2.Core.Platform.Null; +using SlayTheSpire2.LAN.Multiplayer.Helpers; + +// ReSharper disable UnusedMember.Global +// ReSharper disable UnusedType.Global + +namespace SlayTheSpire2.LAN.Multiplayer.Patchs +{ + [HarmonyPatch(typeof(NullPlatformUtilStrategy), "GetPlayerName")] + internal class NullPlatformUtilStrategyGetPlayerNamePatch + { + private static bool Prefix(ulong playerId, List? ____mpNames, ref string __result) + { + if (____mpNames != null) + { + foreach (var mpName in ____mpNames) + { + if (mpName.netId == playerId) + { + __result = mpName.name; + return false; + } + } + } + + if (LanPlayerNameHelper.PlayerNameDictionary.TryGetValue(playerId, out var playerName)) + { + __result = playerName; + return false; + } + + __result = playerId switch + { + 1uL => "Test Host", + 1000uL => "Test Client 1", + 2000uL => "Test Client 2", + 3000uL => "Test Client 3", + _ => playerId.ToString(), + }; + + return false; + } + } +} \ No newline at end of file diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/RunLobbyPatchs.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/RunLobbyPatchs.cs new file mode 100644 index 0000000..cb7f4eb --- /dev/null +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/RunLobbyPatchs.cs @@ -0,0 +1,82 @@ +using System.Reflection; +using HarmonyLib; +using MegaCrit.Sts2.Core.Multiplayer.Game; +using MegaCrit.Sts2.Core.Multiplayer.Game.Lobby; +using MegaCrit.Sts2.Core.Platform; +using MegaCrit.Sts2.Core.Runs; +using MegaCrit.Sts2.Core.Saves; +using SlayTheSpire2.LAN.Multiplayer.Helpers; +using SlayTheSpire2.LAN.Multiplayer.Models; + +// ReSharper disable UnusedMember.Global +// ReSharper disable UnusedType.Global + +namespace SlayTheSpire2.LAN.Multiplayer.Patchs +{ + [HarmonyPatch] + internal class RunLobbyConstructorPatchs + { + private static IEnumerable TargetMethods() + { + yield return typeof(StartRunLobby).GetConstructor([ + typeof(GameMode), typeof(INetGameService), typeof(IStartRunLobbyListener), typeof(int) + ])!; + yield return typeof(RunLobby).GetConstructor([ + typeof(GameMode), typeof(INetGameService), typeof(IRunLobbyListener), typeof(IPlayerCollection), + typeof(IEnumerable) + ])!; + yield return typeof(LoadRunLobby).GetConstructor([ + typeof(INetGameService), typeof(ILoadRunLobbyListener), typeof(SerializableRun) + ])!; + } + + private static void Prefix(INetGameService netService) + { + if (netService.Platform == PlatformType.None) + { + LanPlayerNameHelper.NetService = netService; + + netService.RegisterMessageHandler(LanPlayerNameHelper + .HandleLanPlayerNameResponseMessage); + + if (netService.Type == NetGameType.Host) + { + netService.RegisterMessageHandler(LanPlayerNameHelper + .HandleLanPlayerNameRequestMessage); + } + } + } + } + + [HarmonyPatch] + internal class RunLobbyCleanUpPatchs + { + private static IEnumerable TargetMethods() + { + yield return typeof(StartRunLobby).GetMethod("CleanUp", BindingFlags.Instance | BindingFlags.Public)!; + yield return typeof(RunLobby).GetMethod("Dispose", BindingFlags.Instance | BindingFlags.Public)!; + yield return typeof(LoadRunLobby).GetMethod("CleanUp", BindingFlags.Instance | BindingFlags.Public)!; + } + + private static void Prefix(object __instance) + { + var netService = __instance is StartRunLobby or LoadRunLobby + ? Traverse.Create(__instance).Property("NetService").GetValue() + : Traverse.Create(__instance).Field("_netService").GetValue(); + + if (netService.Platform == PlatformType.None) + { + LanPlayerNameHelper.SetDefaultPlayerNameDictionary(); + + netService.UnregisterMessageHandler(LanPlayerNameHelper + .HandleLanPlayerNameResponseMessage); + + if (netService.Type == NetGameType.Host) + { + netService.UnregisterMessageHandler(LanPlayerNameHelper + .HandleLanPlayerNameRequestMessage); + } + } + } + } +} \ No newline at end of file diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/RunManagerPatch.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/RunManagerPatch.cs index b2397b1..2c49779 100644 --- a/SlayTheSpire2.LAN.Multiplayer/Patchs/RunManagerPatch.cs +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/RunManagerPatch.cs @@ -8,11 +8,12 @@ using SlayTheSpire2.LAN.Multiplayer.Helpers; namespace SlayTheSpire2.LAN.Multiplayer.Patchs { [HarmonyPatch(typeof(RunManager), "CleanUp")] - internal class RunManagerPatch + internal class RunManagerCleanUpPatch { private static void Postfix() { LanMapDrawingsHelper.DisableDrawingHashSet.Clear(); + LanPlayerNameHelper.SetDefaultPlayerNameDictionary(); } } } \ No newline at end of file diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/RunSaveManagerPatch.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/RunSaveManagerPatch.cs index 0ae91b8..338dc41 100644 --- a/SlayTheSpire2.LAN.Multiplayer/Patchs/RunSaveManagerPatch.cs +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/RunSaveManagerPatch.cs @@ -8,6 +8,7 @@ using MegaCrit.Sts2.Core.Runs; using MegaCrit.Sts2.Core.Saves; using MegaCrit.Sts2.Core.Saves.Managers; using SlayTheSpire2.LAN.Multiplayer.Helpers; +using SlayTheSpire2.LAN.Multiplayer.Models; // ReSharper disable UnusedMember.Global // ReSharper disable UnusedType.Global @@ -35,8 +36,12 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs return; var value = RunManager.Instance.ToSave(preFinishedRoom); - var savePath = RunManager.Instance.NetService.Type.IsMultiplayer() - ? RunManager.Instance.NetService.Platform == PlatformType.None + + var isMultiplayer = RunManager.Instance.NetService.Type.IsMultiplayer(); + var isNonePlatform = RunManager.Instance.NetService.Platform == PlatformType.None; + + var savePath = isMultiplayer + ? isNonePlatform ? LanRunSaveManagerHelper.CurrentMultiplayerRunSavePath : Traverse.Create(runSaveManager).Property("CurrentMultiplayerRunSavePath").GetValue() : Traverse.Create(runSaveManager).Property("CurrentRunSavePath").GetValue(); @@ -55,6 +60,25 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs stream.Seek(0L, SeekOrigin.Begin); await saveStore.WriteFileAsync(savePath, stream.ToArray()); + if (isMultiplayer && isNonePlatform) + { + using var playerNamesStream = new MemoryStream(); + if (!forceSynchronous) + { + await JsonSerializer.SerializeAsync(playerNamesStream, LanPlayerNameHelper.PlayerNameDictionary, + LanPlayerNamesContext.Default.LanPlayerNames, CancellationToken.None); + } + else + { + await JsonSerializer.SerializeAsync(playerNamesStream, LanPlayerNameHelper.PlayerNameDictionary, + LanPlayerNamesContext.Default.LanPlayerNames); + } + + playerNamesStream.Seek(0L, SeekOrigin.Begin); + await saveStore.WriteFileAsync(LanRunSaveManagerHelper.CurrentMultiplayerRunPlayerNamesPath, + playerNamesStream.ToArray()); + } + saved?.Invoke(); } } diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NCharacterSelectScreenPatchs.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NCharacterSelectScreenPatchs.cs new file mode 100644 index 0000000..f4a44fd --- /dev/null +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NCharacterSelectScreenPatchs.cs @@ -0,0 +1,32 @@ +using System.Reflection; +using HarmonyLib; +using MegaCrit.Sts2.Core.Multiplayer.Game; +using MegaCrit.Sts2.Core.Nodes.Screens.CharacterSelect; +using MegaCrit.Sts2.Core.Platform; +using SlayTheSpire2.LAN.Multiplayer.Helpers; + +// ReSharper disable UnusedMember.Global +// ReSharper disable UnusedType.Global + +namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens +{ + [HarmonyPatch] + internal class NCharacterSelectScreenInitializeMultiplayerPatchs + { + private static IEnumerable TargetMethods() + { + yield return typeof(NCharacterSelectScreen).GetMethod("InitializeMultiplayerAsClient", + BindingFlags.Instance | BindingFlags.Public)!; + yield return typeof(NCharacterSelectScreen).GetMethod("InitializeMultiplayerAsHost", + BindingFlags.Instance | BindingFlags.Public)!; + } + + private static void Prefix(NCharacterSelectScreen __instance, INetGameService gameService) + { + if (gameService.Platform == PlatformType.None) + { + LanPlayerNameHelper.CharacterSelectScreen = __instance; + } + } + } +} \ No newline at end of file diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/NJoinFriendScreenPatch.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NJoinFriendScreenPatch.cs similarity index 91% rename from SlayTheSpire2.LAN.Multiplayer/Patchs/NJoinFriendScreenPatch.cs rename to SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NJoinFriendScreenPatch.cs index 8f8ce15..324bd2e 100644 --- a/SlayTheSpire2.LAN.Multiplayer/Patchs/NJoinFriendScreenPatch.cs +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NJoinFriendScreenPatch.cs @@ -12,10 +12,10 @@ using SlayTheSpire2.LAN.Multiplayer.Helpers; // ReSharper disable UnusedMember.Global // ReSharper disable UnusedType.Global -namespace SlayTheSpire2.LAN.Multiplayer.Patchs +namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens { [HarmonyPatch(typeof(NJoinFriendScreen), "_Ready")] - internal class NJoinFriendScreenPatch + internal class NJoinFriendScreenReadyPatch { private static void Prefix(NJoinFriendScreen __instance) { @@ -28,11 +28,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs lanPanel.PatchMarginLeft = 12; lanPanel.PatchMarginRight = 12; - lanPanel.AnchorLeft = 0.5f; - lanPanel.AnchorTop = 0.5f; - lanPanel.AnchorRight = 0.5f; - lanPanel.AnchorBottom = 0.5f; - + lanPanel.SetAnchorsPreset(Control.LayoutPreset.Center); lanPanel.OffsetLeft = 450; lanPanel.OffsetTop = -338; lanPanel.OffsetRight = 790; @@ -49,8 +45,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs lanPanel.AddChild(vBoxContainer); vBoxContainer.Alignment = BoxContainer.AlignmentMode.Center; - vBoxContainer.SetAnchorsPreset(Control.LayoutPreset.FullRect); - vBoxContainer.Size = new Vector2(350, 676); + vBoxContainer.SetAnchorsAndOffsetsPreset(Control.LayoutPreset.FullRect); vBoxContainer.AddThemeConstantOverride("separation", 24); if (__instance.GetNode("TitleLabel").Duplicate() is MegaLabel ipAddressLabel) diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/NMapDrawingsPatch.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NMapDrawingsPatch.cs similarity index 83% rename from SlayTheSpire2.LAN.Multiplayer/Patchs/NMapDrawingsPatch.cs rename to SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NMapDrawingsPatch.cs index 9378973..d99bd6a 100644 --- a/SlayTheSpire2.LAN.Multiplayer/Patchs/NMapDrawingsPatch.cs +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NMapDrawingsPatch.cs @@ -7,10 +7,10 @@ using SlayTheSpire2.LAN.Multiplayer.Helpers; // ReSharper disable UnusedMember.Global // ReSharper disable UnusedType.Global -namespace SlayTheSpire2.LAN.Multiplayer.Patchs +namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens { [HarmonyPatch(typeof(NMapDrawings), "CreateLineForPlayer")] - internal class NMapDrawingsPatch + internal class NMapDrawingsCreateLineForPlayerPatch { private static void Postfix(Player player, Line2D __result) { diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NMultiplayerLoadGameScreenPatch.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NMultiplayerLoadGameScreenPatch.cs new file mode 100644 index 0000000..2ac2cc9 --- /dev/null +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NMultiplayerLoadGameScreenPatch.cs @@ -0,0 +1,32 @@ +using System.Reflection; +using HarmonyLib; +using MegaCrit.Sts2.Core.Multiplayer.Game; +using MegaCrit.Sts2.Core.Nodes.Screens.CharacterSelect; +using MegaCrit.Sts2.Core.Platform; +using SlayTheSpire2.LAN.Multiplayer.Helpers; + +// ReSharper disable UnusedMember.Global +// ReSharper disable UnusedType.Global + +namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens +{ + [HarmonyPatch] + internal class NMultiplayerLoadGameScreenInitializePatchs + { + private static IEnumerable TargetMethods() + { + yield return typeof(NMultiplayerLoadGameScreen).GetMethod("InitializeAsHost", + BindingFlags.Instance | BindingFlags.Public)!; + yield return typeof(NMultiplayerLoadGameScreen).GetMethod("InitializeAsClient", + BindingFlags.Instance | BindingFlags.Public)!; + } + + private static void Prefix(NMultiplayerLoadGameScreen __instance, INetGameService gameService) + { + if (gameService.Platform == PlatformType.None) + { + LanPlayerNameHelper.MultiplayerLoadGameScreen = __instance; + } + } + } +} \ No newline at end of file diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/NMultiplayerPlayerExpandedStatePatch.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NMultiplayerPlayerExpandedStatePatch.cs similarity index 96% rename from SlayTheSpire2.LAN.Multiplayer/Patchs/NMultiplayerPlayerExpandedStatePatch.cs rename to SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NMultiplayerPlayerExpandedStatePatch.cs index 433a19e..24e8e37 100644 --- a/SlayTheSpire2.LAN.Multiplayer/Patchs/NMultiplayerPlayerExpandedStatePatch.cs +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NMultiplayerPlayerExpandedStatePatch.cs @@ -12,10 +12,10 @@ using SlayTheSpire2.LAN.Multiplayer.Helpers; // ReSharper disable UnusedMember.Global // ReSharper disable UnusedType.Global -namespace SlayTheSpire2.LAN.Multiplayer.Patchs +namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens { [HarmonyPatch(typeof(NMultiplayerPlayerExpandedState), "_Ready")] - internal class NMultiplayerPlayerExpandedStatePatch + internal class NMultiplayerPlayerExpandedStateReadyPatch { private static void Prefix(NMultiplayerPlayerExpandedState __instance, Player ____player) { diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/NMultiplayerSubmenuPatch.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NMultiplayerSubmenuPatch.cs similarity index 98% rename from SlayTheSpire2.LAN.Multiplayer/Patchs/NMultiplayerSubmenuPatch.cs rename to SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NMultiplayerSubmenuPatch.cs index 1eb8fbf..b7898eb 100644 --- a/SlayTheSpire2.LAN.Multiplayer/Patchs/NMultiplayerSubmenuPatch.cs +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NMultiplayerSubmenuPatch.cs @@ -10,7 +10,7 @@ using SlayTheSpire2.LAN.Multiplayer.Helpers; // ReSharper disable UnusedMember.Global // ReSharper disable UnusedType.Global -namespace SlayTheSpire2.LAN.Multiplayer.Patchs +namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens { [HarmonyPatch(typeof(NMultiplayerSubmenu), "_Ready")] internal class NMultiplayerSubmenuReadyPatch diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/NRestSiteRoomPatch.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NRestSiteRoomPatch.cs similarity index 97% rename from SlayTheSpire2.LAN.Multiplayer/Patchs/NRestSiteRoomPatch.cs rename to SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NRestSiteRoomPatch.cs index 77785a4..3cc8b36 100644 --- a/SlayTheSpire2.LAN.Multiplayer/Patchs/NRestSiteRoomPatch.cs +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NRestSiteRoomPatch.cs @@ -6,10 +6,10 @@ using MegaCrit.Sts2.Core.Runs; // ReSharper disable UnusedMember.Global // ReSharper disable UnusedType.Global -namespace SlayTheSpire2.LAN.Multiplayer.Patchs +namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens { [HarmonyPatch(typeof(NRestSiteRoom), "_Ready")] - internal class NRestSiteRoomPatch + internal class NRestSiteRoomReadyPatch { private static void Prefix(NRestSiteRoom __instance, IRunState ____runState, List ____characterContainers) diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/NSettingsScreenPatch.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NSettingsScreenPatch.cs similarity index 59% rename from SlayTheSpire2.LAN.Multiplayer/Patchs/NSettingsScreenPatch.cs rename to SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NSettingsScreenPatch.cs index b0d5f64..352bef6 100644 --- a/SlayTheSpire2.LAN.Multiplayer/Patchs/NSettingsScreenPatch.cs +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NSettingsScreenPatch.cs @@ -1,21 +1,23 @@ using Godot; using HarmonyLib; using MegaCrit.Sts2.Core.Nodes.Screens.Settings; +using SlayTheSpire2.LAN.Multiplayer.Components; using SlayTheSpire2.LAN.Multiplayer.Helpers; // ReSharper disable UnusedMember.Global // ReSharper disable UnusedType.Global -namespace SlayTheSpire2.LAN.Multiplayer.Patchs +namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens { [HarmonyPatch(typeof(NSettingsScreen), "_Ready")] - internal class NSettingsScreenPatch + internal class NSettingsScreenReadyPatch { private static void Prefix(NSettingsScreen __instance) { var moddingNode = __instance.GetNode("%Modding"); - var vBoxContainer = moddingNode.GetParent(); + var vBoxContainerNode = moddingNode.GetParent(); + var generalSettings = vBoxContainerNode.GetParent(); if (__instance.GetNode("%ModdingDivider").Duplicate() is ColorRect hostPortDivider && moddingNode.Duplicate() is MarginContainer hostPort && @@ -23,8 +25,8 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs { hostPortDivider.Name = "HostPortDivider"; - vBoxContainer.AddChild(hostPortDivider); - vBoxContainer.MoveChild(hostPortDivider, moddingNode.GetIndex() + 1); + vBoxContainerNode.AddChild(hostPortDivider); + vBoxContainerNode.MoveChild(hostPortDivider, moddingNode.GetIndex() + 1); hostPortDivider.Show(); @@ -32,8 +34,8 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs hostPort.RemoveChild(hostPort.GetNode("ModdingButton")); - vBoxContainer.AddChild(hostPort); - vBoxContainer.MoveChild(hostPort, hostPortDivider.GetIndex() + 1); + vBoxContainerNode.AddChild(hostPort); + vBoxContainerNode.MoveChild(hostPort, hostPortDivider.GetIndex() + 1); hostPort.Show(); @@ -64,8 +66,8 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs { hostMaxPlayersDivider.Name = "HostMaxPlayersDivider"; - vBoxContainer.AddChild(hostMaxPlayersDivider); - vBoxContainer.MoveChild(hostMaxPlayersDivider, moddingNode.GetIndex() + 1); + vBoxContainerNode.AddChild(hostMaxPlayersDivider); + vBoxContainerNode.MoveChild(hostMaxPlayersDivider, moddingNode.GetIndex() + 1); hostMaxPlayersDivider.Show(); @@ -73,8 +75,8 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs hostMaxPlayers.RemoveChild(hostMaxPlayers.GetNode("ModdingButton")); - vBoxContainer.AddChild(hostMaxPlayers); - vBoxContainer.MoveChild(hostMaxPlayers, hostMaxPlayersDivider.GetIndex() + 1); + vBoxContainerNode.AddChild(hostMaxPlayers); + vBoxContainerNode.MoveChild(hostMaxPlayers, hostMaxPlayersDivider.GetIndex() + 1); hostMaxPlayers.Show(); @@ -98,14 +100,57 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs hostMaxPlayersLabel.Text = "Host Max Players"; } + if (__instance.GetNode("%ModdingDivider").Duplicate() is ColorRect playerNameDivider && + moddingNode.Duplicate() is MarginContainer playerName && + playerName.GetNode("Label") is RichTextLabel playerNameLabel) + { + playerNameDivider.Name = "PlayerNameDivider"; + + vBoxContainerNode.AddChild(playerNameDivider); + vBoxContainerNode.MoveChild(playerNameDivider, moddingNode.GetIndex() + 1); + + playerNameDivider.Show(); + + playerName.Name = "PlayerName"; + + playerName.RemoveChild(playerName.GetNode("ModdingButton")); + + vBoxContainerNode.AddChild(playerName); + vBoxContainerNode.MoveChild(playerName, playerNameDivider.GetIndex() + 1); + + playerName.Show(); + + var playerNameInput = new PlayerNameLineEdit { Name = "PlayerNameInput" }; + + playerName.AddChild(playerNameInput); + + playerNameInput.CustomMinimumSize = new Vector2(324, 64); + playerNameInput.SizeFlagsHorizontal = Control.SizeFlags.ShrinkEnd; + playerNameInput.Alignment = HorizontalAlignment.Center; + + playerNameInput.MaxLength = 16; + playerNameInput.Text = SettingsHelper.Instance.SettingsModel.PlayerName; + playerNameInput.TextChanged += value => + { + if (playerNameInput.IsEmpty || !playerNameInput.IsInvalid) + { + SettingsHelper.Instance.SettingsModel.PlayerName = value; + LanPlayerNameHelper.SetHostPlayerName(); + SettingsHelper.Instance.WriteSettings(); + } + }; + + playerNameLabel.Text = "Player Name"; + } + if (__instance.GetNode("%ModdingDivider").Duplicate() is ColorRect netIdDivider && moddingNode.Duplicate() is MarginContainer netId && netId.GetNode("Label") is RichTextLabel netIdLabel) { netIdDivider.Name = "NetIDDivider"; - vBoxContainer.AddChild(netIdDivider); - vBoxContainer.MoveChild(netIdDivider, moddingNode.GetIndex() + 1); + vBoxContainerNode.AddChild(netIdDivider); + vBoxContainerNode.MoveChild(netIdDivider, moddingNode.GetIndex() + 1); netIdDivider.Show(); @@ -113,8 +158,8 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs netId.RemoveChild(netId.GetNode("ModdingButton")); - vBoxContainer.AddChild(netId); - vBoxContainer.MoveChild(netId, netIdDivider.GetIndex() + 1); + vBoxContainerNode.AddChild(netId); + vBoxContainerNode.MoveChild(netId, netIdDivider.GetIndex() + 1); netId.Show(); @@ -138,6 +183,11 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs netIdLabel.Text = "NetID"; } + + if (generalSettings is NSettingsPanel nSettingsPanel) + { + Traverse.Create(nSettingsPanel).Method("RefreshSize").GetValue(); + } } } } \ No newline at end of file diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NTreasureRoomRelicCollectionPatch.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NTreasureRoomRelicCollectionPatch.cs new file mode 100644 index 0000000..538d99b --- /dev/null +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/Screens/NTreasureRoomRelicCollectionPatch.cs @@ -0,0 +1,122 @@ +using Godot; +using HarmonyLib; +using MegaCrit.Sts2.Core.Assets; +using MegaCrit.Sts2.Core.Helpers; +using MegaCrit.Sts2.Core.Nodes.GodotExtensions; +using MegaCrit.Sts2.Core.Nodes.Screens.TreasureRoomRelic; +using MegaCrit.Sts2.Core.Runs; + +// ReSharper disable UnusedMember.Global +// ReSharper disable UnusedType.Global + +namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens +{ + [HarmonyPatch(typeof(NTreasureRoomRelicCollection), "_Ready")] + internal class NTreasureRoomRelicCollectionReadyPatch + { + private static void Prefix(NTreasureRoomRelicCollection __instance) + { + var runState = Traverse.Create(RunManager.Instance).Property("State").GetValue(); + + if (runState is { Players.Count: > 4 }) + { + var container = __instance.GetNode("Container"); + var multiplayerRelicHolder = container.GetNode("MultiplayerRelicHolder4"); + + for (var i = 4; i < runState.Players.Count; i++) + { + var newMultiplayerRelicHolder = multiplayerRelicHolder.Duplicate(); + container.AddChild(newMultiplayerRelicHolder); + + if (newMultiplayerRelicHolder is NTreasureRoomRelicHolder nTreasureRoomRelicHolder) + { + nTreasureRoomRelicHolder.Name = $"MultiplayerRelicHolder{i + 1}"; + } + } + } + } + + private static void Postfix(NTreasureRoomRelicCollection __instance, + List ____multiplayerHolders) + { + if (____multiplayerHolders.Count > 4) + { + var container = __instance.GetNode("Container"); + + var scrollContainer = new NScrollableContainer { Name = "ScrollableContainer" }; + + var mask = new TextureRect { Name = "Mask" }; + scrollContainer.AddChild(mask); + + mask.ClipContents = true; + + var viewport = new Control { Name = "Viewport" }; + mask.AddChild(viewport); + + var gridContainer = new GridContainer { Name = "RelicContainer" }; + viewport.AddChild(gridContainer); + + var scrollbar = PreloadManager.Cache.GetScene(SceneHelper.GetScenePath("ui/scrollbar")) + .Instantiate(); + scrollContainer.AddChild(scrollbar); + + container.AddChild(scrollContainer); + + scrollContainer.SetAnchorsPreset(Control.LayoutPreset.FullRect); + scrollContainer.OffsetLeft = -510; + scrollContainer.OffsetTop = -250; + scrollContainer.OffsetRight = 510; + scrollContainer.OffsetBottom = 250; + + mask.SetAnchorsPreset(Control.LayoutPreset.CenterTop); + mask.OffsetLeft = -450; + mask.OffsetTop = 250; + mask.OffsetRight = 450; + mask.OffsetBottom = 830; + + viewport.SetAnchorsPreset(Control.LayoutPreset.TopWide); + viewport.OffsetLeft = 110; + viewport.OffsetTop = 110; + viewport.OffsetRight = -120; + viewport.OffsetBottom = 580; + + scrollbar.AnchorLeft = 0.794f; + scrollbar.AnchorTop = 0.187f; + scrollbar.AnchorRight = 0.818f; + scrollbar.AnchorBottom = 0.947f; + scrollbar.OffsetLeft = -200; + scrollbar.OffsetTop = 158; + scrollbar.OffsetRight = -199; + scrollbar.OffsetBottom = -163; + + gridContainer.SetAnchorsAndOffsetsPreset(Control.LayoutPreset.TopWide); + + gridContainer.Columns = 4; + gridContainer.CustomMinimumSize = new Vector2(0, 469); + + gridContainer.AddThemeConstantOverride("h_separation", 42); + gridContainer.AddThemeConstantOverride("v_separation", 42); + + scrollContainer.SetContent(gridContainer, 20, 30); + + //The animation will change MultiplayerHolder position. Manually fix the position. + + var position = new Vector2(0, 110); + + for (var i = 0; i < ____multiplayerHolders.Count; i++) + { + if (i > 0) + { + position = i % 4 == 0 + ? new Vector2(0, position.Y + 178) + : new Vector2(position.X + 178, position.Y); + } + + var multiplayerHolder = ____multiplayerHolders[i]; + multiplayerHolder.Position = position; + multiplayerHolder.Reparent(gridContainer, false); + } + } + } + } +} \ No newline at end of file