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 Bifrost Breakout v0.1.0
BepInEx/plugins/Bifrost-Breakout/BifrostBreakout.dll
Decompiled 3 hours agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("Bifrost Breakout")] [assembly: AssemblyDescription("Roaming monsters, migrating wildlife, warbands, unexpected encounters, and a world whose ecology changes as you progress.")] [assembly: AssemblyCompany("Elwood")] [assembly: AssemblyProduct("Bifrost Breakout")] [assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyVersion("0.1.0.0")] namespace Elwood.Hellworld; internal sealed class AquaticZdoTracker { private readonly string[] _prefabNames; private readonly string _ownershipMarkerKey; private readonly HashSet<ZDOID> _trackedIds = new HashSet<ZDOID>(); private readonly List<ZDO> _recoveryBuffer = new List<ZDO>(); private readonly List<ZDOID> _staleIds = new List<ZDOID>(); private int _recoveryPrefabIndex; private int _recoverySectorIndex; private bool _recoveryComplete; internal bool RecoveryComplete => _recoveryComplete; internal AquaticZdoTracker(string ownershipMarkerKey, params string[] prefabNames) { if (string.IsNullOrEmpty(ownershipMarkerKey)) { throw new ArgumentException("An aquatic ownership marker is required.", "ownershipMarkerKey"); } if (prefabNames == null || prefabNames.Length == 0) { throw new ArgumentException("At least one aquatic prefab name is required.", "prefabNames"); } _ownershipMarkerKey = ownershipMarkerKey; _prefabNames = prefabNames; } internal void Reset() { _trackedIds.Clear(); _recoveryBuffer.Clear(); _recoveryPrefabIndex = 0; _recoverySectorIndex = 0; _recoveryComplete = false; } internal bool RecoverStep(out int recovered) { //IL_0067: Unknown result type (might be due to invalid IL or missing references) recovered = 0; if (_recoveryComplete) { return true; } if (ZDOMan.instance == null) { return false; } if (!ZDOMan.instance.GetAllZDOsWithPrefabIterative(_prefabNames[_recoveryPrefabIndex], _recoveryBuffer, ref _recoverySectorIndex)) { return false; } for (int i = 0; i < _recoveryBuffer.Count; i++) { ZDO val = _recoveryBuffer[i]; if (val != null && val.GetBool(_ownershipMarkerKey, false) && _trackedIds.Add(val.m_uid)) { recovered++; } } _recoveryBuffer.Clear(); _recoverySectorIndex = 0; _recoveryPrefabIndex++; _recoveryComplete = _recoveryPrefabIndex >= _prefabNames.Length; return _recoveryComplete; } internal void Track(ZDO zdo) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) if (zdo != null && zdo.GetBool(_ownershipMarkerKey, false)) { _trackedIds.Add(zdo.m_uid); } } internal void Untrack(ZDO zdo) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) if (zdo != null) { _trackedIds.Remove(zdo.m_uid); } } internal void ResolveTracked(List<ZDO> result) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0032: 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_008c: Unknown result type (might be due to invalid IL or missing references) result.Clear(); _staleIds.Clear(); ZDOMan instance = ZDOMan.instance; if (instance == null) { return; } foreach (ZDOID trackedId in _trackedIds) { ZDO zDO = instance.GetZDO(trackedId); if (zDO == null || !zDO.GetBool(_ownershipMarkerKey, false)) { _staleIds.Add(trackedId); } else { result.Add(zDO); } } for (int i = 0; i < _staleIds.Count; i++) { _trackedIds.Remove(_staleIds[i]); } } internal bool Destroy(ZDO zdo) { if (zdo == null) { return false; } Untrack(zdo); ZNetView val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.FindInstance(zdo) : null); if ((Object)(object)val != (Object)null && val.IsValid()) { if (!val.IsOwner()) { val.ClaimOwnership(); } ZNetScene.instance.Destroy(((Component)val).gameObject); return true; } if (ZDOMan.instance != null) { ZDOMan.instance.DestroyZDO(zdo); } return false; } } [BepInPlugin("Elwood.Hellworld", "Bifrost Breakout", "0.1.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInProcess("valheim.exe")] public sealed class HellworldPlugin : BaseUnityPlugin { public const string PluginGuid = "Elwood.Hellworld"; public const string PluginName = "Bifrost Breakout"; public const string PluginVersion = "0.1.0"; public const string SpawnThatGuid = "asharppen.valheim.spawn_that"; private Harmony _harmony; private void Awake() { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Expected O, but got Unknown //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Expected O, but got Unknown //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Expected O, but got Unknown //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Expected O, but got Unknown //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Expected O, but got Unknown //IL_01af: Unknown result type (might be due to invalid IL or missing references) //IL_01b9: Expected O, but got Unknown ConfigEntry<bool> warbandsEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Warbands", "WarbandsEnabled", true, "Give each qualifying war-boss creature one entourage when that leader is first initialized."); ConfigEntry<int> followersPerLeader = ((BaseUnityPlugin)this).Config.Bind<int>("Warbands", "FollowersPerLeader", 3, new ConfigDescription("Base follower count. One-star leaders add one; two-star leaders add two.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 12), Array.Empty<object>())); ConfigEntry<float> spawnRadiusMin = ((BaseUnityPlugin)this).Config.Bind<float>("Warbands", "FollowerSpawnRadiusMin", 4f, new ConfigDescription("Minimum horizontal follower spawn radius in meters.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 30f), Array.Empty<object>())); ConfigEntry<float> spawnRadiusMax = ((BaseUnityPlugin)this).Config.Bind<float>("Warbands", "FollowerSpawnRadiusMax", 8f, new ConfigDescription("Maximum horizontal follower spawn radius in meters.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 50f), Array.Empty<object>())); ConfigEntry<bool> debugLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "DebugLogging", false, "Log concise warband detection, marker, placement, and spawn diagnostics."); ConfigEntry<bool> enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Mountain Strongholds", "DvergrOutpostsEnabled", true, "Clone the three intact vanilla Dvergr guard towers into Mountain world generation."); ConfigEntry<int> quantityPerVariant = ((BaseUnityPlugin)this).Config.Bind<int>("Mountain Strongholds", "DvergrOutpostsPerVariant", 120, new ConfigDescription("Requested world-generation quantity for each of the three guard-tower variants. Terrain and separation usually limit the actual count.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 500), Array.Empty<object>())); ConfigEntry<float> minimumSeparation = ((BaseUnityPlugin)this).Config.Bind<float>("Mountain Strongholds", "DvergrOutpostMinimumSeparation", 300f, new ConfigDescription("Shared minimum separation in metres between all Bifrost Breakout Mountain Dvergr outposts.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(128f, 1000f), Array.Empty<object>())); WarbandSystem.Initialize(((BaseUnityPlugin)this).Logger, warbandsEnabled, followersPerLeader, spawnRadiusMin, spawnRadiusMax, debugLogging); MountainStrongholdSystem.Initialize(((BaseUnityPlugin)this).Logger, enabled, quantityPerVariant, minimumSeparation); WhiteWhaleSystem.Initialize((BaseUnityPlugin)(object)this, ((BaseUnityPlugin)this).Logger, ((BaseUnityPlugin)this).Config); ShorelineBonemawSystem.Initialize((BaseUnityPlugin)(object)this, ((BaseUnityPlugin)this).Logger, ((BaseUnityPlugin)this).Config); _harmony = new Harmony("Elwood.Hellworld"); _harmony.PatchAll(typeof(HellworldPlugin).Assembly); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Bifrost Breakout 0.1.0 public alpha loaded; Mountain strongholds, warbands, aquatic ecology, Project Snake Eyes, and post-Bonemass Ocean flyers are configuration-controlled."); } private void OnDestroy() { if (_harmony != null) { _harmony.UnpatchSelf(); _harmony = null; } WarbandSystem.Shutdown(); MountainStrongholdSystem.Shutdown(); WhiteWhaleSystem.Shutdown(); ShorelineBonemawSystem.Shutdown(); } } [HarmonyPatch(typeof(ZoneSystem), "SetupLocations")] internal static class ZoneSystemSetupLocationsMountainStrongholdPatch { [HarmonyPostfix] private static void Postfix(ZoneSystem __instance) { MountainStrongholdSystem.RegisterMountainOutposts(__instance); } } [HarmonyPatch(typeof(Character), "Start")] internal static class CharacterStartWarbandPatch { [HarmonyPostfix] private static void Postfix(Character __instance) { WhiteWhaleSystem.TryInitializeCharacter(__instance); ShorelineBonemawSystem.TryInitializeCharacter(__instance); WarbandSystem.TryProcessLeader(__instance); } } [HarmonyPatch(typeof(Humanoid), "Start")] internal static class HumanoidStartWarbandPatch { [HarmonyPrefix] private static void Prefix(Humanoid __instance) { WarbandSystem.PrepareFollowerLoadout(__instance); } [HarmonyPostfix] private static void Postfix(Humanoid __instance) { ShorelineBonemawSystem.TryInitializeCharacter((Character)(object)__instance); WarbandSystem.TryProcessLeader((Character)(object)__instance); } } [HarmonyPatch(typeof(Character), "OnDeath")] internal static class CharacterDeathWhiteWhalePatch { [HarmonyPrefix] private static void Prefix(Character __instance) { WhiteWhaleSystem.OnCharacterDeath(__instance); } } [HarmonyPatch(typeof(Player), "PlacePiece")] internal static class PlayerPlacePieceWhiteWhalePatch { [HarmonyPostfix] private static void Postfix(Player __instance, Vector3 __1) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) WhiteWhaleSystem.ReportShoreBuild(__instance, __1); } } internal static class MountainStrongholdSystem { private const string OutpostGroup = "Hellworld_Mountain_DvergrOutposts"; private const string OutpostNamePrefix = "Hellworld_Mountain_DvergrOutpost_"; private const float MinimumAltitude = 20f; private const float MaximumAltitude = 1000f; private static readonly string[] SourceLocationNames = new string[3] { "Mistlands_GuardTower1_new", "Mistlands_GuardTower2_new", "Mistlands_GuardTower3_new" }; private static ManualLogSource _log; private static ConfigEntry<bool> _enabled; private static ConfigEntry<int> _quantityPerVariant; private static ConfigEntry<float> _minimumSeparation; internal static void Initialize(ManualLogSource log, ConfigEntry<bool> enabled, ConfigEntry<int> quantityPerVariant, ConfigEntry<float> minimumSeparation) { _log = log; _enabled = enabled; _quantityPerVariant = quantityPerVariant; _minimumSeparation = minimumSeparation; } internal static void Shutdown() { _log = null; _enabled = null; _quantityPerVariant = null; _minimumSeparation = null; } internal static void RegisterMountainOutposts(ZoneSystem zoneSystem) { //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)zoneSystem == (Object)null || _enabled == null || !_enabled.Value) { return; } try { if (zoneSystem.m_locations.Exists((ZoneLocation location) => location != null && location.m_name != null && location.m_name.StartsWith("Hellworld_Mountain_DvergrOutpost_", StringComparison.Ordinal))) { return; } int quantity = Mathf.Clamp(_quantityPerVariant.Value, 1, 500); float minDistanceFromSimilar = Mathf.Clamp(_minimumSeparation.Value, 128f, 1000f); int num = 0; for (int num2 = 0; num2 < SourceLocationNames.Length; num2++) { string text = SourceLocationNames[num2]; ZoneLocation val = FindLocationByPrefabName(zoneSystem.m_locations, text); if (val == null) { if (_log != null) { _log.LogWarning((object)("Mountain Dvergr outpost source location was not found: " + text)); } continue; } ZoneLocation val2 = val.Clone(); val2.m_name = "Hellworld_Mountain_DvergrOutpost_" + (num2 + 1); val2.m_enable = true; val2.m_biome = (Biome)4; val2.m_biomeArea = (BiomeArea)3; val2.m_quantity = quantity; val2.m_prioritized = true; val2.m_centerFirst = false; val2.m_unique = false; val2.m_group = "Hellworld_Mountain_DvergrOutposts"; val2.m_minDistanceFromSimilar = minDistanceFromSimilar; val2.m_groupMax = string.Empty; val2.m_maxDistanceFromSimilar = 0f; val2.m_inForest = false; val2.m_minDistance = 0f; val2.m_maxDistance = 0f; val2.m_minDistanceFromCenter = 0f; val2.m_maxDistanceFromCenter = 0f; val2.m_minAltitude = 20f; val2.m_maxAltitude = 1000f; zoneSystem.m_locations.Add(val2); num++; if (_log != null) { _log.LogInfo((object)("Registered Mountain Dvergr outpost " + text + " (quantity " + quantity + ", shared minimum separation " + minDistanceFromSimilar.ToString("0") + "m).")); } } if (_log != null) { _log.LogInfo((object)("Mountain Dvergr strongholds registered " + num + "/" + SourceLocationNames.Length + " vanilla guard-tower variants. Existing worlds require location regeneration or a new world for placement.")); } } catch (Exception ex) { if (_log != null) { _log.LogError((object)("Mountain Dvergr outpost registration failed safely: " + ex)); } } } private static ZoneLocation FindLocationByPrefabName(List<ZoneLocation> locations, string prefabName) { return locations.Find((ZoneLocation location) => location != null && string.Equals(location.m_prefabName, prefabName, StringComparison.Ordinal)); } } internal static class ShorelineBonemawSystem { private const string PrefabName = "BonemawSerpent"; private const string OwnershipMarkerKey = "Elwood.Hellworld.BifrostBonemaw"; private const string ExpiryKey = "Elwood.Hellworld.BifrostBonemawExpiry"; private const string AwaySinceKey = "Elwood.Hellworld.BifrostBonemawAwaySince"; private const int UnstarredCharacterLevel = 1; private static readonly List<ZDO> TrackedZdos = new List<ZDO>(); private static BaseUnityPlugin _host; private static ManualLogSource _logger; private static Coroutine _coroutine; private static ZoneSystem _observedZoneSystem; private static AquaticZdoTracker _tracker; private static bool _initialized; private static double _nextSpawnCheckTime; private static double _nextCapLogTime; private static ConfigEntry<bool> _enabled; private static ConfigEntry<float> _tickSeconds; private static ConfigEntry<float> _spawnCheckIntervalSeconds; private static ConfigEntry<float> _spawnChancePercent; private static ConfigEntry<float> _spawnDistanceMin; private static ConfigEntry<float> _spawnDistanceMax; private static ConfigEntry<float> _minimumWaterDepth; private static ConfigEntry<int> _spawnCandidateAttempts; private static ConfigEntry<float> _shoreInteractionRadius; private static ConfigEntry<float> _localCapRadius; private static ConfigEntry<float> _lifetimeSeconds; private static ConfigEntry<float> _playerRetentionDistance; private static ConfigEntry<float> _awayCleanupDelaySeconds; internal static void Initialize(BaseUnityPlugin host, ManualLogSource logger, ConfigFile config) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Expected O, but got Unknown //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Expected O, but got Unknown //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Expected O, but got Unknown //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Expected O, but got Unknown //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Expected O, but got Unknown //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Expected O, but got Unknown //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01c2: Expected O, but got Unknown //IL_01f0: Unknown result type (might be due to invalid IL or missing references) //IL_01fa: Expected O, but got Unknown //IL_0228: Unknown result type (might be due to invalid IL or missing references) //IL_0232: Expected O, but got Unknown //IL_0260: Unknown result type (might be due to invalid IL or missing references) //IL_026a: Expected O, but got Unknown //IL_0298: Unknown result type (might be due to invalid IL or missing references) //IL_02a2: Expected O, but got Unknown //IL_02d0: Unknown result type (might be due to invalid IL or missing references) //IL_02da: Expected O, but got Unknown Shutdown(); _host = host; _logger = logger; _tracker = new AquaticZdoTracker("Elwood.Hellworld.BifrostBonemaw", "BonemawSerpent"); _enabled = config.Bind<bool>("Bifrost Shoreline Bonemaws", "Enabled", true, "Spawn conservative, mod-owned vanilla Bonemaws in coastal water without changing their AI or faction."); _tickSeconds = config.Bind<float>("Bifrost Shoreline Bonemaws", "TickSeconds", 10f, new ConfigDescription("Seconds between lightweight tracking and cleanup checks.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 60f), Array.Empty<object>())); _spawnCheckIntervalSeconds = config.Bind<float>("Bifrost Shoreline Bonemaws", "SpawnCheckIntervalSeconds", 60f, new ConfigDescription("Seconds between bounded coastal spawn checks.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(15f, 1800f), Array.Empty<object>())); _spawnChancePercent = config.Bind<float>("Bifrost Shoreline Bonemaws", "SpawnChancePercent", 20f, new ConfigDescription("Chance that an eligible coastal check spawns one Bonemaw. At the defaults this averages one opportunity per five minutes.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>())); _spawnDistanceMin = config.Bind<float>("Bifrost Shoreline Bonemaws", "SpawnDistanceMin", 15f, new ConfigDescription("Minimum horizontal spawn distance from the selected active coastal player.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(8f, 100f), Array.Empty<object>())); _spawnDistanceMax = config.Bind<float>("Bifrost Shoreline Bonemaws", "SpawnDistanceMax", 30f, new ConfigDescription("Maximum horizontal spawn distance from the selected active coastal player.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 150f), Array.Empty<object>())); _minimumWaterDepth = config.Bind<float>("Bifrost Shoreline Bonemaws", "MinimumWaterDepth", 5f, new ConfigDescription("Minimum water depth for a Bonemaw spawn, matching its native world-spawner altitude requirement.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 30f), Array.Empty<object>())); _spawnCandidateAttempts = config.Bind<int>("Bifrost Shoreline Bonemaws", "SpawnCandidateAttempts", 32, new ConfigDescription("Maximum bounded water samples per eligible coastal player.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(4, 64), Array.Empty<object>())); _shoreInteractionRadius = config.Bind<float>("Bifrost Shoreline Bonemaws", "ShoreInteractionRadius", 30f, new ConfigDescription("Maximum sampled distance from a valid spawn point to land or very shallow water.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 60f), Array.Empty<object>())); _localCapRadius = config.Bind<float>("Bifrost Shoreline Bonemaws", "LocalCapRadius", 150f, new ConfigDescription("Radius around a coastal player in which only one mod-owned Bonemaw may exist.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(50f, 500f), Array.Empty<object>())); _lifetimeSeconds = config.Bind<float>("Bifrost Shoreline Bonemaws", "LifetimeSeconds", 600f, new ConfigDescription("Maximum lifetime of a mod-owned shoreline Bonemaw.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(60f, 3600f), Array.Empty<object>())); _playerRetentionDistance = config.Bind<float>("Bifrost Shoreline Bonemaws", "PlayerRetentionDistance", 200f, new ConfigDescription("A Bonemaw begins its early-cleanup timer when no living player remains within this distance.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(50f, 1000f), Array.Empty<object>())); _awayCleanupDelaySeconds = config.Bind<float>("Bifrost Shoreline Bonemaws", "AwayCleanupDelaySeconds", 60f, new ConfigDescription("Continuous player-absence time required before early cleanup.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(15f, 600f), Array.Empty<object>())); _initialized = true; _coroutine = ((MonoBehaviour)_host).StartCoroutine(Run()); } internal static void Shutdown() { _initialized = false; if ((Object)(object)_host != (Object)null && _coroutine != null) { ((MonoBehaviour)_host).StopCoroutine(_coroutine); } _coroutine = null; _host = null; _logger = null; _observedZoneSystem = null; _nextSpawnCheckTime = 0.0; _nextCapLogTime = 0.0; if (_tracker != null) { _tracker.Reset(); } _tracker = null; TrackedZdos.Clear(); } internal static void TryInitializeCharacter(Character character) { if (!((Object)(object)character == (Object)null) && IsOwnedBonemaw(character)) { ZNetView component = ((Component)character).GetComponent<ZNetView>(); if ((Object)(object)component != (Object)null && component.IsValid() && component.IsOwner() && character.GetLevel() != 1) { character.SetLevel(1); } } } private static IEnumerator Run() { while (_initialized) { float num = ((_tickSeconds != null) ? Mathf.Max(2f, _tickSeconds.Value) : 10f); yield return (object)new WaitForSeconds(num); try { Tick(); } catch (Exception ex) { _logger.LogError((object)("Bifrost shoreline Bonemaw tick failed cleanly: " + ex)); } } } private static void Tick() { if (!IsServerReady()) { return; } double timeSeconds = ZNet.instance.GetTimeSeconds(); ResetForNewWorld(); int num = 0; bool flag = false; for (int i = 0; i < 4; i++) { if (flag) { break; } flag = _tracker.RecoverStep(out var recovered); num += recovered; } if (num > 0) { _logger.LogInfo((object)("Bifrost shoreline Bonemaw tracking recovered " + num + " marked creature(s) from world ZDO state.")); } if (flag) { MaintainTrackedBonemaws(timeSeconds); if (!_enabled.Value) { DestroyAllOwned("feature disabled"); } else if (!(timeSeconds < _nextSpawnCheckTime)) { _nextSpawnCheckTime = timeSeconds + (double)Mathf.Max(15f, _spawnCheckIntervalSeconds.Value); TrySpawnCoastalBonemaw(timeSeconds); } } } private static bool IsServerReady() { if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer() && (Object)(object)ZoneSystem.instance != (Object)null && (Object)(object)ZNetScene.instance != (Object)null) { return ZDOMan.instance != null; } return false; } private static void ResetForNewWorld() { if (_observedZoneSystem != ZoneSystem.instance) { _observedZoneSystem = ZoneSystem.instance; _nextSpawnCheckTime = 0.0; _nextCapLogTime = 0.0; _tracker.Reset(); } } private static void MaintainTrackedBonemaws(double now) { //IL_009d: Unknown result type (might be due to invalid IL or missing references) _tracker.ResolveTracked(TrackedZdos); for (int num = TrackedZdos.Count - 1; num >= 0; num--) { ZDO val = TrackedZdos[num]; if (!IsPrefab(val, "BonemawSerpent")) { DestroyOwned(val, "marker was attached to a non-Bonemaw prefab"); } else { long num2 = val.GetLong("Elwood.Hellworld.BifrostBonemawExpiry", 0L); if (num2 <= 0) { num2 = (long)Math.Ceiling(now + (double)Mathf.Max(60f, _lifetimeSeconds.Value)); val.Set("Elwood.Hellworld.BifrostBonemawExpiry", num2); } if (now >= (double)num2) { DestroyOwned(val, "10-minute lifetime expired"); } else if (HasLivingPlayerWithin(val.GetPosition(), _playerRetentionDistance.Value)) { if (val.GetLong("Elwood.Hellworld.BifrostBonemawAwaySince", 0L) != 0L) { val.Set("Elwood.Hellworld.BifrostBonemawAwaySince", 0L); } } else { long num3 = val.GetLong("Elwood.Hellworld.BifrostBonemawAwaySince", 0L); if (num3 <= 0) { val.Set("Elwood.Hellworld.BifrostBonemawAwaySince", (long)Math.Floor(now)); } else if (now - (double)num3 >= (double)Mathf.Max(15f, _awayCleanupDelaySeconds.Value)) { DestroyOwned(val, "no living player remained within " + _playerRetentionDistance.Value.ToString("0", CultureInfo.InvariantCulture) + "m for " + _awayCleanupDelaySeconds.Value.ToString("0", CultureInfo.InvariantCulture) + "s"); } } } } } private static void TrySpawnCoastalBonemaw(double now) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) List<Player> allPlayers = Player.GetAllPlayers(); if (allPlayers == null || allPlayers.Count == 0) { return; } int num = Random.Range(0, allPlayers.Count); for (int i = 0; i < allPlayers.Count; i++) { Player val = allPlayers[(num + i) % allPlayers.Count]; if ((Object)(object)val == (Object)null || ((Character)val).IsDead()) { continue; } if (CountOwnedWithin(((Component)val).transform.position, _localCapRadius.Value) >= 1) { LogCapBlock(val, now); } else { if (!TryFindCoastalWaterPosition(((Component)val).transform.position, out var point, out var depth)) { continue; } if (CountOwnedWithin(point, _localCapRadius.Value) < 1) { float num2 = Mathf.Clamp(_spawnChancePercent.Value, 0f, 100f); if (!(num2 <= 0f) && (!(num2 < 100f) || !(Random.Range(0f, 100f) >= num2))) { SpawnBonemaw(val, point, depth, now); } break; } LogCapBlock(val, now); } } } private static int CountOwnedWithin(Vector3 center, float radius) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) _tracker.ResolveTracked(TrackedZdos); float num = Mathf.Max(1f, radius) * Mathf.Max(1f, radius); int num2 = 0; for (int i = 0; i < TrackedZdos.Count; i++) { Vector3 val = TrackedZdos[i].GetPosition() - center; if (((Vector3)(ref val)).sqrMagnitude <= num) { num2++; } } return num2; } private static bool TryFindCoastalWaterPosition(Vector3 center, out Vector3 point, out float depth) { //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //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_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) float num = Mathf.Min(_spawnDistanceMin.Value, _spawnDistanceMax.Value); float num2 = Mathf.Max(_spawnDistanceMin.Value, _spawnDistanceMax.Value); float waterLevel = ZoneSystem.instance.m_waterLevel; int num3 = Mathf.Clamp(_spawnCandidateAttempts.Value, 4, 64); float num6 = default(float); for (int i = 0; i < num3; i++) { float num4 = Random.Range(0f, 360f) * ((float)Math.PI / 180f); float num5 = Random.Range(num, num2); Vector3 val = center + new Vector3(Mathf.Sin(num4), 0f, Mathf.Cos(num4)) * num5; if (ZoneSystem.instance.GetGroundHeight(val, ref num6)) { float num7 = waterLevel - num6; val.y = num6 + 0.5f; if (!(num7 < _minimumWaterDepth.Value) && !(val.y >= waterLevel) && !ZoneSystem.instance.IsBlocked(val) && IsNearLand(val, _shoreInteractionRadius.Value)) { point = val; depth = num7; return true; } } } point = Vector3.zero; depth = 0f; return false; } private static bool IsNearLand(Vector3 center, float radius) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) float waterLevel = ZoneSystem.instance.m_waterLevel; float num3 = default(float); for (int i = 1; i <= 2; i++) { float num = radius * ((float)i / 2f); for (int j = 0; j < 12; j++) { float num2 = (float)j / 12f * (float)Math.PI * 2f; Vector3 val = center + new Vector3(Mathf.Sin(num2), 0f, Mathf.Cos(num2)) * num; if (ZoneSystem.instance.GetGroundHeight(val, ref num3) && waterLevel - num3 <= 0.5f) { return true; } } } return false; } private static void SpawnBonemaw(Player player, Vector3 spawnPoint, float depth, double now) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) GameObject prefab = ZNetScene.instance.GetPrefab("BonemawSerpent"); if ((Object)(object)prefab == (Object)null) { _logger.LogError((object)"Bifrost shoreline ecology cannot spawn: current ZNetScene has no prefab named 'BonemawSerpent'."); return; } GameObject val = Object.Instantiate<GameObject>(prefab, spawnPoint, Quaternion.identity); Character component = val.GetComponent<Character>(); MonsterAI component2 = val.GetComponent<MonsterAI>(); ZNetView component3 = val.GetComponent<ZNetView>(); if ((Object)(object)component == (Object)null || (Object)(object)component2 == (Object)null || (Object)(object)component3 == (Object)null || !component3.IsValid() || component3.GetZDO() == null) { Object.Destroy((Object)(object)val); _logger.LogError((object)"Bifrost shoreline Bonemaw aborted cleanly: instantiated prefab lacked Character, MonsterAI, or a valid ZNetView/ZDO."); return; } ZDO zDO = component3.GetZDO(); zDO.Set("Elwood.Hellworld.BifrostBonemaw", true); zDO.Set("Elwood.Hellworld.BifrostBonemawExpiry", (long)Math.Ceiling(now + (double)Mathf.Max(60f, _lifetimeSeconds.Value))); zDO.Set("Elwood.Hellworld.BifrostBonemawAwaySince", 0L); component.SetLevel(1); _tracker.Track(zDO); _logger.LogInfo((object)("BIFROST BONEMAW: spawned ordinary BonemawSerpent for coastal player " + player.GetPlayerName() + "; level=" + component.GetLevel() + ", faction=" + ((object)component.GetFaction()/*cast due to .constrained prefix*/).ToString() + ", huntPlayer=" + ((BaseAI)component2).HuntPlayer() + ", vanillaAIUntouched=true, horizontalDistance=" + HorizontalDistance(((Component)player).transform.position, spawnPoint).ToString("0.0", CultureInfo.InvariantCulture) + "m, depth=" + depth.ToString("0.0", CultureInfo.InvariantCulture) + "m, lifetime=" + _lifetimeSeconds.Value.ToString("0", CultureInfo.InvariantCulture) + "s.")); } private static void LogCapBlock(Player player, double now) { if (!(now < _nextCapLogTime)) { _nextCapLogTime = now + 60.0; _logger.LogInfo((object)("Bifrost shoreline Bonemaw spawn blocked by local mod-owned cap near " + player.GetPlayerName() + " (1 within " + _localCapRadius.Value.ToString("0", CultureInfo.InvariantCulture) + "m).")); } } private static bool HasLivingPlayerWithin(Vector3 center, float radius) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) float num = Mathf.Max(1f, radius) * Mathf.Max(1f, radius); List<Player> allPlayers = Player.GetAllPlayers(); for (int i = 0; i < allPlayers.Count; i++) { Player val = allPlayers[i]; if ((Object)(object)val != (Object)null && !((Character)val).IsDead()) { Vector3 val2 = ((Component)val).transform.position - center; if (((Vector3)(ref val2)).sqrMagnitude <= num) { return true; } } } return false; } private static bool IsOwnedBonemaw(Character character) { ZNetView component = ((Component)character).GetComponent<ZNetView>(); if ((Object)(object)component != (Object)null && component.IsValid() && component.GetZDO() != null) { return component.GetZDO().GetBool("Elwood.Hellworld.BifrostBonemaw", false); } return false; } private static bool IsPrefab(ZDO zdo, string prefabName) { GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(zdo.GetPrefab()) : null); if (!((Object)(object)val == (Object)null)) { return string.Equals(((Object)val).name, prefabName, StringComparison.Ordinal); } return true; } private static float HorizontalDistance(Vector3 first, Vector3 second) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) float num = first.x - second.x; float num2 = first.z - second.z; return Mathf.Sqrt(num * num + num2 * num2); } private static void DestroyAllOwned(string reason) { _tracker.ResolveTracked(TrackedZdos); for (int num = TrackedZdos.Count - 1; num >= 0; num--) { DestroyOwned(TrackedZdos[num], reason); } } private static void DestroyOwned(ZDO zdo, string reason) { bool flag = _tracker.Destroy(zdo); _logger.LogInfo((object)("Bifrost shoreline Bonemaw cleanup (" + (flag ? "loaded" : "unloaded") + "): " + reason + ".")); } } internal sealed class WarbandFollowerDefinition { internal string Prefab { get; private set; } internal int Count { get; private set; } internal int Level { get; private set; } internal string ForcedWeaponPrefab { get; private set; } internal WarbandFollowerDefinition(string prefab, int count, int level) : this(prefab, count, level, null) { } internal WarbandFollowerDefinition(string prefab, int count, int level, string forcedWeaponPrefab) { Prefab = prefab; Count = count; Level = level; ForcedWeaponPrefab = forcedWeaponPrefab; } } internal sealed class WarbandDefinition { internal string LeaderPrefab { get; private set; } internal string RequiredTemplateId { get; private set; } internal int LeaderLevel { get; private set; } internal bool UsesGenericFollowerCount { get; private set; } internal WarbandFollowerDefinition[] Followers { get; private set; } internal WarbandDefinition(string leaderPrefab, string followerPrefab) { LeaderPrefab = leaderPrefab; RequiredTemplateId = null; LeaderLevel = 0; UsesGenericFollowerCount = true; Followers = new WarbandFollowerDefinition[1] { new WarbandFollowerDefinition(followerPrefab, 1, 1) }; } internal WarbandDefinition(string leaderPrefab, string requiredTemplateId, params WarbandFollowerDefinition[] followers) : this(leaderPrefab, requiredTemplateId, 0, followers) { } internal WarbandDefinition(string leaderPrefab, string requiredTemplateId, int leaderLevel, params WarbandFollowerDefinition[] followers) { LeaderPrefab = leaderPrefab; RequiredTemplateId = requiredTemplateId; LeaderLevel = leaderLevel; UsesGenericFollowerCount = false; Followers = followers; } } internal static class WarbandSystem { internal const string ProcessedMarkerKey = "Elwood.Hellworld.WarbandSpawned"; internal const string FollowerMarkerKey = "Elwood.Hellworld.WarbandFollower"; internal const string FollowerWeaponKey = "Elwood.Hellworld.WarbandFollowerWeapon"; private const string SpawnTemplateIdKey = "spawn_template_id"; private const string WolfAlphaTemplateId = "HellWorld_Mountain_WolfAlpha"; private const string FenringHuntTemplateId = "HellWorld_Mountain_FenringHunt"; private const string DvergrPatrolTemplateId = "HellWorld_Mountain_DvergrPatrol"; private const int PlacementAttemptsPerFollower = 8; private const float MaximumVerticalPlacementDelta = 6f; private const float MinimumSurfaceNormalY = 0.6f; private const float WaterClearance = 0.25f; private const float MinimumFollowerSeparation = 1.5f; private static readonly Dictionary<string, WarbandDefinition> Definitions = new Dictionary<string, WarbandDefinition>(StringComparer.Ordinal) { { "Troll", new WarbandDefinition("Troll", "Greydwarf") }, { "Abomination", new WarbandDefinition("Abomination", "Draugr") }, { "Ghost", new WarbandDefinition("Ghost", null, 3, new WarbandFollowerDefinition("Skeleton", 3, 3, "skeleton_bow")) }, { "StoneGolem", new WarbandDefinition("StoneGolem", null, 1, new WarbandFollowerDefinition("Greydwarf_Shaman", 2, 1), new WarbandFollowerDefinition("Greydwarf_Elite", 1, 1)) }, { "GoblinBrute", new WarbandDefinition("GoblinBrute", "Goblin") }, { "SeekerBrute", new WarbandDefinition("SeekerBrute", "Seeker") }, { "Morgen", new WarbandDefinition("Morgen", "Charred_Twitcher") }, { "Morgen_NonSleeping", new WarbandDefinition("Morgen_NonSleeping", "Charred_Twitcher") }, { "Wolf", new WarbandDefinition("Wolf", "HellWorld_Mountain_WolfAlpha", new WarbandFollowerDefinition("Wolf", 3, 1)) }, { "Fenring", new WarbandDefinition("Fenring", "HellWorld_Mountain_FenringHunt", new WarbandFollowerDefinition("Fenring_Cultist", 1, 2), new WarbandFollowerDefinition("Ulv", 3, 1)) }, { "Dverger", new WarbandDefinition("Dverger", "HellWorld_Mountain_DvergrPatrol", new WarbandFollowerDefinition("DvergerMageFire", 1, 1), new WarbandFollowerDefinition("DvergerMageIce", 1, 1), new WarbandFollowerDefinition("DvergerMageSupport", 1, 1)) } }; private static ManualLogSource _log; private static ConfigEntry<bool> _warbandsEnabled; private static ConfigEntry<int> _followersPerLeader; private static ConfigEntry<float> _spawnRadiusMin; private static ConfigEntry<float> _spawnRadiusMax; private static ConfigEntry<bool> _debugLogging; private static bool _definitionsValid; internal static void Initialize(ManualLogSource log, ConfigEntry<bool> warbandsEnabled, ConfigEntry<int> followersPerLeader, ConfigEntry<float> spawnRadiusMin, ConfigEntry<float> spawnRadiusMax, ConfigEntry<bool> debugLogging) { _log = log; _warbandsEnabled = warbandsEnabled; _followersPerLeader = followersPerLeader; _spawnRadiusMin = spawnRadiusMin; _spawnRadiusMax = spawnRadiusMax; _debugLogging = debugLogging; _definitionsValid = ValidateDefinitions(); } internal static void Shutdown() { _log = null; _warbandsEnabled = null; _followersPerLeader = null; _spawnRadiusMin = null; _spawnRadiusMax = null; _debugLogging = null; _definitionsValid = false; } internal static void TryProcessLeader(Character leader) { //IL_020c: Unknown result type (might be due to invalid IL or missing references) //IL_0211: Unknown result type (might be due to invalid IL or missing references) //IL_0225: Unknown result type (might be due to invalid IL or missing references) //IL_022a: Unknown result type (might be due to invalid IL or missing references) //IL_01cb: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)leader == (Object)null || !_definitionsValid || _warbandsEnabled == null || !_warbandsEnabled.Value) { return; } try { ZNetView component = ((Component)leader).GetComponent<ZNetView>(); if ((Object)(object)component == (Object)null || !component.IsValid() || !component.IsOwner()) { return; } ZDO zDO = component.GetZDO(); if (zDO == null) { Debug("Leader has no valid ZDO; skipping."); } else { if (zDO.GetBool("Elwood.Hellworld.WarbandFollower", false)) { return; } string text = ResolvePrefabName(zDO); if (text == null || !Definitions.TryGetValue(text, out var value)) { return; } string a = zDO.GetString("spawn_template_id", string.Empty); if (!string.IsNullOrEmpty(value.RequiredTemplateId) && !string.Equals(a, value.RequiredTemplateId, StringComparison.Ordinal)) { return; } Debug("Warband leader detected: " + text); if (zDO.GetBool("Elwood.Hellworld.WarbandSpawned", false)) { Debug(text + " already has processed warband marker; skipping."); return; } if (value.LeaderLevel > 0 && leader.GetLevel() != value.LeaderLevel) { leader.SetLevel(value.LeaderLevel); Debug(text + " fixed-squad leader level set to " + value.LeaderLevel + "."); } List<WarbandFollowerDefinition> list = BuildFollowerRequests(value, leader); Dictionary<string, GameObject> dictionary = ResolveFollowerPrefabs(list); if (dictionary == null) { return; } int num = Mathf.Clamp(leader.GetLevel(), 1, 3); int count = list.Count; zDO.Set("Elwood.Hellworld.WarbandSpawned", true); float num2 = Mathf.Clamp(_spawnRadiusMin.Value, 1f, 30f); float num3 = Mathf.Clamp(_spawnRadiusMax.Value, 2f, 50f); if (num3 < num2) { float num4 = num2; num2 = num3; num3 = num4; } List<Vector3> list2 = FindSpawnPositions(((Component)leader).transform.position, count, num2, num3); int num5 = 0; for (int i = 0; i < list2.Count; i++) { WarbandFollowerDefinition warbandFollowerDefinition = list[i]; try { Quaternion val = Quaternion.Euler(0f, Random.Range(0f, 360f), 0f); GameObject val2 = Object.Instantiate<GameObject>(dictionary[warbandFollowerDefinition.Prefab], list2[i], val); if ((Object)(object)val2 != (Object)null) { GameObject forcedWeaponPrefab = (string.IsNullOrEmpty(warbandFollowerDefinition.ForcedWeaponPrefab) ? null : dictionary[warbandFollowerDefinition.ForcedWeaponPrefab]); MarkAndLevelFollower(val2, warbandFollowerDefinition.Level, warbandFollowerDefinition.ForcedWeaponPrefab, forcedWeaponPrefab); num5++; } } catch (Exception ex) { if (_log != null) { _log.LogError((object)("Failed to spawn " + warbandFollowerDefinition.Prefab + " for " + text + ": " + ex.Message)); } } } string text2 = "Spawned " + num5 + "/" + count + " followers for " + text + " (leader level " + num + (value.UsesGenericFollowerCount ? ", generic star-scaled band)." : (", fixed squad: " + DescribeFollowers(value) + ").")); if (!value.UsesGenericFollowerCount && _log != null) { _log.LogInfo((object)text2); } else { Debug(text2); } } } catch (Exception ex2) { if (_log != null) { _log.LogError((object)("Warband processing failed safely: " + ex2)); } } } internal static void PrepareFollowerLoadout(Humanoid follower) { if ((Object)(object)follower == (Object)null) { return; } try { ZNetView component = ((Component)follower).GetComponent<ZNetView>(); ZDO val = (((Object)(object)component == (Object)null || !component.IsValid()) ? null : component.GetZDO()); if (val == null || !val.GetBool("Elwood.Hellworld.WarbandFollower", false)) { return; } string text = val.GetString("Elwood.Hellworld.WarbandFollowerWeapon", string.Empty); if (string.IsNullOrEmpty(text) || (Object)(object)ZNetScene.instance == (Object)null) { return; } GameObject prefab = ZNetScene.instance.GetPrefab(text); if ((Object)(object)prefab == (Object)null) { if (_log != null) { _log.LogWarning((object)("Could not restore fixed follower weapon prefab: " + text)); } } else { ApplyFixedWeaponLoadout(follower, prefab); } } catch (Exception ex) { if (_log != null) { _log.LogError((object)("Fixed follower loadout preparation failed safely: " + ex)); } } } private static string DescribeFollowers(WarbandDefinition definition) { List<string> list = new List<string>(); WarbandFollowerDefinition[] followers = definition.Followers; foreach (WarbandFollowerDefinition warbandFollowerDefinition in followers) { list.Add(warbandFollowerDefinition.Count + "x " + warbandFollowerDefinition.Prefab + " level " + warbandFollowerDefinition.Level + (string.IsNullOrEmpty(warbandFollowerDefinition.ForcedWeaponPrefab) ? string.Empty : (" with " + warbandFollowerDefinition.ForcedWeaponPrefab))); } return string.Join(", ", list.ToArray()); } private static List<WarbandFollowerDefinition> BuildFollowerRequests(WarbandDefinition definition, Character leader) { List<WarbandFollowerDefinition> list = new List<WarbandFollowerDefinition>(); if (definition.UsesGenericFollowerCount) { int num = Mathf.Clamp(Mathf.Clamp(leader.GetLevel(), 1, 3) - 1, 0, 2); int num2 = Mathf.Clamp(_followersPerLeader.Value, 1, 12) + num; for (int i = 0; i < num2; i++) { list.Add(definition.Followers[0]); } return list; } WarbandFollowerDefinition[] followers = definition.Followers; foreach (WarbandFollowerDefinition warbandFollowerDefinition in followers) { for (int k = 0; k < warbandFollowerDefinition.Count; k++) { list.Add(warbandFollowerDefinition); } } return list; } private static Dictionary<string, GameObject> ResolveFollowerPrefabs(List<WarbandFollowerDefinition> requests) { ZNetScene instance = ZNetScene.instance; if ((Object)(object)instance == (Object)null) { return null; } Dictionary<string, GameObject> dictionary = new Dictionary<string, GameObject>(StringComparer.Ordinal); foreach (WarbandFollowerDefinition request in requests) { if (dictionary.ContainsKey(request.Prefab)) { continue; } GameObject prefab = instance.GetPrefab(request.Prefab); if ((Object)(object)prefab == (Object)null) { if (_log != null) { _log.LogWarning((object)("Could not resolve follower prefab: " + request.Prefab)); } return null; } dictionary.Add(request.Prefab, prefab); if (string.IsNullOrEmpty(request.ForcedWeaponPrefab) || dictionary.ContainsKey(request.ForcedWeaponPrefab)) { continue; } GameObject prefab2 = instance.GetPrefab(request.ForcedWeaponPrefab); if ((Object)(object)prefab2 == (Object)null) { if (_log != null) { _log.LogWarning((object)("Could not resolve fixed follower weapon prefab: " + request.ForcedWeaponPrefab)); } return null; } dictionary.Add(request.ForcedWeaponPrefab, prefab2); } return dictionary; } private static void MarkAndLevelFollower(GameObject follower, int level, string forcedWeaponPrefabName, GameObject forcedWeaponPrefab) { ZNetView component = follower.GetComponent<ZNetView>(); ZDO val = (((Object)(object)component == (Object)null || !component.IsValid()) ? null : component.GetZDO()); if (val != null) { val.Set("Elwood.Hellworld.WarbandFollower", true); val.Set("Elwood.Hellworld.WarbandSpawned", true); if (!string.IsNullOrEmpty(forcedWeaponPrefabName)) { val.Set("Elwood.Hellworld.WarbandFollowerWeapon", forcedWeaponPrefabName); } } Character component2 = follower.GetComponent<Character>(); if ((Object)(object)component2 != (Object)null) { component2.SetLevel(Mathf.Clamp(level, 1, 3)); } Humanoid component3 = follower.GetComponent<Humanoid>(); if ((Object)(object)component3 != (Object)null && (Object)(object)forcedWeaponPrefab != (Object)null) { ApplyFixedWeaponLoadout(component3, forcedWeaponPrefab); } } private static void ApplyFixedWeaponLoadout(Humanoid follower, GameObject weaponPrefab) { follower.m_defaultItems = (GameObject[])(object)new GameObject[0]; follower.m_randomWeapon = (GameObject[])(object)new GameObject[1] { weaponPrefab }; follower.m_randomShield = (GameObject[])(object)new GameObject[0]; follower.m_randomSets = (ItemSet[])(object)new ItemSet[0]; follower.m_randomItems = (RandomItem[])(object)new RandomItem[0]; } private static string ResolvePrefabName(ZDO zdo) { ZNetScene instance = ZNetScene.instance; if ((Object)(object)instance == (Object)null) { return null; } int prefab = zdo.GetPrefab(); if (prefab == 0) { return null; } GameObject prefab2 = instance.GetPrefab(prefab); if (!((Object)(object)prefab2 == (Object)null)) { return ((Object)prefab2).name; } return null; } private static List<Vector3> FindSpawnPositions(Vector3 leaderPosition, int requestedCount, float radiusMin, float radiusMax) { //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) List<Vector3> list = new List<Vector3>(requestedCount); ZoneSystem instance = ZoneSystem.instance; if ((Object)(object)instance == (Object)null) { Debug("ZoneSystem is unavailable; no follower placement attempted."); return list; } float num5 = default(float); Vector3 val2 = default(Vector3); GameObject val3 = default(GameObject); Vector3 val4 = default(Vector3); for (int i = 0; i < requestedCount; i++) { bool flag = false; for (int j = 0; j < 8; j++) { float num = 360f / (float)requestedCount * (float)i; float num2 = Random.Range(-35f, 35f); float num3 = (num + num2) * ((float)Math.PI / 180f); float num4 = Random.Range(radiusMin, radiusMax); Vector3 val = leaderPosition + new Vector3(Mathf.Cos(num3) * num4, 4f, Mathf.Sin(num3) * num4); if (instance.GetSolidHeight(val, ref num5, ref val2, ref val3) && !(Mathf.Abs(num5 - leaderPosition.y) > 6f) && !(val2.y < 0.6f) && !(num5 <= instance.m_waterLevel + 0.25f) && (!((Object)(object)val3 != (Object)null) || !((Object)(object)val3.GetComponentInParent<Character>() != (Object)null))) { ((Vector3)(ref val4))..ctor(val.x, num5 + 0.1f, val.z); if (!IsTooCloseToExistingPosition(val4, list)) { list.Add(val4); flag = true; break; } } } if (!flag) { Debug("Could not find safe placement for follower " + (i + 1) + "."); } } return list; } private static bool IsTooCloseToExistingPosition(Vector3 candidate, List<Vector3> positions) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: 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) //IL_0018: 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_001e: Unknown result type (might be due to invalid IL or missing references) float num = 2.25f; foreach (Vector3 position in positions) { Vector3 val = candidate - position; if (((Vector3)(ref val)).sqrMagnitude < num) { return true; } } return false; } private static bool ValidateDefinitions() { bool result = true; foreach (KeyValuePair<string, WarbandDefinition> definition in Definitions) { WarbandDefinition value = definition.Value; if (!string.Equals(definition.Key, value.LeaderPrefab, StringComparison.Ordinal)) { result = false; if (_log != null) { _log.LogError((object)("Warband definition key does not match leader prefab: " + definition.Key)); } } if (value.Followers == null || value.Followers.Length == 0) { result = false; if (_log != null) { _log.LogError((object)("Warband definition has no followers: " + definition.Key)); } continue; } if (value.LeaderLevel < 0 || value.LeaderLevel > 3) { result = false; if (_log != null) { _log.LogError((object)("Invalid fixed leader level for: " + definition.Key)); } } WarbandFollowerDefinition[] followers = value.Followers; foreach (WarbandFollowerDefinition warbandFollowerDefinition in followers) { if (string.IsNullOrEmpty(warbandFollowerDefinition.Prefab) || warbandFollowerDefinition.Count < 1 || warbandFollowerDefinition.Level < 1 || warbandFollowerDefinition.Level > 3) { result = false; if (_log != null) { _log.LogError((object)("Invalid follower definition for leader: " + definition.Key)); } } if (warbandFollowerDefinition.ForcedWeaponPrefab != null && string.IsNullOrWhiteSpace(warbandFollowerDefinition.ForcedWeaponPrefab)) { result = false; if (_log != null) { _log.LogError((object)("Invalid fixed follower weapon for leader: " + definition.Key)); } } } } return result; } private static void Debug(string message) { if (_log != null && _debugLogging != null && _debugLogging.Value) { _log.LogInfo((object)message); } } } internal static class WhiteWhaleSystem { private sealed class SeaState { internal float Disturbance; internal int LastLoggedBucket; internal double NextLogTime; internal string LastActivity = "quiet"; } internal const string InternalId = "Hellworld_WhiteSerpent"; internal const string DefeatedGlobalKey = "defeated_hellworld_white_whale"; private const string EncounterPrefabName = "Serpent"; private const string MarkerKey = "Elwood.Hellworld.WhiteWhale"; private const string ExpiryKey = "Elwood.Hellworld.WhiteWhaleExpiry"; private const string SchemaKey = "Elwood.Hellworld.WhiteWhaleSchema"; private const string DeathHandledKey = "Elwood.Hellworld.WhiteWhaleDeathHandled"; private const string ShoreBuildRpc = "Elwood.Hellworld.WhiteWhaleShoreBuild"; private const int TwoStarCharacterLevel = 3; private const int VanillaAiSchemaVersion = 1; private static readonly Dictionary<long, SeaState> SeaStates = new Dictionary<long, SeaState>(); private static readonly List<long> MissingPlayers = new List<long>(); private static readonly List<ZDO> TrackedEncounterZdos = new List<ZDO>(); private static BaseUnityPlugin _host; private static ManualLogSource _logger; private static Coroutine _coroutine; private static ZRoutedRpc _registeredRpc; private static ZoneSystem _observedZoneSystem; private static AquaticZdoTracker _tracker; private static bool _initialized; private static bool _legacyCooldownKeyCleared; private static double _nextSpawnAttemptTime; private static string _lastAttemptBlockReason = string.Empty; private static double _nextAttemptBlockLogTime; private static ConfigEntry<bool> _enabled; private static ConfigEntry<float> _tickSeconds; private static ConfigEntry<float> _disturbanceThreshold; private static ConfigEntry<float> _swimRate; private static ConfigEntry<float> _boatRate; private static ConfigEntry<float> _wadingRate; private static ConfigEntry<float> _shoreRate; private static ConfigEntry<float> _quietDecayRate; private static ConfigEntry<float> _shoreBuildBurst; private static ConfigEntry<float> _shorelineRadius; private static ConfigEntry<float> _spawnDistanceMin; private static ConfigEntry<float> _spawnDistanceMax; private static ConfigEntry<float> _minimumWaterDepth; private static ConfigEntry<int> _spawnCandidateAttempts; private static ConfigEntry<float> _spawnRetrySeconds; private static ConfigEntry<float> _encounterDurationSeconds; private static ConfigEntry<bool> _debugLogging; internal static void Initialize(BaseUnityPlugin host, ManualLogSource logger, ConfigFile config) { //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected O, but got Unknown //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Expected O, but got Unknown //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Expected O, but got Unknown //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01ae: Expected O, but got Unknown //IL_01dc: Unknown result type (might be due to invalid IL or missing references) //IL_01e6: Expected O, but got Unknown //IL_0214: Unknown result type (might be due to invalid IL or missing references) //IL_021e: Expected O, but got Unknown //IL_024c: Unknown result type (might be due to invalid IL or missing references) //IL_0256: Expected O, but got Unknown //IL_027a: Unknown result type (might be due to invalid IL or missing references) //IL_0284: Expected O, but got Unknown //IL_02b2: Unknown result type (might be due to invalid IL or missing references) //IL_02bc: Expected O, but got Unknown //IL_02ea: Unknown result type (might be due to invalid IL or missing references) //IL_02f4: Expected O, but got Unknown Shutdown(); _host = host; _logger = logger; _tracker = new AquaticZdoTracker("Elwood.Hellworld.WhiteWhale", "Serpent", "BonemawSerpent"); _enabled = config.Bind<bool>("White Whale", "Enabled", true, "Enable Project Snake Eyes: a recurring, regularly named two-star vanilla Serpent encounter."); _tickSeconds = config.Bind<float>("White Whale", "TickSeconds", 5f, new ConfigDescription("Seconds between lightweight Sea Disturbance and encounter checks.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 30f), Array.Empty<object>())); _disturbanceThreshold = config.Bind<float>("White Whale", "DisturbanceThreshold", 100f, new ConfigDescription("Sea Disturbance required before Project Snake Eyes attempts an encounter.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 1000f), Array.Empty<object>())); _swimRate = BindRate(config, "SwimmingDisturbancePerSecond", 10f, "Disturbance gained per second while swimming or treading water."); _boatRate = BindRate(config, "BoatDisturbancePerSecond", 1f, "Disturbance gained per second while aboard a ship."); _wadingRate = BindRate(config, "WadingDisturbancePerSecond", 2f, "Disturbance gained per second while in water but not swimming."); _shoreRate = BindRate(config, "ShorelineDisturbancePerSecond", 0.08f, "Disturbance gained per second while operating near sampled shoreline water."); _quietDecayRate = BindRate(config, "QuietDecayPerSecond", 0.25f, "Disturbance lost per second while safely inland."); _shoreBuildBurst = config.Bind<float>("White Whale", "ShoreBuildDisturbance", 10f, new ConfigDescription("One-time disturbance added when a piece is placed near sampled shoreline water.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1000f), Array.Empty<object>())); _shorelineRadius = config.Bind<float>("White Whale", "ShorelineSampleRadius", 25f, new ConfigDescription("Radius used by the bounded shoreline-water samples.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 80f), Array.Empty<object>())); _spawnDistanceMin = config.Bind<float>("White Whale", "SpawnDistanceMin", 10f, new ConfigDescription("Minimum water-spawn distance from the selected swimming/boating player.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 150f), Array.Empty<object>())); _spawnDistanceMax = config.Bind<float>("White Whale", "SpawnDistanceMax", 15f, new ConfigDescription("Maximum water-spawn distance from the selected swimming/boating player. The close-range default is intended to test native Serpent sensing and attacks.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 200f), Array.Empty<object>())); _minimumWaterDepth = config.Bind<float>("White Whale", "MinimumWaterDepth", 1.5f, new ConfigDescription("Minimum water depth required for spawning. The spawn point must still be below the water surface.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.75f, 30f), Array.Empty<object>())); _spawnCandidateAttempts = config.Bind<int>("White Whale", "SpawnCandidateAttempts", 24, new ConfigDescription("Maximum bounded water samples per encounter attempt.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(4, 64), Array.Empty<object>())); _spawnRetrySeconds = config.Bind<float>("White Whale", "FailedSpawnRetrySeconds", 30f, new ConfigDescription("Delay after no valid water is found; prevents tight retry loops.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 300f), Array.Empty<object>())); _encounterDurationSeconds = config.Bind<float>("White Whale", "EncounterDurationSeconds", 300f, new ConfigDescription("Maximum lifetime before the marked encounter is removed without manipulating its AI.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(30f, 1800f), Array.Empty<object>())); _debugLogging = config.Bind<bool>("Debug", "WhiteWhaleDebugLogging", true, "Log Project Snake Eyes disturbance milestones, water failures, state changes, and encounter cleanup."); _initialized = true; _coroutine = ((MonoBehaviour)_host).StartCoroutine(Run()); } internal static void Shutdown() { _initialized = false; if ((Object)(object)_host != (Object)null && _coroutine != null) { ((MonoBehaviour)_host).StopCoroutine(_coroutine); } _coroutine = null; _host = null; _registeredRpc = null; _observedZoneSystem = null; _legacyCooldownKeyCleared = false; _nextSpawnAttemptTime = 0.0; _lastAttemptBlockReason = string.Empty; _nextAttemptBlockLogTime = 0.0; if (_tracker != null) { _tracker.Reset(); } _tracker = null; SeaStates.Clear(); MissingPlayers.Clear(); TrackedEncounterZdos.Clear(); } internal static void TryInitializeCharacter(Character character) { if (!((Object)(object)character == (Object)null) && IsWhiteWhale(character)) { character.m_defeatSetGlobalKey = string.Empty; ZNetView component = ((Component)character).GetComponent<ZNetView>(); if ((Object)(object)component != (Object)null && component.IsValid() && component.IsOwner() && character.GetLevel() != 3) { character.SetLevel(3); } } } internal static void OnCharacterDeath(Character character) { if ((Object)(object)character == (Object)null || !IsWhiteWhale(character)) { return; } character.m_defeatSetGlobalKey = string.Empty; ZNetView component = ((Component)character).GetComponent<ZNetView>(); if (!((Object)(object)component != (Object)null) || !component.IsValid() || !component.IsOwner()) { return; } ZDO zDO = component.GetZDO(); if (zDO != null && !zDO.GetBool("Elwood.Hellworld.WhiteWhaleDeathHandled", false)) { zDO.Set("Elwood.Hellworld.WhiteWhaleDeathHandled", true); if (_tracker != null) { _tracker.Untrack(zDO); } if (_logger != null) { _logger.LogWarning((object)"Project Snake Eyes Serpent killed; no respawn cooldown is applied. Sea Disturbance may build toward another encounter normally."); } } } internal static void ReportShoreBuild(Player player, Vector3 position) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) if (_initialized && _enabled != null && _enabled.Value && !((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)Player.m_localPlayer)) { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC("Elwood.Hellworld.WhiteWhaleShoreBuild", new object[2] { player.GetPlayerID(), position }); } } } private static ConfigEntry<float> BindRate(ConfigFile config, string key, float value, string description) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown return config.Bind<float>("White Whale", key, value, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>())); } private static IEnumerator Run() { while (_initialized) { float wait = ((_tickSeconds != null) ? Mathf.Max(1f, _tickSeconds.Value) : 5f); yield return (object)new WaitForSeconds(wait); try { RegisterRpcIfReady(); Tick(wait); } catch (Exception ex) { _logger.LogError((object)("Project Snake Eyes tick failed cleanly: " + ex)); } } } private static void RegisterRpcIfReady() { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null && _registeredRpc != instance) { instance.Register<long, Vector3>("Elwood.Hellworld.WhiteWhaleShoreBuild", (Action<long, long, Vector3>)OnShoreBuildRpc); _registeredRpc = instance; } } private static void OnShoreBuildRpc(long sender, long playerId, Vector3 position) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) if (IsServerReady() && _enabled.Value) { Player player = Player.GetPlayer(playerId); if ((Object)(object)player == (Object)null || Vector3.Distance(((Component)player).transform.position, position) > 50f) { DebugLog("Ignored unverifiable shoreline build report from peer " + sender + "."); } else if (IsNearWater(position, _shorelineRadius.Value, 1f)) { SeaState seaState = GetSeaState(playerId); AddDisturbance(seaState, _shoreBuildBurst.Value); DebugLog("Shore build disturbance +" + _shoreBuildBurst.Value.ToString("0.0") + " for " + player.GetPlayerName() + "; total=" + seaState.Disturbance.ToString("0.0") + "."); } } } private static void Tick(float elapsed) { if (!IsServerReady()) { return; } double timeSeconds = ZNet.instance.GetTimeSeconds(); ResetTransientStateForNewWorld(); int num = 0; bool flag = false; for (int i = 0; i < 4; i++) { if (flag) { break; } flag = _tracker.RecoverStep(out var recovered); num += recovered; } if (num > 0) { DebugLog("Project Snake Eyes tracking recovered " + num + " marked aquatic ZDO(s) from world state."); } if (!flag) { return; } if (!_enabled.Value) { DestroyAllMarkedWhiteWhales("feature disabled"); return; } ClearLegacyCooldownKey(); ZDO singleActiveZdo = GetSingleActiveZdo(timeSeconds); if (singleActiveZdo != null) { LogAttemptBlocked("active", "existing active encounter", timeSeconds); MaintainEncounterBookkeeping(singleActiveZdo); return; } UpdateSeaDisturbance(elapsed, timeSeconds); Player val = SelectEncounterCandidate(); if ((Object)(object)val == (Object)null) { _lastAttemptBlockReason = string.Empty; return; } if (timeSeconds < _nextSpawnAttemptTime) { LogAttemptBlocked("spawn-retry", "failed-water retry delay (about " + (_nextSpawnAttemptTime - timeSeconds).ToString("0", CultureInfo.InvariantCulture) + "s remaining)", timeSeconds); return; } SeaState seaState = GetSeaState(val.GetPlayerID()); _lastAttemptBlockReason = string.Empty; DebugLog("Project Snake Eyes encounter trigger reached for " + val.GetPlayerName() + ": disturbance=" + seaState.Disturbance.ToString("0.0", CultureInfo.InvariantCulture) + "/" + _disturbanceThreshold.Value.ToString("0.0", CultureInfo.InvariantCulture) + ", activity=" + seaState.LastActivity + "."); TryStartEncounter(val, timeSeconds); } private static bool IsServerReady() { if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer() && (Object)(object)ZoneSystem.instance != (Object)null) { return (Object)(object)ZNetScene.instance != (Object)null; } return false; } private static void ResetTransientStateForNewWorld() { if (_observedZoneSystem != ZoneSystem.instance) { _observedZoneSystem = ZoneSystem.instance; _legacyCooldownKeyCleared = false; _nextSpawnAttemptTime = 0.0; _lastAttemptBlockReason = string.Empty; _nextAttemptBlockLogTime = 0.0; SeaStates.Clear(); MissingPlayers.Clear(); _tracker.Reset(); DebugLog("Project Snake Eyes transient state reset for the newly loaded world; any retired defeat/death-cooldown key will be removed."); } } private static void ClearLegacyCooldownKey() { if (!_legacyCooldownKeyCleared && !((Object)(object)ZoneSystem.instance == (Object)null)) { string text = default(string); if (ZoneSystem.instance.GetGlobalKey("defeated_hellworld_white_whale", ref text)) { ZoneSystem.instance.RemoveGlobalKey("defeated_hellworld_white_whale"); _logger.LogWarning((object)"Project Snake Eyes removed the retired defeat/death-cooldown global key; recurrence is controlled only by active-encounter tracking and Sea Disturbance."); } _legacyCooldownKeyCleared = true; } } private static ZDO GetSingleActiveZdo(double now) { _tracker.ResolveTracked(TrackedEncounterZdos); ZDO val = null; for (int i = 0; i < TrackedEncounterZdos.Count; i++) { ZDO val2 = TrackedEncounterZdos[i]; if (val2.GetBool("Elwood.Hellworld.WhiteWhaleDeathHandled", false)) { _tracker.Untrack(val2); DebugLog("Project Snake Eyes stopped tracking a defeated encounter; no respawn cooldown was applied."); } else if (!IsCurrentEncounterPrefab(val2)) { DestroyMarkedZdo(val2, "retiring obsolete non-Serpent Project Snake Eyes instance"); } else if (val2.GetInt("Elwood.Hellworld.WhiteWhaleSchema", 0) < 1) { DestroyMarkedZdo(val2, "retiring pre-0.3.3 forced-AI encounter instance"); } else if (val != null) { DestroyMarkedZdo(val2, "duplicate active Project Snake Eyes encounter"); } else { val = val2; } } if (val == null) { return null; } long num = val.GetLong("Elwood.Hellworld.WhiteWhaleExpiry", 0L); if (num > 0 && now >= (double)num) { DestroyMarkedZdo(val, "maximum vanilla-AI encounter duration reached"); return null; } return val; } private static bool IsCurrentEncounterPrefab(ZDO zdo) { if (zdo == null || (Object)(object)ZNetScene.instance == (Object)null) { return true; } GameObject prefab = ZNetScene.instance.GetPrefab(zdo.GetPrefab()); if (!((Object)(object)prefab == (Object)null)) { return string.Equals(((Object)prefab).name, "Serpent", StringComparison.Ordinal); } return true; } private static void MaintainEncounterBookkeeping(ZDO activeZdo) { Character val = FindLoadedCharacter(activeZdo); if (!((Object)(object)val == (Object)null)) { TryInitializeCharacter(val); } } private static void UpdateSeaDisturbance(float elapsed, double now) { //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) List<Player> allPlayers = Player.GetAllPlayers(); MissingPlayers.Clear(); foreach (long key in SeaStates.Keys) { MissingPlayers.Add(key); } for (int i = 0; i < allPlayers.Count; i++) { Player val = allPlayers[i]; if ((Object)(object)val == (Object)null || ((Character)val).IsDead()) { continue; } long playerID = val.GetPlayerID(); if (playerID == 0L) { continue; } MissingPlayers.Remove(playerID); SeaState seaState = GetSeaState(playerID); float num; string lastActivity; if (((Character)val).IsSwimming()) { num = _swimRate.Value; lastActivity = "swimming/treading water"; } else if (IsAboardShip(val)) { num = _boatRate.Value; lastActivity = "aboard ship"; } else if (((Character)val).InWater()) { num = _wadingRate.Value; lastActivity = "wading"; } else { Vector3 velocity = ((Character)val).GetVelocity(); if (((Vector3)(ref velocity)).sqrMagnitude > 0.04f && IsNearWater(((Component)val).transform.position, _shorelineRadius.Value, 1f)) { num = _shoreRate.Value; lastActivity = "moving near shoreline"; } else { num = 0f - _quietDecayRate.Value; lastActivity = "quiet inland"; } } AddDisturbance(seaState, num * elapsed); seaState.LastActivity = lastActivity; LogDisturbanceMilestone(val, seaState, now); } for (int j = 0; j < MissingPlayers.Count; j++) { SeaStates.Remove(MissingPlayers[j]); } } private static void LogDisturbanceMilestone(Player player, SeaState state, double now) { if (_debugLogging.Value) { float num = Mathf.Max(1f, _disturbanceThreshold.Value); int num2 = Mathf.FloorToInt(state.Disturbance / num * 4f); num2 = Mathf.Clamp(num2, 0, 4); if (num2 != state.LastLoggedBucket && !(now < state.NextLogTime)) { state.LastLoggedBucket = num2; state.NextLogTime = now + 15.0; DebugLog("Sea Disturbance " + player.GetPlayerName() + "=" + state.Disturbance.ToString("0.0") + "/" + num.ToString("0.0") + " (" + state.LastActivity + ")."); } } } private static Player SelectEncounterCandidate() { List<Player> allPlayers = Player.GetAllPlayers(); Player result = null; float num = _disturbanceThreshold.Value; for (int i = 0; i < allPlayers.Count; i++) { Player val = allPlayers[i]; if (!((Object)(object)val == (Object)null) && !((Character)val).IsDead() && IsEncounterMaritime(val) && SeaStates.TryGetValue(val.GetPlayerID(), out var value) && value.Disturbance >= num) { result = val; num = value.Disturbance; } } return result; } private unsafe static void TryStartEncounter(Player target, double now) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_0204: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Unknown result type (might be due to invalid IL or missing references) //IL_035a: Unknown result type (might be due to invalid IL or missing references) //IL_035b: Unknown result type (might be due to invalid IL or missing references) //IL_0401: Unknown result type (might be due to invalid IL or missing references) //IL_0406: Unknown result type (might be due to invalid IL or missing references) if (!TryFindWaterPosition(((Component)target).transform.position, Mathf.Min(_spawnDistanceMin.Value, _spawnDistanceMax.Value), Mathf.Max(_spawnDistanceMin.Value, _spawnDistanceMax.Value), _spawnCandidateAttempts.Value, out var point)) { _nextSpawnAttemptTime = now + (double)_spawnRetrySeconds.Value; _logger.LogWarning((object)("Project Snake Eyes water candidate not found: no unblocked water at least " + _minimumWaterDepth.Value.ToString("0.0", CultureInfo.InvariantCulture) + "m deep within " + _spawnDistanceMin.Value.ToString("0.0", CultureInfo.InvariantCulture) + "-" + _spawnDistanceMax.Value.ToString("0.0", CultureInfo.InvariantCulture) + "m after " + _spawnCandidateAttempts.Value + " bounded samples; retry delayed " + _spawnRetrySeconds.Value.ToString("0", CultureInfo.InvariantCulture) + "s.")); return; } float num = HorizontalDistance(((Component)target).transform.position, point); float waterDepth = GetWaterDepth(point); string[] obj = new string[7] { "Project Snake Eyes water candidate found: horizontalDistance=", num.ToString("0.0", CultureInfo.InvariantCulture), "m, depth=", waterDepth.ToString("0.0", CultureInfo.InvariantCulture), "m, point=", null, null }; Vector3 val = point; obj[5] = ((object)(*(Vector3*)(&val))/*cast due to .constrained prefix*/).ToString(); obj[6] = "."; DebugLog(string.Concat(obj)); GameObject prefab = ZNetScene.instance.GetPrefab("Serpent"); if ((Object)(object)prefab == (Object)null) { _nextSpawnAttemptTime = now + (double)_spawnRetrySeconds.Value; _logger.LogError((object)"Project Snake Eyes cannot start: current ZNetScene has no prefab named 'Serpent'."); return; } GameObject val2 = Object.Instantiate<GameObject>(prefab, point, Quaternion.identity); Character component = val2.GetComponent<Character>(); MonsterAI component2 = val2.GetComponent<MonsterAI>(); ZNetView component3 = val2.GetComponent<ZNetView>(); if ((Object)(object)component == (Object)null || (Object)(object)component2 == (Object)null || (Object)(object)component3 == (Object)null || !component3.IsValid() || component3.GetZDO() == null) { Object.Destroy((Object)(object)val2); _nextSpawnAttemptTime = now + (double)_spawnRetrySeconds.Value; _logger.LogError((object)"Project Snake Eyes aborted cleanly: instantiated Serpent lacked Character, MonsterAI, or a valid ZNetView/ZDO."); return; } ZDO zDO = component3.GetZDO(); zDO.Set("Elwood.Hellworld.WhiteWhale", true); zDO.Set("Elwood.Hellworld.WhiteWhaleExpiry", (long)Math.Ceiling(now + (double)_encounterDurationSeconds.Value)); zDO.Set("Elwood.Hellworld.WhiteWhaleSchema", 1); zDO.Set("Elwood.Hellworld.WhiteWhaleDeathHandled", false); _tracker.Track(zDO); TryInitializeCharacter(component); component.SetLevel(3); SeaState seaState = GetSeaState(target.GetPlayerID()); seaState.Disturbance = 0f; seaState.LastLoggedBucket = 0; seaState.NextLogTime = 0.0; seaState.LastActivity = "encounter spawned; disturbance reset"; _nextSpawnAttemptTime = 0.0; ManualLogSource logger = _logger; string[] obj2 = new string[21] { "PROJECT SNAKE EYES: spawned regularly named Serpent for ", target.GetPlayerName(), " at ", null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null }; val = point; obj2[3] = ((object)(*(Vector3*)(&val))/*cast due to .constrained prefix*/).ToString(); obj2[4] = "; Character level="; obj2[5] = component.GetLevel().ToString(); obj2[6] = ", vanillaAIUntouched=true, huntPlayer="; obj2[7] = ((BaseAI)component2).HuntPlayer().ToString(); obj2[8] = ", alerted="; obj2[9] = ((BaseAI)component2).IsAlerted().ToString(); obj2[10] = ", avoidLand="; obj2[11] = component2.m_avoidLand.ToString(); obj2[12] = ", attackPlayerObjects="; obj2[13] = component2.m_attackPlayerObjects.ToString(); obj2[14] = ", distanceFromPlayer="; obj2[15] = Vector3.Distance(((Component)target).transform.position, point).ToString("0.0", CultureInfo.InvariantCulture); obj2[16] = "m, horizontalDistance="; obj2[17] = num.ToString("0.0", CultureInfo.InvariantCulture); obj2[18] = "m, depth="; obj2[19] = waterDepth.ToString("0.0", CultureInfo.InvariantCulture); obj2[20] = "m."; logger.LogWarning((object)string.Concat(obj2)); } private static bool TryFindWaterPosition(Vector3 center, float minDistance, float maxDistance, int attempts, out Vector3 point) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0048: 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_0059: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_009d: 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) float waterLevel = ZoneSystem.instance.m_waterLevel; float num3 = default(float); for (int i = 0; i < attempts; i++) { float num = Random.Range(0f, 360f) * ((float)Math.PI / 180f); float num2 = Random.Range(minDistance, maxDistance); Vector3 val = center + new Vector3(Mathf.Sin(num), 0f, Mathf.Cos(num)) * num2; if (ZoneSystem.instance.GetGroundHeight(val, ref num3)) { float num4 = waterLevel - num3; val.y = num3 + 0.5f; if (!(num4 < _minimumWaterDepth.Value) && !(val.y >= waterLevel) && !ZoneSystem.instance.IsBlocked(val)) { point = val; return true; } } } point = Vector3.zero; return false; } private static float HorizontalDistance(Vector3 first, Vector3 second) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) float num = first.x - second.x; float num2 = first.z - second.z; return Mathf.Sqrt(num * num + num2 * num2); } private static bool IsNearWater(Vector3 center, float radius, float minimumDepth) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: 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_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //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) float waterLevel = ZoneSystem.instance.m_waterLevel; float num2 = default(float); for (int i = 0; i <= 8; i++) { Vector3 val = center; if (i > 0) { float num = (float)(i - 1) / 8f * (float)Math.PI * 2f; val += new Vector3(Mathf.Sin(num), 0f, Mathf.Cos(num)) * radius; } if (ZoneSystem.instance.GetGroundHeight(val, ref num2) && waterLevel - num2 >= minimumDepth) { return true; } } return false; } private static float GetWaterDepth(Vector3 position) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) float num = default(float); if (!ZoneSystem.instance.GetGroundHeight(position, ref num)) { return -1f; } return ZoneSystem.instance.m_waterLevel - num; } private static bool IsAboardShip(Player player) { if (!((Object)(object)player.GetControlledShip() != (Object)null)) { return (Object)(object)((Character)player).GetStandingOnShip() != (Object)null; } return true; } private static bool IsEncounterMaritime(Player player) { if (!((Character)player).IsSwimming()) { return IsAboardShip(player); } return true; } private static SeaState GetSeaState(long playerId) { if (!SeaStates.TryGetValue(playerId, out var value)) { value = new SeaState(); SeaStates.Add(playerId, value); } return value; } private static void AddDisturbance(SeaState state, float amount) { float num = Mathf.Max(1f, _disturbanceThreshold.Value) * 2f; state.Disturbance = Mathf.Clamp(state.Disturbance + amount, 0f, num); } private static bool IsWhiteWhale(Character character) { ZNetView component = ((Component)character).GetComponent<ZNetView>(); if ((Object)(object)component != (Object)null && component.IsValid() && component.GetZDO() != null) { return component.GetZDO().GetBool("Elwood.Hellworld.WhiteWhale", false); } return false; } private static Character FindLoadedCharacter(ZDO zdo) { if (zdo == null || (Object)(object)ZNetScene.instance == (Object)null) { return null; } ZNetView val = ZNetScene.instance.FindInstance(zdo); if (!((Object)(object)val != (Object)null)) { return null; } return ((Component)val).GetComponent<Character>(); } private static void DestroyAllMarkedWhiteWhales(string reason) { if (_tracker != null) { _tracker.ResolveTracked(TrackedEncounterZdos); for (int num = TrackedEncounterZdos.Count - 1; num >= 0; num--) { DestroyMarkedZdo(TrackedEncounterZdos[num], reason); } } } private static void DestroyMarkedZdo(ZDO zdo, string reason) { bool flag = _tracker != null && _tracker.Destroy(zdo); DebugLog("Destroyed " + (flag ? "loaded Project Snake Eyes instance" : "unloaded Project Snake Eyes ZDO") + ": " + reason + "."); } private static void DebugLog(string message) { if (_debugLogging != null && _debugLogging.Value && _logger != null) { _logger.LogInfo((object)message); } } private static void LogAttemptBlocked(string reasonKey, string detail, double now) { if (!string.Equals(_lastAttemptBlockReason, reasonKey, StringComparison.Ordinal) || now >= _nextAttemptBlockLogTime) { DebugLog("Project Snake Eyes encounter attempt blocked: " + detail + "."); _lastAttemptBlockReason = reasonKey; _nextAttemptBlockLogTime = now + 30.0; } } }