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.Runs;
using MegaCrit.Sts2.Core.Saves;
using SlayTheSpire2.LAN.Multiplayer.Services;
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedType.Global
@@ -138,7 +139,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Helpers
ushort port, int maxPlayers)
{
var readSaveResult =
LanRunSaveManagerHelper.LoadAndCanonicalizeMultiplayerRunSave(
LanRunSaveManagerService.Instance.LoadAndCanonicalizeMultiplayerRunSave(
PlatformUtil.GetLocalPlayerId(PlatformType.None));
if (!readSaveResult.Success || readSaveResult.SaveData == null)
{
@@ -177,7 +178,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Helpers
return;
var readSaveResult =
LanRunSaveManagerHelper.LoadAndCanonicalizeMultiplayerRunSave(
LanRunSaveManagerService.Instance.LoadAndCanonicalizeMultiplayerRunSave(
PlatformUtil.GetLocalPlayerId(PlatformType.None));
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...");
}
LanRunSaveManagerHelper.DeleteCurrentMultiplayerRun();
LanRunSaveManagerService.Instance.DeleteCurrentMultiplayerRun();
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 LanPlayerNames playerNameDictionary;
public PlayerNames playerNames;
public bool ShouldBroadcast => false;
public NetTransferMode Mode => NetTransferMode.Reliable;
@@ -14,8 +14,8 @@ namespace SlayTheSpire2.LAN.Multiplayer.Models
public void Serialize(PacketWriter writer)
{
writer.WriteInt(playerNameDictionary.Count);
foreach (var keyValue in playerNameDictionary)
writer.WriteInt(playerNames.Count);
foreach (var keyValue in playerNames)
{
writer.WriteULong(keyValue.Key);
writer.WriteString(keyValue.Value);
@@ -25,10 +25,10 @@ namespace SlayTheSpire2.LAN.Multiplayer.Models
public void Deserialize(PacketReader reader)
{
var count = reader.ReadInt();
playerNameDictionary = new LanPlayerNames();
playerNames = new PlayerNames();
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.Game;
using MegaCrit.Sts2.Core.Multiplayer.Messages.Lobby;
using SlayTheSpire2.LAN.Multiplayer.Helpers;
using SlayTheSpire2.LAN.Multiplayer.Models;
using SlayTheSpire2.LAN.Multiplayer.Services;
// ReSharper disable UnusedMember.Global
// ReSharper disable UnusedType.Global
@@ -27,10 +27,12 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs
if (joinFlow.NetService == null)
return result;
joinFlow.NetService.RegisterMessageHandler<LanPlayerNameResponseMessage>(LanPlayerNameHelper
var lanPlayerNameService = LanPlayerNameService.Instance;
joinFlow.NetService.RegisterMessageHandler<LanPlayerNameResponseMessage>(lanPlayerNameService
.HandleLanPlayerNameResponseMessage);
await LanPlayerNameHelper.AttemptPlayerName(joinFlow.NetService);
await lanPlayerNameService.AttemptPlayerName(joinFlow.NetService);
return result;
}
@@ -52,10 +54,12 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs
if (joinFlow.NetService == null)
return result;
joinFlow.NetService.RegisterMessageHandler<LanPlayerNameResponseMessage>(LanPlayerNameHelper
var lanPlayerNameService = LanPlayerNameService.Instance;
joinFlow.NetService.RegisterMessageHandler<LanPlayerNameResponseMessage>(lanPlayerNameService
.HandleLanPlayerNameResponseMessage);
await LanPlayerNameHelper.AttemptPlayerName(joinFlow.NetService);
await lanPlayerNameService.AttemptPlayerName(joinFlow.NetService);
return result;
}
@@ -77,10 +81,12 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs
if (joinFlow.NetService == null)
return result;
joinFlow.NetService.RegisterMessageHandler<LanPlayerNameResponseMessage>(LanPlayerNameHelper
var lanPlayerNameService = LanPlayerNameService.Instance;
joinFlow.NetService.RegisterMessageHandler<LanPlayerNameResponseMessage>(lanPlayerNameService
.HandleLanPlayerNameResponseMessage);
await LanPlayerNameHelper.AttemptPlayerName(joinFlow.NetService);
await lanPlayerNameService.AttemptPlayerName(joinFlow.NetService);
return result;
}
@@ -95,7 +101,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs
new ClientConnectionFailedException(
$"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 })
{
@@ -109,7 +115,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs
{
private static void Postfix()
{
var lanPlayerNameCompletion = LanPlayerNameHelper.LanPlayerNameCompletion;
var lanPlayerNameCompletion = LanPlayerNameService.Instance.LanPlayerNameCompletion;
if (lanPlayerNameCompletion?.Task is { IsCompleted: false })
{
@@ -1,6 +1,6 @@
using HarmonyLib;
using MegaCrit.Sts2.Core.Platform.Null;
using SlayTheSpire2.LAN.Multiplayer.Helpers;
using SlayTheSpire2.LAN.Multiplayer.Services;
// ReSharper disable UnusedMember.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;
return false;
@@ -5,8 +5,8 @@ using MegaCrit.Sts2.Core.Multiplayer.Game.Lobby;
using MegaCrit.Sts2.Core.Platform;
using MegaCrit.Sts2.Core.Runs;
using MegaCrit.Sts2.Core.Saves;
using SlayTheSpire2.LAN.Multiplayer.Helpers;
using SlayTheSpire2.LAN.Multiplayer.Models;
using SlayTheSpire2.LAN.Multiplayer.Services;
// ReSharper disable UnusedMember.Global
// ReSharper disable UnusedType.Global
@@ -34,14 +34,16 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs
{
if (netService.Platform == PlatformType.None)
{
LanPlayerNameHelper.NetService = netService;
var lanPlayerNameService = LanPlayerNameService.Instance;
netService.RegisterMessageHandler<LanPlayerNameResponseMessage>(LanPlayerNameHelper
lanPlayerNameService.NetService = netService;
netService.RegisterMessageHandler<LanPlayerNameResponseMessage>(lanPlayerNameService
.HandleLanPlayerNameResponseMessage);
if (netService.Type == NetGameType.Host)
{
netService.RegisterMessageHandler<LanPlayerNameRequestMessage>(LanPlayerNameHelper
netService.RegisterMessageHandler<LanPlayerNameRequestMessage>(lanPlayerNameService
.HandleLanPlayerNameRequestMessage);
}
}
@@ -53,30 +55,57 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs
{
private static IEnumerable<MethodBase> TargetMethods()
{
yield return typeof(StartRunLobby).GetMethod("CleanUp", BindingFlags.Instance | BindingFlags.Public)!;
yield return typeof(RunLobby).GetMethod("Dispose", BindingFlags.Instance | BindingFlags.Public)!;
yield return typeof(LoadRunLobby).GetMethod("CleanUp", BindingFlags.Instance | BindingFlags.Public)!;
const BindingFlags flags = 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
? Traverse.Create(__instance).Property("NetService").GetValue<INetGameService>()
: Traverse.Create(__instance).Field("_netService").GetValue<INetGameService>();
var netService = Traverse.Create(__instance).Property("NetService").GetValue<INetGameService>();
if (netService.Platform == PlatformType.None)
{
LanPlayerNameHelper.SetDefaultPlayerNameDictionary();
var lanPlayerNameService = LanPlayerNameService.Instance;
netService.UnregisterMessageHandler<LanPlayerNameResponseMessage>(LanPlayerNameHelper
if (disconnectSession)
{
lanPlayerNameService.SetDefaultPlayerNames();
}
netService.UnregisterMessageHandler<LanPlayerNameResponseMessage>(lanPlayerNameService
.HandleLanPlayerNameResponseMessage);
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);
}
}
}
}
}
}
@@ -1,6 +1,6 @@
using HarmonyLib;
using MegaCrit.Sts2.Core.Runs;
using SlayTheSpire2.LAN.Multiplayer.Helpers;
using SlayTheSpire2.LAN.Multiplayer.Services;
// ReSharper disable UnusedMember.Global
// ReSharper disable UnusedType.Global
@@ -12,8 +12,8 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs
{
private static void Postfix()
{
LanMapDrawingsHelper.DisableDrawingHashSet.Clear();
LanPlayerNameHelper.SetDefaultPlayerNameDictionary();
LanMapDrawingsService.Instance.DisableDrawingHashSet.Clear();
LanPlayerNameService.Instance.SetDefaultPlayerNames();
}
}
}
@@ -7,8 +7,8 @@ using MegaCrit.Sts2.Core.Rooms;
using MegaCrit.Sts2.Core.Runs;
using MegaCrit.Sts2.Core.Saves;
using MegaCrit.Sts2.Core.Saves.Managers;
using SlayTheSpire2.LAN.Multiplayer.Helpers;
using SlayTheSpire2.LAN.Multiplayer.Models;
using SlayTheSpire2.LAN.Multiplayer.Services;
// ReSharper disable UnusedMember.Global
// ReSharper disable UnusedType.Global
@@ -42,7 +42,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs
var savePath = isMultiplayer
? isNonePlatform
? LanRunSaveManagerHelper.CurrentMultiplayerRunSavePath
? LanRunSaveManagerService.Instance.CurrentMultiplayerRunSavePath
: Traverse.Create(runSaveManager).Property("CurrentMultiplayerRunSavePath").GetValue<string>()
: Traverse.Create(runSaveManager).Property("CurrentRunSavePath").GetValue<string>();
using var stream = new MemoryStream();
@@ -62,20 +62,22 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs
if (isMultiplayer && isNonePlatform)
{
var lanPlayerNameService = LanPlayerNameService.Instance;
using var playerNamesStream = new MemoryStream();
if (!forceSynchronous)
{
await JsonSerializer.SerializeAsync(playerNamesStream, LanPlayerNameHelper.PlayerNameDictionary,
LanPlayerNamesContext.Default.LanPlayerNames, CancellationToken.None);
await JsonSerializer.SerializeAsync(playerNamesStream, lanPlayerNameService.PlayerNames,
PlayerNamesContext.Default.PlayerNames, CancellationToken.None);
}
else
{
await JsonSerializer.SerializeAsync(playerNamesStream, LanPlayerNameHelper.PlayerNameDictionary,
LanPlayerNamesContext.Default.LanPlayerNames);
await JsonSerializer.SerializeAsync(playerNamesStream, lanPlayerNameService.PlayerNames,
PlayerNamesContext.Default.PlayerNames);
}
playerNamesStream.Seek(0L, SeekOrigin.Begin);
await saveStore.WriteFileAsync(LanRunSaveManagerHelper.CurrentMultiplayerRunPlayerNamesPath,
await saveStore.WriteFileAsync(LanRunSaveManagerService.Instance.CurrentMultiplayerRunPlayerNamesPath,
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.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;
}
}
}
}
}
@@ -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 SlayTheSpire2.LAN.Multiplayer.Models;
// ReSharper disable MemberCanBeMadeStatic.Local
// ReSharper disable MemberCanBePrivate.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 =>
Traverse.Create(SaveManager.Instance).Field("_saveStore").GetValue<ISaveStore>();
private static readonly Lazy<LanRunSaveManagerService> Lazy = new(() => new LanRunSaveManagerService());
public static MigrationManager MigrationManager => Traverse.Create(SaveManager.Instance)
.Field("_migrationManager").GetValue<MigrationManager>();
public static LanRunSaveManagerService Instance => Lazy.Value;
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");
public static string CurrentMultiplayerRunPlayerNamesPath =>
public string CurrentMultiplayerRunPlayerNamesPath =>
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();
if (readSaveResult is { Success: true, SaveData: not null })
{
try
{
var lanPlayerNameService = LanPlayerNameService.Instance;
var data = RunManager.CanonicalizeSave(readSaveResult.SaveData, localPlayerId);
var playerNamesJson = SaveStore.ReadFile(CurrentMultiplayerRunPlayerNamesPath);
if (!string.IsNullOrEmpty(playerNamesJson))
{
LanPlayerNameHelper.PlayerNameDictionary =
JsonSerializer.Deserialize<LanPlayerNames>(playerNamesJson) ?? new LanPlayerNames();
lanPlayerNameService.PlayerNames = JsonSerializer.Deserialize<PlayerNames>(playerNamesJson) ??
new PlayerNames();
}
LanPlayerNameHelper.SetHostPlayerName();
lanPlayerNameService.SetHostPlayerName();
return new ReadSaveResult<SerializableRun>(data, ReadSaveStatus.Success);
}
catch (Exception value)
@@ -60,7 +70,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Helpers
return readSaveResult;
}
public static ReadSaveResult<SerializableRun> LoadMultiplayerRunSave()
public ReadSaveResult<SerializableRun> LoadMultiplayerRunSave()
{
var readSaveResult = MigrationManager.LoadSave<SerializableRun>(CurrentMultiplayerRunSavePath);
if (readSaveResult.Success)
@@ -86,13 +96,13 @@ namespace SlayTheSpire2.LAN.Multiplayer.Helpers
return readSaveResult;
}
public static void DeleteCurrentMultiplayerRun()
public void DeleteCurrentMultiplayerRun()
{
SaveStore.DeleteFile(CurrentMultiplayerRunSavePath);
SaveStore.DeleteFile(CurrentMultiplayerRunPlayerNamesPath);
}
public static void RenameBrokenMultiplayerRunSave(ReadSaveStatus status)
public void RenameBrokenMultiplayerRunSave(ReadSaveStatus status)
{
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 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;
private readonly GodotFileIo _modsDir =
new(Path.Combine(UserDataPathProvider.GetAccountScopedBasePath(null), "mods"));
private SettingsHelper()
private SettingsService()
{
if (_modsDir.FileExists("lan_settings.json"))
{
@@ -4,8 +4,8 @@
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyVersion>1.3.0.0</AssemblyVersion>
<FileVersion>1.3.0.0</FileVersion>
<AssemblyVersion>1.5.0.0</AssemblyVersion>
<FileVersion>1.5.0.0</FileVersion>
</PropertyGroup>
<ItemGroup>