using 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 _trackedIds = new HashSet(); private readonly List _recoveryBuffer = new List(); private readonly List _staleIds = new List(); 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 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 warbandsEnabled = ((BaseUnityPlugin)this).Config.Bind("Warbands", "WarbandsEnabled", true, "Give each qualifying war-boss creature one entourage when that leader is first initialized."); ConfigEntry followersPerLeader = ((BaseUnityPlugin)this).Config.Bind("Warbands", "FollowersPerLeader", 3, new ConfigDescription("Base follower count. One-star leaders add one; two-star leaders add two.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 12), Array.Empty())); ConfigEntry spawnRadiusMin = ((BaseUnityPlugin)this).Config.Bind("Warbands", "FollowerSpawnRadiusMin", 4f, new ConfigDescription("Minimum horizontal follower spawn radius in meters.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 30f), Array.Empty())); ConfigEntry spawnRadiusMax = ((BaseUnityPlugin)this).Config.Bind("Warbands", "FollowerSpawnRadiusMax", 8f, new ConfigDescription("Maximum horizontal follower spawn radius in meters.", (AcceptableValueBase)(object)new AcceptableValueRange(2f, 50f), Array.Empty())); ConfigEntry debugLogging = ((BaseUnityPlugin)this).Config.Bind("Debug", "DebugLogging", false, "Log concise warband detection, marker, placement, and spawn diagnostics."); ConfigEntry enabled = ((BaseUnityPlugin)this).Config.Bind("Mountain Strongholds", "DvergrOutpostsEnabled", true, "Clone the three intact vanilla Dvergr guard towers into Mountain world generation."); ConfigEntry quantityPerVariant = ((BaseUnityPlugin)this).Config.Bind("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(1, 500), Array.Empty())); ConfigEntry minimumSeparation = ((BaseUnityPlugin)this).Config.Bind("Mountain Strongholds", "DvergrOutpostMinimumSeparation", 300f, new ConfigDescription("Shared minimum separation in metres between all Bifrost Breakout Mountain Dvergr outposts.", (AcceptableValueBase)(object)new AcceptableValueRange(128f, 1000f), Array.Empty())); 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 _enabled; private static ConfigEntry _quantityPerVariant; private static ConfigEntry _minimumSeparation; internal static void Initialize(ManualLogSource log, ConfigEntry enabled, ConfigEntry quantityPerVariant, ConfigEntry 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 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 TrackedZdos = new List(); 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 _enabled; private static ConfigEntry _tickSeconds; private static ConfigEntry _spawnCheckIntervalSeconds; private static ConfigEntry _spawnChancePercent; private static ConfigEntry _spawnDistanceMin; private static ConfigEntry _spawnDistanceMax; private static ConfigEntry _minimumWaterDepth; private static ConfigEntry _spawnCandidateAttempts; private static ConfigEntry _shoreInteractionRadius; private static ConfigEntry _localCapRadius; private static ConfigEntry _lifetimeSeconds; private static ConfigEntry _playerRetentionDistance; private static ConfigEntry _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("Bifrost Shoreline Bonemaws", "Enabled", true, "Spawn conservative, mod-owned vanilla Bonemaws in coastal water without changing their AI or faction."); _tickSeconds = config.Bind("Bifrost Shoreline Bonemaws", "TickSeconds", 10f, new ConfigDescription("Seconds between lightweight tracking and cleanup checks.", (AcceptableValueBase)(object)new AcceptableValueRange(2f, 60f), Array.Empty())); _spawnCheckIntervalSeconds = config.Bind("Bifrost Shoreline Bonemaws", "SpawnCheckIntervalSeconds", 60f, new ConfigDescription("Seconds between bounded coastal spawn checks.", (AcceptableValueBase)(object)new AcceptableValueRange(15f, 1800f), Array.Empty())); _spawnChancePercent = config.Bind("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(0f, 100f), Array.Empty())); _spawnDistanceMin = config.Bind("Bifrost Shoreline Bonemaws", "SpawnDistanceMin", 15f, new ConfigDescription("Minimum horizontal spawn distance from the selected active coastal player.", (AcceptableValueBase)(object)new AcceptableValueRange(8f, 100f), Array.Empty())); _spawnDistanceMax = config.Bind("Bifrost Shoreline Bonemaws", "SpawnDistanceMax", 30f, new ConfigDescription("Maximum horizontal spawn distance from the selected active coastal player.", (AcceptableValueBase)(object)new AcceptableValueRange(10f, 150f), Array.Empty())); _minimumWaterDepth = config.Bind("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(2f, 30f), Array.Empty())); _spawnCandidateAttempts = config.Bind("Bifrost Shoreline Bonemaws", "SpawnCandidateAttempts", 32, new ConfigDescription("Maximum bounded water samples per eligible coastal player.", (AcceptableValueBase)(object)new AcceptableValueRange(4, 64), Array.Empty())); _shoreInteractionRadius = config.Bind("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(10f, 60f), Array.Empty())); _localCapRadius = config.Bind("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(50f, 500f), Array.Empty())); _lifetimeSeconds = config.Bind("Bifrost Shoreline Bonemaws", "LifetimeSeconds", 600f, new ConfigDescription("Maximum lifetime of a mod-owned shoreline Bonemaw.", (AcceptableValueBase)(object)new AcceptableValueRange(60f, 3600f), Array.Empty())); _playerRetentionDistance = config.Bind("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(50f, 1000f), Array.Empty())); _awayCleanupDelaySeconds = config.Bind("Bifrost Shoreline Bonemaws", "AwayCleanupDelaySeconds", 60f, new ConfigDescription("Continuous player-absence time required before early cleanup.", (AcceptableValueBase)(object)new AcceptableValueRange(15f, 600f), Array.Empty())); _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(); 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 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(prefab, spawnPoint, Quaternion.identity); Character component = val.GetComponent(); MonsterAI component2 = val.GetComponent(); ZNetView component3 = val.GetComponent(); 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 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(); 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 Definitions = new Dictionary(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 _warbandsEnabled; private static ConfigEntry _followersPerLeader; private static ConfigEntry _spawnRadiusMin; private static ConfigEntry _spawnRadiusMax; private static ConfigEntry _debugLogging; private static bool _definitionsValid; internal static void Initialize(ManualLogSource log, ConfigEntry warbandsEnabled, ConfigEntry followersPerLeader, ConfigEntry spawnRadiusMin, ConfigEntry spawnRadiusMax, ConfigEntry 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(); 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 list = BuildFollowerRequests(value, leader); Dictionary 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 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(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(); 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 list = new List(); 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 BuildFollowerRequests(WarbandDefinition definition, Character leader) { List list = new List(); 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 ResolveFollowerPrefabs(List requests) { ZNetScene instance = ZNetScene.instance; if ((Object)(object)instance == (Object)null) { return null; } Dictionary dictionary = new Dictionary(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(); 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(); if ((Object)(object)component2 != (Object)null) { component2.SetLevel(Mathf.Clamp(level, 1, 3)); } Humanoid component3 = follower.GetComponent(); 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 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 list = new List(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() != (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 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 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 SeaStates = new Dictionary(); private static readonly List MissingPlayers = new List(); private static readonly List TrackedEncounterZdos = new List(); 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 _enabled; private static ConfigEntry _tickSeconds; private static ConfigEntry _disturbanceThreshold; private static ConfigEntry _swimRate; private static ConfigEntry _boatRate; private static ConfigEntry _wadingRate; private static ConfigEntry _shoreRate; private static ConfigEntry _quietDecayRate; private static ConfigEntry _shoreBuildBurst; private static ConfigEntry _shorelineRadius; private static ConfigEntry _spawnDistanceMin; private static ConfigEntry _spawnDistanceMax; private static ConfigEntry _minimumWaterDepth; private static ConfigEntry _spawnCandidateAttempts; private static ConfigEntry _spawnRetrySeconds; private static ConfigEntry _encounterDurationSeconds; private static ConfigEntry _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("White Whale", "Enabled", true, "Enable Project Snake Eyes: a recurring, regularly named two-star vanilla Serpent encounter."); _tickSeconds = config.Bind("White Whale", "TickSeconds", 5f, new ConfigDescription("Seconds between lightweight Sea Disturbance and encounter checks.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 30f), Array.Empty())); _disturbanceThreshold = config.Bind("White Whale", "DisturbanceThreshold", 100f, new ConfigDescription("Sea Disturbance required before Project Snake Eyes attempts an encounter.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 1000f), Array.Empty())); _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("White Whale", "ShoreBuildDisturbance", 10f, new ConfigDescription("One-time disturbance added when a piece is placed near sampled shoreline water.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1000f), Array.Empty())); _shorelineRadius = config.Bind("White Whale", "ShorelineSampleRadius", 25f, new ConfigDescription("Radius used by the bounded shoreline-water samples.", (AcceptableValueBase)(object)new AcceptableValueRange(5f, 80f), Array.Empty())); _spawnDistanceMin = config.Bind("White Whale", "SpawnDistanceMin", 10f, new ConfigDescription("Minimum water-spawn distance from the selected swimming/boating player.", (AcceptableValueBase)(object)new AcceptableValueRange(5f, 150f), Array.Empty())); _spawnDistanceMax = config.Bind("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(5f, 200f), Array.Empty())); _minimumWaterDepth = config.Bind("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(0.75f, 30f), Array.Empty())); _spawnCandidateAttempts = config.Bind("White Whale", "SpawnCandidateAttempts", 24, new ConfigDescription("Maximum bounded water samples per encounter attempt.", (AcceptableValueBase)(object)new AcceptableValueRange(4, 64), Array.Empty())); _spawnRetrySeconds = config.Bind("White Whale", "FailedSpawnRetrySeconds", 30f, new ConfigDescription("Delay after no valid water is found; prevents tight retry loops.", (AcceptableValueBase)(object)new AcceptableValueRange(10f, 300f), Array.Empty())); _encounterDurationSeconds = config.Bind("White Whale", "EncounterDurationSeconds", 300f, new ConfigDescription("Maximum lifetime before the marked encounter is removed without manipulating its AI.", (AcceptableValueBase)(object)new AcceptableValueRange(30f, 1800f), Array.Empty())); _debugLogging = config.Bind("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(); 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(); 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 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("White Whale", key, value, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange(0f, 100f), Array.Empty())); } 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("Elwood.Hellworld.WhiteWhaleShoreBuild", (Action)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 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 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(prefab, point, Quaternion.identity); Character component = val2.GetComponent(); MonsterAI component2 = val2.GetComponent(); ZNetView component3 = val2.GetComponent(); 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(); 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(); } 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; } } }