Decompiled source of Bifrost Breakout v0.1.0

BepInEx/plugins/Bifrost-Breakout/BifrostBreakout.dll

Decompiled 3 hours ago
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<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;
		}
	}
}