diff --git a/HouseRules_Core/HR.cs b/HouseRules_Core/HR.cs
index 387ddaeb..9dc91779 100644
--- a/HouseRules_Core/HR.cs
+++ b/HouseRules_Core/HR.cs
@@ -18,7 +18,8 @@ public static void SelectRuleset(string ruleset)
{
if (IsRulesetActive)
{
- throw new InvalidOperationException("May not select a new ruleset while one is currently active.");
+ LifecycleDirector.DeactivateReconnect();
+ // throw new InvalidOperationException("May not select a new ruleset while one is currently active.");
}
if (Ruleset.None.Name.Equals(ruleset, StringComparison.OrdinalIgnoreCase))
diff --git a/HouseRules_Core/HouseRules_Core.csproj b/HouseRules_Core/HouseRules_Core.csproj
index bee9c864..72732aab 100644
--- a/HouseRules_Core/HouseRules_Core.csproj
+++ b/HouseRules_Core/HouseRules_Core.csproj
@@ -68,6 +68,7 @@
+
diff --git a/HouseRules_Core/LifecycleDirector.cs b/HouseRules_Core/LifecycleDirector.cs
index 87387386..67865442 100644
--- a/HouseRules_Core/LifecycleDirector.cs
+++ b/HouseRules_Core/LifecycleDirector.cs
@@ -2,27 +2,34 @@
{
using System;
using System.Linq;
- using System.Reflection;
using System.Text;
using Boardgame;
+ using Boardgame.BoardgameActions;
using Boardgame.Networking;
using HarmonyLib;
using HouseRules.Types;
+ using Photon.Pun;
using Photon.Realtime;
internal static class LifecycleDirector
{
- private const float WelcomeMessageDurationSeconds = 30f;
private const string ModdedRoomPropertyKey = "modded";
-
+ private const float WelcomeMessageDurationSeconds = 30f;
private static GameContext _gameContext;
private static bool _isCreatingGame;
private static bool _isLoadingGame;
+ private static bool _isReconnect = false;
+ private static string roomCode;
+ private static string lastCode;
internal static bool IsRulesetActive { get; private set; }
internal static void Patch(Harmony harmony)
{
+ harmony.Patch(
+ original: AccessTools.Method(typeof(GameStateMachine), "OnRoomJoined"),
+ postfix: new HarmonyMethod(typeof(LifecycleDirector), nameof(GameStateMachine_OnRoomJoined_Postfix)));
+
harmony.Patch(
original: AccessTools.Method(typeof(GameStartup), "InitializeGame"),
postfix: new HarmonyMethod(typeof(LifecycleDirector), nameof(GameStartup_InitializeGame_Postfix)));
@@ -35,6 +42,10 @@ internal static void Patch(Harmony harmony)
original: AccessTools.Method(typeof(CreatingGameState), "OnJoinedRoom"),
prefix: new HarmonyMethod(typeof(LifecycleDirector), nameof(CreatingGameState_OnJoinedRoom_Prefix)));
+ harmony.Patch(
+ original: AccessTools.Method(typeof(PlayingState), "OnMasterClientChanged"),
+ prefix: new HarmonyMethod(typeof(LifecycleDirector), nameof(PlayingGameState_OnMasterClientChanged_Prefix)));
+
harmony.Patch(
original: AccessTools.Method(typeof(GameStateMachine), "GoToPlayingState"),
postfix: new HarmonyMethod(typeof(LifecycleDirector), nameof(GameStateMachine_GoToPlayingState_Postfix)));
@@ -45,9 +56,7 @@ internal static void Patch(Harmony harmony)
harmony.Patch(
original: AccessTools.Method(typeof(PostGameControllerBase), "OnPlayAgainClicked"),
- postfix: new HarmonyMethod(
- typeof(LifecycleDirector),
- nameof(PostGameControllerBase_OnPlayAgainClicked_Postfix)));
+ postfix: new HarmonyMethod(typeof(LifecycleDirector), nameof(PostGameControllerBase_OnPlayAgainClicked_Postfix)));
harmony.Patch(
original: AccessTools.Method(typeof(GameStateMachine), "EndGame"),
@@ -55,9 +64,11 @@ internal static void Patch(Harmony harmony)
harmony.Patch(
original: AccessTools.Method(typeof(SerializableEventQueue), "DisconnectLocalPlayer"),
- prefix: new HarmonyMethod(
- typeof(LifecycleDirector),
- nameof(SerializableEventQueue_DisconnectLocalPlayer_Prefix)));
+ prefix: new HarmonyMethod(typeof(LifecycleDirector), nameof(SerializableEventQueue_DisconnectLocalPlayer_Prefix)));
+
+ harmony.Patch(
+ original: AccessTools.Method(typeof(ReconnectState), "OnClickLeaveGameAfterReconnect"),
+ postfix: new HarmonyMethod(typeof(LifecycleDirector), nameof(ReconnectState_OnClickLeaveGameAfterReconnect_Postfix)));
}
private static void GameStartup_InitializeGame_Postfix(GameStartup __instance)
@@ -66,6 +77,26 @@ private static void GameStartup_InitializeGame_Postfix(GameStartup __instance)
_gameContext = gameContext;
}
+ private static void ReconnectState_OnClickLeaveGameAfterReconnect_Postfix()
+ {
+ DeactivateReconnect();
+ }
+
+ private static void GameStateMachine_OnRoomJoined_Postfix()
+ {
+ if (!_isReconnect)
+ {
+ return;
+ }
+
+ lastCode = PhotonNetwork.CurrentRoom.Name;
+ if (lastCode != roomCode)
+ {
+ CoreMod.Logger.Warning($"Room {lastCode} doesn't match original room {roomCode}. Deactivating reconnection rules!");
+ DeactivateReconnect();
+ }
+ }
+
private static void CreatingGameState_TryCreateRoom_Prefix()
{
if (HR.SelectedRuleset == Ruleset.None)
@@ -125,10 +156,47 @@ private static void CreatingGameState_OnJoinedRoom_Prefix()
var levelSequence = Traverse.Create(_gameContext.gameStateMachine).Field("levelSequence").Value;
MotherbrainGlobalVars.CurrentConfig = levelSequence.gameConfig;
+ if (_isReconnect)
+ {
+ DeactivateReconnect();
+ }
+
+ roomCode = PhotonNetwork.CurrentRoom.Name;
+ CoreMod.Logger.Msg($"New game in room {roomCode} started");
ActivateRuleset();
OnPreGameCreated();
}
+ private static void PlayingGameState_OnMasterClientChanged_Prefix()
+ {
+ if (!_isReconnect)
+ {
+ return;
+ }
+
+ if (!GameStateMachine.IsMasterClient)
+ {
+ return;
+ }
+
+ if (HR.SelectedRuleset == Ruleset.None)
+ {
+ return;
+ }
+
+ if (_gameContext.gameStateMachine.goBackToMenuState)
+ {
+ return;
+ }
+
+ CoreMod.Logger.Warning($"<--- Resuming ruleset after disconnection from room {roomCode} --->");
+
+ ActivateRuleset();
+ OnPreGameCreated();
+ OnPostGameCreated();
+ _isReconnect = false;
+ }
+
private static void GameStateMachine_GoToPlayingState_Postfix()
{
if (!_isCreatingGame)
@@ -172,11 +240,31 @@ private static void GameStateMachine_EndGame_Prefix()
DeactivateRuleset();
}
- private static void SerializableEventQueue_DisconnectLocalPlayer_Prefix()
+ private static void SerializableEventQueue_DisconnectLocalPlayer_Prefix(BoardgameActionOnLocalPlayerDisconnect.DisconnectContext context)
{
- DeactivateRuleset();
- }
+ if (HR.SelectedRuleset == Ruleset.None)
+ {
+ return;
+ }
+
+ if (!GameStateMachine.IsMasterClient)
+ {
+ return;
+ }
+ if (context == BoardgameActionOnLocalPlayerDisconnect.DisconnectContext.ReconnectState)
+ {
+ CoreMod.Logger.Warning($"<- Disconnected from room {roomCode} ->");
+ _isReconnect = true;
+ DeactivateRuleset();
+ }
+ else
+ {
+ CoreMod.Logger.Msg($"<- MANUALLY disconnected from room {roomCode} ->");
+ _isReconnect = true;
+ DeactivateRuleset();
+ }
+ }
///
/// Add properties to the room to indicate its modded nature.
@@ -201,13 +289,12 @@ private static void AddModdedRoomProperties(RoomOptions roomOptions)
newOptions[0] = ModdedRoomPropertyKey;
roomOptions.CustomRoomPropertiesForLobby.CopyTo(newOptions, 1);
roomOptions.CustomRoomPropertiesForLobby = newOptions;
-
roomOptions.CustomRoomProperties.Add(ModdedRoomPropertyKey, true);
}
private static void ActivateRuleset()
{
- if (IsRulesetActive)
+ if (IsRulesetActive && !_isReconnect)
{
CoreMod.Logger.Warning("Ruleset activation was attempted whilst a ruleset was already activated. This should not happen. Please report this to HouseRules developers.");
return;
@@ -226,13 +313,22 @@ private static void ActivateRuleset()
IsRulesetActive = true;
- CoreMod.Logger.Msg($"Activating ruleset: {HR.SelectedRuleset.Name} (with {HR.SelectedRuleset.Rules.Count} rules)");
+ CoreMod.Logger.Warning($"Activating ruleset: {HR.SelectedRuleset.Name} (with {HR.SelectedRuleset.Rules.Count} rules)");
foreach (var rule in HR.SelectedRuleset.Rules)
{
try
{
- CoreMod.Logger.Msg($"Activating rule type: {rule.GetType()}");
- rule.OnActivate(_gameContext);
+ var isDisabled = rule is IDisableOnReconnect;
+ if (_isReconnect && isDisabled)
+ {
+ CoreMod.Logger.Msg($"Skip activating rule type: {rule.GetType()}");
+ continue;
+ }
+ else
+ {
+ CoreMod.Logger.Msg($"Activating rule type: {rule.GetType()}");
+ rule.OnActivate(_gameContext);
+ }
}
catch (Exception e)
{
@@ -249,15 +345,27 @@ private static void DeactivateRuleset()
return;
}
- IsRulesetActive = false;
+ if (!_isReconnect)
+ {
+ IsRulesetActive = false;
+ }
CoreMod.Logger.Msg($"Deactivating ruleset: {HR.SelectedRuleset.Name} (with {HR.SelectedRuleset.Rules.Count} rules)");
foreach (var rule in HR.SelectedRuleset.Rules)
{
try
{
- CoreMod.Logger.Msg($"Deactivating rule type: {rule.GetType()}");
- rule.OnDeactivate(_gameContext);
+ var isDisabled = rule is IDisableOnReconnect;
+ if (_isReconnect && isDisabled)
+ {
+ CoreMod.Logger.Msg($"Skip deactivating rule type: {rule.GetType()}");
+ continue;
+ }
+ else
+ {
+ CoreMod.Logger.Msg($"Deactivating rule type: {rule.GetType()}");
+ rule.OnDeactivate(_gameContext);
+ }
}
catch (Exception e)
{
@@ -267,6 +375,32 @@ private static void DeactivateRuleset()
}
}
+ public static void DeactivateReconnect()
+ {
+ _isReconnect = false;
+ IsRulesetActive = false;
+
+ CoreMod.Logger.Warning($"Deactivating reconnection: {HR.SelectedRuleset.Name} (with {HR.SelectedRuleset.Rules.Count} rules)");
+
+ foreach (var rule in HR.SelectedRuleset.Rules)
+ {
+ try
+ {
+ var isDisabled = rule is IDisableOnReconnect;
+ if (isDisabled)
+ {
+ CoreMod.Logger.Msg($"Deactivating reconnection for rule type: {rule.GetType()}");
+ rule.OnDeactivate(_gameContext);
+ }
+ }
+ catch (Exception e)
+ {
+ // TODO(orendain): Consider rolling back or disable rule.
+ CoreMod.Logger.Warning($"Failed to deactivate reconnection for rule [{rule.GetType()}]: {e}");
+ }
+ }
+ }
+
private static void OnPreGameCreated()
{
if (HR.SelectedRuleset == Ruleset.None)
@@ -283,8 +417,17 @@ private static void OnPreGameCreated()
{
try
{
- CoreMod.Logger.Msg($"Calling OnPreGameCreated for rule type: {rule.GetType()}");
- rule.OnPreGameCreated(_gameContext);
+ var isDisabled = rule is IDisableOnReconnect;
+ if (_isReconnect && isDisabled)
+ {
+ CoreMod.Logger.Msg($"Skip OnPreGameCreated for rule type: {rule.GetType()}");
+ continue;
+ }
+ else
+ {
+ CoreMod.Logger.Msg($"Calling OnPreGameCreated for rule type: {rule.GetType()}");
+ rule.OnPreGameCreated(_gameContext);
+ }
}
catch (Exception e)
{
@@ -310,8 +453,17 @@ private static void OnPostGameCreated()
{
try
{
- CoreMod.Logger.Msg($"Calling OnPostGameCreated for rule type: {rule.GetType()}");
- rule.OnPostGameCreated(_gameContext);
+ var isDisabled = rule is IDisableOnReconnect;
+ if (_isReconnect && isDisabled)
+ {
+ CoreMod.Logger.Msg($"Skip OnPostGameCreated for rule type: {rule.GetType()}");
+ continue;
+ }
+ else
+ {
+ CoreMod.Logger.Msg($"Calling OnPostGameCreated for rule type: {rule.GetType()}");
+ rule.OnPostGameCreated(_gameContext);
+ }
}
catch (Exception e)
{
diff --git a/HouseRules_Core/Types/IDisableOnReconnect.cs b/HouseRules_Core/Types/IDisableOnReconnect.cs
new file mode 100644
index 00000000..b714f086
--- /dev/null
+++ b/HouseRules_Core/Types/IDisableOnReconnect.cs
@@ -0,0 +1,9 @@
+namespace HouseRules.Types
+{
+ ///
+ /// Represents a rule that is not safe to apply after a disconnect in a multiplayer environment.
+ ///
+ public interface IDisableOnReconnect
+ {
+ }
+}
diff --git a/HouseRules_Essentials/Rules/LevelSequenceOverriddenRule.cs b/HouseRules_Essentials/Rules/LevelSequenceOverriddenRule.cs
index c3abc8c6..5481b5ef 100644
--- a/HouseRules_Essentials/Rules/LevelSequenceOverriddenRule.cs
+++ b/HouseRules_Essentials/Rules/LevelSequenceOverriddenRule.cs
@@ -8,7 +8,7 @@
using HarmonyLib;
using HouseRules.Types;
- public sealed class LevelSequenceOverriddenRule : Rule, IConfigWritable>, IPatchable, IMultiplayerSafe
+ public sealed class LevelSequenceOverriddenRule : Rule, IConfigWritable>, IPatchable, IMultiplayerSafe, IDisableOnReconnect
{
public override string Description => "LevelSequence is overridden";
diff --git a/HouseRules_Essentials/Rules/PieceAbilityListOverriddenRule.cs b/HouseRules_Essentials/Rules/PieceAbilityListOverriddenRule.cs
index 4c19d9dc..3b3f7f71 100644
--- a/HouseRules_Essentials/Rules/PieceAbilityListOverriddenRule.cs
+++ b/HouseRules_Essentials/Rules/PieceAbilityListOverriddenRule.cs
@@ -9,7 +9,7 @@
using HouseRules.Types;
public sealed class PieceAbilityListOverriddenRule : Rule,
- IConfigWritable>>, IMultiplayerSafe
+ IConfigWritable>>, IMultiplayerSafe, IDisableOnReconnect
{
public override string Description => "Piece abilities are adjusted";
diff --git a/HouseRules_Essentials/Rules/PieceBehavioursListOverriddenRule.cs b/HouseRules_Essentials/Rules/PieceBehavioursListOverriddenRule.cs
index c2c6bfe7..5df2c2cd 100644
--- a/HouseRules_Essentials/Rules/PieceBehavioursListOverriddenRule.cs
+++ b/HouseRules_Essentials/Rules/PieceBehavioursListOverriddenRule.cs
@@ -10,7 +10,7 @@
using Behaviour = DataKeys.Behaviour;
public sealed class PieceBehavioursListOverriddenRule : Rule,
- IConfigWritable>>, IMultiplayerSafe
+ IConfigWritable>>, IMultiplayerSafe, IDisableOnReconnect
{
public override string Description => "Piece behaviours are adjusted";
diff --git a/HouseRules_Essentials/Rules/PieceConfigAdjustedRule.cs b/HouseRules_Essentials/Rules/PieceConfigAdjustedRule.cs
index 6d81299a..3f361c84 100644
--- a/HouseRules_Essentials/Rules/PieceConfigAdjustedRule.cs
+++ b/HouseRules_Essentials/Rules/PieceConfigAdjustedRule.cs
@@ -9,7 +9,7 @@
using HouseRules.Types;
public sealed class PieceConfigAdjustedRule : Rule, IConfigWritable>,
- IMultiplayerSafe
+ IMultiplayerSafe, IDisableOnReconnect
{
public override string Description => "Piece configuration is adjusted";
diff --git a/HouseRules_Essentials/Rules/PieceImmunityListAdjustedRule.cs b/HouseRules_Essentials/Rules/PieceImmunityListAdjustedRule.cs
index 1c42dd68..6b065465 100644
--- a/HouseRules_Essentials/Rules/PieceImmunityListAdjustedRule.cs
+++ b/HouseRules_Essentials/Rules/PieceImmunityListAdjustedRule.cs
@@ -9,7 +9,7 @@
using HouseRules.Types;
public sealed class PieceImmunityListAdjustedRule : Rule,
- IConfigWritable>>, IMultiplayerSafe
+ IConfigWritable>>, IMultiplayerSafe, IDisableOnReconnect
{
public override string Description => "Piece immunities are adjusted";
@@ -21,7 +21,7 @@ public sealed class PieceImmunityListAdjustedRule : Rule,
///
/// Initializes a new instance of the class.
///
- /// Dict of piece name and List
+ /// Dict of piece name and List.
/// Replaces original settings with new list.
public PieceImmunityListAdjustedRule(Dictionary> adjustments)
{
diff --git a/HouseRules_Essentials/Rules/PiecePieceTypeListOverriddenRule.cs b/HouseRules_Essentials/Rules/PiecePieceTypeListOverriddenRule.cs
index 46ace698..d918e38b 100644
--- a/HouseRules_Essentials/Rules/PiecePieceTypeListOverriddenRule.cs
+++ b/HouseRules_Essentials/Rules/PiecePieceTypeListOverriddenRule.cs
@@ -9,7 +9,7 @@
using HouseRules.Types;
public sealed class PiecePieceTypeListOverriddenRule : Rule,
- IConfigWritable>>, IMultiplayerSafe
+ IConfigWritable>>, IMultiplayerSafe, IDisableOnReconnect
{
public override string Description => "Piece piece types are adjusted";
diff --git a/HouseRules_Essentials/Rules/PieceUseWhenKilledOverriddenRule.cs b/HouseRules_Essentials/Rules/PieceUseWhenKilledOverriddenRule.cs
index 4e1e259c..6dbdb4e0 100644
--- a/HouseRules_Essentials/Rules/PieceUseWhenKilledOverriddenRule.cs
+++ b/HouseRules_Essentials/Rules/PieceUseWhenKilledOverriddenRule.cs
@@ -9,7 +9,7 @@
using HouseRules.Types;
public sealed class PieceUseWhenKilledOverriddenRule : Rule,
- IConfigWritable>>, IMultiplayerSafe
+ IConfigWritable>>, IMultiplayerSafe, IDisableOnReconnect
{
public override string Description => "Piece UseWhenKilled lists are overridden";
diff --git a/HouseRules_Essentials/Rules/StartCardsModifiedRule.cs b/HouseRules_Essentials/Rules/StartCardsModifiedRule.cs
index cf369478..4c461a73 100644
--- a/HouseRules_Essentials/Rules/StartCardsModifiedRule.cs
+++ b/HouseRules_Essentials/Rules/StartCardsModifiedRule.cs
@@ -9,7 +9,8 @@
using HarmonyLib;
using HouseRules.Types;
- public sealed class StartCardsModifiedRule : Rule, IConfigWritable>>, IPatchable, IMultiplayerSafe
+ public sealed class StartCardsModifiedRule : Rule, IConfigWritable>>,
+ IPatchable, IMultiplayerSafe, IDisableOnReconnect
{
public override string Description => "Hero start cards are modified";