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
@@ -0,0 +1,102 @@
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.MainMenu;
using MegaCrit.Sts2.Core.Runs;
using MegaCrit.Sts2.Core.TestSupport;
using SlayTheSpire2.LAN.Multiplayer.Helpers;
using SlayTheSpire2.LAN.Multiplayer.Services;
namespace SlayTheSpire2.LAN.Multiplayer.Components
{
internal class LanMultiplayerHostSubmenu : NMultiplayerHostSubmenu
{
private static readonly string ScenePath = SceneHelper.GetScenePath("screens/multiplayer_host_submenu");
public static LanMultiplayerHostSubmenu? Instance { get; private set; }
private Control? _loadingOverlay;
public new static NMultiplayerHostSubmenu? Create()
{
if (Instance != null)
return Instance;
if (TestMode.IsOn)
return null;
var multiplayerHostSubmenu =
PreloadManager.Cache.GetScene(ScenePath).Instantiate<NMultiplayerHostSubmenu>();
var lanMultiplayerHostSubmenu = new LanMultiplayerHostSubmenu();
lanMultiplayerHostSubmenu.SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect);
foreach (var child in multiplayerHostSubmenu.GetChildren())
{
child.Reparent(lanMultiplayerHostSubmenu, false);
}
multiplayerHostSubmenu.QueueFree();
Instance = lanMultiplayerHostSubmenu;
return lanMultiplayerHostSubmenu;
}
public override void _Ready()
{
ConnectSignals();
var loadingOverlay = GetNode<Control>("LoadingOverlay");
Traverse.Create(this).Field("_loadingOverlay").SetValue(loadingOverlay);
_loadingOverlay = loadingOverlay;
var standardButton = GetNode<NSubmenuButton>("StandardButton");
Traverse.Create(this).Field("_standardButton").SetValue(standardButton);
standardButton.Connect(NClickableControl.SignalName.Released, Callable.From<NButton>(OnStandardPressed));
standardButton.SetIconAndLocalization("STANDARD_MP");
var dailyButton = GetNode<NSubmenuButton>("DailyButton");
Traverse.Create(this).Field("_dailyButton").SetValue(dailyButton);
dailyButton.Connect(NClickableControl.SignalName.Released, Callable.From<NButton>(OnDailyPressed));
dailyButton.SetIconAndLocalization("DAILY_MP");
var customButton = GetNode<NSubmenuButton>("CustomRunButton");
Traverse.Create(this).Field("_customButton").SetValue(customButton);
customButton.Connect(NClickableControl.SignalName.Released, Callable.From<NButton>(OnCustomPressed));
customButton.SetIconAndLocalization("CUSTOM_MP");
}
private void OnStandardPressed(NButton _)
{
StartHost(GameMode.Standard);
}
private void OnDailyPressed(NButton _)
{
StartHost(GameMode.Daily);
}
private void OnCustomPressed(NButton _)
{
StartHost(GameMode.Custom);
}
private new void StartHost(GameMode gameMode)
{
if (_loadingOverlay != null)
{
var settingsModel = SettingsService.Instance.SettingsModel;
LanHostHelper.StartHost(gameMode, _loadingOverlay, _stack, settingsModel.HostPort,
settingsModel.HostMaxPlayers);
}
}
}
}
@@ -14,6 +14,7 @@ using MegaCrit.Sts2.Core.Nodes.Screens.MainMenu;
using MegaCrit.Sts2.Core.Platform; using MegaCrit.Sts2.Core.Platform;
using MegaCrit.Sts2.Core.Runs; using MegaCrit.Sts2.Core.Runs;
using MegaCrit.Sts2.Core.Saves; using MegaCrit.Sts2.Core.Saves;
using SlayTheSpire2.LAN.Multiplayer.Services;
// ReSharper disable MemberCanBePrivate.Global // ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedType.Global // ReSharper disable UnusedType.Global
@@ -138,7 +139,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Helpers
ushort port, int maxPlayers) ushort port, int maxPlayers)
{ {
var readSaveResult = var readSaveResult =
LanRunSaveManagerHelper.LoadAndCanonicalizeMultiplayerRunSave( LanRunSaveManagerService.Instance.LoadAndCanonicalizeMultiplayerRunSave(
PlatformUtil.GetLocalPlayerId(PlatformType.None)); PlatformUtil.GetLocalPlayerId(PlatformType.None));
if (!readSaveResult.Success || readSaveResult.SaveData == null) if (!readSaveResult.Success || readSaveResult.SaveData == null)
{ {
@@ -177,7 +178,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Helpers
return; return;
var readSaveResult = var readSaveResult =
LanRunSaveManagerHelper.LoadAndCanonicalizeMultiplayerRunSave( LanRunSaveManagerService.Instance.LoadAndCanonicalizeMultiplayerRunSave(
PlatformUtil.GetLocalPlayerId(PlatformType.None)); PlatformUtil.GetLocalPlayerId(PlatformType.None));
if (readSaveResult is { Success: true, SaveData: not null }) if (readSaveResult is { Success: true, SaveData: not null })
{ {
@@ -206,7 +207,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Helpers
$"ERROR: Failed to load multiplayer run save: status={readSaveResult.Status}. Deleting current run..."); $"ERROR: Failed to load multiplayer run save: status={readSaveResult.Status}. Deleting current run...");
} }
LanRunSaveManagerHelper.DeleteCurrentMultiplayerRun(); LanRunSaveManagerService.Instance.DeleteCurrentMultiplayerRun();
updateButtons(); updateButtons();
} }
} }
@@ -1,9 +0,0 @@
// ReSharper disable ClassNeverInstantiated.Global
namespace SlayTheSpire2.LAN.Multiplayer.Helpers
{
internal class LanMapDrawingsHelper
{
public static readonly HashSet<ulong> DisableDrawingHashSet = [];
}
}
@@ -1,119 +0,0 @@
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<LanPlayerNameResponseMessage>? 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<LanPlayerNameResponseMessage>();
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<List<NRemoteLobbyPlayer>>();
foreach (var node in nodes)
{
var megaLabel = Traverse.Create(node).Field("_nameplateLabel").GetValue<MegaLabel>();
if (!GodotObject.IsInstanceValid(megaLabel))
continue;
megaLabel.SetTextAutoSize(PlatformUtil.GetPlayerName(PlatformType.None, node.PlayerId));
}
}
}
}
@@ -1,13 +0,0 @@
using MegaCrit.Sts2.Core.Nodes.Screens.MainMenu;
// ReSharper disable ClassNeverInstantiated.Global
namespace SlayTheSpire2.LAN.Multiplayer.Helpers
{
internal class NMultiplayerSubmenuButtonHelpers
{
public static NSubmenuButton? LanHostButton;
public static NSubmenuButton? LanLoadButton;
public static NSubmenuButton? LanAbandonButton;
}
}
@@ -6,7 +6,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Models
{ {
public struct LanPlayerNameResponseMessage : INetMessage public struct LanPlayerNameResponseMessage : INetMessage
{ {
public LanPlayerNames playerNameDictionary; public PlayerNames playerNames;
public bool ShouldBroadcast => false; public bool ShouldBroadcast => false;
public NetTransferMode Mode => NetTransferMode.Reliable; public NetTransferMode Mode => NetTransferMode.Reliable;
@@ -14,8 +14,8 @@ namespace SlayTheSpire2.LAN.Multiplayer.Models
public void Serialize(PacketWriter writer) public void Serialize(PacketWriter writer)
{ {
writer.WriteInt(playerNameDictionary.Count); writer.WriteInt(playerNames.Count);
foreach (var keyValue in playerNameDictionary) foreach (var keyValue in playerNames)
{ {
writer.WriteULong(keyValue.Key); writer.WriteULong(keyValue.Key);
writer.WriteString(keyValue.Value); writer.WriteString(keyValue.Value);
@@ -25,10 +25,10 @@ namespace SlayTheSpire2.LAN.Multiplayer.Models
public void Deserialize(PacketReader reader) public void Deserialize(PacketReader reader)
{ {
var count = reader.ReadInt(); var count = reader.ReadInt();
playerNameDictionary = new LanPlayerNames(); playerNames = new PlayerNames();
for (var i = 0; i < count; i++) for (var i = 0; i < count; i++)
{ {
playerNameDictionary.Add(reader.ReadULong(), reader.ReadString()); playerNames.Add(reader.ReadULong(), reader.ReadString());
} }
} }
} }
@@ -1,9 +0,0 @@
using System.Text.Json.Serialization;
namespace SlayTheSpire2.LAN.Multiplayer.Models
{
[JsonSerializable(typeof(LanPlayerNames))]
public partial class LanPlayerNamesContext : JsonSerializerContext;
public class LanPlayerNames : Dictionary<ulong, string>;
}
@@ -0,0 +1,9 @@
using System.Text.Json.Serialization;
namespace SlayTheSpire2.LAN.Multiplayer.Models
{
[JsonSerializable(typeof(PlayerNames))]
public partial class PlayerNamesContext : JsonSerializerContext;
public class PlayerNames : Dictionary<ulong, string>;
}
@@ -3,8 +3,8 @@ 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 SlayTheSpire2.LAN.Multiplayer.Helpers;
using SlayTheSpire2.LAN.Multiplayer.Models; using SlayTheSpire2.LAN.Multiplayer.Models;
using SlayTheSpire2.LAN.Multiplayer.Services;
// ReSharper disable UnusedMember.Global // ReSharper disable UnusedMember.Global
// ReSharper disable UnusedType.Global // ReSharper disable UnusedType.Global
@@ -27,10 +27,12 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs
if (joinFlow.NetService == null) if (joinFlow.NetService == null)
return result; return result;
joinFlow.NetService.RegisterMessageHandler<LanPlayerNameResponseMessage>(LanPlayerNameHelper var lanPlayerNameService = LanPlayerNameService.Instance;
joinFlow.NetService.RegisterMessageHandler<LanPlayerNameResponseMessage>(lanPlayerNameService
.HandleLanPlayerNameResponseMessage); .HandleLanPlayerNameResponseMessage);
await LanPlayerNameHelper.AttemptPlayerName(joinFlow.NetService); await lanPlayerNameService.AttemptPlayerName(joinFlow.NetService);
return result; return result;
} }
@@ -52,10 +54,12 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs
if (joinFlow.NetService == null) if (joinFlow.NetService == null)
return result; return result;
joinFlow.NetService.RegisterMessageHandler<LanPlayerNameResponseMessage>(LanPlayerNameHelper var lanPlayerNameService = LanPlayerNameService.Instance;
joinFlow.NetService.RegisterMessageHandler<LanPlayerNameResponseMessage>(lanPlayerNameService
.HandleLanPlayerNameResponseMessage); .HandleLanPlayerNameResponseMessage);
await LanPlayerNameHelper.AttemptPlayerName(joinFlow.NetService); await lanPlayerNameService.AttemptPlayerName(joinFlow.NetService);
return result; return result;
} }
@@ -77,10 +81,12 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs
if (joinFlow.NetService == null) if (joinFlow.NetService == null)
return result; return result;
joinFlow.NetService.RegisterMessageHandler<LanPlayerNameResponseMessage>(LanPlayerNameHelper var lanPlayerNameService = LanPlayerNameService.Instance;
joinFlow.NetService.RegisterMessageHandler<LanPlayerNameResponseMessage>(lanPlayerNameService
.HandleLanPlayerNameResponseMessage); .HandleLanPlayerNameResponseMessage);
await LanPlayerNameHelper.AttemptPlayerName(joinFlow.NetService); await lanPlayerNameService.AttemptPlayerName(joinFlow.NetService);
return result; return result;
} }
@@ -95,7 +101,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs
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 = LanPlayerNameHelper.LanPlayerNameCompletion; var lanPlayerNameCompletion = LanPlayerNameService.Instance.LanPlayerNameCompletion;
if (lanPlayerNameCompletion?.Task is { IsCompleted: false }) if (lanPlayerNameCompletion?.Task is { IsCompleted: false })
{ {
@@ -109,7 +115,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs
{ {
private static void Postfix() private static void Postfix()
{ {
var lanPlayerNameCompletion = LanPlayerNameHelper.LanPlayerNameCompletion; var lanPlayerNameCompletion = LanPlayerNameService.Instance.LanPlayerNameCompletion;
if (lanPlayerNameCompletion?.Task is { IsCompleted: false }) if (lanPlayerNameCompletion?.Task is { IsCompleted: false })
{ {
@@ -1,6 +1,6 @@
using HarmonyLib; using HarmonyLib;
using MegaCrit.Sts2.Core.Platform.Null; using MegaCrit.Sts2.Core.Platform.Null;
using SlayTheSpire2.LAN.Multiplayer.Helpers; using SlayTheSpire2.LAN.Multiplayer.Services;
// ReSharper disable UnusedMember.Global // ReSharper disable UnusedMember.Global
// ReSharper disable UnusedType.Global // ReSharper disable UnusedType.Global
@@ -24,7 +24,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs
} }
} }
if (LanPlayerNameHelper.PlayerNameDictionary.TryGetValue(playerId, out var playerName)) if (LanPlayerNameService.Instance.PlayerNames.TryGetValue(playerId, out var playerName))
{ {
__result = playerName; __result = playerName;
return false; return false;
@@ -5,8 +5,8 @@ using MegaCrit.Sts2.Core.Multiplayer.Game.Lobby;
using MegaCrit.Sts2.Core.Platform; using MegaCrit.Sts2.Core.Platform;
using MegaCrit.Sts2.Core.Runs; using MegaCrit.Sts2.Core.Runs;
using MegaCrit.Sts2.Core.Saves; using MegaCrit.Sts2.Core.Saves;
using SlayTheSpire2.LAN.Multiplayer.Helpers;
using SlayTheSpire2.LAN.Multiplayer.Models; using SlayTheSpire2.LAN.Multiplayer.Models;
using SlayTheSpire2.LAN.Multiplayer.Services;
// ReSharper disable UnusedMember.Global // ReSharper disable UnusedMember.Global
// ReSharper disable UnusedType.Global // ReSharper disable UnusedType.Global
@@ -34,14 +34,16 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs
{ {
if (netService.Platform == PlatformType.None) if (netService.Platform == PlatformType.None)
{ {
LanPlayerNameHelper.NetService = netService; var lanPlayerNameService = LanPlayerNameService.Instance;
netService.RegisterMessageHandler<LanPlayerNameResponseMessage>(LanPlayerNameHelper lanPlayerNameService.NetService = netService;
netService.RegisterMessageHandler<LanPlayerNameResponseMessage>(lanPlayerNameService
.HandleLanPlayerNameResponseMessage); .HandleLanPlayerNameResponseMessage);
if (netService.Type == NetGameType.Host) if (netService.Type == NetGameType.Host)
{ {
netService.RegisterMessageHandler<LanPlayerNameRequestMessage>(LanPlayerNameHelper netService.RegisterMessageHandler<LanPlayerNameRequestMessage>(lanPlayerNameService
.HandleLanPlayerNameRequestMessage); .HandleLanPlayerNameRequestMessage);
} }
} }
@@ -53,30 +55,57 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs
{ {
private static IEnumerable<MethodBase> TargetMethods() private static IEnumerable<MethodBase> TargetMethods()
{ {
yield return typeof(StartRunLobby).GetMethod("CleanUp", BindingFlags.Instance | BindingFlags.Public)!; const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public;
yield return typeof(RunLobby).GetMethod("Dispose", BindingFlags.Instance | BindingFlags.Public)!;
yield return typeof(LoadRunLobby).GetMethod("CleanUp", BindingFlags.Instance | BindingFlags.Public)!; yield return typeof(StartRunLobby).GetMethod("CleanUp", flags)!;
yield return typeof(LoadRunLobby).GetMethod("CleanUp", flags)!;
} }
private static void Prefix(object __instance) private static void Prefix(object __instance, bool disconnectSession)
{ {
var netService = __instance is StartRunLobby or LoadRunLobby var netService = Traverse.Create(__instance).Property("NetService").GetValue<INetGameService>();
? Traverse.Create(__instance).Property("NetService").GetValue<INetGameService>()
: Traverse.Create(__instance).Field("_netService").GetValue<INetGameService>();
if (netService.Platform == PlatformType.None) if (netService.Platform == PlatformType.None)
{ {
LanPlayerNameHelper.SetDefaultPlayerNameDictionary(); var lanPlayerNameService = LanPlayerNameService.Instance;
netService.UnregisterMessageHandler<LanPlayerNameResponseMessage>(LanPlayerNameHelper if (disconnectSession)
{
lanPlayerNameService.SetDefaultPlayerNames();
}
netService.UnregisterMessageHandler<LanPlayerNameResponseMessage>(lanPlayerNameService
.HandleLanPlayerNameResponseMessage); .HandleLanPlayerNameResponseMessage);
if (netService.Type == NetGameType.Host) if (netService.Type == NetGameType.Host)
{ {
netService.UnregisterMessageHandler<LanPlayerNameRequestMessage>(LanPlayerNameHelper netService.UnregisterMessageHandler<LanPlayerNameRequestMessage>(lanPlayerNameService
.HandleLanPlayerNameRequestMessage);
}
}
}
[HarmonyPatch(typeof(RunLobby), "Dispose")]
internal class RunLobbyDisposePatch
{
private static void Prefix(INetGameService ____netService)
{
if (____netService.Platform == PlatformType.None)
{
var lanPlayerNameService = LanPlayerNameService.Instance;
lanPlayerNameService.SetDefaultPlayerNames();
____netService.UnregisterMessageHandler<LanPlayerNameResponseMessage>(lanPlayerNameService
.HandleLanPlayerNameResponseMessage);
if (____netService.Type == NetGameType.Host)
{
____netService.UnregisterMessageHandler<LanPlayerNameRequestMessage>(lanPlayerNameService
.HandleLanPlayerNameRequestMessage); .HandleLanPlayerNameRequestMessage);
} }
} }
} }
} }
}
} }
@@ -1,6 +1,6 @@
using HarmonyLib; using HarmonyLib;
using MegaCrit.Sts2.Core.Runs; using MegaCrit.Sts2.Core.Runs;
using SlayTheSpire2.LAN.Multiplayer.Helpers; using SlayTheSpire2.LAN.Multiplayer.Services;
// ReSharper disable UnusedMember.Global // ReSharper disable UnusedMember.Global
// ReSharper disable UnusedType.Global // ReSharper disable UnusedType.Global
@@ -12,8 +12,8 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs
{ {
private static void Postfix() private static void Postfix()
{ {
LanMapDrawingsHelper.DisableDrawingHashSet.Clear(); LanMapDrawingsService.Instance.DisableDrawingHashSet.Clear();
LanPlayerNameHelper.SetDefaultPlayerNameDictionary(); LanPlayerNameService.Instance.SetDefaultPlayerNames();
} }
} }
} }
@@ -7,8 +7,8 @@ using MegaCrit.Sts2.Core.Rooms;
using MegaCrit.Sts2.Core.Runs; using MegaCrit.Sts2.Core.Runs;
using MegaCrit.Sts2.Core.Saves; using MegaCrit.Sts2.Core.Saves;
using MegaCrit.Sts2.Core.Saves.Managers; using MegaCrit.Sts2.Core.Saves.Managers;
using SlayTheSpire2.LAN.Multiplayer.Helpers;
using SlayTheSpire2.LAN.Multiplayer.Models; using SlayTheSpire2.LAN.Multiplayer.Models;
using SlayTheSpire2.LAN.Multiplayer.Services;
// ReSharper disable UnusedMember.Global // ReSharper disable UnusedMember.Global
// ReSharper disable UnusedType.Global // ReSharper disable UnusedType.Global
@@ -42,7 +42,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs
var savePath = isMultiplayer var savePath = isMultiplayer
? isNonePlatform ? isNonePlatform
? LanRunSaveManagerHelper.CurrentMultiplayerRunSavePath ? LanRunSaveManagerService.Instance.CurrentMultiplayerRunSavePath
: Traverse.Create(runSaveManager).Property("CurrentMultiplayerRunSavePath").GetValue<string>() : Traverse.Create(runSaveManager).Property("CurrentMultiplayerRunSavePath").GetValue<string>()
: Traverse.Create(runSaveManager).Property("CurrentRunSavePath").GetValue<string>(); : Traverse.Create(runSaveManager).Property("CurrentRunSavePath").GetValue<string>();
using var stream = new MemoryStream(); using var stream = new MemoryStream();
@@ -62,20 +62,22 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs
if (isMultiplayer && isNonePlatform) if (isMultiplayer && isNonePlatform)
{ {
var lanPlayerNameService = LanPlayerNameService.Instance;
using var playerNamesStream = new MemoryStream(); using var playerNamesStream = new MemoryStream();
if (!forceSynchronous) if (!forceSynchronous)
{ {
await JsonSerializer.SerializeAsync(playerNamesStream, LanPlayerNameHelper.PlayerNameDictionary, await JsonSerializer.SerializeAsync(playerNamesStream, lanPlayerNameService.PlayerNames,
LanPlayerNamesContext.Default.LanPlayerNames, CancellationToken.None); PlayerNamesContext.Default.PlayerNames, CancellationToken.None);
} }
else else
{ {
await JsonSerializer.SerializeAsync(playerNamesStream, LanPlayerNameHelper.PlayerNameDictionary, await JsonSerializer.SerializeAsync(playerNamesStream, lanPlayerNameService.PlayerNames,
LanPlayerNamesContext.Default.LanPlayerNames); PlayerNamesContext.Default.PlayerNames);
} }
playerNamesStream.Seek(0L, SeekOrigin.Begin); playerNamesStream.Seek(0L, SeekOrigin.Begin);
await saveStore.WriteFileAsync(LanRunSaveManagerHelper.CurrentMultiplayerRunPlayerNamesPath, await saveStore.WriteFileAsync(LanRunSaveManagerService.Instance.CurrentMultiplayerRunPlayerNamesPath,
playerNamesStream.ToArray()); playerNamesStream.ToArray());
} }
@@ -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.GodotExtensions;
using MegaCrit.Sts2.Core.Nodes.Screens.MainMenu; using MegaCrit.Sts2.Core.Nodes.Screens.MainMenu;
using SlayTheSpire2.LAN.Multiplayer.Components; using SlayTheSpire2.LAN.Multiplayer.Components;
using SlayTheSpire2.LAN.Multiplayer.Helpers; using SlayTheSpire2.LAN.Multiplayer.Services;
// ReSharper disable UnusedMember.Global // ReSharper disable UnusedMember.Global
// ReSharper disable UnusedType.Global // ReSharper disable UnusedType.Global
@@ -61,7 +61,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
vBoxContainer.AddChild(addressLineEdit); vBoxContainer.AddChild(addressLineEdit);
addressLineEdit.Text = SettingsHelper.Instance.SettingsModel.IPAddress; addressLineEdit.Text = SettingsService.Instance.SettingsModel.IPAddress;
addressLineEdit.Alignment = HorizontalAlignment.Center; addressLineEdit.Alignment = HorizontalAlignment.Center;
addressLineEdit.CustomMinimumSize = new Vector2(300, 50); addressLineEdit.CustomMinimumSize = new Vector2(300, 50);
addressLineEdit.SizeFlagsHorizontal = Control.SizeFlags.ShrinkCenter; addressLineEdit.SizeFlagsHorizontal = Control.SizeFlags.ShrinkCenter;
@@ -83,8 +83,8 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
if (!addressInfo.IsValid) if (!addressInfo.IsValid)
return; return;
SettingsHelper.Instance.SettingsModel.IPAddress = addressLineEdit.Text; SettingsService.Instance.SettingsModel.IPAddress = addressLineEdit.Text;
SettingsHelper.Instance.WriteSettings(); SettingsService.Instance.WriteSettings();
ushort port = 33771; ushort port = 33771;
@@ -98,7 +98,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
{ {
TaskHelper.RunSafely( TaskHelper.RunSafely(
__instance.JoinGameAsync(new ENetClientConnectionInitializer( __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 HarmonyLib;
using MegaCrit.Sts2.Core.Entities.Players; using MegaCrit.Sts2.Core.Entities.Players;
using MegaCrit.Sts2.Core.Nodes.Screens.Map; using MegaCrit.Sts2.Core.Nodes.Screens.Map;
using SlayTheSpire2.LAN.Multiplayer.Helpers; using SlayTheSpire2.LAN.Multiplayer.Services;
// ReSharper disable UnusedMember.Global // ReSharper disable UnusedMember.Global
// ReSharper disable UnusedType.Global // ReSharper disable UnusedType.Global
@@ -14,7 +14,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
{ {
private static void Postfix(Player player, Line2D __result) private static void Postfix(Player player, Line2D __result)
{ {
if (LanMapDrawingsHelper.DisableDrawingHashSet.Contains(player.NetId)) if (LanMapDrawingsService.Instance.DisableDrawingHashSet.Contains(player.NetId))
{ {
__result.Visible = false; __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.CardLibrary;
using MegaCrit.Sts2.Core.Nodes.Screens.Map; using MegaCrit.Sts2.Core.Nodes.Screens.Map;
using MegaCrit.Sts2.Core.Runs; using MegaCrit.Sts2.Core.Runs;
using SlayTheSpire2.LAN.Multiplayer.Helpers; using SlayTheSpire2.LAN.Multiplayer.Services;
// ReSharper disable UnusedMember.Global // ReSharper disable UnusedMember.Global
// ReSharper disable UnusedType.Global // ReSharper disable UnusedType.Global
@@ -21,32 +21,30 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
{ {
if (____player.NetId != RunManager.Instance.NetService.NetId) if (____player.NetId != RunManager.Instance.NetService.NetId)
{ {
var lanPanel = new HBoxContainer { Name = "LANPanel" }; var container = __instance.GetNode("ScreenContents/Container");
__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 var disableDrawing = PreloadManager.Cache
.GetScene(SceneHelper.GetScenePath("screens/card_library/card_library_tickbox")) .GetScene(SceneHelper.GetScenePath("screens/card_library/card_library_tickbox"))
.Instantiate<Control>(); .Instantiate<Control>();
if (disableDrawing is NLibraryStatTickbox cardLibraryTickBox) if (disableDrawing is NLibraryStatTickbox cardLibraryTickBox &&
container.GetNode("MarginContainer") is MarginContainer marginContainer)
{ {
disableDrawing.Name = "DisableDrawing"; 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.SetLabel("Disable drawing");
cardLibraryTickBox.IsTicked = LanMapDrawingsHelper.DisableDrawingHashSet.Contains(____player.NetId); var lanMapDrawingsService = LanMapDrawingsService.Instance;
cardLibraryTickBox.IsTicked =
lanMapDrawingsService.DisableDrawingHashSet.Contains(____player.NetId);
cardLibraryTickBox.Toggled += tickBox => cardLibraryTickBox.Toggled += tickBox =>
{ {
@@ -64,7 +62,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
} }
} }
LanMapDrawingsHelper.DisableDrawingHashSet.Add(____player.NetId); lanMapDrawingsService.DisableDrawingHashSet.Add(____player.NetId);
} }
else 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.GodotExtensions;
using MegaCrit.Sts2.Core.Nodes.Screens.MainMenu; using MegaCrit.Sts2.Core.Nodes.Screens.MainMenu;
using MegaCrit.Sts2.Core.Runs; using MegaCrit.Sts2.Core.Runs;
using MegaCrit.Sts2.Core.Saves;
using SlayTheSpire2.LAN.Multiplayer.Components;
using SlayTheSpire2.LAN.Multiplayer.Helpers; using SlayTheSpire2.LAN.Multiplayer.Helpers;
using SlayTheSpire2.LAN.Multiplayer.Services;
// ReSharper disable UnusedMember.Global // ReSharper disable UnusedMember.Global
// ReSharper disable UnusedType.Global // ReSharper disable UnusedType.Global
@@ -30,11 +33,20 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
{ {
var traverse = Traverse.Create(__instance); var traverse = Traverse.Create(__instance);
var settingsModel = SettingsHelper.Instance.SettingsModel; var settingsModel = SettingsService.Instance.SettingsModel;
LanHostHelper.StartHost(GameMode.Standard, traverse.Field("_loadingOverlay").GetValue<Control>(), var stack = traverse.Field("_stack").GetValue<NSubmenuStack>();
traverse.Field("_stack").GetValue<NSubmenuStack>(),
settingsModel.HostPort, settingsModel.HostMaxPlayers); 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"); lanHostButton.SetIconAndLocalization("HOST");
var lanHostTitle = Traverse.Create(lanHostButton).Field("_title").GetValue<MegaLabel>(); var lanHostTitle = Traverse.Create(lanHostButton).Field("_title").GetValue<MegaLabel>();
@@ -42,7 +54,9 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
NSubmenuButtonDuplicateMaterial(lanHostButton); NSubmenuButtonDuplicateMaterial(lanHostButton);
NMultiplayerSubmenuButtonHelpers.LanHostButton = lanHostButton; var lanMultiplayerSubmenuButtonService = LanMultiplayerSubmenuButtonService.Instance;
lanMultiplayerSubmenuButtonService.LanHostButton = lanHostButton;
if (buttonContainerNode.GetNode("LoadButton").Duplicate() is not NSubmenuButton lanLoadButton) if (buttonContainerNode.GetNode("LoadButton").Duplicate() is not NSubmenuButton lanLoadButton)
return; return;
@@ -55,7 +69,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
{ {
var traverse = Traverse.Create(__instance); var traverse = Traverse.Create(__instance);
var settingsModel = SettingsHelper.Instance.SettingsModel; var settingsModel = SettingsService.Instance.SettingsModel;
LanHostHelper.StartLoad(lanLoadButton, traverse.Field("_loadingOverlay").GetValue<Control>(), LanHostHelper.StartLoad(lanLoadButton, traverse.Field("_loadingOverlay").GetValue<Control>(),
traverse.Field("_stack").GetValue<NSubmenuStack>(), traverse.Field("_stack").GetValue<NSubmenuStack>(),
@@ -67,7 +81,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
NSubmenuButtonDuplicateMaterial(lanLoadButton); NSubmenuButtonDuplicateMaterial(lanLoadButton);
NMultiplayerSubmenuButtonHelpers.LanLoadButton = lanLoadButton; lanMultiplayerSubmenuButtonService.LanLoadButton = lanLoadButton;
if (buttonContainerNode.GetNode("AbandonButton").Duplicate() is not NSubmenuButton lanAbandonButton) if (buttonContainerNode.GetNode("AbandonButton").Duplicate() is not NSubmenuButton lanAbandonButton)
return; return;
@@ -89,7 +103,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
NSubmenuButtonDuplicateMaterial(lanAbandonButton); NSubmenuButtonDuplicateMaterial(lanAbandonButton);
NMultiplayerSubmenuButtonHelpers.LanAbandonButton = lanAbandonButton; lanMultiplayerSubmenuButtonService.LanAbandonButton = lanAbandonButton;
} }
private static void NSubmenuButtonDuplicateMaterial(NSubmenuButton nSubmenuButton) private static void NSubmenuButtonDuplicateMaterial(NSubmenuButton nSubmenuButton)
@@ -108,20 +122,24 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
{ {
private static void Postfix() 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 = lanMultiplayerSubmenuButtonService.LanAbandonButton.Visible =
LanRunSaveManagerHelper.HasMultiplayerRunSave; LanRunSaveManagerService.Instance.HasMultiplayerRunSave;
} }
} }
} }
@@ -2,7 +2,7 @@
using HarmonyLib; using HarmonyLib;
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.Helpers; using SlayTheSpire2.LAN.Multiplayer.Services;
// ReSharper disable UnusedMember.Global // ReSharper disable UnusedMember.Global
// ReSharper disable UnusedType.Global // ReSharper disable UnusedType.Global
@@ -50,11 +50,11 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
hostPortLineEdit.Step = 1; hostPortLineEdit.Step = 1;
hostPortLineEdit.MinValue = 0; hostPortLineEdit.MinValue = 0;
hostPortLineEdit.MaxValue = 65535; hostPortLineEdit.MaxValue = 65535;
hostPortLineEdit.Value = SettingsHelper.Instance.SettingsModel.HostPort; hostPortLineEdit.Value = SettingsService.Instance.SettingsModel.HostPort;
hostPortLineEdit.ValueChanged += value => hostPortLineEdit.ValueChanged += value =>
{ {
SettingsHelper.Instance.SettingsModel.HostPort = (ushort)value; SettingsService.Instance.SettingsModel.HostPort = (ushort)value;
SettingsHelper.Instance.WriteSettings(); SettingsService.Instance.WriteSettings();
}; };
hostPortLabel.Text = "Host Port"; hostPortLabel.Text = "Host Port";
@@ -90,11 +90,11 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
hostMaxPlayersInput.Step = 1; hostMaxPlayersInput.Step = 1;
hostMaxPlayersInput.MinValue = 2; hostMaxPlayersInput.MinValue = 2;
hostMaxPlayersInput.Value = SettingsHelper.Instance.SettingsModel.HostMaxPlayers; hostMaxPlayersInput.Value = SettingsService.Instance.SettingsModel.HostMaxPlayers;
hostMaxPlayersInput.ValueChanged += value => hostMaxPlayersInput.ValueChanged += value =>
{ {
SettingsHelper.Instance.SettingsModel.HostMaxPlayers = (int)value; SettingsService.Instance.SettingsModel.HostMaxPlayers = (int)value;
SettingsHelper.Instance.WriteSettings(); SettingsService.Instance.WriteSettings();
}; };
hostMaxPlayersLabel.Text = "Host Max Players"; hostMaxPlayersLabel.Text = "Host Max Players";
@@ -129,14 +129,14 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
playerNameInput.Alignment = HorizontalAlignment.Center; playerNameInput.Alignment = HorizontalAlignment.Center;
playerNameInput.MaxLength = 16; playerNameInput.MaxLength = 16;
playerNameInput.Text = SettingsHelper.Instance.SettingsModel.PlayerName; playerNameInput.Text = SettingsService.Instance.SettingsModel.PlayerName;
playerNameInput.TextChanged += value => playerNameInput.TextChanged += value =>
{ {
if (playerNameInput.IsEmpty || !playerNameInput.IsInvalid) if (playerNameInput.IsEmpty || !playerNameInput.IsInvalid)
{ {
SettingsHelper.Instance.SettingsModel.PlayerName = value; SettingsService.Instance.SettingsModel.PlayerName = value;
LanPlayerNameHelper.SetHostPlayerName(); LanPlayerNameService.Instance.SetHostPlayerName();
SettingsHelper.Instance.WriteSettings(); SettingsService.Instance.WriteSettings();
} }
}; };
@@ -174,11 +174,11 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
netIdInput.Step = 1; netIdInput.Step = 1;
netIdInput.MinValue = 2; netIdInput.MinValue = 2;
netIdInput.MaxValue = ulong.MaxValue; netIdInput.MaxValue = ulong.MaxValue;
netIdInput.Value = SettingsHelper.Instance.SettingsModel.NetId; netIdInput.Value = SettingsService.Instance.SettingsModel.NetId;
netIdInput.ValueChanged += value => netIdInput.ValueChanged += value =>
{ {
SettingsHelper.Instance.SettingsModel.NetId = (ulong)value; SettingsService.Instance.SettingsModel.NetId = (ulong)value;
SettingsHelper.Instance.WriteSettings(); SettingsService.Instance.WriteSettings();
}; };
netIdLabel.Text = "NetID"; 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;
}
}
}
}
}
@@ -0,0 +1,17 @@
// ReSharper disable ClassNeverInstantiated.Global
namespace SlayTheSpire2.LAN.Multiplayer.Services
{
internal class LanMapDrawingsService
{
private static readonly Lazy<LanMapDrawingsService> Lazy = new(() => new LanMapDrawingsService());
public static LanMapDrawingsService Instance => Lazy.Value;
public readonly HashSet<ulong> DisableDrawingHashSet = [];
private LanMapDrawingsService()
{
}
}
}
@@ -0,0 +1,22 @@
using MegaCrit.Sts2.Core.Nodes.Screens.MainMenu;
// ReSharper disable ClassNeverInstantiated.Global
namespace SlayTheSpire2.LAN.Multiplayer.Services
{
internal class LanMultiplayerSubmenuButtonService
{
private static readonly Lazy<LanMultiplayerSubmenuButtonService> Lazy = new(() =>
new LanMultiplayerSubmenuButtonService());
public static LanMultiplayerSubmenuButtonService Instance => Lazy.Value;
public NSubmenuButton? LanHostButton;
public NSubmenuButton? LanLoadButton;
public NSubmenuButton? LanAbandonButton;
private LanMultiplayerSubmenuButtonService()
{
}
}
}
@@ -0,0 +1,143 @@
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.Platform;
using SlayTheSpire2.LAN.Multiplayer.Components;
using SlayTheSpire2.LAN.Multiplayer.Models;
// ReSharper disable ClassNeverInstantiated.Global
namespace SlayTheSpire2.LAN.Multiplayer.Services
{
internal class LanPlayerNameService
{
private static readonly Lazy<LanPlayerNameService> Lazy = new(() => new LanPlayerNameService());
public static LanPlayerNameService Instance => Lazy.Value;
public PlayerNames PlayerNames = GetDefaultPlayerNames();
public INetGameService? NetService;
public TaskCompletionSource<LanPlayerNameResponseMessage>? LanPlayerNameCompletion;
private LanPlayerNameService()
{
}
public void SetHostPlayerName()
{
if (!PlayerNameLineEdit.GetPlayerNameIsInvalid(SettingsService.Instance.SettingsModel.PlayerName))
{
PlayerNames[1u] = SettingsService.Instance.SettingsModel.PlayerName;
}
else
{
PlayerNames.Remove(1u);
}
}
public void SetDefaultPlayerNames()
{
PlayerNames = GetDefaultPlayerNames();
}
private static PlayerNames GetDefaultPlayerNames()
{
return !PlayerNameLineEdit.GetPlayerNameIsInvalid(SettingsService.Instance.SettingsModel.PlayerName)
? new PlayerNames { { 1u, SettingsService.Instance.SettingsModel.PlayerName } }
: new PlayerNames();
}
public void HandleLanPlayerNameResponseMessage(LanPlayerNameResponseMessage lanPlayerNameResponseMessage,
ulong senderId)
{
PlayerNames = lanPlayerNameResponseMessage.playerNames;
UpdatePlayerName();
LanPlayerNameCompletion?.SetResult(lanPlayerNameResponseMessage);
}
public void HandleLanPlayerNameRequestMessage(LanPlayerNameRequestMessage lanPlayerNameRequestMessage,
ulong senderId)
{
if (PlayerNameLineEdit.GetPlayerNameIsInvalid(lanPlayerNameRequestMessage.playerName))
{
NetService?.SendMessage(
new LanPlayerNameResponseMessage { playerNames = PlayerNames }, senderId);
return;
}
PlayerNames[senderId] = lanPlayerNameRequestMessage.playerName;
UpdatePlayerName();
NetService?.SendMessage(new LanPlayerNameResponseMessage { playerNames = PlayerNames });
}
public async Task AttemptPlayerName(NetClientGameService gameService)
{
LanPlayerNameCompletion = new TaskCompletionSource<LanPlayerNameResponseMessage>();
var message = new LanPlayerNameRequestMessage
{ playerName = SettingsService.Instance.SettingsModel.PlayerName };
gameService.SendMessage(message);
await LanPlayerNameCompletion.Task;
LanPlayerNameCompletion = null;
}
private static void UpdatePlayerName()
{
var runScreenService = RunScreenService.Instance;
if (runScreenService.CharacterSelectScreen != null)
{
UpdateNameplateLabel(runScreenService.CharacterSelectScreen);
}
if (runScreenService.DailyRunScreen != null)
{
UpdateNameplateLabel(runScreenService.DailyRunScreen);
}
if (runScreenService.CustomRunScreen != null)
{
UpdateNameplateLabel(runScreenService.CustomRunScreen);
}
if (runScreenService.MultiplayerLoadGameScreen != null)
{
UpdateNameplateLabel(runScreenService.MultiplayerLoadGameScreen);
}
if (runScreenService.DailyRunLoadScreen != null)
{
UpdateNameplateLabel(runScreenService.DailyRunLoadScreen);
}
if (runScreenService.CustomRunLoadScreen != null)
{
UpdateNameplateLabel(runScreenService.CustomRunLoadScreen);
}
}
private static void UpdateNameplateLabel(Control container)
{
var nodes = Traverse.Create(container).Field("_remotePlayerContainer")
.Field("_nodes").GetValue<List<NRemoteLobbyPlayer>>();
foreach (var node in nodes)
{
var megaLabel = Traverse.Create(node).Field("_nameplateLabel").GetValue<MegaLabel>();
if (!GodotObject.IsInstanceValid(megaLabel))
continue;
megaLabel.SetTextAutoSize(PlatformUtil.GetPlayerName(PlatformType.None, node.PlayerId));
}
}
}
}
@@ -7,45 +7,55 @@ using MegaCrit.Sts2.Core.Saves.Managers;
using MegaCrit.Sts2.Core.Saves.Migrations; using MegaCrit.Sts2.Core.Saves.Migrations;
using SlayTheSpire2.LAN.Multiplayer.Models; using SlayTheSpire2.LAN.Multiplayer.Models;
// ReSharper disable MemberCanBeMadeStatic.Local
// ReSharper disable MemberCanBePrivate.Global // ReSharper disable MemberCanBePrivate.Global
// ReSharper disable ClassNeverInstantiated.Global // ReSharper disable ClassNeverInstantiated.Global
namespace SlayTheSpire2.LAN.Multiplayer.Helpers namespace SlayTheSpire2.LAN.Multiplayer.Services
{ {
internal class LanRunSaveManagerHelper internal class LanRunSaveManagerService
{ {
public static ISaveStore SaveStore => private static readonly Lazy<LanRunSaveManagerService> Lazy = new(() => new LanRunSaveManagerService());
Traverse.Create(SaveManager.Instance).Field("_saveStore").GetValue<ISaveStore>();
public static MigrationManager MigrationManager => Traverse.Create(SaveManager.Instance) public static LanRunSaveManagerService Instance => Lazy.Value;
.Field("_migrationManager").GetValue<MigrationManager>();
public static IProfileIdProvider ProfileIdProvider => SaveManager.Instance; private SaveManager ProfileIdProvider => SaveManager.Instance;
public static string CurrentMultiplayerRunSavePath => private ISaveStore SaveStore => Traverse.Create(ProfileIdProvider).Field("_saveStore").GetValue<ISaveStore>();
private MigrationManager MigrationManager => Traverse.Create(ProfileIdProvider).Field("_migrationManager")
.GetValue<MigrationManager>();
public string CurrentMultiplayerRunSavePath =>
RunSaveManager.GetRunSavePath(ProfileIdProvider.CurrentProfileId, "current_lan_run_mp.save"); RunSaveManager.GetRunSavePath(ProfileIdProvider.CurrentProfileId, "current_lan_run_mp.save");
public static string CurrentMultiplayerRunPlayerNamesPath => public string CurrentMultiplayerRunPlayerNamesPath =>
RunSaveManager.GetRunSavePath(ProfileIdProvider.CurrentProfileId, "current_lan_run_mp_player_names.json"); RunSaveManager.GetRunSavePath(ProfileIdProvider.CurrentProfileId, "current_lan_run_mp_player_names.json");
public static bool HasMultiplayerRunSave => SaveStore.FileExists(CurrentMultiplayerRunSavePath); public bool HasMultiplayerRunSave => SaveStore.FileExists(CurrentMultiplayerRunSavePath);
public static ReadSaveResult<SerializableRun> LoadAndCanonicalizeMultiplayerRunSave(ulong localPlayerId) private LanRunSaveManagerService()
{
}
public ReadSaveResult<SerializableRun> LoadAndCanonicalizeMultiplayerRunSave(ulong localPlayerId)
{ {
var readSaveResult = LoadMultiplayerRunSave(); var readSaveResult = LoadMultiplayerRunSave();
if (readSaveResult is { Success: true, SaveData: not null }) if (readSaveResult is { Success: true, SaveData: not null })
{ {
try try
{ {
var lanPlayerNameService = LanPlayerNameService.Instance;
var data = RunManager.CanonicalizeSave(readSaveResult.SaveData, localPlayerId); var data = RunManager.CanonicalizeSave(readSaveResult.SaveData, localPlayerId);
var playerNamesJson = SaveStore.ReadFile(CurrentMultiplayerRunPlayerNamesPath); var playerNamesJson = SaveStore.ReadFile(CurrentMultiplayerRunPlayerNamesPath);
if (!string.IsNullOrEmpty(playerNamesJson)) if (!string.IsNullOrEmpty(playerNamesJson))
{ {
LanPlayerNameHelper.PlayerNameDictionary = lanPlayerNameService.PlayerNames = JsonSerializer.Deserialize<PlayerNames>(playerNamesJson) ??
JsonSerializer.Deserialize<LanPlayerNames>(playerNamesJson) ?? new LanPlayerNames(); new PlayerNames();
} }
LanPlayerNameHelper.SetHostPlayerName(); lanPlayerNameService.SetHostPlayerName();
return new ReadSaveResult<SerializableRun>(data, ReadSaveStatus.Success); return new ReadSaveResult<SerializableRun>(data, ReadSaveStatus.Success);
} }
catch (Exception value) catch (Exception value)
@@ -60,7 +70,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Helpers
return readSaveResult; return readSaveResult;
} }
public static ReadSaveResult<SerializableRun> LoadMultiplayerRunSave() public ReadSaveResult<SerializableRun> LoadMultiplayerRunSave()
{ {
var readSaveResult = MigrationManager.LoadSave<SerializableRun>(CurrentMultiplayerRunSavePath); var readSaveResult = MigrationManager.LoadSave<SerializableRun>(CurrentMultiplayerRunSavePath);
if (readSaveResult.Success) if (readSaveResult.Success)
@@ -86,13 +96,13 @@ namespace SlayTheSpire2.LAN.Multiplayer.Helpers
return readSaveResult; return readSaveResult;
} }
public static void DeleteCurrentMultiplayerRun() public void DeleteCurrentMultiplayerRun()
{ {
SaveStore.DeleteFile(CurrentMultiplayerRunSavePath); SaveStore.DeleteFile(CurrentMultiplayerRunSavePath);
SaveStore.DeleteFile(CurrentMultiplayerRunPlayerNamesPath); SaveStore.DeleteFile(CurrentMultiplayerRunPlayerNamesPath);
} }
public static void RenameBrokenMultiplayerRunSave(ReadSaveStatus status) public void RenameBrokenMultiplayerRunSave(ReadSaveStatus status)
{ {
try try
{ {
@@ -0,0 +1,67 @@
using Godot;
using HarmonyLib;
using MegaCrit.Sts2.Core.Localization;
using MegaCrit.Sts2.Core.Multiplayer.Game.Lobby;
using MegaCrit.Sts2.Core.Nodes.CommonUi;
using MegaCrit.Sts2.Core.Nodes.Multiplayer;
using MegaCrit.Sts2.Core.Nodes.Screens.CharacterSelect;
using MegaCrit.Sts2.Core.Nodes.Screens.CustomRun;
using MegaCrit.Sts2.Core.Nodes.Screens.DailyRun;
namespace SlayTheSpire2.LAN.Multiplayer.Services
{
internal class RunScreenService
{
private static readonly Lazy<RunScreenService> Lazy = new(() => new RunScreenService());
public static RunScreenService Instance => Lazy.Value;
public NCharacterSelectScreen? CharacterSelectScreen;
public NDailyRunScreen? DailyRunScreen;
public NCustomRunScreen? CustomRunScreen;
public NMultiplayerLoadGameScreen? MultiplayerLoadGameScreen;
public NDailyRunLoadScreen? DailyRunLoadScreen;
public NCustomRunLoadScreen? CustomRunLoadScreen;
private RunScreenService()
{
}
public static async Task<bool> ShouldAllowRunToBegin(LoadRunLobby runLobby)
{
if (runLobby.ConnectedPlayerIds.Count >= runLobby.Run.Players.Count)
return true;
var locString = new LocString("gameplay_ui", "CONFIRM_LOAD_SAVE.body");
locString.Add("MissingCount", runLobby.Run.Players.Count - runLobby.ConnectedPlayerIds.Count);
var nGenericPopup = NGenericPopup.Create();
if (nGenericPopup != null)
{
NModalContainer.Instance?.Add(nGenericPopup);
var nVerticalPopup = Traverse.Create(nGenericPopup).Field("_verticalPopup").GetValue<NVerticalPopup>();
nVerticalPopup.NoButton.SetAnchorsPreset(Control.LayoutPreset.CenterBottom);
nVerticalPopup.NoButton.OffsetLeft = -90;
nVerticalPopup.NoButton.OffsetTop = -152;
nVerticalPopup.NoButton.OffsetRight = 90;
nVerticalPopup.NoButton.OffsetBottom = -80;
nVerticalPopup.YesButton.Visible = false;
return await nGenericPopup.WaitForConfirmation(locString,
new LocString("gameplay_ui", "CONFIRM_LOAD_SAVE.header"),
new LocString("gameplay_ui", "CONFIRM_LOAD_SAVE.cancel"),
new LocString("gameplay_ui", "CONFIRM_LOAD_SAVE.confirm"));
}
return false;
}
}
}
@@ -2,20 +2,20 @@
using MegaCrit.Sts2.Core.Saves; using MegaCrit.Sts2.Core.Saves;
using SlayTheSpire2.LAN.Multiplayer.Models; using SlayTheSpire2.LAN.Multiplayer.Models;
namespace SlayTheSpire2.LAN.Multiplayer.Helpers namespace SlayTheSpire2.LAN.Multiplayer.Services
{ {
internal class SettingsHelper internal class SettingsService
{ {
private static readonly Lazy<SettingsHelper> Lazy = new(() => new SettingsHelper()); private static readonly Lazy<SettingsService> Lazy = new(() => new SettingsService());
public static SettingsHelper Instance => Lazy.Value; public static SettingsService Instance => Lazy.Value;
public readonly SettingsModel SettingsModel; public readonly SettingsModel SettingsModel;
private readonly GodotFileIo _modsDir = private readonly GodotFileIo _modsDir =
new(Path.Combine(UserDataPathProvider.GetAccountScopedBasePath(null), "mods")); new(Path.Combine(UserDataPathProvider.GetAccountScopedBasePath(null), "mods"));
private SettingsHelper() private SettingsService()
{ {
if (_modsDir.FileExists("lan_settings.json")) if (_modsDir.FileExists("lan_settings.json"))
{ {
@@ -4,8 +4,8 @@
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AssemblyVersion>1.3.0.0</AssemblyVersion> <AssemblyVersion>1.5.0.0</AssemblyVersion>
<FileVersion>1.3.0.0</FileVersion> <FileVersion>1.5.0.0</FileVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>