From 784f6f638dc22d402f797b90383caeb45bd24a53 Mon Sep 17 00:00:00 2001 From: The Gray Alien <103865052+TheGrayAlien@users.noreply.github.com> Date: Sat, 21 Jun 2025 01:58:08 -0400 Subject: [PATCH 1/2] New rule Randomizes the turn order every round. If a piece is downed then it is set to go last. Example: { "Rule": "TurnOrderRandomized", "Config": true } --- .../HouseRulesEssentialsBase.cs | 1 + .../TurnOrderRandomizedRule.cs | 115 ++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 HouseRules.Essentials/TurnOrderRandomizedRule.cs diff --git a/HouseRules.Essentials/HouseRulesEssentialsBase.cs b/HouseRules.Essentials/HouseRulesEssentialsBase.cs index b88dce9e..4f1d671e 100644 --- a/HouseRules.Essentials/HouseRulesEssentialsBase.cs +++ b/HouseRules.Essentials/HouseRulesEssentialsBase.cs @@ -103,6 +103,7 @@ private static void RegisterRuleTypes() HR.Rulebook.Register(typeof(StatusEffectConfigRule)); HR.Rulebook.Register(typeof(TileEffectDurationOverriddenRule)); HR.Rulebook.Register(typeof(TurnOrderOverriddenRule)); + HR.Rulebook.Register(typeof(TurnOrderRandomizedRule)); } private static void RegisterRulesets() diff --git a/HouseRules.Essentials/TurnOrderRandomizedRule.cs b/HouseRules.Essentials/TurnOrderRandomizedRule.cs new file mode 100644 index 00000000..95579229 --- /dev/null +++ b/HouseRules.Essentials/TurnOrderRandomizedRule.cs @@ -0,0 +1,115 @@ +namespace HouseRules.Essentials.Rules +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Boardgame.BoardEntities; + using Boardgame.TurnOrder; + using HarmonyLib; + using HouseRules.Core.Types; + using Random = UnityEngine.Random; + + public sealed class TurnOrderRandomizedRule : Rule, IConfigWritable, IPatchable, IMultiplayerSafe + { + public override string Description => "Hero turn order is randomized each new round."; + + private static bool _isActivated; + + public TurnOrderRandomizedRule(bool value) + { + } + + public bool GetConfigObject() => true; + + protected override void OnActivate(Context context) + { + _isActivated = true; + } + + protected override void OnDeactivate(Context context) => _isActivated = false; + + private static void Patch(Harmony harmony) + { + harmony.Patch( + original: AccessTools.Constructor(typeof(RearrangePlayerTurnOrder), new[] { typeof(TurnQueue) }), + prefix: new HarmonyMethod( + typeof(TurnOrderRandomizedRule), + nameof(RearrangePlayerTurnOrder_Constructor_Prefix))); + } + + private static bool RearrangePlayerTurnOrder_Constructor_Prefix( + RearrangePlayerTurnOrder __instance, + TurnQueue turnQueue) + { + if (!_isActivated) + { + return true; + } + + var playerPieces = turnQueue.GetPlayerPieces(); + var playerScores = ComputeScores(playerPieces); + var precomputedPlayerComparison = PrecomputedPieceComparison(playerScores); + + playerPieces.Sort(precomputedPlayerComparison); + + __instance.newTurnOrder = playerPieces.Select(piece => piece.networkID).ToArray(); + Traverse.Create(__instance).Property("updateNeeded").Value = true; + return false; + } + + /// + /// Returns a list of (pieceID, score) mappings, where higher scores represent priority in the turn order. + /// + private static Dictionary ComputeScores(IEnumerable pieces) + { + return pieces.ToDictionary(piece => piece.networkID, piece => ComputeScore(piece)); + } + + /// + /// Randomizes the score for the specified piece, where a higher score represents priority in the turn order. + /// + private static int ComputeScore(Piece piece) + { + var score = Random.Range(2, 101); + if (piece.IsDowned()) + { + score = 1; + } + + return score; + } + + /// + /// Returns a comparison that references the specified (pieceID, score) mappings. + /// + private static Comparison PrecomputedPieceComparison(IReadOnlyDictionary pieceScores) + { + return (piece1, piece2) => + { + if (piece1 == piece2) + { + return 0; + } + + if (!pieceScores.TryGetValue(piece1.networkID, out var piece1Score)) + { + HouseRulesEssentialsBase.LogWarning( + $"[TurnOrderOverridden] Could not find a turn order score for piece [{piece1.networkID}]. Resulting order may be unexpected."); + return 1; + } + + if (!pieceScores.TryGetValue(piece2.networkID, out var piece2Score)) + { + HouseRulesEssentialsBase.LogWarning( + $"[TurnOrderOverridden] Could not find a turn order score for piece [{piece2.networkID}]. Resulting order may be unexpected."); + return 1; + } + + var scoreDifference = piece1Score - piece2Score; + + // Return negative score difference as a high score represents priority in the turn order, whereas a comparison represents that with negative numbers. + return -scoreDifference; + }; + } + } +} From ae3e78989fe095688d6059f11723e037283613aa Mon Sep 17 00:00:00 2001 From: The Gray Alien <103865052+TheGrayAlien@users.noreply.github.com> Date: Sat, 21 Jun 2025 02:02:16 -0400 Subject: [PATCH 2/2] Update README for new rule Add new rule to the Essentials README. --- HouseRules.Essentials/README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/HouseRules.Essentials/README.md b/HouseRules.Essentials/README.md index 56518225..e590c953 100644 --- a/HouseRules.Essentials/README.md +++ b/HouseRules.Essentials/README.md @@ -1043,3 +1043,16 @@ The [Settings Reference](../docs/SettingsReference.md) contains lists of all dif } }, ``` + +#### __TurnOrderRandomized__: Randomize hero turn order every round. + - To configure: + - Specify `true` to have the player turn order randomize every round. + + ###### _Example JSON config for TurnOrderRandomized_ + + ```json + { + "Rule": "TurnOrderRandomized", + "Config": true + }, + ```