From c68ddb3cd019b10a3d71bfb9ca6d295f0d3048b6 Mon Sep 17 00:00:00 2001 From: Brynzananas Date: Tue, 1 Sep 2026 22:09:03 +0500 Subject: [PATCH 1/3] Added enabling/disabling stage Combat Directors event. --- R2API.Director/DirectorAPIexternal.cs | 16 ++++++ R2API.Director/DirectorAPIinternal.cs | 51 +++++++++++++++++++ R2API.Director/README.md | 5 ++ .../RunCombatDirectorsFixedUpdate.cs | 28 ++++++++++ R2API.Director/thunderstore.toml | 4 +- 5 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 R2API.Director/RunCombatDirectorsFixedUpdate.cs diff --git a/R2API.Director/DirectorAPIexternal.cs b/R2API.Director/DirectorAPIexternal.cs index 5b9fac6a..ad72be32 100644 --- a/R2API.Director/DirectorAPIexternal.cs +++ b/R2API.Director/DirectorAPIexternal.cs @@ -28,6 +28,22 @@ public static partial class DirectorAPI #pragma warning restore CS0618 // Type or member is obsolete public static bool Loaded => true; + /// + /// Subscribe to this event to modify activity of stage combat directors. Fired during CombatDirector.FixedUpdate. + /// + public static event GetCombatDirectorActivityCountDelegate GetCombatDirectorActivityCount + { + add + { + SetHooks(); + _getCombatDirectorActivityCount += value; + } + remove + { + _getCombatDirectorActivityCount -= value; + } + } + /// /// Event used to edit . /// diff --git a/R2API.Director/DirectorAPIinternal.cs b/R2API.Director/DirectorAPIinternal.cs index fdf6672e..ca34b38f 100644 --- a/R2API.Director/DirectorAPIinternal.cs +++ b/R2API.Director/DirectorAPIinternal.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using HG; +using Mono.Cecil.Cil; using MonoMod.Cil; using R2API.Utils; using RoR2; @@ -18,6 +19,11 @@ public static partial class DirectorAPI private static bool _hooksEnabled = false; + public delegate void GetCombatDirectorActivityCountDelegate(CombatDirector combatDirector, ref int activityCount); + + private static event GetCombatDirectorActivityCountDelegate _getCombatDirectorActivityCount; + + internal static HashSet currentStageCombatDirectorsHashSet = []; internal static void SetHooks() { if (_hooksEnabled) @@ -41,6 +47,9 @@ internal static void SetHooks() On.RoR2.SceneCatalog.Init += InitStageEnumToSceneDefs; + On.RoR2.DirectorCore.OnEnable += AddRunCombatDirectorsFixedUpdateComponent; + + IL.RoR2.CombatDirector.FixedUpdate += CombatDirector_FixedUpdate; _hooksEnabled = true; } @@ -51,8 +60,50 @@ internal static void UnsetHooks() On.RoR2.SceneCatalog.Init -= InitStageEnumToSceneDefs; + On.RoR2.DirectorCore.OnEnable -= AddRunCombatDirectorsFixedUpdateComponent; + + IL.RoR2.CombatDirector.FixedUpdate -= CombatDirector_FixedUpdate; + _hooksEnabled = false; } + private static void CombatDirector_FixedUpdate(ILContext il) + { + ILCursor c = new ILCursor(il); + ILLabel iLLabel = null; + if (!c.TryGotoNext(MoveType.After, + x => x.MatchCall(typeof(Run).GetPropertyGetter(nameof(Run.instance))), + x => x.MatchCall("op_Implicit"), + x => x.MatchBrfalse(out iLLabel) + )) + { + DirectorPlugin.Logger.LogError(il.Method.Name + " IL Hook failed!"); + return; + } + c.Emit(OpCodes.Ldarg_0); + c.EmitDelegate(HandleCombatDirectorInactivity); + c.Emit(OpCodes.Brfalse_S, iLLabel); + } + private static bool HandleCombatDirectorInactivity(CombatDirector combatDirector) + { + if (!currentStageCombatDirectorsHashSet.Contains(combatDirector) || !combatDirector.enabled) return true; + int activityCount = 0; + _getCombatDirectorActivityCount?.Invoke(combatDirector, ref activityCount); + if (activityCount < 0) return false; + return true; + } + internal static bool HandleCombatDirectorActivity(CombatDirector combatDirector) + { + if (combatDirector.enabled) return false; + int activityCount = 0; + _getCombatDirectorActivityCount?.Invoke(combatDirector, ref activityCount); + if (activityCount > 0) return true; + return false; + } + private static void AddRunCombatDirectorsFixedUpdateComponent(On.RoR2.DirectorCore.orig_OnEnable orig, DirectorCore self) + { + orig(self); + RunCombatDirectorsFixedUpdate runCombatDirectorsFixedUdpate = self.gameObject.EnsureComponent(); + } private static void ApplyChangesOnStart(On.RoR2.ClassicStageInfo.orig_Start orig, ClassicStageInfo classicStageInfo) { diff --git a/R2API.Director/README.md b/R2API.Director/README.md index de4f322c..389659f7 100644 --- a/R2API.Director/README.md +++ b/R2API.Director/README.md @@ -16,10 +16,15 @@ Alongside this, R2API.Director also comes bundled with DirectorAPIHelpers, which Additionaly you can clone existing spawn cards and set new values for cloned spawn cards using CharacterSpawnCardClone, InteractableSpawnCardClone and MultiCharacterSpawnCardClone. +Also provides an event to easily enable/disable stage Combat Directors. + ## Related Pages ## Changelog +## `3.1.0` +* Added enabling/disabling stage Combat Directors event. + ### `3.0.1` * Fix spawn card clones not being able to be applied more than once for the dccspool in a single game session. diff --git a/R2API.Director/RunCombatDirectorsFixedUpdate.cs b/R2API.Director/RunCombatDirectorsFixedUpdate.cs new file mode 100644 index 00000000..30a4650c --- /dev/null +++ b/R2API.Director/RunCombatDirectorsFixedUpdate.cs @@ -0,0 +1,28 @@ +using RoR2; +using RoR2BepInExPack.Utilities; +using System; +using System.Collections.Generic; +using System.Text; +using UnityEngine; +using static R2API.DirectorAPI; + +namespace R2API; +public class RunCombatDirectorsFixedUpdate : MonoBehaviour +{ + public CombatDirector[] combatDirectors; + public void Awake() + { + currentStageCombatDirectorsHashSet.Clear(); + combatDirectors = GetComponents(); + if (combatDirectors == null) return; + foreach (CombatDirector combatDirector in combatDirectors) currentStageCombatDirectorsHashSet.Add(combatDirector); + } + public void FixedUpdate() + { + foreach (CombatDirector combatDirector in combatDirectors) + { + if (!combatDirector || !HandleCombatDirectorActivity(combatDirector)) continue; + combatDirector.FixedUpdate(); + } + } +} diff --git a/R2API.Director/thunderstore.toml b/R2API.Director/thunderstore.toml index 2a6879f4..1f54d333 100644 --- a/R2API.Director/thunderstore.toml +++ b/R2API.Director/thunderstore.toml @@ -5,8 +5,8 @@ schemaVersion = "0.0.1" [package] namespace = "RiskofThunder" name = "R2API_Director" -versionNumber = "3.0.1" -description = "API for easily modifiying the Director (RoR2 monster / interactable spawner) behaviour and cloning Spawn Cards" +versionNumber = "3.1.0" +description = "API for easily modifiying the Director (RoR2 monster / interactable spawner) behaviour, cloning Spawn Cards and enabling/disabling stage Combat Directors" websiteUrl = "https://github.com/risk-of-thunder/R2API" containsNsfwContent = false From 90be7072d8434fde6edfac8d5430d69c079a19b6 Mon Sep 17 00:00:00 2001 From: Brynzananas Date: Tue, 1 Sep 2026 22:32:04 +0500 Subject: [PATCH 2/3] Add combatDirectors nullcheck to RunCombatDirectorsFixedUpdate.FixedUpdate --- R2API.Director/RunCombatDirectorsFixedUpdate.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/R2API.Director/RunCombatDirectorsFixedUpdate.cs b/R2API.Director/RunCombatDirectorsFixedUpdate.cs index 30a4650c..30043f38 100644 --- a/R2API.Director/RunCombatDirectorsFixedUpdate.cs +++ b/R2API.Director/RunCombatDirectorsFixedUpdate.cs @@ -19,6 +19,7 @@ public void Awake() } public void FixedUpdate() { + if (combatDirectors == null) return; foreach (CombatDirector combatDirector in combatDirectors) { if (!combatDirector || !HandleCombatDirectorActivity(combatDirector)) continue; From 9178af6fe8450389dbeb44d0c1f3147af894a0b1 Mon Sep 17 00:00:00 2001 From: Brynzananas Date: Tue, 1 Sep 2026 23:26:44 +0500 Subject: [PATCH 3/3] make it affect all combat directors --- R2API.Director/DirectorAPIexternal.cs | 4 +++- R2API.Director/DirectorAPIinternal.cs | 20 ++++++++++++++++--- R2API.Director/README.md | 4 ++-- .../RunCombatDirectorsFixedUpdate.cs | 14 +++++++++---- R2API.Director/thunderstore.toml | 2 +- 5 files changed, 33 insertions(+), 11 deletions(-) diff --git a/R2API.Director/DirectorAPIexternal.cs b/R2API.Director/DirectorAPIexternal.cs index ad72be32..544e9da9 100644 --- a/R2API.Director/DirectorAPIexternal.cs +++ b/R2API.Director/DirectorAPIexternal.cs @@ -29,7 +29,7 @@ public static partial class DirectorAPI public static bool Loaded => true; /// - /// Subscribe to this event to modify activity of stage combat directors. Fired during CombatDirector.FixedUpdate. + /// Subscribe to this event to modify activity of combat directors. Fired during CombatDirector.FixedUpdate. If activityCount is higher than 0, Combat Director will remain running even when disabled. If activityCount is lower than 0, Combat Director will not run. /// public static event GetCombatDirectorActivityCountDelegate GetCombatDirectorActivityCount { @@ -943,4 +943,6 @@ public class MonsterFamilyHolder /// public string? SelectionChatString; } + + public static bool IsStageCombatDirector(this CombatDirector combatDirector) => IsStageCombatDirectorInternal(combatDirector); } diff --git a/R2API.Director/DirectorAPIinternal.cs b/R2API.Director/DirectorAPIinternal.cs index ca34b38f..a169cee6 100644 --- a/R2API.Director/DirectorAPIinternal.cs +++ b/R2API.Director/DirectorAPIinternal.cs @@ -23,7 +23,9 @@ public static partial class DirectorAPI private static event GetCombatDirectorActivityCountDelegate _getCombatDirectorActivityCount; - internal static HashSet currentStageCombatDirectorsHashSet = []; + internal static HashSet _currentStageCombatDirectorsHashSet = []; + + internal static List _allCombatDirectors = []; internal static void SetHooks() { if (_hooksEnabled) @@ -50,6 +52,9 @@ internal static void SetHooks() On.RoR2.DirectorCore.OnEnable += AddRunCombatDirectorsFixedUpdateComponent; IL.RoR2.CombatDirector.FixedUpdate += CombatDirector_FixedUpdate; + + On.RoR2.CombatDirector.Awake += CombatDirector_Awake; + _hooksEnabled = true; } @@ -64,8 +69,15 @@ internal static void UnsetHooks() IL.RoR2.CombatDirector.FixedUpdate -= CombatDirector_FixedUpdate; + On.RoR2.CombatDirector.Awake -= CombatDirector_Awake; + _hooksEnabled = false; } + private static void CombatDirector_Awake(On.RoR2.CombatDirector.orig_Awake orig, CombatDirector self) + { + orig(self); + _allCombatDirectors.Add(self); + } private static void CombatDirector_FixedUpdate(ILContext il) { ILCursor c = new ILCursor(il); @@ -85,7 +97,7 @@ private static void CombatDirector_FixedUpdate(ILContext il) } private static bool HandleCombatDirectorInactivity(CombatDirector combatDirector) { - if (!currentStageCombatDirectorsHashSet.Contains(combatDirector) || !combatDirector.enabled) return true; + if (!combatDirector.enabled) return true; int activityCount = 0; _getCombatDirectorActivityCount?.Invoke(combatDirector, ref activityCount); if (activityCount < 0) return false; @@ -93,7 +105,7 @@ private static bool HandleCombatDirectorInactivity(CombatDirector combatDirector } internal static bool HandleCombatDirectorActivity(CombatDirector combatDirector) { - if (combatDirector.enabled) return false; + if (combatDirector.enabled || combatDirector.moneyWaves == null) return false; int activityCount = 0; _getCombatDirectorActivityCount?.Invoke(combatDirector, ref activityCount); if (activityCount > 0) return true; @@ -770,4 +782,6 @@ private static void SetInteractableCategoryWeights(DirectorCardCategorySelection dccs.categories[i] = category; } } + + private static bool IsStageCombatDirectorInternal(CombatDirector combatDirector) => _currentStageCombatDirectorsHashSet.Contains(combatDirector); } diff --git a/R2API.Director/README.md b/R2API.Director/README.md index 389659f7..d8b695bd 100644 --- a/R2API.Director/README.md +++ b/R2API.Director/README.md @@ -16,14 +16,14 @@ Alongside this, R2API.Director also comes bundled with DirectorAPIHelpers, which Additionaly you can clone existing spawn cards and set new values for cloned spawn cards using CharacterSpawnCardClone, InteractableSpawnCardClone and MultiCharacterSpawnCardClone. -Also provides an event to easily enable/disable stage Combat Directors. +Also provides an event to easily enable/disable Combat Directors. Provides an extension method IsStageCombatDirector to check is Combat Director part of stage Combat Directors ## Related Pages ## Changelog ## `3.1.0` -* Added enabling/disabling stage Combat Directors event. +* Added enabling/disabling Combat Directors event. ### `3.0.1` * Fix spawn card clones not being able to be applied more than once for the dccspool in a single game session. diff --git a/R2API.Director/RunCombatDirectorsFixedUpdate.cs b/R2API.Director/RunCombatDirectorsFixedUpdate.cs index 30043f38..84862f00 100644 --- a/R2API.Director/RunCombatDirectorsFixedUpdate.cs +++ b/R2API.Director/RunCombatDirectorsFixedUpdate.cs @@ -12,17 +12,23 @@ public class RunCombatDirectorsFixedUpdate : MonoBehaviour public CombatDirector[] combatDirectors; public void Awake() { - currentStageCombatDirectorsHashSet.Clear(); + _currentStageCombatDirectorsHashSet.Clear(); combatDirectors = GetComponents(); if (combatDirectors == null) return; - foreach (CombatDirector combatDirector in combatDirectors) currentStageCombatDirectorsHashSet.Add(combatDirector); + foreach (CombatDirector combatDirector in combatDirectors) _currentStageCombatDirectorsHashSet.Add(combatDirector); } public void FixedUpdate() { if (combatDirectors == null) return; - foreach (CombatDirector combatDirector in combatDirectors) + for (int i = 0; i < _allCombatDirectors.Count; i++) { - if (!combatDirector || !HandleCombatDirectorActivity(combatDirector)) continue; + CombatDirector combatDirector = _allCombatDirectors[i]; + if (!combatDirector) + { + _allCombatDirectors.RemoveAt(i); + continue; + } + if (!HandleCombatDirectorActivity(combatDirector)) continue; combatDirector.FixedUpdate(); } } diff --git a/R2API.Director/thunderstore.toml b/R2API.Director/thunderstore.toml index 1f54d333..a1a725ff 100644 --- a/R2API.Director/thunderstore.toml +++ b/R2API.Director/thunderstore.toml @@ -6,7 +6,7 @@ schemaVersion = "0.0.1" namespace = "RiskofThunder" name = "R2API_Director" versionNumber = "3.1.0" -description = "API for easily modifiying the Director (RoR2 monster / interactable spawner) behaviour, cloning Spawn Cards and enabling/disabling stage Combat Directors" +description = "API for easily modifiying the Director (RoR2 monster / interactable spawner) behaviour, cloning Spawn Cards and enabling/disabling Combat Directors" websiteUrl = "https://github.com/risk-of-thunder/R2API" containsNsfwContent = false