Added supports other game mode LAN (Daily, Custom)

Now game cannot start before all players are present
This commit is contained in:
kmyuhkyuk
2026-03-17 10:42:57 +08:00
parent 67a7af8c05
commit e1f48422c3
30 changed files with 727 additions and 335 deletions
@@ -1,32 +0,0 @@
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;
}
}
}
}
@@ -7,7 +7,7 @@ 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;
using SlayTheSpire2.LAN.Multiplayer.Services;
// ReSharper disable UnusedMember.Global
// ReSharper disable UnusedType.Global
@@ -61,7 +61,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
vBoxContainer.AddChild(addressLineEdit);
addressLineEdit.Text = SettingsHelper.Instance.SettingsModel.IPAddress;
addressLineEdit.Text = SettingsService.Instance.SettingsModel.IPAddress;
addressLineEdit.Alignment = HorizontalAlignment.Center;
addressLineEdit.CustomMinimumSize = new Vector2(300, 50);
addressLineEdit.SizeFlagsHorizontal = Control.SizeFlags.ShrinkCenter;
@@ -83,8 +83,8 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
if (!addressInfo.IsValid)
return;
SettingsHelper.Instance.SettingsModel.IPAddress = addressLineEdit.Text;
SettingsHelper.Instance.WriteSettings();
SettingsService.Instance.SettingsModel.IPAddress = addressLineEdit.Text;
SettingsService.Instance.WriteSettings();
ushort port = 33771;
@@ -98,7 +98,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
{
TaskHelper.RunSafely(
__instance.JoinGameAsync(new ENetClientConnectionInitializer(
SettingsHelper.Instance.SettingsModel.NetId, addressInfo.Address, port)));
SettingsService.Instance.SettingsModel.NetId, addressInfo.Address, port)));
}
}));
@@ -0,0 +1,41 @@
using HarmonyLib;
using MegaCrit.Sts2.Core.Helpers;
using MegaCrit.Sts2.Core.Nodes.Screens.MainMenu;
using SlayTheSpire2.LAN.Multiplayer.Components;
// ReSharper disable UnusedMember.Global
// ReSharper disable UnusedType.Global
namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
{
[HarmonyPatch(typeof(NMainMenuSubmenuStack), "GetSubmenuType", typeof(Type))]
internal class NMainMenuSubmenuStackGetSubmenuTypePatch
{
private static bool Prefix(NMainMenuSubmenuStack __instance, Type type, ref NSubmenu __result)
{
if (type == typeof(LanMultiplayerHostSubmenu))
{
if (LanMultiplayerHostSubmenu.Instance == null)
{
var lanMultiplayerHostSubmenu = LanMultiplayerHostSubmenu.Create();
if (lanMultiplayerHostSubmenu != null)
{
lanMultiplayerHostSubmenu.Visible = false;
__instance.AddChildSafely(lanMultiplayerHostSubmenu);
__result = lanMultiplayerHostSubmenu;
}
}
else
{
__result = LanMultiplayerHostSubmenu.Instance;
}
return false;
}
return true;
}
}
}
@@ -2,7 +2,7 @@
using HarmonyLib;
using MegaCrit.Sts2.Core.Entities.Players;
using MegaCrit.Sts2.Core.Nodes.Screens.Map;
using SlayTheSpire2.LAN.Multiplayer.Helpers;
using SlayTheSpire2.LAN.Multiplayer.Services;
// ReSharper disable UnusedMember.Global
// ReSharper disable UnusedType.Global
@@ -14,7 +14,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
{
private static void Postfix(Player player, Line2D __result)
{
if (LanMapDrawingsHelper.DisableDrawingHashSet.Contains(player.NetId))
if (LanMapDrawingsService.Instance.DisableDrawingHashSet.Contains(player.NetId))
{
__result.Visible = false;
}
@@ -1,32 +0,0 @@
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;
}
}
}
}
@@ -7,7 +7,7 @@ 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;
using SlayTheSpire2.LAN.Multiplayer.Services;
// ReSharper disable UnusedMember.Global
// ReSharper disable UnusedType.Global
@@ -21,32 +21,30 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
{
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 container = __instance.GetNode("ScreenContents/Container");
var disableDrawing = PreloadManager.Cache
.GetScene(SceneHelper.GetScenePath("screens/card_library/card_library_tickbox"))
.Instantiate<Control>();
if (disableDrawing is NLibraryStatTickbox cardLibraryTickBox)
if (disableDrawing is NLibraryStatTickbox cardLibraryTickBox &&
container.GetNode("MarginContainer") is MarginContainer marginContainer)
{
disableDrawing.Name = "DisableDrawing";
lanPanel.AddChild(cardLibraryTickBox);
container.AddChild(cardLibraryTickBox);
container.MoveChild(cardLibraryTickBox, 0);
marginContainer.RemoveThemeConstantOverride("margin_top");
cardLibraryTickBox.SizeFlagsHorizontal = Control.SizeFlags.ShrinkCenter;
cardLibraryTickBox.SetLabel("Disable drawing");
cardLibraryTickBox.IsTicked = LanMapDrawingsHelper.DisableDrawingHashSet.Contains(____player.NetId);
var lanMapDrawingsService = LanMapDrawingsService.Instance;
cardLibraryTickBox.IsTicked =
lanMapDrawingsService.DisableDrawingHashSet.Contains(____player.NetId);
cardLibraryTickBox.Toggled += tickBox =>
{
@@ -64,7 +62,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
}
}
LanMapDrawingsHelper.DisableDrawingHashSet.Add(____player.NetId);
lanMapDrawingsService.DisableDrawingHashSet.Add(____player.NetId);
}
else
{
@@ -76,7 +74,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
}
}
LanMapDrawingsHelper.DisableDrawingHashSet.Remove(____player.NetId);
lanMapDrawingsService.DisableDrawingHashSet.Remove(____player.NetId);
}
};
}
@@ -5,7 +5,10 @@ using MegaCrit.Sts2.Core.Helpers;
using MegaCrit.Sts2.Core.Nodes.GodotExtensions;
using MegaCrit.Sts2.Core.Nodes.Screens.MainMenu;
using MegaCrit.Sts2.Core.Runs;
using MegaCrit.Sts2.Core.Saves;
using SlayTheSpire2.LAN.Multiplayer.Components;
using SlayTheSpire2.LAN.Multiplayer.Helpers;
using SlayTheSpire2.LAN.Multiplayer.Services;
// ReSharper disable UnusedMember.Global
// ReSharper disable UnusedType.Global
@@ -30,11 +33,20 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
{
var traverse = Traverse.Create(__instance);
var settingsModel = SettingsHelper.Instance.SettingsModel;
var settingsModel = SettingsService.Instance.SettingsModel;
LanHostHelper.StartHost(GameMode.Standard, traverse.Field("_loadingOverlay").GetValue<Control>(),
traverse.Field("_stack").GetValue<NSubmenuStack>(),
settingsModel.HostPort, settingsModel.HostMaxPlayers);
var stack = traverse.Field("_stack").GetValue<NSubmenuStack>();
if (SaveManager.Instance.Progress.NumberOfRuns > 0)
{
stack.PushSubmenuType<LanMultiplayerHostSubmenu>();
}
else
{
LanHostHelper.StartHost(GameMode.Standard,
traverse.Field("_loadingOverlay").GetValue<Control>(), stack, settingsModel.HostPort,
settingsModel.HostMaxPlayers);
}
}));
lanHostButton.SetIconAndLocalization("HOST");
var lanHostTitle = Traverse.Create(lanHostButton).Field("_title").GetValue<MegaLabel>();
@@ -42,7 +54,9 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
NSubmenuButtonDuplicateMaterial(lanHostButton);
NMultiplayerSubmenuButtonHelpers.LanHostButton = lanHostButton;
var lanMultiplayerSubmenuButtonService = LanMultiplayerSubmenuButtonService.Instance;
lanMultiplayerSubmenuButtonService.LanHostButton = lanHostButton;
if (buttonContainerNode.GetNode("LoadButton").Duplicate() is not NSubmenuButton lanLoadButton)
return;
@@ -55,7 +69,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
{
var traverse = Traverse.Create(__instance);
var settingsModel = SettingsHelper.Instance.SettingsModel;
var settingsModel = SettingsService.Instance.SettingsModel;
LanHostHelper.StartLoad(lanLoadButton, traverse.Field("_loadingOverlay").GetValue<Control>(),
traverse.Field("_stack").GetValue<NSubmenuStack>(),
@@ -67,7 +81,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
NSubmenuButtonDuplicateMaterial(lanLoadButton);
NMultiplayerSubmenuButtonHelpers.LanLoadButton = lanLoadButton;
lanMultiplayerSubmenuButtonService.LanLoadButton = lanLoadButton;
if (buttonContainerNode.GetNode("AbandonButton").Duplicate() is not NSubmenuButton lanAbandonButton)
return;
@@ -89,7 +103,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
NSubmenuButtonDuplicateMaterial(lanAbandonButton);
NMultiplayerSubmenuButtonHelpers.LanAbandonButton = lanAbandonButton;
lanMultiplayerSubmenuButtonService.LanAbandonButton = lanAbandonButton;
}
private static void NSubmenuButtonDuplicateMaterial(NSubmenuButton nSubmenuButton)
@@ -108,20 +122,24 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
{
private static void Postfix()
{
if (NMultiplayerSubmenuButtonHelpers.LanHostButton != null)
var lanMultiplayerSubmenuButtonService = LanMultiplayerSubmenuButtonService.Instance;
if (lanMultiplayerSubmenuButtonService.LanHostButton != null)
{
NMultiplayerSubmenuButtonHelpers.LanHostButton.Visible = !LanRunSaveManagerHelper.HasMultiplayerRunSave;
lanMultiplayerSubmenuButtonService.LanHostButton.Visible =
!LanRunSaveManagerService.Instance.HasMultiplayerRunSave;
}
if (NMultiplayerSubmenuButtonHelpers.LanLoadButton != null)
if (lanMultiplayerSubmenuButtonService.LanLoadButton != null)
{
NMultiplayerSubmenuButtonHelpers.LanLoadButton.Visible = LanRunSaveManagerHelper.HasMultiplayerRunSave;
lanMultiplayerSubmenuButtonService.LanLoadButton.Visible =
LanRunSaveManagerService.Instance.HasMultiplayerRunSave;
}
if (NMultiplayerSubmenuButtonHelpers.LanAbandonButton != null)
if (lanMultiplayerSubmenuButtonService.LanAbandonButton != null)
{
NMultiplayerSubmenuButtonHelpers.LanAbandonButton.Visible =
LanRunSaveManagerHelper.HasMultiplayerRunSave;
lanMultiplayerSubmenuButtonService.LanAbandonButton.Visible =
LanRunSaveManagerService.Instance.HasMultiplayerRunSave;
}
}
}
@@ -2,7 +2,7 @@
using HarmonyLib;
using MegaCrit.Sts2.Core.Nodes.Screens.Settings;
using SlayTheSpire2.LAN.Multiplayer.Components;
using SlayTheSpire2.LAN.Multiplayer.Helpers;
using SlayTheSpire2.LAN.Multiplayer.Services;
// ReSharper disable UnusedMember.Global
// ReSharper disable UnusedType.Global
@@ -50,11 +50,11 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
hostPortLineEdit.Step = 1;
hostPortLineEdit.MinValue = 0;
hostPortLineEdit.MaxValue = 65535;
hostPortLineEdit.Value = SettingsHelper.Instance.SettingsModel.HostPort;
hostPortLineEdit.Value = SettingsService.Instance.SettingsModel.HostPort;
hostPortLineEdit.ValueChanged += value =>
{
SettingsHelper.Instance.SettingsModel.HostPort = (ushort)value;
SettingsHelper.Instance.WriteSettings();
SettingsService.Instance.SettingsModel.HostPort = (ushort)value;
SettingsService.Instance.WriteSettings();
};
hostPortLabel.Text = "Host Port";
@@ -90,11 +90,11 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
hostMaxPlayersInput.Step = 1;
hostMaxPlayersInput.MinValue = 2;
hostMaxPlayersInput.Value = SettingsHelper.Instance.SettingsModel.HostMaxPlayers;
hostMaxPlayersInput.Value = SettingsService.Instance.SettingsModel.HostMaxPlayers;
hostMaxPlayersInput.ValueChanged += value =>
{
SettingsHelper.Instance.SettingsModel.HostMaxPlayers = (int)value;
SettingsHelper.Instance.WriteSettings();
SettingsService.Instance.SettingsModel.HostMaxPlayers = (int)value;
SettingsService.Instance.WriteSettings();
};
hostMaxPlayersLabel.Text = "Host Max Players";
@@ -129,14 +129,14 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
playerNameInput.Alignment = HorizontalAlignment.Center;
playerNameInput.MaxLength = 16;
playerNameInput.Text = SettingsHelper.Instance.SettingsModel.PlayerName;
playerNameInput.Text = SettingsService.Instance.SettingsModel.PlayerName;
playerNameInput.TextChanged += value =>
{
if (playerNameInput.IsEmpty || !playerNameInput.IsInvalid)
{
SettingsHelper.Instance.SettingsModel.PlayerName = value;
LanPlayerNameHelper.SetHostPlayerName();
SettingsHelper.Instance.WriteSettings();
SettingsService.Instance.SettingsModel.PlayerName = value;
LanPlayerNameService.Instance.SetHostPlayerName();
SettingsService.Instance.WriteSettings();
}
};
@@ -174,11 +174,11 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
netIdInput.Step = 1;
netIdInput.MinValue = 2;
netIdInput.MaxValue = ulong.MaxValue;
netIdInput.Value = SettingsHelper.Instance.SettingsModel.NetId;
netIdInput.Value = SettingsService.Instance.SettingsModel.NetId;
netIdInput.ValueChanged += value =>
{
SettingsHelper.Instance.SettingsModel.NetId = (ulong)value;
SettingsHelper.Instance.WriteSettings();
SettingsService.Instance.SettingsModel.NetId = (ulong)value;
SettingsService.Instance.WriteSettings();
};
netIdLabel.Text = "NetID";
@@ -0,0 +1,90 @@
using System.Reflection;
using HarmonyLib;
using MegaCrit.Sts2.Core.Multiplayer.Game;
using MegaCrit.Sts2.Core.Multiplayer.Game.Lobby;
using MegaCrit.Sts2.Core.Nodes.Screens.CharacterSelect;
using MegaCrit.Sts2.Core.Nodes.Screens.CustomRun;
using MegaCrit.Sts2.Core.Nodes.Screens.DailyRun;
using MegaCrit.Sts2.Core.Platform;
using SlayTheSpire2.LAN.Multiplayer.Services;
// ReSharper disable UnusedMember.Global
// ReSharper disable UnusedType.Global
namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
{
[HarmonyPatch]
internal class RunLoadScreenInitializePatchs
{
private static IEnumerable<MethodInfo> TargetMethods()
{
const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public;
yield return typeof(NMultiplayerLoadGameScreen).GetMethod("InitializeAsHost", flags)!;
yield return typeof(NMultiplayerLoadGameScreen).GetMethod("InitializeAsClient", flags)!;
yield return typeof(NDailyRunLoadScreen).GetMethod("InitializeAsHost", flags)!;
yield return typeof(NDailyRunLoadScreen).GetMethod("InitializeAsClient", flags)!;
yield return typeof(NCustomRunLoadScreen).GetMethod("InitializeAsHost", flags)!;
yield return typeof(NCustomRunLoadScreen).GetMethod("InitializeAsClient", flags)!;
}
private static void Prefix(object __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 RunLoadScreenShouldAllowRunToBeginPatchs
{
private static IEnumerable<MethodInfo> TargetMethods()
{
const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public;
yield return typeof(NDailyRunLoadScreen).GetMethod("ShouldAllowRunToBegin", flags)!;
yield return typeof(NCustomRunLoadScreen).GetMethod("ShouldAllowRunToBegin", flags)!;
}
private static bool Prefix(LoadRunLobby ____lobby, ref Task<bool> __result)
{
if (____lobby.NetService.Platform == PlatformType.None)
{
__result = RunScreenService.ShouldAllowRunToBegin(____lobby);
return false;
}
return true;
}
}
[HarmonyPatch(typeof(NMultiplayerLoadGameScreen), "ShouldAllowRunToBegin")]
internal class RunLoadScreenShouldAllowRunToBeginPatch
{
private static bool Prefix(LoadRunLobby ____runLobby, ref Task<bool> __result)
{
if (____runLobby.NetService.Platform == PlatformType.None)
{
__result = RunScreenService.ShouldAllowRunToBegin(____runLobby);
return false;
}
return true;
}
}
}
@@ -0,0 +1,51 @@
using System.Reflection;
using HarmonyLib;
using MegaCrit.Sts2.Core.Multiplayer.Game;
using MegaCrit.Sts2.Core.Nodes.Screens.CharacterSelect;
using MegaCrit.Sts2.Core.Nodes.Screens.CustomRun;
using MegaCrit.Sts2.Core.Nodes.Screens.DailyRun;
using MegaCrit.Sts2.Core.Platform;
using SlayTheSpire2.LAN.Multiplayer.Services;
// ReSharper disable UnusedMember.Global
// ReSharper disable UnusedType.Global
namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
{
[HarmonyPatch]
internal class RunScreenInitializePatchs
{
private static IEnumerable<MethodInfo> TargetMethods()
{
const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public;
yield return typeof(NCharacterSelectScreen).GetMethod("InitializeMultiplayerAsClient", flags)!;
yield return typeof(NCharacterSelectScreen).GetMethod("InitializeMultiplayerAsHost", flags)!;
yield return typeof(NDailyRunScreen).GetMethod("InitializeMultiplayerAsClient", flags)!;
yield return typeof(NDailyRunScreen).GetMethod("InitializeMultiplayerAsHost", flags)!;
yield return typeof(NCustomRunScreen).GetMethod("InitializeMultiplayerAsClient", flags)!;
yield return typeof(NCustomRunScreen).GetMethod("InitializeMultiplayerAsHost", flags)!;
}
private static void Prefix(object __instance, INetGameService gameService)
{
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;
}
}
}
}
}