diff --git a/R2API.Director/DirectorAPIexternal.cs b/R2API.Director/DirectorAPIexternal.cs index 5b9fac6a..544e9da9 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 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 + { + add + { + SetHooks(); + _getCombatDirectorActivityCount += value; + } + remove + { + _getCombatDirectorActivityCount -= value; + } + } + /// /// Event used to edit . /// @@ -927,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 fdf6672e..a169cee6 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,13 @@ 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 List _allCombatDirectors = []; internal static void SetHooks() { if (_hooksEnabled) @@ -41,6 +49,12 @@ internal static void SetHooks() On.RoR2.SceneCatalog.Init += InitStageEnumToSceneDefs; + On.RoR2.DirectorCore.OnEnable += AddRunCombatDirectorsFixedUpdateComponent; + + IL.RoR2.CombatDirector.FixedUpdate += CombatDirector_FixedUpdate; + + On.RoR2.CombatDirector.Awake += CombatDirector_Awake; + _hooksEnabled = true; } @@ -51,8 +65,57 @@ internal static void UnsetHooks() On.RoR2.SceneCatalog.Init -= InitStageEnumToSceneDefs; + On.RoR2.DirectorCore.OnEnable -= AddRunCombatDirectorsFixedUpdateComponent; + + 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); + 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 (!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 || combatDirector.moneyWaves == null) 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) { @@ -719,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 de4f322c..d8b695bd 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 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 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..84862f00 --- /dev/null +++ b/R2API.Director/RunCombatDirectorsFixedUpdate.cs @@ -0,0 +1,35 @@ +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() + { + if (combatDirectors == null) return; + for (int i = 0; i < _allCombatDirectors.Count; i++) + { + 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 2a6879f4..a1a725ff 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 Combat Directors" websiteUrl = "https://github.com/risk-of-thunder/R2API" containsNsfwContent = false