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
This commit is contained in:
kmyuhkyuk
2026-03-15 08:06:26 +08:00
parent 24a06e3a00
commit 6ff7a9dc2b
25 changed files with 785 additions and 107 deletions
@@ -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<MethodInfo> 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;
}
}
}
}
@@ -0,0 +1,114 @@
using Godot;
using HarmonyLib;
using MegaCrit.Sts2.addons.mega_text;
using MegaCrit.Sts2.Core.Helpers;
using MegaCrit.Sts2.Core.Localization;
using MegaCrit.Sts2.Core.Multiplayer.Connection;
using MegaCrit.Sts2.Core.Nodes.GodotExtensions;
using MegaCrit.Sts2.Core.Nodes.Screens.MainMenu;
using SlayTheSpire2.LAN.Multiplayer.Components;
using SlayTheSpire2.LAN.Multiplayer.Helpers;
// ReSharper disable UnusedMember.Global
// ReSharper disable UnusedType.Global
namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
{
[HarmonyPatch(typeof(NJoinFriendScreen), "_Ready")]
internal class NJoinFriendScreenReadyPatch
{
private static void Prefix(NJoinFriendScreen __instance)
{
var lanPanel = new NinePatchRect { Name = "LANPanel" };
__instance.AddChild(lanPanel);
lanPanel.PatchMarginTop = 12;
lanPanel.PatchMarginBottom = 12;
lanPanel.PatchMarginLeft = 12;
lanPanel.PatchMarginRight = 12;
lanPanel.SetAnchorsPreset(Control.LayoutPreset.Center);
lanPanel.OffsetLeft = 450;
lanPanel.OffsetTop = -338;
lanPanel.OffsetRight = 790;
lanPanel.OffsetBottom = 338;
if (__instance.GetNode("Panel") is NinePatchRect panel)
{
lanPanel.Texture = panel.Texture;
lanPanel.SelfModulate = panel.SelfModulate;
}
var vBoxContainer = new VBoxContainer();
lanPanel.AddChild(vBoxContainer);
vBoxContainer.Alignment = BoxContainer.AlignmentMode.Center;
vBoxContainer.SetAnchorsAndOffsetsPreset(Control.LayoutPreset.FullRect);
vBoxContainer.AddThemeConstantOverride("separation", 24);
if (__instance.GetNode("TitleLabel").Duplicate() is MegaLabel ipAddressLabel)
{
lanPanel.AddChild(ipAddressLabel);
ipAddressLabel.SetTextAutoSize("LAN IP:");
ipAddressLabel.CustomMinimumSize = new Vector2(300, 0);
ipAddressLabel.SizeFlagsHorizontal = Control.SizeFlags.ShrinkCenter;
}
var addressLineEdit = new AddressLineEdit { Name = "AddressInput" };
vBoxContainer.AddChild(addressLineEdit);
addressLineEdit.Text = SettingsHelper.Instance.SettingsModel.IPAddress;
addressLineEdit.Alignment = HorizontalAlignment.Center;
addressLineEdit.CustomMinimumSize = new Vector2(300, 50);
addressLineEdit.SizeFlagsHorizontal = Control.SizeFlags.ShrinkCenter;
if (__instance.GetNode<NJoinFriendRefreshButton>("RefreshButton").Duplicate() is NJoinFriendRefreshButton
joinButton)
{
joinButton.Name = "JointButton";
vBoxContainer.AddChild(joinButton);
joinButton.CustomMinimumSize = new Vector2(150, 50);
joinButton.SizeFlagsHorizontal = Control.SizeFlags.ShrinkCenter;
joinButton.Connect(NClickableControl.SignalName.Released, Callable.From<NClickableControl>(_ =>
{
var addressInfo = addressLineEdit.GetAddressInfo();
if (!addressInfo.IsValid)
return;
SettingsHelper.Instance.SettingsModel.IPAddress = addressLineEdit.Text;
SettingsHelper.Instance.WriteSettings();
ushort port = 33771;
if (addressInfo.Port.HasValue)
{
port = addressInfo.Port.Value;
}
DisplayServer.WindowSetTitle("Slay The Spire 2 (Client)");
if (addressInfo.Address != null)
{
TaskHelper.RunSafely(
__instance.JoinGameAsync(new ENetClientConnectionInitializer(
SettingsHelper.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());
}
}
}
}
@@ -0,0 +1,23 @@
using Godot;
using HarmonyLib;
using MegaCrit.Sts2.Core.Entities.Players;
using MegaCrit.Sts2.Core.Nodes.Screens.Map;
using SlayTheSpire2.LAN.Multiplayer.Helpers;
// ReSharper disable UnusedMember.Global
// ReSharper disable UnusedType.Global
namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
{
[HarmonyPatch(typeof(NMapDrawings), "CreateLineForPlayer")]
internal class NMapDrawingsCreateLineForPlayerPatch
{
private static void Postfix(Player player, Line2D __result)
{
if (LanMapDrawingsHelper.DisableDrawingHashSet.Contains(player.NetId))
{
__result.Visible = false;
}
}
}
}
@@ -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<MethodInfo> 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;
}
}
}
}
@@ -0,0 +1,86 @@
using Godot;
using HarmonyLib;
using MegaCrit.Sts2.Core.Assets;
using MegaCrit.Sts2.Core.Entities.Players;
using MegaCrit.Sts2.Core.Helpers;
using MegaCrit.Sts2.Core.Nodes.Multiplayer;
using MegaCrit.Sts2.Core.Nodes.Screens.CardLibrary;
using MegaCrit.Sts2.Core.Nodes.Screens.Map;
using MegaCrit.Sts2.Core.Runs;
using SlayTheSpire2.LAN.Multiplayer.Helpers;
// ReSharper disable UnusedMember.Global
// ReSharper disable UnusedType.Global
namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
{
[HarmonyPatch(typeof(NMultiplayerPlayerExpandedState), "_Ready")]
internal class NMultiplayerPlayerExpandedStateReadyPatch
{
private static void Prefix(NMultiplayerPlayerExpandedState __instance, Player ____player)
{
if (____player.NetId != RunManager.Instance.NetService.NetId)
{
var lanPanel = new HBoxContainer { Name = "LANPanel" };
__instance.AddChild(lanPanel);
lanPanel.AnchorLeft = 0.5f;
lanPanel.AnchorTop = 0;
lanPanel.AnchorRight = 0.5f;
lanPanel.AnchorBottom = 0;
lanPanel.OffsetLeft = -162;
lanPanel.OffsetTop = 100;
lanPanel.OffsetRight = 162;
lanPanel.OffsetBottom = 100;
var disableDrawing = PreloadManager.Cache
.GetScene(SceneHelper.GetScenePath("screens/card_library/card_library_tickbox"))
.Instantiate<Control>();
if (disableDrawing is NLibraryStatTickbox cardLibraryTickBox)
{
disableDrawing.Name = "DisableDrawing";
lanPanel.AddChild(cardLibraryTickBox);
cardLibraryTickBox.SetLabel("Disable drawing");
cardLibraryTickBox.IsTicked = LanMapDrawingsHelper.DisableDrawingHashSet.Contains(____player.NetId);
cardLibraryTickBox.Toggled += tickBox =>
{
var drawingState = Traverse.Create(NMapScreen.Instance?.Drawings)
.Method("GetDrawingStateForPlayer", ____player.NetId).GetValue();
var drawViewport = Traverse.Create(drawingState).Field("drawViewport").GetValue<SubViewport>();
if (tickBox.IsTicked)
{
if (drawViewport != null)
{
foreach (var line2D in drawViewport.GetChildren().OfType<Line2D>())
{
line2D.Visible = false;
}
}
LanMapDrawingsHelper.DisableDrawingHashSet.Add(____player.NetId);
}
else
{
if (drawViewport != null)
{
foreach (var line2D in drawViewport.GetChildren().OfType<Line2D>())
{
line2D.Visible = true;
}
}
LanMapDrawingsHelper.DisableDrawingHashSet.Remove(____player.NetId);
}
};
}
}
}
}
}
@@ -0,0 +1,128 @@
using Godot;
using HarmonyLib;
using MegaCrit.Sts2.addons.mega_text;
using MegaCrit.Sts2.Core.Helpers;
using MegaCrit.Sts2.Core.Nodes.GodotExtensions;
using MegaCrit.Sts2.Core.Nodes.Screens.MainMenu;
using MegaCrit.Sts2.Core.Runs;
using SlayTheSpire2.LAN.Multiplayer.Helpers;
// ReSharper disable UnusedMember.Global
// ReSharper disable UnusedType.Global
namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
{
[HarmonyPatch(typeof(NMultiplayerSubmenu), "_Ready")]
internal class NMultiplayerSubmenuReadyPatch
{
private static void Prefix(NMultiplayerSubmenu __instance)
{
var buttonContainerNode = __instance.GetNode("ButtonContainer");
if (buttonContainerNode.GetNode("HostButton").Duplicate() is not NSubmenuButton lanHostButton)
return;
buttonContainerNode.AddChild(lanHostButton);
buttonContainerNode.MoveChild(lanHostButton, 1);
lanHostButton.Connect(NClickableControl.SignalName.Released,
Callable.From<NButton>(_ =>
{
var traverse = Traverse.Create(__instance);
var settingsModel = SettingsHelper.Instance.SettingsModel;
LanHostHelper.StartHost(GameMode.Standard, traverse.Field("_loadingOverlay").GetValue<Control>(),
traverse.Field("_stack").GetValue<NSubmenuStack>(),
settingsModel.HostPort, settingsModel.HostMaxPlayers);
}));
lanHostButton.SetIconAndLocalization("HOST");
var lanHostTitle = Traverse.Create(lanHostButton).Field("_title").GetValue<MegaLabel>();
lanHostTitle.Text = $"LAN {lanHostTitle.Text}";
NSubmenuButtonDuplicateMaterial(lanHostButton);
NMultiplayerSubmenuButtonHelpers.LanHostButton = lanHostButton;
if (buttonContainerNode.GetNode("LoadButton").Duplicate() is not NSubmenuButton lanLoadButton)
return;
buttonContainerNode.AddChild(lanLoadButton);
buttonContainerNode.MoveChild(lanLoadButton, 2);
lanLoadButton.Connect(NClickableControl.SignalName.Released,
Callable.From<NButton>(_ =>
{
var traverse = Traverse.Create(__instance);
var settingsModel = SettingsHelper.Instance.SettingsModel;
LanHostHelper.StartLoad(lanLoadButton, traverse.Field("_loadingOverlay").GetValue<Control>(),
traverse.Field("_stack").GetValue<NSubmenuStack>(),
settingsModel.HostPort, settingsModel.HostMaxPlayers);
}));
lanLoadButton.SetIconAndLocalization("MP_LOAD");
var lanLoadButtonTitle = Traverse.Create(lanLoadButton).Field("_title").GetValue<MegaLabel>();
lanLoadButtonTitle.Text = $"LAN {lanLoadButtonTitle.Text}";
NSubmenuButtonDuplicateMaterial(lanLoadButton);
NMultiplayerSubmenuButtonHelpers.LanLoadButton = lanLoadButton;
if (buttonContainerNode.GetNode("AbandonButton").Duplicate() is not NSubmenuButton lanAbandonButton)
return;
buttonContainerNode.AddChild(lanAbandonButton);
buttonContainerNode.MoveChild(lanAbandonButton, 3);
lanAbandonButton.Connect(NClickableControl.SignalName.Released,
Callable.From<NButton>(_ =>
{
var traverse = Traverse.Create(__instance);
TaskHelper.RunSafely(
LanHostHelper.TryAbandonMultiplayerRun(() => traverse.Method("UpdateButtons").GetValue()));
}));
lanAbandonButton.SetIconAndLocalization("MP_ABANDON");
var lanAbandonButtonTitle = Traverse.Create(lanAbandonButton).Field("_title").GetValue<MegaLabel>();
lanAbandonButtonTitle.Text = $"LAN {lanAbandonButtonTitle.Text}";
NSubmenuButtonDuplicateMaterial(lanAbandonButton);
NMultiplayerSubmenuButtonHelpers.LanAbandonButton = lanAbandonButton;
}
private static void NSubmenuButtonDuplicateMaterial(NSubmenuButton nSubmenuButton)
{
var traverse = Traverse.Create(nSubmenuButton);
var bgPanel = traverse.Field("_bgPanel").GetValue<Control>();
bgPanel.Material = bgPanel.Material.Duplicate() as Material;
traverse.Field("_hsv").SetValue(bgPanel.Material);
}
}
[HarmonyPatch(typeof(NMultiplayerSubmenu), "UpdateButtons")]
internal class NMultiplayerSubmenuUpdateButtonsPatch
{
private static void Postfix()
{
if (NMultiplayerSubmenuButtonHelpers.LanHostButton != null)
{
NMultiplayerSubmenuButtonHelpers.LanHostButton.Visible = !LanRunSaveManagerHelper.HasMultiplayerRunSave;
}
if (NMultiplayerSubmenuButtonHelpers.LanLoadButton != null)
{
NMultiplayerSubmenuButtonHelpers.LanLoadButton.Visible = LanRunSaveManagerHelper.HasMultiplayerRunSave;
}
if (NMultiplayerSubmenuButtonHelpers.LanAbandonButton != null)
{
NMultiplayerSubmenuButtonHelpers.LanAbandonButton.Visible =
LanRunSaveManagerHelper.HasMultiplayerRunSave;
}
}
}
}
@@ -0,0 +1,111 @@
using Godot;
using HarmonyLib;
using MegaCrit.Sts2.Core.Nodes.Rooms;
using MegaCrit.Sts2.Core.Runs;
// ReSharper disable UnusedMember.Global
// ReSharper disable UnusedType.Global
namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
{
[HarmonyPatch(typeof(NRestSiteRoom), "_Ready")]
internal class NRestSiteRoomReadyPatch
{
private static void Prefix(NRestSiteRoom __instance, IRunState ____runState,
List<Control> ____characterContainers)
{
if (____runState.Players.Count > 4)
{
var bgContainer = __instance.GetNode("BgContainer");
var lastLeftCharacterUp = bgContainer.GetNode("Character_3");
var lastLeftCharacterDown = bgContainer.GetNode("Character_1");
var lastRightCharacterUp = bgContainer.GetNode("Character_4");
var lastRightCharacterDown = bgContainer.GetNode("Character_2");
for (var i = 4; i < ____runState.Players.Count; i++)
{
if (i % 2 == 0)
{
if (i % 4 == 0)
{
AddCharacter(bgContainer, ref lastLeftCharacterUp, ____characterContainers, i, true);
}
else
{
AddCharacter(bgContainer, ref lastLeftCharacterDown, ____characterContainers, i, true);
}
}
else
{
if (i % 4 == 1)
{
AddCharacter(bgContainer, ref lastRightCharacterUp, ____characterContainers, i, false);
}
else
{
AddCharacter(bgContainer, ref lastRightCharacterDown, ____characterContainers, i, false);
}
}
}
}
}
private static void Postfix(NRestSiteRoom __instance, IRunState ____runState)
{
if (____runState.Players.Count > 4)
{
var bgContainer = __instance.GetNode("BgContainer");
var restSiteBackground = bgContainer.GetChild(0);
var lastLeftRestSite = restSiteBackground.GetNode("RestSiteLLog");
var lastRightRestSite = restSiteBackground.GetNode("RestSiteRLog");
for (var i = 4; i < ____runState.Players.Count; i++)
{
if (i % 2 == 0 && i % 4 == 0)
{
AddRestSite(restSiteBackground, ref lastLeftRestSite, true);
}
else if (i % 4 == 1)
{
AddRestSite(restSiteBackground, ref lastRightRestSite, false);
}
}
}
}
private static void AddRestSite(Node restSiteBackground, ref Node restSite, bool isLeft)
{
var nextRestSiteIndex = restSite.GetIndex() + 1;
restSite = restSite.Duplicate();
restSiteBackground.AddChild(restSite);
restSite.MoveChild(restSite, nextRestSiteIndex);
if (restSite is Control control)
{
control.Position = new Vector2(isLeft ? control.Position.X - 200 : control.Position.X + 200,
control.Position.Y - 50);
}
}
private static void AddCharacter(Node bgContainer, ref Node character, List<Control> characterContainers,
int index, bool isLeft)
{
var nextCharacterIndex = character.GetIndex() + 1;
character = character.Duplicate();
bgContainer.AddChild(character);
bgContainer.MoveChild(character, nextCharacterIndex);
if (character is Control control)
{
control.Name = $"Character_{index + 1}";
control.Position = new Vector2(isLeft ? control.Position.X - 200 : control.Position.X + 200,
control.Position.Y - 50);
characterContainers.Add(control);
}
}
}
}
@@ -0,0 +1,193 @@
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.Screens
{
[HarmonyPatch(typeof(NSettingsScreen), "_Ready")]
internal class NSettingsScreenReadyPatch
{
private static void Prefix(NSettingsScreen __instance)
{
var moddingNode = __instance.GetNode("%Modding");
var vBoxContainerNode = moddingNode.GetParent();
var generalSettings = vBoxContainerNode.GetParent();
if (__instance.GetNode("%ModdingDivider").Duplicate() is ColorRect hostPortDivider &&
moddingNode.Duplicate() is MarginContainer hostPort &&
hostPort.GetNode("Label") is RichTextLabel hostPortLabel)
{
hostPortDivider.Name = "HostPortDivider";
vBoxContainerNode.AddChild(hostPortDivider);
vBoxContainerNode.MoveChild(hostPortDivider, moddingNode.GetIndex() + 1);
hostPortDivider.Show();
hostPort.Name = "HostPort";
hostPort.RemoveChild(hostPort.GetNode("ModdingButton"));
vBoxContainerNode.AddChild(hostPort);
vBoxContainerNode.MoveChild(hostPort, hostPortDivider.GetIndex() + 1);
hostPort.Show();
var hostPortLineEdit = new SpinBox { Name = "HostPortInput" };
hostPort.AddChild(hostPortLineEdit);
hostPortLineEdit.CustomMinimumSize = new Vector2(324, 64);
hostPortLineEdit.SizeFlagsHorizontal = Control.SizeFlags.ShrinkEnd;
hostPortLineEdit.GetLineEdit().Alignment = HorizontalAlignment.Center;
hostPortLineEdit.Step = 1;
hostPortLineEdit.MinValue = 0;
hostPortLineEdit.MaxValue = 65535;
hostPortLineEdit.Value = SettingsHelper.Instance.SettingsModel.HostPort;
hostPortLineEdit.ValueChanged += value =>
{
SettingsHelper.Instance.SettingsModel.HostPort = (ushort)value;
SettingsHelper.Instance.WriteSettings();
};
hostPortLabel.Text = "Host Port";
}
if (__instance.GetNode("%ModdingDivider").Duplicate() is ColorRect hostMaxPlayersDivider &&
moddingNode.Duplicate() is MarginContainer hostMaxPlayers &&
hostMaxPlayers.GetNode("Label") is RichTextLabel hostMaxPlayersLabel)
{
hostMaxPlayersDivider.Name = "HostMaxPlayersDivider";
vBoxContainerNode.AddChild(hostMaxPlayersDivider);
vBoxContainerNode.MoveChild(hostMaxPlayersDivider, moddingNode.GetIndex() + 1);
hostMaxPlayersDivider.Show();
hostMaxPlayers.Name = "HostMaxPlayers";
hostMaxPlayers.RemoveChild(hostMaxPlayers.GetNode("ModdingButton"));
vBoxContainerNode.AddChild(hostMaxPlayers);
vBoxContainerNode.MoveChild(hostMaxPlayers, hostMaxPlayersDivider.GetIndex() + 1);
hostMaxPlayers.Show();
var hostMaxPlayersInput = new SpinBox { Name = "HostMaxPlayersInput" };
hostMaxPlayers.AddChild(hostMaxPlayersInput);
hostMaxPlayersInput.CustomMinimumSize = new Vector2(324, 64);
hostMaxPlayersInput.SizeFlagsHorizontal = Control.SizeFlags.ShrinkEnd;
hostMaxPlayersInput.GetLineEdit().Alignment = HorizontalAlignment.Center;
hostMaxPlayersInput.Step = 1;
hostMaxPlayersInput.MinValue = 2;
hostMaxPlayersInput.Value = SettingsHelper.Instance.SettingsModel.HostMaxPlayers;
hostMaxPlayersInput.ValueChanged += value =>
{
SettingsHelper.Instance.SettingsModel.HostMaxPlayers = (int)value;
SettingsHelper.Instance.WriteSettings();
};
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";
vBoxContainerNode.AddChild(netIdDivider);
vBoxContainerNode.MoveChild(netIdDivider, moddingNode.GetIndex() + 1);
netIdDivider.Show();
netId.Name = "NetID";
netId.RemoveChild(netId.GetNode("ModdingButton"));
vBoxContainerNode.AddChild(netId);
vBoxContainerNode.MoveChild(netId, netIdDivider.GetIndex() + 1);
netId.Show();
var netIdInput = new SpinBox { Name = "NetIDInput" };
netId.AddChild(netIdInput);
netIdInput.CustomMinimumSize = new Vector2(324, 64);
netIdInput.SizeFlagsHorizontal = Control.SizeFlags.ShrinkEnd;
netIdInput.GetLineEdit().Alignment = HorizontalAlignment.Center;
netIdInput.Step = 1;
netIdInput.MinValue = 2;
netIdInput.MaxValue = ulong.MaxValue;
netIdInput.Value = SettingsHelper.Instance.SettingsModel.NetId;
netIdInput.ValueChanged += value =>
{
SettingsHelper.Instance.SettingsModel.NetId = (ulong)value;
SettingsHelper.Instance.WriteSettings();
};
netIdLabel.Text = "NetID";
}
if (generalSettings is NSettingsPanel nSettingsPanel)
{
Traverse.Create(nSettingsPanel).Method("RefreshSize").GetValue();
}
}
}
}
@@ -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<RunState?>();
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<NTreasureRoomRelicHolder> ____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<NScrollbar>();
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);
}
}
}
}
}