Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of PersistentRaids v1.0.10
Cjayride.PersistentRaids.dll
Decompiled 2 weeks agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: TargetFramework(".NETFramework,Version=v4.8.1", FrameworkDisplayName = ".NET Framework 4.8.1")] [assembly: AssemblyCompany("Cjayride.PersistentRaids")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.10.0")] [assembly: AssemblyInformationalVersion("1.0.10+326a9db0625175261219567e320b756790f4e2d4")] [assembly: AssemblyProduct("Cjayride.PersistentRaids")] [assembly: AssemblyTitle("Cjayride.PersistentRaids")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.10.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace Cjayride.PersistentRaids { internal static class CapService { private static readonly Dictionary<int, bool> CountablePrefabs = new Dictionary<int, bool>(); private static readonly HashSet<ZDOID> Tracked = new HashSet<ZDOID>(); private static readonly List<ZDO> RaidBuffer = new List<ZDO>(); private static readonly List<ZDO> NearBuffer = new List<ZDO>(); private static readonly List<ZDOID> DeadIds = new List<ZDOID>(); private static readonly List<Vector3> PlayerBuffer = new List<Vector3>(); private static string _filterKey; private static bool _didWorldScan; internal static void Track(ZDO zdo) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) if (zdo != null) { Tracked.Add(zdo.m_uid); } } internal static void EnforceCap() { //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) if (!ModConfig.EnableMonsterCap.Value || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || ZDOMan.instance == null) { return; } List<Vector3> players = PlayerPositions(); List<ZDO> list = RaidZdos(); ModConfig.CapScopeMode value = ModConfig.CapScope.Value; if (value != ModConfig.CapScopeMode.ServerWide) { float num = Mathf.Max(1f, ModConfig.PlayerAreaRadius.Value); float num2 = num * num; int num3 = Mathf.Max(0, ModConfig.MaxPerPlayerArea.Value); for (int i = 0; i < players.Count; i++) { Vector3 player = players[i]; NearBuffer.Clear(); for (int j = 0; j < list.Count; j++) { Vector3 val = list[j].GetPosition() - player; if (((Vector3)(ref val)).sqrMagnitude <= num2) { NearBuffer.Add(list[j]); } } if (NearBuffer.Count > num3) { NearBuffer.Sort(delegate(ZDO a, ZDO b) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) Vector3 val2 = b.GetPosition() - player; float sqrMagnitude = ((Vector3)(ref val2)).sqrMagnitude; val2 = a.GetPosition() - player; return sqrMagnitude.CompareTo(((Vector3)(ref val2)).sqrMagnitude); }); RemoveExtras(NearBuffer, NearBuffer.Count - num3, list, "within " + num + " m of a player (cap " + num3 + ")"); } } } if (value == ModConfig.CapScopeMode.PerPlayerArea) { return; } int num4 = Mathf.Max(0, ModConfig.MaxActiveMonsters.Value); if (list.Count > num4) { list.Sort((ZDO a, ZDO b) => NearestPlayer(b.GetPosition(), players).CompareTo(NearestPlayer(a.GetPosition(), players))); RemoveExtras(list, list.Count - num4, list, "on the server (cap " + num4 + ")"); } } private static void RemoveExtras(List<ZDO> ordered, int extra, List<ZDO> raid, string where) { //IL_007f: Unknown result type (might be due to invalid IL or missing references) if (ModConfig.LogCapEvents.Value) { Plugin.Log.LogInfo((object)("Raid creature cap hit: " + ordered.Count + " raid creatures " + where + ".")); } if (ModConfig.CapBehavior.Value != 0) { long sessionID = ZDOMan.GetSessionID(); int num = 0; for (int i = 0; i < extra && i < ordered.Count; i++) { ZDO val = ordered[i]; val.SetOwner(sessionID); Tracked.Remove(val.m_uid); ZDOMan.instance.DestroyZDO(val); raid.Remove(val); num++; } if (ModConfig.LogCapEvents.Value) { Plugin.Log.LogInfo((object)("Removed " + num + " raid creatures, furthest first.")); } } } private static List<ZDO> RaidZdos() { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) RefreshFilterCache(); RaidBuffer.Clear(); DeadIds.Clear(); if (!_didWorldScan || Tracked.Count == 0) { ScanWorld(); _didWorldScan = true; return RaidBuffer; } foreach (ZDOID item in Tracked) { ZDO zDO = ZDOMan.instance.GetZDO(item); if (zDO == null) { DeadIds.Add(item); } else if (!IsCountable(zDO.GetPrefab()) || !zDO.GetBool(ZDOVars.s_eventCreature, false) || zDO.GetBool(ZDOVars.s_tamed, false)) { DeadIds.Add(item); } else { RaidBuffer.Add(zDO); } } for (int i = 0; i < DeadIds.Count; i++) { Tracked.Remove(DeadIds[i]); } return RaidBuffer; } private static void ScanWorld() { //IL_005b: Unknown result type (might be due to invalid IL or missing references) Tracked.Clear(); foreach (ZDO value in ZDOMan.instance.m_objectsByID.Values) { if (value != null && IsCountable(value.GetPrefab()) && value.GetBool(ZDOVars.s_eventCreature, false) && !value.GetBool(ZDOVars.s_tamed, false)) { Tracked.Add(value.m_uid); RaidBuffer.Add(value); } } } private static bool IsCountable(int prefabHash) { if (CountablePrefabs.TryGetValue(prefabHash, out var value)) { return value; } value = false; GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(prefabHash) : null); if ((Object)(object)val != (Object)null && (Object)(object)val.GetComponent<MonsterAI>() != (Object)null) { Character component = val.GetComponent<Character>(); value = (Object)(object)component != (Object)null && !component.m_boss && CreatureRules.PassesFilter(component); } CountablePrefabs[prefabHash] = value; return value; } private static void RefreshFilterCache() { string text = ModConfig.CreatureWhitelist.Value + "|" + ModConfig.CreatureBlacklist.Value; if (text != _filterKey) { _filterKey = text; CountablePrefabs.Clear(); } } private static List<Vector3> PlayerPositions() { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) PlayerBuffer.Clear(); foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer != null && peer.IsReady()) { PlayerBuffer.Add(peer.m_refPos); } } if ((Object)(object)Player.m_localPlayer != (Object)null) { PlayerBuffer.Add(((Component)Player.m_localPlayer).transform.position); } return PlayerBuffer; } private static float NearestPlayer(Vector3 position, List<Vector3> players) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) float num = float.MaxValue; for (int i = 0; i < players.Count; i++) { Vector3 val = players[i] - position; float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude; if (sqrMagnitude < num) { num = sqrMagnitude; } } return num; } internal static int Clear(bool eventOnly, bool includeBosses, string prefabFilter) { int num = 0; List<Character> list = new List<Character>(Character.GetAllCharacters()); for (int i = 0; i < list.Count; i++) { Character val = list[i]; if (!CreatureRules.IsEnemy(val) || val.IsTamed() || (!includeBosses && val.IsBoss()) || (!string.IsNullOrEmpty(prefabFilter) && !CreatureRules.PrefabName(val).Equals(prefabFilter, StringComparison.OrdinalIgnoreCase))) { continue; } if (eventOnly) { MonsterAI component = ((Component)val).GetComponent<MonsterAI>(); if ((Object)(object)component == (Object)null || !component.IsEventCreature()) { continue; } } ZNetView component2 = ((Component)val).GetComponent<ZNetView>(); if ((Object)(object)component2 != (Object)null && component2.IsValid()) { component2.Destroy(); num++; } } return num; } } public class ConfigurationManagerAttributes { public bool? IsAdminOnly; } internal static class CreatureRules { internal sealed class RaidChaseApplied : MonoBehaviour { } internal sealed class RaidState : MonoBehaviour { internal int filterRevision = -1; internal bool keepHunt; } private static int _rulesFrame = -1; private static bool _rulesCache; private static string _whiteRaw; private static string _blackRaw; private static HashSet<string> _white = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private static HashSet<string> _black = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private static int _filterRevision; internal static int FilterRevision => _filterRevision; internal static bool PlayersOnline() { if ((Object)(object)ZNet.instance == (Object)null) { return false; } if (ZNet.instance.IsServer()) { if (ZNet.instance.GetPeers().Count <= 0) { return (Object)(object)Player.m_localPlayer != (Object)null; } return true; } return (Object)(object)Player.m_localPlayer != (Object)null; } internal static bool RulesActive() { int frameCount = Time.frameCount; if (frameCount == _rulesFrame) { return _rulesCache; } _rulesFrame = frameCount; if (ModConfig.Enabled == null || !ModConfig.Enabled.Value) { _rulesCache = false; return false; } if (ModConfig.OnlyApplyWhilePlayersOnline.Value && !PlayersOnline()) { _rulesCache = false; return false; } _rulesCache = true; return true; } internal static void RefreshFilters() { string value = ModConfig.CreatureWhitelist.Value; string value2 = ModConfig.CreatureBlacklist.Value; if (!(value == _whiteRaw) || !(value2 == _blackRaw)) { _whiteRaw = value; _blackRaw = value2; _filterRevision++; FillSet(_white, value); FillSet(_black, value2); } } private static void FillSet(HashSet<string> set, string raw) { set.Clear(); if (string.IsNullOrWhiteSpace(raw)) { return; } string[] array = raw.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length > 0) { set.Add(text); } } } internal static string PrefabName(Character character) { string name = ((Object)((Component)character).gameObject).name; int num = name.IndexOf("(Clone)", StringComparison.Ordinal); if (num <= 0) { return name; } return name.Substring(0, num); } internal static bool PassesFilter(Character character) { if ((Object)(object)character == (Object)null || character.IsPlayer()) { return false; } RefreshFilters(); string item = PrefabName(character); if (_black.Count > 0 && _black.Contains(item)) { return false; } if (_white.Count == 0) { return true; } return _white.Contains(item); } internal static bool IsEnemy(Character character) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Invalid comparison between Unknown and I4 if ((Object)(object)character == (Object)null || character.IsPlayer()) { return false; } Faction faction = character.GetFaction(); if ((int)faction != 0) { return (int)faction != 10; } return false; } internal static bool ShouldPersist(Character character, MonsterAI monster) { if (!RulesActive() || !PassesFilter(character) || !IsEnemy(character)) { return false; } if (character.IsBoss()) { return ModConfig.NeverDespawnBosses.Value; } if (ModConfig.NeverDespawnMonsters.Value) { return IsRaidCreature(monster); } return false; } internal static void ApplyPersistence(MonsterAI monster) { if ((Object)(object)monster == (Object)null) { return; } Character character = ((BaseAI)monster).m_character; if ((Object)(object)character == (Object)null || !ShouldPersist(character, monster)) { return; } ZNetView nview = ((BaseAI)monster).m_nview; if (!((Object)(object)nview == (Object)null) && nview.IsValid()) { ZDO zDO = nview.GetZDO(); if (zDO != null && !zDO.Persistent) { zDO.Persistent = true; } CapService.Track(zDO); } } internal static bool IsRaidCreature(MonsterAI monster) { if ((Object)(object)monster != (Object)null) { return monster.IsEventCreature(); } return false; } internal static bool ShouldKeepHunt(BaseAI ai) { MonsterAI val = (MonsterAI)(object)((ai is MonsterAI) ? ai : null); if ((Object)(object)val == (Object)null || !val.IsEventCreature()) { return false; } RaidState raidState = ((Component)val).GetComponent<RaidState>(); if ((Object)(object)raidState != (Object)null && raidState.filterRevision == _filterRevision) { return raidState.keepHunt; } bool flag = RulesActive() && PassesFilter(ai.m_character) && (ModConfig.EnablePersistentChase.Value || ModConfig.EventMonstersSurviveRaidEnd.Value); if ((Object)(object)raidState == (Object)null) { raidState = ((Component)val).gameObject.AddComponent<RaidState>(); } raidState.filterRevision = _filterRevision; raidState.keepHunt = flag; return flag; } internal static void ApplyRaidChase(MonsterAI monster) { if ((Object)(object)monster == (Object)null || !IsRaidCreature(monster) || !RulesActive() || !PassesFilter(((BaseAI)monster).m_character) || (!ModConfig.EnablePersistentChase.Value && !ModConfig.EventMonstersSurviveRaidEnd.Value)) { return; } EnforceFightAi(monster); if (ModConfig.EnablePersistentChase.Value && (Object)(object)((Component)monster).GetComponent<RaidChaseApplied>() == (Object)null) { ((Component)monster).gameObject.AddComponent<RaidChaseApplied>(); float num = Mathf.Max(1f, ModConfig.ChaseRangeMultiplier.Value); monster.m_maxChaseDistance *= num; ((BaseAI)monster).m_viewRange = ((BaseAI)monster).m_viewRange * num; monster.m_alertRange *= num; float value = ModConfig.MaxChaseDistance.Value; if (value > 0f) { monster.m_maxChaseDistance = Mathf.Min(monster.m_maxChaseDistance, value); } } } internal static void EnforceFightAi(MonsterAI monster) { if (!((Object)(object)monster == (Object)null) && (!((BaseAI)monster).m_huntPlayer || !((BaseAI)monster).IsAlerted() || !monster.m_enableHuntPlayer || monster.m_fleeIfNotAlerted || monster.m_fleeIfHurtWhenTargetCantBeReached || !(monster.m_fleeIfLowHealth <= 0f))) { monster.m_fleeIfNotAlerted = false; monster.m_fleeIfHurtWhenTargetCantBeReached = false; monster.m_fleeIfLowHealth = 0f; monster.m_enableHuntPlayer = true; if (!((BaseAI)monster).m_huntPlayer) { ((BaseAI)monster).SetHuntPlayer(true); } if (!((BaseAI)monster).IsAlerted()) { ((BaseAI)monster).SetAlerted(true); } } } internal static void DebugLog(string message) { if (ModConfig.EnableDebugLogging != null && ModConfig.EnableDebugLogging.Value) { Plugin.Log.LogInfo((object)message); } } } [HarmonyPatch(typeof(BaseAI), "MoveAwayAndDespawn", new Type[] { typeof(float), typeof(bool) })] internal static class PatchMoveAwayAndDespawn { private static bool Prefix(BaseAI __instance) { MonsterAI val = (MonsterAI)(object)((__instance is MonsterAI) ? __instance : null); if ((Object)(object)val != (Object)null && !val.m_eventCreature && ((Object)(object)__instance.m_character == (Object)null || !__instance.m_character.IsBoss())) { return true; } if ((Object)(object)__instance.m_character != (Object)null && CreatureRules.ShouldPersist(__instance.m_character, val)) { CreatureRules.DebugLog("Blocked MoveAwayAndDespawn " + ((Object)__instance.m_character).name); return false; } return true; } } [HarmonyPatch(typeof(MonsterAI), "HuntPlayer")] internal static class PatchMonsterHuntPlayer { private static bool Prefix(MonsterAI __instance, ref bool __result) { if (!__instance.m_eventCreature || !CreatureRules.ShouldKeepHunt((BaseAI)(object)__instance)) { return true; } if (__instance.DespawnInDay() && EnvMan.IsDay() && !ModConfig.NeverDespawnNightMonsters.Value) { return true; } __result = true; return false; } } [HarmonyPatch(typeof(BaseAI), "SetAlerted")] internal static class PatchSetAlerted { private static bool Prefix(BaseAI __instance, bool alert) { if (!alert) { MonsterAI val = (MonsterAI)(object)((__instance is MonsterAI) ? __instance : null); if (val != null && val.m_eventCreature && CreatureRules.ShouldKeepHunt(__instance)) { return false; } } return true; } } [HarmonyPatch(typeof(BaseAI), "SetHuntPlayer")] internal static class PatchSetHuntPlayer { private static bool Prefix(BaseAI __instance, bool hunt) { if (!hunt) { MonsterAI val = (MonsterAI)(object)((__instance is MonsterAI) ? __instance : null); if (val != null && val.m_eventCreature && CreatureRules.ShouldKeepHunt(__instance)) { return false; } } return true; } } [HarmonyPatch(typeof(BaseAI), "Flee", new Type[] { typeof(float), typeof(Vector3) })] internal static class PatchFlee { private static bool Prefix(BaseAI __instance, ref bool __result) { MonsterAI val = (MonsterAI)(object)((__instance is MonsterAI) ? __instance : null); if (val != null && val.m_eventCreature && CreatureRules.ShouldKeepHunt(__instance)) { __result = false; return false; } return true; } } [HarmonyPatch(typeof(MonsterAI), "UpdateAI")] internal static class PatchMonsterUpdateAI { private static void Postfix(MonsterAI __instance) { if (__instance.m_eventCreature && CreatureRules.ShouldKeepHunt((BaseAI)(object)__instance)) { CreatureRules.EnforceFightAi(__instance); } } } [HarmonyPatch(typeof(MonsterAI), "Awake")] internal static class PatchMonsterAwake { private static void Postfix(MonsterAI __instance) { if ((Object)(object)((BaseAI)__instance).m_character != (Object)null && ((BaseAI)__instance).m_character.IsBoss()) { CreatureRules.ApplyPersistence(__instance); } else if (__instance.IsEventCreature()) { CreatureRules.ApplyRaidChase(__instance); CreatureRules.ApplyPersistence(__instance); } } } [HarmonyPatch(typeof(MonsterAI), "SetEventCreature")] internal static class PatchSetEventCreature { private static void Postfix(MonsterAI __instance) { CreatureRules.ApplyRaidChase(__instance); CreatureRules.ApplyPersistence(__instance); if ((Object)(object)((BaseAI)__instance).m_nview != (Object)null && ((BaseAI)__instance).m_nview.IsValid()) { CapService.Track(((BaseAI)__instance).m_nview.GetZDO()); } } } [HarmonyPatch(typeof(MonsterAI), "SetDespawnInDay")] internal static class PatchSetDespawnInDay { private static bool Prefix(MonsterAI __instance, ref bool despawn) { if (despawn && __instance.m_eventCreature && CreatureRules.RulesActive() && ModConfig.NeverDespawnNightMonsters.Value && CreatureRules.PassesFilter(((BaseAI)__instance).m_character)) { despawn = false; } return true; } } internal static class ModConfig { internal enum CapScopeMode { ServerWide, PerPlayerArea, Both } internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<bool> EnablePersistentChase; internal static ConfigEntry<float> ChaseRangeMultiplier; internal static ConfigEntry<float> MaxChaseDistance; internal static ConfigEntry<bool> NeverDespawnMonsters; internal static ConfigEntry<bool> NeverDespawnBosses; internal static ConfigEntry<bool> EventMonstersSurviveRaidEnd; internal static ConfigEntry<bool> NeverDespawnNightMonsters; internal static ConfigEntry<float> EndEventAfterNoPlayersInAreaMinutes; internal static ConfigEntry<float> MaxRaidWallClockMinutes; internal static ConfigEntry<bool> AllowMultipleRaids; internal static ConfigEntry<bool> ContinueSpawningWithoutPlayers; internal static ConfigEntry<float> SpawnWithoutPlayersDuration; internal static ConfigEntry<bool> OverrideRaidDurations; internal static ConfigEntry<float> OverrideDurationSeconds; internal static ConfigEntry<bool> EnableMonsterCap; internal static ConfigEntry<CapScopeMode> CapScope; internal static ConfigEntry<int> MaxActiveMonsters; internal static ConfigEntry<int> MaxPerPlayerArea; internal static ConfigEntry<float> PlayerAreaRadius; internal static ConfigEntry<int> CapBehavior; internal static ConfigEntry<bool> OnlyApplyWhilePlayersOnline; internal static ConfigEntry<float> AutoCleanupInterval; internal static ConfigEntry<string> CreatureWhitelist; internal static ConfigEntry<string> CreatureBlacklist; internal static ConfigEntry<bool> EnableDebugLogging; internal static ConfigEntry<bool> LogCapEvents; private static ConfigDescription Admin(string desc) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown return new ConfigDescription(desc, (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { IsAdminOnly = true } }); } internal static void Bind(ConfigFile config) { Enabled = config.Bind<bool>("General", "Enabled", true, Admin("Master switch.")); EnablePersistentChase = config.Bind<bool>("RaidChase", "EnablePersistentChase", true, Admin("ALL RandomEvent/raid creatures hunt and fight (no shy flee). Open-world mobs stay vanilla.")); ChaseRangeMultiplier = config.Bind<float>("RaidChase", "ChaseRangeMultiplier", 3f, Admin("Multiplies view/alert/chase range for raid creatures only. 1 = vanilla.")); MaxChaseDistance = config.Bind<float>("RaidChase", "MaxChaseDistance", 200f, Admin("Absolute chase cap in meters. 0 = multiplier only.")); NeverDespawnMonsters = config.Bind<bool>("Despawn", "NeverDespawnMonsters", true, Admin("Block far-despawn and mark ZDOs persistent for RAID/event creatures only. Open-world wildlife culls like vanilla so it does not pile up around bases.")); NeverDespawnBosses = config.Bind<bool>("Despawn", "NeverDespawnBosses", true, Admin("Bosses persist / skip far-despawn. Hunt range is still raid-only.")); EventMonstersSurviveRaidEnd = config.Bind<bool>("Despawn", "EventMonstersSurviveRaidEnd", true, Admin("Event creatures keep hunting after the raid timer ends.")); NeverDespawnNightMonsters = config.Bind<bool>("Despawn", "NeverDespawnNightMonsters", true, Admin("Ignore despawn-in-day on night-spawned event creatures.")); EndEventAfterNoPlayersInAreaMinutes = config.Bind<float>("RaidTimeout", "EndEventAfterNoPlayersInAreaMinutes", 25f, Admin("When no player is in the raid event circle this many real minutes, end the random event so Custom Raids can roll again. Raid creatures may remain (EventMonstersSurviveRaidEnd). 0 = vanilla pause forever.")); MaxRaidWallClockMinutes = config.Bind<float>("RaidTimeout", "MaxRaidWallClockMinutes", 90f, Admin("Force-end the random event this many real minutes after it started, even if PauseIfNoPlayerInArea froze the timer. Safety net for stuck events. 0 = off.")); AllowMultipleRaids = config.Bind<bool>("MultipleRaids", "AllowMultipleRaids", false, Admin("OFF by default. Custom Raids will not start a new raid while any event is still current. Leave false.")); ContinueSpawningWithoutPlayers = config.Bind<bool>("MultipleRaids", "ContinueSpawningWithoutPlayers", false, Admin("OFF by default. When on, keeps the raid timer (and spawn checks) running with nobody in the circle. Leave off so each raid's PauseIfNoPlayerInArea and Duration work as configured.")); SpawnWithoutPlayersDuration = config.Bind<float>("MultipleRaids", "SpawnWithoutPlayersDuration", 600f, Admin("Unused (kept so old configs still load).")); OverrideRaidDurations = config.Bind<bool>("MultipleRaids", "OverrideRaidDurations", false, Admin("OFF by default. When on, replaces every random-event Duration with OverrideDurationSeconds. Leave off to use each Custom Raids Duration.")); OverrideDurationSeconds = config.Bind<float>("MultipleRaids", "OverrideDurationSeconds", 60f, Admin("Only used when OverrideRaidDurations is on. Seconds.")); EnableMonsterCap = config.Bind<bool>("Safety", "EnableMonsterCap", true, Admin("Cap how many raid creatures can exist on the server at once. Only raid creatures count. Tames, wildlife, normal spawns, and bosses are never counted or removed.")); CapScope = config.Bind<CapScopeMode>("Safety", "CapScope", CapScopeMode.ServerWide, Admin("ServerWide = MaxActiveMonsters limits raid creatures on the whole server. PerPlayerArea = MaxPerPlayerArea limits raid creatures within PlayerAreaRadius of each player, which is what affects lag in a fight. Both = apply both limits.")); MaxActiveMonsters = config.Bind<int>("Safety", "MaxActiveMonsters", 60, Admin("Most raid creatures allowed on the whole server, including ones from earlier raids that are still alive. Used by ServerWide and Both.")); MaxPerPlayerArea = config.Bind<int>("Safety", "MaxPerPlayerArea", 40, Admin("Most raid creatures allowed within PlayerAreaRadius of any one player. Extras furthest from that player are removed first. Used by PerPlayerArea and Both.")); PlayerAreaRadius = config.Bind<float>("Safety", "PlayerAreaRadius", 200f, Admin("Radius in meters around each player for MaxPerPlayerArea. About 200 covers the area a player has loaded.")); CapBehavior = config.Bind<int>("Safety", "CapBehavior", 2, Admin("0 = only log when the cap is passed. 1 or 2 = remove the raid creatures furthest from any player until back at the cap.")); OnlyApplyWhilePlayersOnline = config.Bind<bool>("Safety", "OnlyApplyWhilePlayersOnline", true, Admin("When no players are online, never-despawn rules are off so the server can unload.")); AutoCleanupInterval = config.Bind<float>("Safety", "AutoCleanupInterval", 2f, Admin("Minutes between cap checks on the server. 0 turns the automatic check off (persistraid cap still works).")); CreatureWhitelist = config.Bind<string>("Filters", "CreatureWhitelist", "", Admin("Comma prefab names. Empty = all enemies.")); CreatureBlacklist = config.Bind<string>("Filters", "CreatureBlacklist", "", Admin("Comma prefab names to ignore.")); EnableDebugLogging = config.Bind<bool>("Logging", "EnableDebugLogging", false, "Log blocked despawns and extra raids."); LogCapEvents = config.Bind<bool>("Logging", "LogCapEvents", true, "Log when the monster cap is hit."); } } [BepInPlugin("cjayride.persistentraids", "PersistentRaids", "1.0.10")] public class Plugin : BaseUnityPlugin { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleEvent <>9__7_0; internal void <Awake>b__7_0(ConsoleEventArgs args) { if (!Object.op_Implicit((Object)(object)ZNet.instance) || !ZNet.instance.IsServer()) { args.Context.AddString("PersistentRaids: server only"); return; } if (args.Length < 2) { args.Context.AddString("persistraid clear | event | stop | cap | boss <prefab>"); return; } switch (args[1].ToLowerInvariant()) { case "clear": { int num = CapService.Clear(eventOnly: false, includeBosses: false, null); args.Context.AddString("Cleared " + num + " non-boss monsters"); break; } case "event": { int num = CapService.Clear(eventOnly: true, includeBosses: false, null); args.Context.AddString("Cleared " + num + " event monsters"); break; } case "stop": ExtraRaids.StopAll(RandEventSystem.instance); args.Context.AddString("Cleared current/stashed random events"); break; case "cap": CapService.EnforceCap(); args.Context.AddString("Cap enforced"); break; case "boss": { if (args.Length < 3) { args.Context.AddString("persistraid boss <prefab>"); break; } int num = CapService.Clear(eventOnly: false, includeBosses: true, args[2]); args.Context.AddString("Cleared " + num + " matching bosses"); break; } default: args.Context.AddString("persistraid clear | event | stop | cap | boss <prefab>"); break; } } } public const string PluginGUID = "cjayride.persistentraids"; public const string PluginName = "PersistentRaids"; public const string PluginVersion = "1.0.10"; internal static Plugin Instance; internal static Harmony Harmony; internal static ManualLogSource Log => ((BaseUnityPlugin)Instance).Logger; private void Awake() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown Instance = this; ModConfig.Bind(((BaseUnityPlugin)this).Config); Harmony = new Harmony("cjayride.persistentraids"); Harmony.PatchAll(Assembly.GetExecutingAssembly()); object obj = <>c.<>9__7_0; if (obj == null) { ConsoleEvent val = delegate(ConsoleEventArgs args) { if (!Object.op_Implicit((Object)(object)ZNet.instance) || !ZNet.instance.IsServer()) { args.Context.AddString("PersistentRaids: server only"); } else if (args.Length < 2) { args.Context.AddString("persistraid clear | event | stop | cap | boss <prefab>"); } else { switch (args[1].ToLowerInvariant()) { case "clear": { int num = CapService.Clear(eventOnly: false, includeBosses: false, null); args.Context.AddString("Cleared " + num + " non-boss monsters"); break; } case "event": { int num = CapService.Clear(eventOnly: true, includeBosses: false, null); args.Context.AddString("Cleared " + num + " event monsters"); break; } case "stop": ExtraRaids.StopAll(RandEventSystem.instance); args.Context.AddString("Cleared current/stashed random events"); break; case "cap": CapService.EnforceCap(); args.Context.AddString("Cap enforced"); break; case "boss": if (args.Length < 3) { args.Context.AddString("persistraid boss <prefab>"); } else { int num = CapService.Clear(eventOnly: false, includeBosses: true, args[2]); args.Context.AddString("Cleared " + num + " matching bosses"); } break; default: args.Context.AddString("persistraid clear | event | stop | cap | boss <prefab>"); break; } } }; <>c.<>9__7_0 = val; obj = (object)val; } new ConsoleCommand("persistraid", "clear | event | stop | cap | boss <prefab> — PersistentRaids cleanup", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); if (ModConfig.AutoCleanupInterval.Value > 0f) { ((MonoBehaviour)this).InvokeRepeating("TickCap", 60f, ModConfig.AutoCleanupInterval.Value * 60f); } ((BaseUnityPlugin)this).Logger.LogInfo((object)"PersistentRaids 1.0.10 loaded. Custom Raids owns raid start. This mod only persists raid creatures."); } private void TickCap() { CapService.EnforceCap(); } } internal static class RaidEventTimeout { private static float _raidStartedAt; private static float _lastPlayerInAreaAt; private static float _nextAreaCheck; private static string _trackedName; internal static void Reset() { _trackedName = null; _nextAreaCheck = 0f; } internal static void OnEventSet(RandomEvent ev) { if (ev == null) { Reset(); return; } _raidStartedAt = Time.time; _lastPlayerInAreaAt = Time.time; _nextAreaCheck = 0f; _trackedName = ev.m_name; } internal static void Tick(RandEventSystem system) { if (!CreatureRules.RulesActive() || (Object)(object)system == (Object)null) { return; } RandomEvent randomEvent = system.m_randomEvent; if (randomEvent == null) { Reset(); return; } float value = ModConfig.EndEventAfterNoPlayersInAreaMinutes.Value; float value2 = ModConfig.MaxRaidWallClockMinutes.Value; if (value <= 0f && value2 <= 0f) { return; } if (_trackedName != randomEvent.m_name) { OnEventSet(randomEvent); } float time = Time.time; if (value2 > 0f && time - _raidStartedAt >= value2 * 60f) { EndEvent(system, randomEvent, "wall-clock max " + value2 + " min"); } else if (!(time < _nextAreaCheck)) { _nextAreaCheck = time + 1f; if (system.IsAnyPlayerInEventArea(randomEvent)) { _lastPlayerInAreaAt = time; } else if (value > 0f && time - _lastPlayerInAreaAt >= value * 60f) { EndEvent(system, randomEvent, "no players in event area for " + value + " min"); } } } private static void EndEvent(RandEventSystem system, RandomEvent ev, string reason) { Plugin.Log.LogInfo((object)("PersistentRaids: ending raid '" + ev.m_name + "' (" + reason + ")")); CreatureRules.DebugLog("Raid timeout " + ev.m_name + ": " + reason); Reset(); system.ResetRandomEvent(); } } internal static class ExtraRaids { internal static readonly List<RandomEvent> Stashed = new List<RandomEvent>(); internal static void StashCurrent(RandEventSystem system) { if (system.m_randomEvent != null) { Stashed.Add(system.m_randomEvent); CreatureRules.DebugLog("Stashed raid " + system.m_randomEvent.m_name + " extras=" + Stashed.Count); system.m_randomEvent = null; } } internal static void StopAll(RandEventSystem system) { Stashed.Clear(); RaidEventTimeout.Reset(); if ((Object)(object)system != (Object)null) { system.ResetRandomEvent(); } } internal static void ApplyDurationOverride(RandomEvent ev) { if (ev != null && ModConfig.OverrideRaidDurations.Value) { float duration = Mathf.Max(1f, ModConfig.OverrideDurationSeconds.Value); CreatureRules.DebugLog("Duration override " + ev.m_name + " " + ev.m_duration + " -> " + duration); ev.m_duration = duration; } } internal static void Tick(RandEventSystem system, float dt) { bool flag = ModConfig.ContinueSpawningWithoutPlayers.Value && CreatureRules.RulesActive(); for (int num = Stashed.Count - 1; num >= 0; num--) { RandomEvent val = Stashed[num]; if (val == null) { Stashed.RemoveAt(num); } else { bool flag2 = system.IsAnyPlayerInEventArea(val) || flag; if (val.Update(true, true, flag2, dt)) { val.OnStop(); Stashed.RemoveAt(num); CreatureRules.DebugLog("Extra raid ended " + val.m_name); } } } } } [HarmonyPatch(typeof(RandEventSystem), "StartRandomEvent")] internal static class PatchStartRandomEvent { private static void Prefix(RandEventSystem __instance) { if (CreatureRules.RulesActive() && ModConfig.AllowMultipleRaids.Value && __instance.m_randomEvent != null) { ExtraRaids.StashCurrent(__instance); } } } [HarmonyPatch(typeof(RandEventSystem), "SetRandomEvent")] internal static class PatchSetRandomEvent { private static void Postfix(RandEventSystem __instance) { if (CreatureRules.RulesActive()) { ExtraRaids.ApplyDurationOverride(__instance.m_randomEvent); RaidEventTimeout.OnEventSet(__instance.m_randomEvent); } } } [HarmonyPatch(typeof(RandEventSystem), "FixedUpdate")] internal static class PatchEventFixedUpdate { private static void Postfix(RandEventSystem __instance) { if (Object.op_Implicit((Object)(object)ZNet.instance) && ZNet.instance.IsServer()) { RaidEventTimeout.Tick(__instance); if (ModConfig.AllowMultipleRaids.Value && ExtraRaids.Stashed.Count != 0) { ExtraRaids.Tick(__instance, Time.fixedDeltaTime); } } } } [HarmonyPatch(typeof(RandomEvent), "Update", new Type[] { typeof(bool), typeof(bool), typeof(bool), typeof(float) })] internal static class PatchRandomEventUpdate { private static void Prefix(ref bool playerInArea) { if (CreatureRules.RulesActive() && ModConfig.ContinueSpawningWithoutPlayers.Value) { playerInArea = true; } } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }