Decompiled source of OceanAdventures v1.0.0

BepInEx/plugins/OceanAdventures/OceanAdventures.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Jotunn.Entities;
using Jotunn.Managers;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using OceanAdventures.Config;
using OceanAdventures.Core;
using OceanAdventures.Runtime;
using Splatform;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("0.0.0.0")]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
}
namespace OceanAdventures
{
	[BepInPlugin("com.oceanadventures.core", "OceanAdventures", "1.0.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	public sealed class OceanAdventuresPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.oceanadventures.core";

		public const string PluginName = "OceanAdventures";

		public const string PluginVersion = "1.0.0";

		internal static ManualLogSource Log;

		private Harmony? harmony;

		private OceanAdventuresRuntime? runtime;

		private void Awake()
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Expected O, but got Unknown
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			OceanAdventuresConfig oceanadventuresConfig = new OceanAdventuresConfig(((BaseUnityPlugin)this).Config);
			harmony = new Harmony("com.oceanadventures.core");
			try
			{
				harmony.PatchAll(typeof(OceanAdventuresShipPatches).Assembly);
				Log.LogInfo((object)("Ghostship physics patch installed: native Ship physics bypassed only for registered ghostships (registered at runtime: " + OceanAdventuresShipPatches.RegisteredCount + ")."));
				Log.LogInfo((object)"Ghostship lifecycle patch installed: native keep lists preserved; active server ghosts added when necessary.");
			}
			catch (Exception ex)
			{
				Log.LogError((object)("Required OceanAdventures patches could not be installed; encounters and contracts are disabled for this session: " + ex));
				try
				{
					harmony.UnpatchSelf();
				}
				catch (Exception ex2)
				{
					Log.LogError((object)("Partial OceanAdventures patches could not be removed: " + ex2));
				}
				harmony = null;
				((Behaviour)this).enabled = false;
				return;
			}
			GameObject val = new GameObject("OceanAdventures.Runtime");
			Object.DontDestroyOnLoad((Object)(object)val);
			runtime = val.AddComponent<OceanAdventuresRuntime>();
			runtime.Initialize(oceanadventuresConfig);
			try
			{
				CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new OceanAdventuresPreviewCommand(runtime));
				CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new OceanAdventuresTestEventCommand(runtime));
				CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new OceanAdventuresClearEventsCommand(runtime));
				CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new OceanAdventuresGhostStatusCommand(runtime));
				CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new OceanAdventuresEventStatusCommand(runtime));
				CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new OceanAdventuresRewardStatusCommand(runtime));
			}
			catch (Exception ex3)
			{
				Log.LogWarning((object)("OceanAdventures test commands could not be registered: " + ex3.Message));
			}
			Log.LogInfo((object)"OceanAdventures 1.0.0 loaded.");
		}

		private void OnDestroy()
		{
			Harmony? obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			if ((Object)(object)runtime != (Object)null)
			{
				Object.Destroy((Object)(object)((Component)runtime).gameObject);
			}
		}
	}
}
namespace OceanAdventures.Runtime
{
	internal enum ObjectiveUpdateResult
	{
		Updated,
		ContractMissing,
		SaveFailed
	}
	internal sealed class ContractService
	{
		private readonly WorldStateStore store;

		private readonly OceanAdventuresConfig config;

		private readonly MapMarkerService markers;

		private readonly RewardService rewards;

		private NetworkBridge? network;

		private readonly HashSet<string> receivedProfiles = new HashSet<string>();

		private readonly Dictionary<string, float> nextMissingRecipeWarning = new Dictionary<string, float>();

		public int OfferAvailabilityMask
		{
			get
			{
				if (!config.ContractsEnabled)
				{
					return 0;
				}
				return Enum.GetValues(typeof(EncounterType)).Cast<EncounterType>().Where(config.IsEnabled)
					.Aggregate(0, (int mask, EncounterType type) => mask | (1 << (int)type));
			}
		}

		public bool HasAuthoritativeProfile(object player)
		{
			string playerId = ValheimReflection.GetPlayerId(player);
			if (CharacterIdentityRules.IsStable(playerId))
			{
				if (!ValheimReflection.IsServer())
				{
					return receivedProfiles.Contains(playerId);
				}
				return true;
			}
			return false;
		}

		public void SyncLocalMarker()
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			MonoBehaviour localPlayer = LocalPlayerService.GetLocalPlayer();
			ContractRecord contractRecord = (((Object)(object)localPlayer != (Object)null && HasAuthoritativeProfile(localPlayer)) ? GetProfile(localPlayer).ActiveContract : null);
			if (contractRecord == null)
			{
				markers.ClearMarker();
			}
			else
			{
				markers.SetMarker(contractRecord.GetTarget());
			}
		}

		public void ClearLocalState()
		{
			markers.ClearMarker();
			receivedProfiles.Clear();
		}

		public ContractService(WorldStateStore store, OceanAdventuresConfig config, MapMarkerService markers, RewardService rewards)
		{
			this.store = store;
			this.config = config;
			this.markers = markers;
			this.rewards = rewards;
		}

		public void AttachNetwork(NetworkBridge bridge)
		{
			network = bridge;
		}

		public IReadOnlyList<ContractOffer> GetOffers(object player)
		{
			if (!config.ContractsEnabled || !HasAuthoritativeProfile(player))
			{
				return Array.Empty<ContractOffer>();
			}
			PlayerVoyageProfile profile = store.GetProfile(ValheimReflection.GetPlayerId(player));
			return ContractCatalog.CreateOffers(profile.OfferSeed + profile.CompletedContracts, config.IsEnabled);
		}

		public PlayerVoyageProfile GetProfile(object player)
		{
			return store.GetProfile(ValheimReflection.GetPlayerId(player));
		}

		public ObjectiveUpdateResult TryUpdateObjective(string ownerId, string contractId, Vector3 target)
		{
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			if (!ValheimReflection.IsServer())
			{
				return ObjectiveUpdateResult.ContractMissing;
			}
			if (string.IsNullOrEmpty(ownerId))
			{
				return ObjectiveUpdateResult.Updated;
			}
			ContractRecord activeContract = store.GetProfile(ownerId).ActiveContract;
			if (activeContract == null || activeContract.Id != contractId)
			{
				return ObjectiveUpdateResult.ContractMissing;
			}
			if (!ContractRules.TryMoveTarget(activeContract, target, store.SaveNow))
			{
				return ObjectiveUpdateResult.SaveFailed;
			}
			MonoBehaviour val = ((IEnumerable<MonoBehaviour>)ValheimReflection.FindByTypeName("Player")).FirstOrDefault((Func<MonoBehaviour, bool>)((MonoBehaviour candidate) => ValheimReflection.GetPlayerId(candidate) == ownerId));
			if ((Object)(object)val == (Object)null)
			{
				return ObjectiveUpdateResult.Updated;
			}
			if (IsLocalCharacter(val))
			{
				markers.SetMarker(target);
			}
			network?.SendPlayerSnapshot(val);
			return ObjectiveUpdateResult.Updated;
		}

		public void PublishProfile(string ownerId)
		{
			if (ValheimReflection.IsServer() && !string.IsNullOrEmpty(ownerId))
			{
				MonoBehaviour val = ((IEnumerable<MonoBehaviour>)ValheimReflection.FindByTypeName("Player")).FirstOrDefault((Func<MonoBehaviour, bool>)((MonoBehaviour candidate) => ValheimReflection.GetPlayerId(candidate) == ownerId));
				if ((Object)(object)val != (Object)null)
				{
					network?.SendPlayerSnapshot(val);
				}
			}
		}

		public bool Accept(object player, EncounterType type, Vector3 origin)
		{
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: 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)
			//IL_0178: Unknown result type (might be due to invalid IL or missing references)
			//IL_017d: Unknown result type (might be due to invalid IL or missing references)
			//IL_019c: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			//IL_0148: Unknown result type (might be due to invalid IL or missing references)
			//IL_0153: Unknown result type (might be due to invalid IL or missing references)
			//IL_0158: Unknown result type (might be due to invalid IL or missing references)
			//IL_0242: Unknown result type (might be due to invalid IL or missing references)
			//IL_0275: Unknown result type (might be due to invalid IL or missing references)
			if (!CharacterIdentityRules.IsStable(ValheimReflection.GetPlayerId(player)))
			{
				return false;
			}
			if (!config.ContractsEnabled || !config.IsEnabled(type) || !Enum.IsDefined(typeof(EncounterType), type))
			{
				return false;
			}
			if (!ValheimReflection.IsServer())
			{
				network?.SendAccept(type, origin);
				return true;
			}
			PlayerVoyageProfile profile = GetProfile(player);
			if (!LedgerAccess.CanUse(player))
			{
				Notify(player, OceanAdventuresNoticeCode.LedgerOutOfRange);
				return false;
			}
			if (profile.ActiveContract != null)
			{
				Notify(player, OceanAdventuresNoticeCode.AlreadyActiveContract);
				return false;
			}
			if (!GetOffers(player).Any((ContractOffer offer) => offer.Type == type))
			{
				Notify(player, OceanAdventuresNoticeCode.OfferNoLongerAvailable);
				return false;
			}
			Vector3 departure = ValheimReflection.GetPosition(player);
			Vector3 forward = ValheimReflection.GetForward(player);
			MonoBehaviour val = (from ship in ValheimReflection.FindByTypeName("Ship").Where(ValheimReflection.IsEligibleShip).Where(ValheimReflection.IsOnNavigableWater)
				where Vector3.Distance(((Component)ship).transform.position, departure) <= 100f
				orderby Vector3.Distance(((Component)ship).transform.position, departure)
				select ship).FirstOrDefault();
			if ((Object)(object)val != (Object)null)
			{
				departure = ((Component)val).transform.position;
				forward = ((Component)val).transform.forward;
			}
			forward = Quaternion.Euler(0f, Random.Range(0f, 360f), 0f) * forward;
			float distance = Random.Range(config.MinimumContractDistance, config.MaximumContractSearchDistance);
			if (!ValheimReflection.TryFindWaterPosition(departure, forward, distance, out var position, requireWaterCorridor: false, ValheimReflection.RequiredEncounterLandClearance(type), diagnostics: false, ValheimReflection.FallbackEncounterLandClearance(type), config.MinimumContractDistance, config.MaximumContractSearchDistance))
			{
				Notify(player, OceanAdventuresNoticeCode.NoOpenWaterRoute);
				return false;
			}
			profile.ActiveContract = new ContractRecord
			{
				Id = Guid.NewGuid().ToString("N"),
				Type = type,
				OwnerId = profile.PlayerId,
				CreatedUtcTicks = DateTime.UtcNow.Ticks,
				State = EncounterState.Dormant
			};
			profile.ActiveContract.SetTarget(position);
			if (!store.SaveNow())
			{
				profile.ActiveContract = null;
				Notify(player, OceanAdventuresNoticeCode.ContractSaveFailed);
				return false;
			}
			if (IsLocalCharacter(player))
			{
				markers.SetMarker(position);
			}
			Notify(player, OceanAdventuresNoticeCode.ContractAccepted);
			return true;
		}

		public bool Cancel(object player)
		{
			if (!CharacterIdentityRules.IsStable(ValheimReflection.GetPlayerId(player)))
			{
				return false;
			}
			if (!ValheimReflection.IsServer())
			{
				network?.SendCancel();
				return true;
			}
			PlayerVoyageProfile profile = GetProfile(player);
			if (!LedgerAccess.CanUse(player))
			{
				Notify(player, OceanAdventuresNoticeCode.LedgerOutOfRange);
				return false;
			}
			if (profile.ActiveContract == null)
			{
				Notify(player, OceanAdventuresNoticeCode.NoActiveContract);
				return false;
			}
			ContractRecord activeContract = profile.ActiveContract;
			profile.ActiveContract = null;
			if (!store.SaveNow())
			{
				profile.ActiveContract = activeContract;
				Notify(player, OceanAdventuresNoticeCode.ContractSaveFailed);
				return false;
			}
			if (IsLocalCharacter(player))
			{
				markers.ClearMarker();
			}
			Notify(player, OceanAdventuresNoticeCode.ContractCancelled);
			return true;
		}

		public bool Complete(object player, EncounterType type, string encounterContractId)
		{
			if (!CharacterIdentityRules.IsStable(ValheimReflection.GetPlayerId(player)))
			{
				return false;
			}
			if (!ValheimReflection.IsServer())
			{
				return false;
			}
			if (type == EncounterType.GhostShip || type == EncounterType.Shipwreck || type == EncounterType.Smugglers)
			{
				return false;
			}
			PlayerVoyageProfile profile = GetProfile(player);
			if (!ContractRules.CanComplete(profile.ActiveContract, type, encounterContractId))
			{
				Player val = (Player)((player is Player) ? player : null);
				if (val != null)
				{
					network?.SendNotice(val, OceanAdventuresNoticeCode.ContractNoLongerAvailable);
				}
				return false;
			}
			if (VanillaRewardCatalog.Entries.Count == 0)
			{
				if (!nextMissingRecipeWarning.TryGetValue(profile.PlayerId, out var value) || Time.time >= value)
				{
					nextMissingRecipeWarning[profile.PlayerId] = Time.time + 30f;
					OceanAdventuresPlugin.Log.LogWarning((object)"Contract completion is waiting because the Vanilla equipment catalog was not captured; the contract and reward journal were not changed.");
					Player val2 = (Player)((player is Player) ? player : null);
					if (val2 != null)
					{
						network?.SendNotice(val2, OceanAdventuresNoticeCode.RewardCatalogUnavailable);
					}
				}
				return false;
			}
			List<string> knownEquipmentRecipes = new List<string>(profile.KnownEquipmentRecipes);
			List<RewardGrant> list = rewards.CreateContractRewards(player, profile);
			if (list.Count > 0)
			{
				list.AddRange(rewards.CreateEncounterLoot(type, config.SmugglerCrewSize, profile, profile.ActiveContract.Id));
			}
			if (list.Count == 0)
			{
				profile.KnownEquipmentRecipes = knownEquipmentRecipes;
				if (!nextMissingRecipeWarning.TryGetValue(profile.PlayerId, out var value2) || Time.time >= value2)
				{
					nextMissingRecipeWarning[profile.PlayerId] = Time.time + 30f;
					OceanAdventuresPlugin.Log.LogWarning((object)"Contract completion is waiting for a personally known Vanilla equipment recipe; no reward or completion was committed.");
					Player val3 = (Player)((player is Player) ? player : null);
					if (val3 != null)
					{
						network?.SendNotice(val3, OceanAdventuresNoticeCode.NoKnownEquipmentRecipe);
					}
				}
				return false;
			}
			nextMissingRecipeWarning.Remove(profile.PlayerId);
			ContractRecord activeContract = profile.ActiveContract;
			int count = profile.Rewards.Count;
			RewardService.Append(profile, list);
			profile.ActiveContract = null;
			profile.CompletedContracts++;
			if (!store.SaveNow())
			{
				profile.ActiveContract = activeContract;
				profile.CompletedContracts--;
				profile.KnownEquipmentRecipes = knownEquipmentRecipes;
				profile.Rewards.RemoveRange(count, list.Count);
				Player val4 = (Player)((player is Player) ? player : null);
				if (val4 != null)
				{
					network?.SendNotice(val4, OceanAdventuresNoticeCode.RewardSaveFailed);
				}
				return false;
			}
			if (IsLocalCharacter(player))
			{
				markers.ClearMarker();
			}
			network?.SendPlayerSnapshot(player);
			OceanAdventuresPlugin.Log.LogInfo((object)("Contract reward queued: " + string.Join(", ", list.Select((RewardGrant item) => item.Prefab + " x" + item.Amount + ((item.Quality > 1) ? (" (quality " + item.Quality + ")") : ""))) + "."));
			return true;
		}

		public bool TryPrepareChest(Player player, EncounterType type, string contractId, out List<RewardGrant> remaining)
		{
			remaining = new List<RewardGrant>();
			if (!ValheimReflection.IsServer() || (type != EncounterType.Shipwreck && type != EncounterType.Smugglers && type != EncounterType.GhostShip))
			{
				return false;
			}
			PlayerVoyageProfile profile = GetProfile(player);
			ContractRecord activeContract = profile.ActiveContract;
			if (!ContractRules.CanComplete(activeContract, type, contractId))
			{
				return false;
			}
			if (activeContract.ChestRewardsPrepared)
			{
				remaining = activeContract.ChestRemaining.Select(CopyGrant).ToList();
				return true;
			}
			if (VanillaRewardCatalog.Entries.Count == 0)
			{
				NotifyChestIssue(player, OceanAdventuresNoticeCode.RewardCatalogUnavailable);
				return false;
			}
			List<string> knownEquipmentRecipes = new List<string>(profile.KnownEquipmentRecipes);
			List<RewardGrant> list = rewards.CreateContractRewards(player, profile);
			if (list.Count == 0)
			{
				profile.KnownEquipmentRecipes = knownEquipmentRecipes;
				NotifyChestIssue(player, OceanAdventuresNoticeCode.NoKnownEquipmentRecipe);
				return false;
			}
			list.AddRange(rewards.CreateEncounterLoot(type, config.SmugglerCrewSize, profile, activeContract.Id));
			activeContract.ChestRewardsPrepared = true;
			activeContract.ChestRemaining = list.Select(CopyGrant).ToList();
			if (!store.SaveNow())
			{
				activeContract.ChestRewardsPrepared = false;
				activeContract.ChestRemaining.Clear();
				profile.KnownEquipmentRecipes = knownEquipmentRecipes;
				NotifyChestIssue(player, OceanAdventuresNoticeCode.RewardSaveFailed);
				return false;
			}
			remaining = activeContract.ChestRemaining.Select(CopyGrant).ToList();
			return true;
		}

		public bool SaveChestRemaining(string ownerId, string contractId, List<RewardGrant> remaining)
		{
			if (!ValheimReflection.IsServer())
			{
				return false;
			}
			ContractRecord activeContract = store.GetProfile(ownerId).ActiveContract;
			if (activeContract == null || activeContract.Id != contractId || !activeContract.ChestRewardsPrepared)
			{
				return false;
			}
			List<RewardGrant> chestRemaining = activeContract.ChestRemaining;
			activeContract.ChestRemaining = remaining.Select(CopyGrant).ToList();
			if (store.SaveNow())
			{
				return true;
			}
			activeContract.ChestRemaining = chestRemaining;
			return false;
		}

		public bool CompleteChest(Player player, EncounterType type, string contractId)
		{
			if (!ValheimReflection.IsServer())
			{
				return false;
			}
			PlayerVoyageProfile profile = GetProfile(player);
			ContractRecord activeContract = profile.ActiveContract;
			if (!ContractRules.CanComplete(activeContract, type, contractId) || !activeContract.ChestRewardsPrepared || activeContract.ChestRemaining.Count != 0)
			{
				return false;
			}
			profile.ActiveContract = null;
			profile.CompletedContracts++;
			if (!store.SaveNow())
			{
				profile.ActiveContract = activeContract;
				profile.CompletedContracts--;
				Notify(player, OceanAdventuresNoticeCode.RewardSaveFailed);
				return false;
			}
			if (IsLocalCharacter(player))
			{
				markers.ClearMarker();
			}
			network?.SendPlayerSnapshot(player);
			return true;
		}

		private static RewardGrant CopyGrant(RewardGrant item)
		{
			return new RewardGrant
			{
				Prefab = item.Prefab,
				Amount = item.Amount,
				Quality = item.Quality
			};
		}

		private void NotifyChestIssue(Player player, OceanAdventuresNoticeCode code)
		{
			string playerId = ValheimReflection.GetPlayerId(player);
			if (!nextMissingRecipeWarning.TryGetValue(playerId, out var value) || !(Time.time < value))
			{
				nextMissingRecipeWarning[playerId] = Time.time + 30f;
				Notify(player, code);
			}
		}

		private static bool IsLocalCharacter(object player)
		{
			MonoBehaviour localPlayer = LocalPlayerService.GetLocalPlayer();
			if ((Object)(object)localPlayer != (Object)null)
			{
				return ValheimReflection.GetPlayerId(localPlayer) == ValheimReflection.GetPlayerId(player);
			}
			return false;
		}

		private void Notify(object player, OceanAdventuresNoticeCode code)
		{
			Player val = (Player)((player is Player) ? player : null);
			if (val != null)
			{
				network?.SendNotice(val, code);
			}
		}

		public void HandleServerRequest(long sender, object player, OceanAdventuresNetworkOp op, ZPackage package)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			switch (op)
			{
			case OceanAdventuresNetworkOp.Accept:
			{
				EncounterType type = (EncounterType)package.ReadInt();
				package.ReadSingle();
				package.ReadSingle();
				package.ReadSingle();
				Accept(player, type, ValheimReflection.GetPosition(player));
				break;
			}
			case OceanAdventuresNetworkOp.Cancel:
				Cancel(player);
				break;
			}
			network?.SendSnapshot(sender, ValheimReflection.GetPlayerId(player));
		}

		public void WriteSnapshot(ZPackage package, string playerId)
		{
			PlayerVoyageProfile profile = store.GetProfile(playerId);
			package.Write(playerId);
			package.Write(profile.CompletedContracts);
			package.Write(profile.OfferSeed);
			package.Write(profile.ActiveContract != null);
			if (profile.ActiveContract != null)
			{
				package.Write(profile.ActiveContract.Id);
				package.Write((int)profile.ActiveContract.Type);
				package.Write((int)profile.ActiveContract.State);
				package.Write(profile.ActiveContract.TargetX);
				package.Write(profile.ActiveContract.TargetY);
				package.Write(profile.ActiveContract.TargetZ);
			}
		}

		public void ApplySnapshot(ZPackage package)
		{
			string text = package.ReadString();
			MonoBehaviour localPlayer = LocalPlayerService.GetLocalPlayer();
			if ((Object)(object)localPlayer == (Object)null || text != ValheimReflection.GetPlayerId(localPlayer))
			{
				return;
			}
			int num = package.ReadInt();
			int offerSeed = package.ReadInt();
			if (num >= 0)
			{
				ContractRecord contractRecord = null;
				if (package.ReadBool())
				{
					contractRecord = new ContractRecord
					{
						Id = package.ReadString(),
						OwnerId = text,
						Type = (EncounterType)package.ReadInt(),
						State = (EncounterState)package.ReadInt(),
						TargetX = package.ReadSingle(),
						TargetY = package.ReadSingle(),
						TargetZ = package.ReadSingle()
					};
				}
				if (contractRecord == null || (contractRecord.Id.Length != 0 && contractRecord.Id.Length <= 64 && Enum.IsDefined(typeof(EncounterType), contractRecord.Type) && Enum.IsDefined(typeof(EncounterState), contractRecord.State) && Finite(contractRecord.TargetX) && Finite(contractRecord.TargetY) && Finite(contractRecord.TargetZ)))
				{
					PlayerVoyageProfile profile = store.GetProfile(text);
					profile.CompletedContracts = num;
					profile.OfferSeed = offerSeed;
					profile.ActiveContract = contractRecord;
					receivedProfiles.Add(text);
					SyncLocalMarker();
				}
			}
		}

		private static bool Finite(float value)
		{
			if (!float.IsNaN(value))
			{
				return !float.IsInfinity(value);
			}
			return false;
		}
	}
	internal sealed class EncounterDirector
	{
		private sealed class ActiveEncounter
		{
			public string Id = string.Empty;

			public EncounterType Type;

			public EncounterState State;

			public string OwnerId = string.Empty;

			public string ContractId = string.Empty;

			public bool ContractActivated;

			public Component? Ship;

			public Vector3 SourceShipPosition;

			public Vector3 SourceShipForward = Vector3.forward;

			public Quaternion SceneRotation = Quaternion.identity;

			public float SourceShipSpeed;

			public float SourceSampleTime;

			public float SinkingStartedAt;

			public float SalvageClaimedAt;

			public GameObject? RewardChest;

			public ZDOID RewardChestId;

			public bool RewardChestReady;

			public bool ChestRewardsPrepared;

			public int RewardChestAttempts;

			public float NextRewardChestAttempt;

			public List<RewardGrant> ChestRemaining = new List<RewardGrant>();

			public float NextStormCommitAttempt;

			public bool StormCommitFailureNotified;

			public Vector3 StormLastShipPosition;

			public bool StormHasShipSample;

			public bool StormGatePending;

			public GameObject? ServerVisual;

			public Vector3 Position;

			public Vector3 Target;

			public Vector3 GhostRouteTarget;

			public object? GhostZdo;

			public GameObject? PendingGhostSpawn;

			public ZDOID GhostNetworkId;

			public float StartedAt;

			public float StormWeatherStartedAt;

			public float LastSeenParticipant;

			public int Progress;

			public Vector3[] StormRoute = Array.Empty<Vector3>();

			public Vector3[] CargoPositions = Array.Empty<Vector3>();

			public readonly WreckCargoState Cargo = new WreckCargoState();

			public float NextWreckCheck;

			public float SurvivorMissingSince = -1f;

			public bool SmugglerDefeatAnnounced;

			public int LastSmugglerDefeatedCount;

			public SmugglerCrewState? SmugglerCrew;

			public float[] SmugglerMissingSince = Array.Empty<float>();

			public float NextSmugglerCheck;

			public bool GhostCargoInitialized;

			public bool GhostCargoEverHadItems;

			public bool GhostCargoWasOpened;

			public bool GhostWaitingForCargo;

			public bool GhostCargoUnavailable;

			public int GhostCargoAttempts;

			public float NextGhostCargoAttempt;

			public float NextGhostCompletionAttempt;

			public readonly GhostRecoveryBudget GhostRecovery = new GhostRecoveryBudget();

			public float NextSourceRebindAttempt;

			public float NextGhostRouteRefresh;

			public bool SourceShipDetachedLogged;

			public float LastBroadcastAt = -999f;

			public Vector3 LastBroadcastPosition;

			public EncounterState LastBroadcastState;

			public readonly List<GameObject> Combatants = new List<GameObject>();

			public readonly List<ZDOID> SceneActorIds = new List<ZDOID>();
		}

		private sealed class QuietZone
		{
			public Vector3 Center;

			public float Until;
		}

		private sealed class FreeShipPlan
		{
			public Component Ship;

			public List<EncounterType> Types = new List<EncounterType>();
		}

		private readonly OceanAdventuresConfig config;

		private readonly WorldStateStore store;

		private readonly ContractService contracts;

		private readonly NetworkBridge network;

		private readonly VisualRegistry visuals;

		private readonly RewardService rewards;

		private readonly StormWeatherService weather;

		private readonly Dictionary<string, ActiveEncounter> active = new Dictionary<string, ActiveEncounter>();

		private readonly List<ActiveEncounter> encounterTickBuffer = new List<ActiveEncounter>();

		private float nextFreeEvent;

		private float nextFreeRoll;

		private EncounterType? lastFreeType;

		private float nextShipScan;

		private bool stormRunning;

		private readonly Dictionary<string, float> contractRetryAfter = new Dictionary<string, float>();

		private readonly Dictionary<string, float> nextReadOnlyNotice = new Dictionary<string, float>();

		private readonly List<QuietZone> localQuietZones = new List<QuietZone>();

		private readonly Dictionary<string, float> nextQuietNotice = new Dictionary<string, float>();

		private readonly Dictionary<string, float> nextRouteNotice = new Dictionary<string, float>();

		public EncounterDirector(OceanAdventuresConfig config, WorldStateStore store, ContractService contracts, NetworkBridge network, VisualRegistry visuals, RewardService rewards, StormWeatherService weather)
		{
			EncounterDirector encounterDirector = this;
			this.config = config;
			this.store = store;
			this.contracts = contracts;
			this.network = network;
			this.visuals = visuals;
			this.rewards = rewards;
			this.weather = weather;
			float num = EncounterPacing.RestoredRemainingSeconds(store.Current.NextFreeEligibleUtcTicks, DateTime.UtcNow.Ticks, config.FreeEncounterIntervalSeconds);
			nextFreeEvent = Time.time + Mathf.Max(EncounterPacing.InitialGraceSeconds(config.FreeEncounterIntervalSeconds), num);
			nextFreeRoll = nextFreeEvent;
			if (store.Current.LastFreeTypeCode > 0)
			{
				lastFreeType = (EncounterType)(store.Current.LastFreeTypeCode - 1);
			}
			NetworkBridge networkBridge = network;
			networkBridge.SnapshotRequested = (Action<long>)Delegate.Combine(networkBridge.SnapshotRequested, (Action<long>)delegate(long peer)
			{
				network.SendWeather(peer, weather.ActiveId);
				foreach (ActiveEncounter value in encounterDirector.active.Values)
				{
					network.SendEncounter(peer, OceanAdventuresNetworkOp.EncounterStart, Snapshot(value));
				}
			});
		}

		public void Tick(bool allowNewEncounters)
		{
			if (!ValheimReflection.IsServer())
			{
				return;
			}
			if (allowNewEncounters && Time.time >= nextShipScan)
			{
				nextShipScan = Time.time + 1f;
				localQuietZones.RemoveAll((QuietZone zone) => zone.Until <= Time.time);
				List<Component> list = ValheimReflection.FindByTypeName("Ship").Where(ValheimReflection.IsEligibleShip).Where(ValheimReflection.IsOnNavigableWater)
					.Cast<Component>()
					.ToList();
				List<Component> list2 = new List<Component>();
				Ship boat = default(Ship);
				foreach (Component ship in list)
				{
					ref Ship reference = ref boat;
					Component obj = ship;
					reference = (Ship)(object)((obj is Ship) ? obj : null);
					Player val = ((boat != null) ? (from candidate in Player.GetAllPlayers()
						where (Object)(object)candidate != (Object)null && !((Character)candidate).IsDead() && CharacterIdentityRules.IsStable(ValheimReflection.GetPlayerId(candidate)) && boat.IsPlayerInBoat(candidate)
						orderby Vector3.Distance(((Component)candidate).transform.position, ship.transform.position)
						select candidate).FirstOrDefault() : null);
					if (!((Object)(object)val == (Object)null))
					{
						ActivateContractIfReady(ship, val);
						list2.Add(ship);
					}
				}
				TrySpawnFreeEncounter(list2);
			}
			encounterTickBuffer.Clear();
			encounterTickBuffer.AddRange(active.Values);
			foreach (ActiveEncounter item in encounterTickBuffer)
			{
				try
				{
					UpdateEncounter(item);
				}
				catch (Exception ex)
				{
					OceanAdventuresPlugin.Log.LogError((object)("Encounter update failed; aborting safely: " + ex));
					EndEncounter(item, EncounterState.Aborted);
				}
			}
		}

		public bool TryActivateContract(object player, string contractId)
		{
			//IL_018f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0195: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0200: Unknown result type (might be due to invalid IL or missing references)
			//IL_0238: Unknown result type (might be due to invalid IL or missing references)
			if (ValheimReflection.IsServer() && network.IsRegistered && config.ContractsEnabled)
			{
				Player character = default(Player);
				ref Player reference = ref character;
				object obj = player;
				reference = (Player)((obj is Player) ? obj : null);
				if (character != null && !((Character)character).IsDead() && CharacterIdentityRules.IsStable(ValheimReflection.GetPlayerId(character)))
				{
					ContractRecord activeContract = contracts.GetProfile(player).ActiveContract;
					if (activeContract == null || activeContract.Id != contractId || activeContract.State != EncounterState.Dormant || !config.IsEnabled(activeContract.Type))
					{
						return false;
					}
					if (!store.CanCommit)
					{
						NoticeReadOnly(character);
						return false;
					}
					if (contractRetryAfter.TryGetValue(activeContract.Id, out var value) && Time.time < value)
					{
						network.SendNotice(character, OceanAdventuresNoticeCode.VoyageRecovering);
						return false;
					}
					if (activeContract.Type == EncounterType.StormFront && stormRunning)
					{
						network.SendNotice(character, OceanAdventuresNoticeCode.StormAlreadyActive);
						return false;
					}
					MonoBehaviour val = ((IEnumerable<MonoBehaviour>)(from candidate in ValheimReflection.FindByTypeName("Ship").Where(ValheimReflection.IsEligibleShip).Where(delegate(MonoBehaviour candidate)
						{
							Ship val2 = (Ship)(object)((candidate is Ship) ? candidate : null);
							return val2 != null && val2.IsPlayerInBoat(character);
						})
							.Where(ValheimReflection.IsOnNavigableWater)
						orderby Vector3.Distance(((Component)candidate).transform.position, ValheimReflection.GetPosition(player))
						select candidate)).FirstOrDefault((Func<MonoBehaviour, bool>)((MonoBehaviour candidate) => Vector3.Distance(((Component)candidate).transform.position, ValheimReflection.GetPosition(player)) <= 45f));
					if ((Object)(object)val == (Object)null || Vector3.Distance(((Component)val).transform.position, activeContract.GetTarget()) > 120f)
					{
						network.SendNotice(character, OceanAdventuresNoticeCode.ShipNotAtContractMarker);
						return false;
					}
					if (!((activeContract.Type == EncounterType.GhostShip) ? ValheimReflection.TrySnapToNavigableWater(activeContract.GetTarget(), out var snapped) : ValheimReflection.TrySnapToWater(activeContract.GetTarget(), out snapped)))
					{
						network.SendNotice(character, OceanAdventuresNoticeCode.ContractTargetNotWater);
						return false;
					}
					activeContract.SetTarget(snapped);
					if (ConflictsWithActiveScene((Component)(object)val, snapped, activeContract.Id))
					{
						network.SendNotice(character, OceanAdventuresNoticeCode.AnotherEncounterActive);
						return false;
					}
					bool num = StartEncounter(activeContract.Type, activeContract.OwnerId, activeContract.Id, (Component)(object)val, snapped);
					if (num)
					{
						contractRetryAfter.Remove(activeContract.Id);
						return num;
					}
					contractRetryAfter[activeContract.Id] = EncounterPacing.PreserveRetryDeadline(Time.time, 15f, contractRetryAfter.TryGetValue(activeContract.Id, out var value2) ? value2 : 0f);
					network.SendNotice(character, OceanAdventuresNoticeCode.VoyageStartUnavailable);
					return num;
				}
			}
			return false;
		}

		public bool StartTestEncounter(EncounterType type, object player)
		{
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_016a: Unknown result type (might be due to invalid IL or missing references)
			//IL_016f: Unknown result type (might be due to invalid IL or missing references)
			//IL_017e: Unknown result type (might be due to invalid IL or missing references)
			//IL_018e: Unknown result type (might be due to invalid IL or missing references)
			//IL_025b: Unknown result type (might be due to invalid IL or missing references)
			//IL_026b: Unknown result type (might be due to invalid IL or missing references)
			Stopwatch stopwatch = Stopwatch.StartNew();
			if (!network.IsRegistered)
			{
				ValheimReflection.ShowMessage("OceanAdventures network is not ready. Wait for the world connection before testing an encounter.");
				return false;
			}
			if (!CharacterIdentityRules.IsStable(ValheimReflection.GetPlayerId(player)))
			{
				ValheimReflection.ShowMessage("Wait until your character has joined the world before starting an OceanAdventures event.");
				return false;
			}
			if (!ValheimReflection.IsServer())
			{
				ValheimReflection.ShowMessage("Run oceanadventures_test_event on the server.");
				return false;
			}
			if (!config.IsEnabled(type))
			{
				ValheimReflection.ShowMessage("This OceanAdventures encounter is disabled in the server configuration.");
				return false;
			}
			if (!store.CanCommit)
			{
				ValheimReflection.ShowMessage("OceanAdventures world rewards are read-only. Repair the profile before starting a real test event.");
				return false;
			}
			if (type == EncounterType.StormFront && stormRunning)
			{
				ValheimReflection.ShowMessage("A OceanAdventures storm is already active.");
				return false;
			}
			Vector3 playerPosition = ValheimReflection.GetPosition(player);
			MonoBehaviour ship = ((IEnumerable<MonoBehaviour>)(from candidate in ValheimReflection.FindByTypeName("Ship").Where(ValheimReflection.IsEligibleShip).Where(delegate(MonoBehaviour candidate)
				{
					object obj = player;
					Player val = (Player)((obj is Player) ? obj : null);
					if (val != null && !((Character)val).IsDead())
					{
						Ship val2 = (Ship)(object)((candidate is Ship) ? candidate : null);
						if (val2 != null)
						{
							return val2.IsPlayerInBoat(val);
						}
					}
					return false;
				})
				orderby Vector3.Distance(((Component)candidate).transform.position, playerPosition)
				select candidate)).FirstOrDefault((Func<MonoBehaviour, bool>)((MonoBehaviour candidate) => Vector3.Distance(((Component)candidate).transform.position, playerPosition) <= 45f));
			if ((Object)(object)ship == (Object)null)
			{
				ValheimReflection.ShowMessage("Board a compatible ship before starting a test encounter.");
				return false;
			}
			if (!ValheimReflection.IsOnNavigableWater((Component)(object)ship))
			{
				ValheimReflection.ShowMessage("Sail into navigable water before starting this OceanAdventures test encounter.");
				return false;
			}
			if (active.Values.Any((ActiveEncounter encounter) => (Object)(object)encounter.Ship == (Object)(object)ship))
			{
				ValheimReflection.ShowMessage("Finish or abort the current OceanAdventures encounter first.");
				return false;
			}
			Vector3 course = Vector3.zero;
			if (!((type == EncounterType.GhostShip) ? GhostSeaNavigation.TryFindSpawn((Component)(object)ship, out var spawn, out course) : ValheimReflection.TryFindWaterPosition(((Component)ship).transform.position, ((Component)ship).transform.forward, 28f, out spawn, requireWaterCorridor: true, ValheimReflection.RequiredEncounterLandClearance(type), diagnostics: true, ValheimReflection.FallbackEncounterLandClearance(type))))
			{
				OceanAdventuresPlugin.Log.LogInfo((object)("Manual " + type.ToString() + " test timing: placement search failed after " + stopwatch.Elapsed.TotalMilliseconds.ToString("0.0") + " ms."));
				ValheimReflection.ShowMessage("No safe water route was found near the ship.");
				return false;
			}
			double totalMilliseconds = stopwatch.Elapsed.TotalMilliseconds;
			Stopwatch stopwatch2 = Stopwatch.StartNew();
			bool flag = StartEncounter(type, string.Empty, string.Empty, (Component)(object)ship, spawn, (type == EncounterType.GhostShip) ? new Vector3?(course) : ((Vector3?)null), diagnostics: true);
			double totalMilliseconds2 = stopwatch2.Elapsed.TotalMilliseconds;
			OceanAdventuresPlugin.Log.LogInfo((object)("Manual " + type.ToString() + " test timing: placement=" + totalMilliseconds.ToString("0.0") + " ms, validation/spawn=" + totalMilliseconds2.ToString("0.0") + " ms, total=" + stopwatch.Elapsed.TotalMilliseconds.ToString("0.0") + " ms."));
			if (!flag)
			{
				ValheimReflection.ShowMessage("No safe OceanAdventures scene fits here. Reposition your ship and try again; check the log if it persists.");
				return false;
			}
			ValheimReflection.ShowMessage("OceanAdventures test encounter spawned: " + type);
			return true;
		}

		public void Shutdown()
		{
			CleanupBatch cleanupBatch = new CleanupBatch();
			foreach (ActiveEncounter encounter in active.Values.ToList())
			{
				cleanupBatch.Run("encounter " + encounter.Id, delegate
				{
					EndEncounter(encounter, EncounterState.Aborted);
				});
			}
			active.Clear();
			localQuietZones.Clear();
			nextQuietNotice.Clear();
			cleanupBatch.Run("orphaned ghosts", delegate
			{
				ValheimReflection.DestroyMarkedNetworkObjects("OceanAdventuresGhostShip");
			});
			cleanupBatch.Run("unloaded ghosts", delegate
			{
				ValheimReflection.DestroyMarkedNetworkZdos("OceanAdventuresGhostShip");
			});
			cleanupBatch.Run("orphaned scene actors", delegate
			{
				ValheimReflection.DestroyMarkedNetworkObjects("OceanAdventuresSceneActor");
			});
			cleanupBatch.Run("unloaded scene actors", delegate
			{
				ValheimReflection.DestroyMarkedNetworkZdos("OceanAdventuresSceneActor");
			});
			cleanupBatch.Run("visual registry", visuals.Clear);
			ReportCleanup(cleanupBatch, "shutdown");
		}

		public int AbortAllEncounters()
		{
			List<ActiveEncounter> list = active.Values.ToList();
			CleanupBatch cleanupBatch = new CleanupBatch();
			foreach (ActiveEncounter encounter in list)
			{
				cleanupBatch.Run("encounter " + encounter.Id, delegate
				{
					EndEncounter(encounter, EncounterState.Aborted, manuallyCleared: true);
				});
			}
			int orphanedGhosts = 0;
			int unloadedGhosts = 0;
			int orphanedActors = 0;
			int unloadedActors = 0;
			cleanupBatch.Run("orphaned ghosts", delegate
			{
				orphanedGhosts = ValheimReflection.DestroyMarkedNetworkObjects("OceanAdventuresGhostShip");
			});
			cleanupBatch.Run("unloaded ghosts", delegate
			{
				unloadedGhosts = ValheimReflection.DestroyMarkedNetworkZdos("OceanAdventuresGhostShip");
			});
			cleanupBatch.Run("orphaned scene actors", delegate
			{
				orphanedActors = ValheimReflection.DestroyMarkedNetworkObjects("OceanAdventuresSceneActor");
			});
			cleanupBatch.Run("unloaded scene actors", delegate
			{
				unloadedActors = ValheimReflection.DestroyMarkedNetworkZdos("OceanAdventuresSceneActor");
			});
			cleanupBatch.Run("visual registry", visuals.Clear);
			ReportCleanup(cleanupBatch, "abort all");
			int num = list.Count + orphanedGhosts + unloadedGhosts + orphanedActors + unloadedActors;
			if (num > 0)
			{
				nextFreeEvent = Mathf.Max(nextFreeEvent, EncounterPacing.NextFreeEligibility(Time.time, 180f));
				PersistFreePacing();
			}
			return num;
		}

		public unsafe void LogGhostStatus()
		{
			//IL_00e2: 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_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_0131: Unknown result type (might be due to invalid IL or missing references)
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_0244: Unknown result type (might be due to invalid IL or missing references)
			//IL_027a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0273: Unknown result type (might be due to invalid IL or missing references)
			//IL_027f: Unknown result type (might be due to invalid IL or missing references)
			//IL_028b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0297: Unknown result type (might be due to invalid IL or missing references)
			//IL_029e: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_0335: Unknown result type (might be due to invalid IL or missing references)
			//IL_03df: Unknown result type (might be due to invalid IL or missing references)
			//IL_03e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0404: Unknown result type (might be due to invalid IL or missing references)
			//IL_041f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0424: Unknown result type (might be due to invalid IL or missing references)
			//IL_045d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0463: Unknown result type (might be due to invalid IL or missing references)
			//IL_0374: Unknown result type (might be due to invalid IL or missing references)
			//IL_037a: Unknown result type (might be due to invalid IL or missing references)
			//IL_037f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0384: Unknown result type (might be due to invalid IL or missing references)
			//IL_0395: Unknown result type (might be due to invalid IL or missing references)
			//IL_039b: Unknown result type (might be due to invalid IL or missing references)
			//IL_060b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0610: Unknown result type (might be due to invalid IL or missing references)
			//IL_06cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_06d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_06d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_06db: Unknown result type (might be due to invalid IL or missing references)
			//IL_070a: Unknown result type (might be due to invalid IL or missing references)
			//IL_070f: Unknown result type (might be due to invalid IL or missing references)
			List<ActiveEncounter> list = active.Values.Where((ActiveEncounter encounter) => encounter.Type == EncounterType.GhostShip).ToList();
			if (list.Count == 0)
			{
				OceanAdventuresPlugin.Log.LogInfo((object)"Ghostship status: no active ghostship encounter.");
				ValheimReflection.ShowMessage("No active OceanAdventures ghostship encounter.");
				return;
			}
			bool flag4 = default(bool);
			bool flag6 = default(bool);
			bool flag8 = default(bool);
			float num6 = default(float);
			foreach (ActiveEncounter item in list)
			{
				bool flag = (Object)(object)item.ServerVisual != (Object)null;
				bool flag2 = visuals.IsNetworked(item.Id);
				bool flag3 = visuals.IsObjectAlive(item.Id);
				GameObject val = visuals.GetObject(item.Id);
				object obj = item.GhostZdo ?? ValheimReflection.GetNetworkZdo(val);
				string text = ((ValheimReflection.Invoke(obj, "GetPosition") is Vector3 val2) ? ((object)(*(Vector3*)(&val2))/*cast due to .constrained prefix*/).ToString() : "missing");
				object obj2;
				Vector3 val4;
				if (!(ValheimReflection.Invoke(obj, "GetRotation") is Quaternion val3))
				{
					obj2 = "missing";
				}
				else
				{
					val4 = ((Quaternion)(ref val3)).eulerAngles;
					obj2 = ((object)(*(Vector3*)(&val4))/*cast due to .constrained prefix*/).ToString();
				}
				string text2 = (string)obj2;
				object obj3 = ValheimReflection.Invoke(obj, "IsOwner");
				int num;
				if (obj3 is bool)
				{
					flag4 = (bool)obj3;
					num = 1;
				}
				else
				{
					num = 0;
				}
				bool flag5 = (byte)((uint)num & (flag4 ? 1u : 0u)) != 0;
				string networkOwnerId = ValheimReflection.GetNetworkOwnerId(obj);
				obj3 = ValheimReflection.Invoke(obj, "GetBool", "OceanAdventuresGhostActive", false);
				int num2;
				if (obj3 is bool)
				{
					flag6 = (bool)obj3;
					num2 = 1;
				}
				else
				{
					num2 = 0;
				}
				bool flag7 = (byte)((uint)num2 & (flag6 ? 1u : 0u)) != 0;
				obj3 = ValheimReflection.ReadMember(obj, "Persistent");
				int num3;
				if (obj3 is bool)
				{
					flag8 = (bool)obj3;
					num3 = 1;
				}
				else
				{
					num3 = 0;
				}
				bool flag9 = (byte)((uint)num3 & (flag8 ? 1u : 0u)) != 0;
				Rigidbody val5 = (((Object)(object)val == (Object)null) ? null : val.GetComponent<Rigidbody>());
				Container val6 = (((Object)(object)val == (Object)null) ? null : val.GetComponentInChildren<Container>(true));
				Ship val7 = (((Object)(object)val == (Object)null) ? null : val.GetComponent<Ship>());
				float surface;
				string text3 = (((Object)(object)val != (Object)null && ValheimReflection.TryGetWaterSurface(val.transform.position, out surface)) ? surface.ToString("0.00") : "unavailable");
				Vector3 val8 = (((Object)(object)val5 == (Object)null) ? Vector3.zero : val5.worldCenterOfMass);
				float num4 = (((Object)(object)val5 == (Object)null) ? (-10000f) : Floating.GetLiquidLevel(new Vector3(val8.x, 30f, val8.z), 0f, (LiquidType)0));
				float num5 = ((num4 > -1000f && !float.IsNaN(num4) && !float.IsInfinity(num4)) ? num4 : 30f);
				string text4 = (((Object)(object)val != (Object)null && Heightmap.GetHeight(val.transform.position, ref num6)) ? (num5 - num6).ToString("0.00") : "unavailable");
				string text5 = (((Object)(object)val5 != (Object)null && (Object)(object)val7 != (Object)null) ? (val5.worldCenterOfMass.y - num5 - val7.m_waterLevelOffset - -1.25f).ToString("0.00") : "unavailable");
				int num7;
				if (item.State == EncounterState.Active && (Object)(object)val != (Object)null)
				{
					val4 = item.GhostRouteTarget - item.Position;
					if (((Vector3)(ref val4)).sqrMagnitude > 1f)
					{
						num7 = (GhostSeaNavigation.IsClearLeg(item.Position, item.GhostRouteTarget, val) ? 1 : 0);
						goto IL_03aa;
					}
				}
				num7 = 0;
				goto IL_03aa;
				IL_03aa:
				bool flag10 = (byte)num7 != 0;
				ManualLogSource log = OceanAdventuresPlugin.Log;
				string[] array = new string[61];
				array[0] = "Ghostship status: state=";
				array[1] = item.State.ToString();
				array[2] = ", position=";
				val4 = item.Position;
				array[3] = ((object)(*(Vector3*)(&val4))/*cast due to .constrained prefix*/).ToString();
				array[4] = ", route=";
				val4 = item.GhostRouteTarget;
				array[5] = ((object)(*(Vector3*)(&val4))/*cast due to .constrained prefix*/).ToString();
				array[6] = ", sourcePosition=";
				val4 = item.SourceShipPosition;
				array[7] = ((object)(*(Vector3*)(&val4))/*cast due to .constrained prefix*/).ToString();
				array[8] = ", sourceSpeed=";
				array[9] = item.SourceShipSpeed.ToString("0.00");
				array[10] = ", sourceDistance=";
				array[11] = Vector3.Distance(item.Position, item.SourceShipPosition).ToString("0.0");
				array[12] = ", commandedSpeed=";
				array[13] = ((item.State == EncounterState.Active) ? 3f : 0f).ToString("0.00");
				array[14] = ", visual=";
				array[15] = flag.ToString();
				array[16] = ", networked=";
				array[17] = flag2.ToString();
				array[18] = ", alive=";
				array[19] = flag3.ToString();
				array[20] = ", recoveries=";
				array[21] = item.GhostRecovery.Attempts.ToString();
				array[22] = ", cargoOpened=";
				array[23] = item.GhostCargoWasOpened.ToString();
				array[24] = ", cargoObserved=";
				array[25] = item.GhostCargoEverHadItems.ToString();
				array[26] = ", cargoLoading=";
				array[27] = item.GhostWaitingForCargo.ToString();
				array[28] = ", zdoPosition=";
				array[29] = text;
				array[30] = ", zdoRotation=";
				array[31] = text2;
				array[32] = ", zdoOwner=";
				array[33] = flag5.ToString();
				array[34] = ", zdoOwnerId=";
				array[35] = networkOwnerId;
				array[36] = ", zdoActive=";
				array[37] = flag7.ToString();
				array[38] = ", zdoPersistent=";
				array[39] = flag9.ToString();
				array[40] = ", bodyKinematic=";
				array[41] = ((Object)(object)val5 != (Object)null && val5.isKinematic).ToString();
				array[42] = ", waterSurface=";
				array[43] = text3;
				array[44] = ", centerOfMassY=";
				object obj4;
				if (!((Object)(object)val5 == (Object)null))
				{
					val4 = val5.worldCenterOfMass;
					obj4 = val4.y.ToString("0.00");
				}
				else
				{
					obj4 = "missing";
				}
				array[45] = (string)obj4;
				array[46] = ", nativeWaterOffset=";
				array[47] = (((Object)(object)val7 == (Object)null) ? "missing" : val7.m_waterLevelOffset.ToString("0.00"));
				array[48] = ", measuredWater=";
				array[49] = ((num4 > -1000f) ? num4.ToString("0.00") : "fallback");
				array[50] = ", measuredDepth=";
				array[51] = text4;
				array[52] = ", COMminusBuoyancyTarget=";
				array[53] = text5;
				array[54] = ", nextLegClear=";
				array[55] = flag10.ToString();
				array[56] = ", bodyRotation=";
				object obj5;
				if (!((Object)(object)val5 == (Object)null))
				{
					Quaternion rotation = val5.rotation;
					val4 = ((Quaternion)(ref rotation)).eulerAngles;
					obj5 = ((object)(*(Vector3*)(&val4))/*cast due to .constrained prefix*/).ToString();
				}
				else
				{
					obj5 = "missing";
				}
				array[57] = (string)obj5;
				array[58] = ", bodyVelocity=";
				object obj6;
				if (!((Object)(object)val5 == (Object)null))
				{
					val4 = ReadBodyVelocity(val5);
					obj6 = ((object)(*(Vector3*)(&val4))/*cast due to .constrained prefix*/).ToString();
				}
				else
				{
					obj6 = "missing";
				}
				array[59] = (string)obj6;
				array[60] = ".";
				log.LogInfo((object)string.Concat(array));
				Player localPlayer = Player.m_localPlayer;
				if (GhostHoldInteractionPatch.TryFindBadIcon((localPlayer != null) ? ((Humanoid)localPlayer).GetInventory() : null, out string issue))
				{
					OceanAdventuresPlugin.Log.LogWarning((object)("Ghostship inventory UI preflight (player): " + issue));
				}
				if ((Object)(object)val6 != (Object)null && GhostHoldInteractionPatch.TryFindBadIcon(val6.GetInventory(), out string issue2))
				{
					OceanAdventuresPlugin.Log.LogWarning((object)("Ghostship inventory UI preflight (hold): " + issue2));
				}
			}
			ValheimReflection.ShowMessage("Ghostship status written to the server log.");
		}

		public void LogEventStatus()
		{
			int num = Mathf.CeilToInt(Mathf.Max(0f, nextFreeEvent - Time.time));
			int num2 = Mathf.CeilToInt(Mathf.Max(0f, nextFreeRoll - Time.time));
			string[] array = active.Values.Select((ActiveEncounter encounter) => encounter.Type.ToString() + "/" + encounter.State).ToArray();
			Player local = Player.m_localPlayer;
			ActiveEncounter activeEncounter = (((Object)(object)local == (Object)null) ? null : active.Values.FirstOrDefault((ActiveEncounter encounter) => NearScene(((Component)local).transform.position, encounter.Position)));
			int num3 = ((!((Object)(object)local == (Object)null)) ? (from zone in localQuietZones
				where zone.Until > Time.time && NearScene(((Component)local).transform.position, zone.Center)
				select Mathf.CeilToInt(zone.Until - Time.time)).DefaultIfEmpty(0).Max() : 0);
			OceanAdventuresPlugin.Log.LogInfo((object)("Encounter pacing: preset=" + config.EffectivePreset.ToString() + ", active=" + ((array.Length == 0) ? "none" : string.Join(", ", array)) + ", quietRemaining=" + num + "s, rollRemaining=" + num2 + "s, localQuietRemaining=" + num3 + "s, lastFreeType=" + (lastFreeType?.ToString() ?? "none") + "."));
			if (activeEncounter != null)
			{
				ValheimReflection.ShowMessage("Nearby OceanAdventures encounter: " + activeEncounter.Type.ToString() + "/" + activeEncounter.State.ToString() + ".");
			}
			else if (num3 > 0)
			{
				ValheimReflection.ShowMessage("This sea area rests for " + num3 + "s. Sail farther or wait.");
			}
			else if (active.Values.Any((ActiveEncounter encounter) => string.IsNullOrEmpty(encounter.OwnerId)))
			{
				ValheimReflection.ShowMessage("A free OceanAdventures encounter is active elsewhere at sea.");
			}
			else
			{
				ValheimReflection.ShowMessage("Next free OceanAdventures roll in " + Mathf.Max(num, num2) + "s (at sea).");
			}
		}

		private void ActivateContractIfReady(Component ship, object player)
		{
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0148: Unknown result type (might be due to invalid IL or missing references)
			//IL_0170: Unknown result type (might be due to invalid IL or missing references)
			if (!config.ContractsEnabled)
			{
				return;
			}
			ContractRecord activeContract = contracts.GetProfile(player).ActiveContract;
			if (activeContract == null || activeContract.State != EncounterState.Dormant || !config.IsEnabled(activeContract.Type) || (contractRetryAfter.TryGetValue(activeContract.Id, out var value) && Time.time < value) || Vector3.Distance(ship.transform.position, activeContract.GetTarget()) > 90f)
			{
				return;
			}
			if (!store.CanCommit)
			{
				Player val = (Player)((player is Player) ? player : null);
				if (val != null)
				{
					NoticeReadOnly(val);
				}
			}
			else
			{
				if (activeContract.Type == EncounterType.StormFront && stormRunning)
				{
					return;
				}
				if (localQuietZones.Any((QuietZone zone) => NearScene(ship.transform.position, zone.Center)))
				{
					Player val2 = (Player)((player is Player) ? player : null);
					if (val2 != null)
					{
						NoticeQuiet(val2);
					}
				}
				else
				{
					if (!ValheimReflection.IsOnNavigableWater(ship))
					{
						return;
					}
					if (!((activeContract.Type == EncounterType.GhostShip) ? ValheimReflection.TrySnapToNavigableWater(activeContract.GetTarget(), out var snapped) : ValheimReflection.TrySnapToWater(activeContract.GetTarget(), out snapped)))
					{
						Player val3 = (Player)((player is Player) ? player : null);
						if (val3 != null)
						{
							NoticeRouteUnavailable(val3, OceanAdventuresNoticeCode.ContractTargetNotWater);
						}
						return;
					}
					activeContract.SetTarget(snapped);
					if (ConflictsWithActiveScene(ship, snapped, activeContract.Id))
					{
						return;
					}
					if (!StartEncounter(activeContract.Type, activeContract.OwnerId, activeContract.Id, ship, snapped))
					{
						contractRetryAfter[activeContract.Id] = EncounterPacing.PreserveRetryDeadline(Time.time, 15f, contractRetryAfter.TryGetValue(activeContract.Id, out var value2) ? value2 : 0f);
						Player val4 = (Player)((player is Player) ? player : null);
						if (val4 != null)
						{
							NoticeRouteUnavailable(val4, OceanAdventuresNoticeCode.VoyageStartUnavailable);
						}
					}
					else
					{
						contractRetryAfter.Remove(activeContract.Id);
					}
				}
			}
		}

		private void TrySpawnFreeEncounter(IReadOnlyList<Component> crewedShips)
		{
			//IL_01e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0248: Unknown result type (might be due to invalid IL or missing references)
			//IL_025a: Unknown result type (might be due to invalid IL or missing references)
			if (!store.CanCommit || Time.time < nextFreeEvent || Time.time < nextFreeRoll || active.Values.Any((ActiveEncounter encounter) => string.IsNullOrEmpty(encounter.OwnerId)))
			{
				return;
			}
			List<Component> list = crewedShips.Where((Component val) => !active.Values.Any((ActiveEncounter encounter) => (Object)(object)encounter.Ship == (Object)(object)val || NearScene(val.transform.position, encounter.Position)) && !localQuietZones.Any((QuietZone zone) => NearScene(val.transform.position, zone.Center))).ToList();
			if (list.Count == 0)
			{
				return;
			}
			nextFreeRoll = Time.time + config.FreeEncounterRollIntervalSeconds;
			if (Random.value >= config.FreeEncounterRollChance)
			{
				return;
			}
			int num = Random.Range(0, list.Count);
			List<FreeShipPlan> list2 = new List<FreeShipPlan>();
			for (int num2 = 0; num2 < list.Count; num2++)
			{
				Component ship = list[(num + num2) % list.Count];
				bool navigableWater = ValheimReflection.IsOnNavigableWater(ship);
				List<EncounterType> enabled = (from EncounterType type in Enum.GetValues(typeof(EncounterType))
					where config.IsEnabled(type) && (type != EncounterType.StormFront || !stormRunning) && (navigableWater || type == EncounterType.GhostShip)
					select type).ToList();
				List<EncounterType> list3 = EncounterPacing.Candidates(enabled, lastFreeType);
				if (list3.Count != 0)
				{
					list2.Add(new FreeShipPlan
					{
						Ship = ship,
						Types = EncounterPacing.OrderedCandidates(enabled, lastFreeType, Random.Range(0, list3.Count))
					});
				}
			}
			foreach (EncounterAttemptSlot item in EncounterPacing.AttemptSlots(list2.Select((FreeShipPlan plan) => plan.Types.Count).ToList()))
			{
				FreeShipPlan freeShipPlan = list2[item.ShipIndex];
				Component ship2 = freeShipPlan.Ship;
				EncounterType encounterType = freeShipPlan.Types[item.TypeOffset];
				Vector3 course = Vector3.zero;
				if (((encounterType == EncounterType.GhostShip) ? GhostSeaNavigation.TryFindSpawn(ship2, out var spawn, out course) : ValheimReflection.TryFindWaterPosition(ship2.transform.position, ship2.transform.forward, 22f, out spawn, requireWaterCorridor: true, ValheimReflection.RequiredEncounterLandClearance(encounterType), diagnostics: false, ValheimReflection.FallbackEncounterLandClearance(encounterType))) && (StartEncounter(encounterType, string.Empty, string.Empty, ship2, spawn, (encounterType == EncounterType.GhostShip) ? new Vector3?(course) : ((Vector3?)null)) || Time.time < nextFreeEvent))
				{
					return;
				}
			}
			nextFreeEvent = Mathf.Max(nextFreeEvent, Time.time + 30f);
		}

		private void NoticeReadOnly(Player player)
		{
			string playerId = ValheimReflection.GetPlayerId(player);
			if (!nextReadOnlyNotice.TryGetValue(playerId, out var value) || !(Time.time < value))
			{
				nextReadOnlyNotice[playerId] = Time.time + 60f;
				network.SendNotice(player, OceanAdventuresNoticeCode.WorldProfileReadOnly);
			}
		}

		private void NoticeQuiet(Player player)
		{
			string playerId = ValheimReflection.GetPlayerId(player);
			if (!nextQuietNotice.TryGetValue(playerId, out var value) || !(Time.time < value))
			{
				nextQuietNotice[playerId] = Time.time + 60f;
				network.SendNotice(player, OceanAdventuresNoticeCode.SeaRestingAfterEncounter);
			}
		}

		private void NoticeRouteUnavailable(Player player, OceanAdventuresNoticeCode code)
		{
			string playerId = ValheimReflection.GetPlayerId(player);
			if (!nextRouteNotice.TryGetValue(playerId, out var value) || !(Time.time < value))
			{
				nextRouteNotice[playerId] = Time.time + 60f;
				network.SendNotice(player, code);
			}
		}

		private static bool NearScene(Vector3 point, Vector3 scene)
		{
			//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 = point.x - scene.x;
			float num2 = point.z - scene.z;
			return EncounterPacing.IsNearScene(num * num + num2 * num2);
		}

		private bool ConflictsWithActiveScene(Component ship, Vector3 target, string contractId)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			return active.Values.Any((ActiveEncounter item) => (Object)(object)item.Ship == (Object)(object)ship || NearScene(target, item.Position) || (!string.IsNullOrEmpty(contractId) && item.ContractId == contractId));
		}

		private bool PersistFreePacing()
		{
			if (!ValheimReflection.IsServer() || !store.CanCommit)
			{
				return false;
			}
			long nextFreeEligibleUtcTicks = store.Current.NextFreeEligibleUtcTicks;
			int lastFreeTypeCode = store.Current.LastFreeTypeCode;
			float num = Mathf.Clamp(nextFreeEvent - Time.time, 0f, config.FreeEncounterIntervalSeconds);
			store.Current.NextFreeEligibleUtcTicks = DateTime.UtcNow.AddSeconds(num).Ticks;
			store.Current.LastFreeTypeCode = (int)(lastFreeType.HasValue ? (lastFreeType.Value + 1) : EncounterType.GhostShip);
			if (store.SaveNow())
			{
				return true;
			}
			store.Current.NextFreeEligibleUtcTicks = nextFreeEligibleUtcTicks;
			store.Current.LastFreeTypeCode = lastFreeTypeCode;
			OceanAdventuresPlugin.Log.LogWarning((object)"Free-encounter pacing could not be saved; a new free scene will not be published without a durable cooldown.");
			return false;
		}

		private unsafe bool StartEncounter(EncounterType type, string ownerId, string contractId, Component ship, Vector3 target, Vector3? plannedGhostCourse = null, bool diagnostics = false)
		{
			//IL_0018: 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_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: 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_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0145: 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_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: 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_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_0114: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_0122: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_021a: Unknown result type (might be due to invalid IL or missing references)
			//IL_021e: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_017e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0183: Unknown result type (might be due to invalid IL or missing references)
			//IL_0184: Unknown result type (might be due to invalid IL or missing references)
			//IL_0189: Unknown result type (might be due to invalid IL or missing references)
			//IL_018b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0251: Unknown result type (might be due to invalid IL or missing references)
			//IL_0255: Unknown result type (might be due to invalid IL or missing references)
			//IL_0325: Unknown result type (might be due to invalid IL or missing references)
			//IL_0326: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_04d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_04dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_04de: Unknown result type (might be due to invalid IL or missing references)
			//IL_04e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_05cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_05ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_050a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0501: Unknown result type (might be due to invalid IL or missing references)
			//IL_050f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0514: Unknown result type (might be due to invalid IL or missing references)
			//IL_0519: Unknown result type (might be due to invalid IL or missing references)
			//IL_0520: Unknown result type (might be due to invalid IL or missing references)
			//IL_0522: Unknown result type (might be due to invalid IL or missing references)
			//IL_0630: Unknown result type (might be due to invalid IL or missing references)
			//IL_0635: Unknown result type (might be due to invalid IL or missing references)
			//IL_059a: Unknown result type (might be due to invalid IL or missing references)
			//IL_059f: Unknown result type (might be due to invalid IL or missing references)
			//IL_079a: Unknown result type (might be due to invalid IL or missing references)
			//IL_079f: Unknown result type (might be due to invalid IL or missing references)
			if (!Finite(ship.transform.position) || !Finite(ship.transform.rotation) || !Finite(target))
			{
				return Reject("non-finite ship or target transform");
			}
			if (ConflictsWithActiveScene(ship, target, contractId))
			{
				return Reject("conflicts with an active OceanAdventures scene");
			}
			if (!((type == EncounterType.GhostShip) ? ValheimReflection.TrySnapToNavigableWater(target, out target) : ValheimReflection.TrySnapToWater(target, out target)))
			{
				return Reject("target has no valid water surface or is shallower than the navigable-depth limit");
			}
			if (!ValheimReflection.HasEncounterLandClearance(target, type))
			{
				return Reject("target misses both the preferred 70 m shoreline reserve and the allowed hull-sized fallback");
			}
			if (type != EncounterType.GhostShip && !ValheimReflection.HasWaterCorridor(ship.transform.position, target))
			{
				return Reject("straight approach samples cross non-navigable water or land");
			}
			Vector3 target2 = plannedGhostCourse ?? target;
			if (type == EncounterType.GhostShip && !plannedGhostCourse.HasValue)
			{
				if (!GhostSeaNavigation.TryFindSafeFacing(target, FlatForward(ship), out var facing))
				{
					return Reject("no hull-sized safe ghostship heading fits at the target");
				}
				if (!GhostSeaNavigation.TryChooseCourse(target, facing, null, out target2))
				{
					target2 = target + facing;
				}
			}
			Quaternion val = ((type == EncounterType.Shipwreck || type == EncounterType.Smugglers) ? WreckSceneLayout.Rotation(FlatForward(ship)) : Quaternion.identity);
			Vector3[] positions = Array.Empty<Vector3>();
			if (type == EncounterType.Smugglers)
			{
				bool flag = false;
				for (int i = 0; i < 4; i++)
				{
					Quaternion val2 = Quaternion.Euler(0f, SmugglerSceneFootprint.YawOffsetDegrees(i), 0f) * val;
					flag = !GhostSeaNavigation.HasSolidObstacle(target);
					if (!flag)
					{
						break;
					}
					for (int j = 0; j < 4; j++)
					{
						SmugglerSceneFootprint.HullCornerOffset(j, out var x, out var z);
						if (!ValheimReflection.TrySnapToWater(target + val2 * new Vector3(x, 0f, z), out var snapped) || GhostSeaNavigation.HasSolidObstacle(snapped))
						{
							flag = false;
							break;
						}
					}
					if (flag)
					{
						val = val2;
						break;
					}
				}
				if (!flag)
				{
					return Reject("no smuggler longship orientation fits the water and obstacle footprint");
				}
			}
			if (type == EncounterType.Shipwreck && !WreckSceneLayout.TryCreate(target, FlatForward(ship), out positions))
			{
				return Reject("shipwreck layout cannot fit its full cargo/deck footprint in the sampled water");
			}
			Vector3[] route = Array.Empty<Vector3>();
			if (type == EncounterType.StormFront && (stormRunning || !StormRoute.TryCreate(target, FlatForward(ship), out route, diagnostics)))
			{
				return Reject(stormRunning ? "another Stormfront is already active" : "no safe seaward storm route passed validation");
			}
			ActiveEncounter activeEncounter = new ActiveEncounter
			{
				Id = Guid.NewGuid().ToString("N"),
				Type = type,
				State = EncounterState.Active,
				OwnerId = ownerId,
				ContractId = contractId,
				Ship = ship,
				SourceShipPosition = ship.transform.position,
				SourceShipForward = FlatForward(ship),
				SceneRotation = val,
				Position = target,
				Target = target,
				StormRoute = route,
				Progress = ((type == EncounterType.StormFront) ? (-1) : 0),
				CargoPositions = positions,
				GhostRouteTarget = target2,
				StartedAt = Time.time,
				LastSeenParticipant = Time.time
			};
			if (!HasFiniteSceneGeometry(activeEncounter))
			{
				return Reject("generated event geometry contains invalid positions or rotations");
			}
			if (string.IsNullOrEmpty(ownerId))
			{
				float num = nextFreeEvent;
				EncounterType? encounterType = lastFreeType;
				nextFreeEvent = Mathf.Max(nextFreeEvent, EncounterPacing.NextFreeEligibility(Time.time, config.FreeEncounterIntervalSeconds));
				lastFreeType = type;
				if (!PersistFreePacing())
				{
					lastFreeType = encounterType;
					nextFreeEvent = Mathf.Max(num, Time.time + 120f);
					return Reject("free-event cooldown could not be saved before publishing the scene");
				}
			}
			active[activeEncounter.Id] = activeEncounter;
			try
			{
				if (!string.IsNullOrEmpty(ownerId))
				{
					ContractRecord activeContract = store.GetProfile(ownerId).ActiveContract;
					if (activeContract == null || activeContract.Id != contractId || activeContract.State != EncounterState.Dormant)
					{
						throw new InvalidOperationException("The requested personal contract is no longer dormant; no scene was spawned.");
					}
					activeContract.State = EncounterState.Active;
					if (!store.SaveNow())
					{
						activeContract.State = EncounterState.Dormant;
						throw new InvalidOperationException("Contract activation could not be saved.");
					}
					activeEncounter.ContractActivated = true;
				}
				if (type == EncounterType.StormFront)
				{
					stormRunning = true;
					weather.Apply(activeEncounter.Id);
					if (!weather.IsApplied && !ValheimReflection.IsDedicatedServer())
					{
						throw new InvalidOperationException("Native ThunderStorm weather was unavailable; the route was not started.");
					}
					network.SendWeather(0L, activeEncounter.Id);
					activeEncounter.StormWeatherStartedAt = Time.time;
				}
				GameObject val3 = null;
				GameObject val4 = null;
				if (type == EncounterType.GhostShip)
				{
					Vector3 val5 = activeEncounter.GhostRouteTarget - target;
					val5.y = 0f;
					Quaternion rotation = Quaternion.LookRotation((((Vector3)(ref val5)).sqrMagnitude > 0.01f) ? ((Vector3)(ref val5)).normalized : FlatForward(ship), Vector3.up);
					val3 = (activeEncounter.PendingGhostSpawn = ValheimReflection.SpawnNetworkedPrefab("VikingShip", target, rotation, ship.gameObject, "OceanAdventuresGhostShip"));
					if ((Object)(object)val3 == (Object)null)
					{
						throw new InvalidOperationException("Ghostship spawn did not return its exact network instance.");
					}
					activeEncounter.GhostZdo = ValheimReflection.GetNetworkZdo(val3);
					object? ghostZdo = activeEncounter.GhostZdo;
					ZDO val6 = (ZDO)((ghostZdo is ZDO) ? ghostZdo : null);
					if (val6 == null)
					{
						throw new InvalidOperationException("Ghostship spawn has no network identity.");
					}
					val6.Set("OceanAdventuresContractOwner", ownerId ?? string.Empty);
					activeEncounter.GhostNetworkId = val6.m_uid;
					activeEncounter.PendingGhostSpawn = null;
					OceanAdventuresPlugin.Log.LogInfo((object)"Ghostship network object adopted; server buoyancy and course control are active.");
				}
				if (type == EncounterType.Smugglers)
				{
					val4 = ValheimReflection.SpawnNetworkedPrefab("VikingShip", target, val, ship.gameObject, "OceanAdventuresSmugglerBoat");
					if ((Object)(object)val4 == (Object)null)
					{
						throw new InvalidOperationException("Smuggler longship spawn did not return its network instance.");
					}
					activeEncounter.RewardChest = val4;
					ZNetView component = val4.GetComponent<ZNetView>();
					if ((Object)(object)component == (Object)null || !component.IsValid())
					{
						throw new InvalidOperationException("Smuggler longship has no network identity.");
					}
					activeEncounter.RewardChestId = component.GetZDO().m_uid;
					component.GetZDO().Set("OceanAdventuresRewardChest", true);
					component.GetZDO().Set("OceanAdventuresChestReady", false);
					component.GetZDO().Set("OceanAdventuresSceneActive", true);
					if ((Object)(object)val4.GetComponent<OceanAdventuresSmugglerBoat>() == (Object)null)
					{
						val4.AddComponent<OceanAdventuresSmugglerBoat>();
					}
					ValheimReflection.SetNetworkMarker(val4, "OceanAdventuresSceneActor");
					if (long.TryParse(ownerId, out var result))
					{
						component.GetZDO().Set(ZDOVars.s_creator, result);
					}
					OceanAdventuresNetworkLifecyclePatches.RegisterSceneActor(component);
					ValheimReflection.SetNetworkPersistence(val4, persistent: true);
					Container componentInChildren = val4.GetComponentInChildren<Container>(true);
					if ((Object)(object)componentInChildren == (Object)null)
					{
						throw new InvalidOperationException("Smuggler longship has no native hold.");
					}
					((Behaviour)componentInChildren).enabled = false;
				}
				if (type == EncounterType.Shipwreck)
				{
					SpawnSurvivor(activeEncounter);
				}
				if (type == EncounterType.Smugglers)
				{
					SpawnSmugglerCrew(activeEncounter);
				}
				activeEncounter.ServerVisual = CreateServerVisual(activeEncounter, val3, val4);
				if (type == EncounterType.GhostShip)
				{
					InitializeGhostCargo(activeEncounter);
					if (activeEncounter.GhostCargoUnavailable)
					{
						AbortUnavailableGhostCargo(activeEncounter);
						return false;
					}
				}
				if (!string.IsNullOrEmpty(ownerId))
				{
					contracts.PublishProfile(ownerId);
				}
				Broadcast(activeEncounter, OceanAdventuresNetworkOp.EncounterStart);
				if (diagnostics)
				{
					ManualLogSource log = OceanAdventuresPlugin.Log;
					string[] obj = new string[5]
					{
						"Manual ",
						type.ToString(),
						" placement passed water, shoreline, footprint and route checks at ",
						null,
						null
					};
					Vector3 position = activeEncounter.Position;
					obj[3] = ((object)(*(Vector3*)(&position))/*cast due to .constrained prefix*/).ToString();
					obj[4] = ".";
					log.LogInfo((object)string.Concat(obj));
				}
				return true;
			}
			catch (Exception ex)
			{
				OceanAdventuresPlugin.Log.LogError((object)("Encounter creation failed: " + ex));
				EndEncounter(activeEncounter, EncounterState.Aborted);
				return false;
			}
			bool Reject(string reason)
			{
				if (diagnostics)
				{
					OceanAdventuresPlugin.Log.LogInfo((object)("Manual " + type.ToString() + " placement rejected: " + reason + "."));
				}
				return false;
			}
		}

		private GameObject? CreateServerVisual(ActiveEncounter encounter, GameObject? networkedGhostShip, GameObject? networkedSmugglerBoat = null)
		{
			EncounterSnapshot snapshot = Snapshot(encounter);
			visuals.ApplyStart(snapshot, networkedGhostShip, networkedSmugglerBoat);
			return visuals.GetObject(encounter.Id);
		}

		private static void SpawnSurvivor(ActiveEncounter encounter)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = ValheimReflection.SpawnNetworkedPrefab(((Object)(object)ValheimReflection.GetPrefab("Dverger") != (Object)null) ? "Dverger" : "DvergerMage", encounter.Position + Vector3.up * 0.5f, Quaternion.identity, null, "OceanAdventuresSurvivor") ?? throw new InvalidOperationException("Native castaway Dvergr prefab is unavailable.");
			encounter.Combatants.Add(val);
			ValheimReflection.SetNetworkMarker(val, "OceanAdventuresSceneActor");
			ValheimReflection.SetNetworkBool(val, "OceanAdventuresSceneActive", value: true);
			ValheimReflection.SetNetworkPersistence(val, persistent: true);
			encounter.SceneActorIds.Add(val.GetComponent<ZNetView>().GetZDO().m_uid);
			val.AddComponent<OceanAdventuresSurvivor>();
		}

		public void InteractScene(Player player, string id, int cargoIndex)
		{
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: Unknown result type (might be due to invalid IL or missing references)
			//IL_015e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0163: Unknown result type (might be due to invalid IL or missing references)
			if (!ValheimReflection.IsServer() || (Object)(object)player == (Object)null || ((Character)player).IsDead() || !active.TryGetValue(id, out ActiveEncounter value))
			{
				return;
			}
			if (value.Type == EncounterType.Smugglers)
			{
				if (cargoIndex == 0 && value.State == EncounterState.ReadyToLoot)
				{
					SmugglerCrewState? smugglerCrew = value.SmugglerCrew;
					if (smugglerCrew == null)
					{
						_ = 1;
					}
					else
						_ = !smugglerCrew.Complete;
				}
			}
			else
			{
				if (value.Type != EncounterType.Shipwreck || (value.State != EncounterState.Active && value.State != EncounterState.ReadyToLoot) || value.Combatants.Count == 0 || (Object)(object)value.Combatants[0] == (Object)null)
				{
					return;
				}
				string playerId = ValheimReflection.GetPlayerId(player);
				bool flag;
				if (cargoIndex == -1)
				{
					Vector3 val = CurrentWaterPosition(value.Position) + value.SceneRotation * WreckSceneLayout.ReturnOffset;
					if (Vector3.Distance(((Component)player).transform.position, val) > 11f)
					{
						return;
					}
					if (value.State == EncounterState.ReadyToLoot && value.Cargo.Complete)
					{
						if (!value.Cargo.CanClaim(value.State, value.OwnerId, playerId))
						{
							network.SendNotice(player, OceanAdventuresNoticeCode.OnlyContractHolder);
						}
						return;
					}
					if (value.State != EncounterState.Active)
					{
						return;
					}
					flag = value.Cargo.TryReturn(playerId);
				}
				else
				{
					if (value.State != EncounterState.Active || cargoIndex < 0 || cargoIndex >= value.CargoPositions.Length || Vector3.Distance(((Component)player).transform.position, CurrentWaterPosition(value.CargoPositions[cargoIndex])) > 4f)
					{
						return;
					}
					flag = value.Cargo.TryCollect(cargoIndex, playerId);
				}
				if (flag)
				{
					if (value.Cargo.Complete)
					{
						value.State = EncounterState.ReadyToLoot;
					}
					visuals.ApplyUpdate(Snapshot(value));
					Broadcast(value, OceanAdventuresNetworkOp.EncounterUpdate);
				}
			}
		}

		private static Vector3 CurrentWaterPosition(Vector3 anchor)
		{
			//IL_0000: 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)
			float liquidLevel = Floating.GetLiquidLevel(anchor, 1f, (LiquidType)0);
			if (liquidLevel > -1000f && !float.IsNaN(liquidLevel) && !float.IsInfinity(liquidLevel))
			{
				anchor.y = liquidLevel;
			}
			return anchor;
		}

		private void UpdateRewardChest(ActiveEncounter encounter)
		{
			//IL_01a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_023d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0242: Unknown result type (might be due to invalid IL or missing references)
			//IL_024a: Unknown result type (might be due to invalid IL or missing references)
			//IL_024f: Unknown result type (might be due to invalid IL or missing references)
			//IL_025a: Unknown result type (might be due to invalid IL or missing references)
			//IL_025f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0261: Unknown result type (might be due to invalid IL or missing references)
			//IL_0266: Unknown result type (might be due to invalid IL or missing references)
			//IL_026b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0272: Unknown result type (might be due to invalid IL or missing references)
			//IL_027a: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0157: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_033f: Unknown result type (might be due to invalid IL or missing references)
			//IL_035d: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_03db: Unknown result type (might be due to invalid IL or missing references)
			if (Time.time < encounter.NextRewardChestAttempt)
			{
				return;
			}
			encounter.NextRewardChestAttempt = Time.time + 0.5f;
			Player val = null;
			if (!string.IsNullOrEmpty(encounter.OwnerId))
			{
				ContractRecord activeContract = store.GetProfile(encounter.OwnerId).ActiveContract;
				if (activeContract == null || activeContract.Id != encounter.ContractId)
				{
					EndEncounter(encounter, EncounterState.Aborted);
					return;
				}
				val = ((IEnumerable<Player>)Player.GetAllPlayers()).FirstOrDefault((Func<Player, bool>)((Player candidate) => (Object)(object)candidate != (Object)null && !((Character)candidate).IsDead() && ValheimReflection.GetPlayerId(candidate) == encounter.OwnerId));
			}
			if (!encounter.ChestRewardsPrepared)
			{
				if ((Object)(object)val != (Object)null)
				{
					if (!contracts.TryPrepareChest(val, encounter.Type, encounter.ContractId, out List<RewardGrant> remaining))
					{
						encounter.NextRewardChestAttempt = Time.time + 10f;
						return;
					}
					encounter.ChestRemaining = remaining;
				}
				else
				{
					if (!string.IsNullOrEmpty(encounter.OwnerId))
					{
						return;
					}
					encounter.ChestRemaining = rewards.CreateFreeEncounterLoot(encounter.Type, encounter.SmugglerCrew?.Count ?? 0, HighestKnownTierNear(encounter.Position), encounter.Id);
				}
				encounter.ChestRewardsPrepared = true;
			}
			if ((Object)(object)encounter.RewardChest == (Object)null)
			{
				if (!((ZDOID)(ref encounter.RewardChestId)).Equals(default(ZDOID)))
				{
					ZNetScene instance = ZNetScene.instance;
					GameObject val2 = ((instance != null) ? instance.FindInstance(encounter.RewardChestId) : null);
					if ((Object)(object)val2 == (Object)null)
					{
						if (++encounter.RewardChestAttempts >= 20)
						{
							AbortUnavailableRewardChest(encounter, "The original reward vessel was lost; this scene cannot mint another copy.");
						}
						return;
					}
					encounter.RewardChest = val2;
				}
				else
				{
					if (encounter.Type == EncounterType.Smugglers)
					{
						AbortUnavailableRewardChest(encounter, "The smuggler longship was not available for its hold.");
						return;
					}
					Vector3 returnOffset = WreckSceneLayout.ReturnOffset;
					Vector3 position = CurrentWaterPosition(encounter.Position) + encounter.SceneRotation * returnOffset;
					GameObject val3 = ValheimReflection.SpawnNetworkedPrefab("piece_chest_wood", position, encounter.SceneRotation, null, "OceanAdventuresRewardChest");
					if ((Object)(object)val3 == (Object)null)
					{
						AbortUnavailableRewardChest(encounter, "Native wooden chest prefab is unavailable.");
						return;
					}
					encounter.RewardChest = val3;
					ZNetView component = val3.GetComponent<ZNetView>();
					if ((Object)(object)component == (Object)null || !component.IsValid())
					{
						AbortUnavailableRewardChest(encounter, "Native reward chest has no network identity.");
						return;
					}
					encounter.RewardChestId = component.GetZDO().m_uid;
					OceanAdventuresNetworkLifecyclePatches.RegisterSceneActor(component);
					ValheimReflection.SetNetworkMarker(val3, "OceanAdventuresSceneActor");
					ValheimReflection.SetNetworkBool(val3, "OceanAdventuresSceneActive", value: true);
					ValheimReflection.SetNetworkBool(val3, "OceanAdventuresChestReady", value: false);
					ValheimReflection.SetNetworkPersistence(val3, persistent: true);
					component.GetZDO().Set("OceanAdventuresChestHeightOffset", returnOffset.y);
					(val3.GetComponent<SceneWaterAnchor>() ?? val3.AddComponent<SceneWaterAnchor>()).Configure(returnOffset.y);
					Container componentInChildren = val3.GetComponentInChildren<Container>(true);
					if ((Object)(object)componentInChildren == (Object)null || !ConfigureRewardChestAccess(val3, componentInChildren, encounter.OwnerId))
					{
						AbortUnavailableRewardChest(encounter, "Native reward chest cannot enforce its owner access.");
						return;
					}
					BoxCollider obj = ((Component)componentInChildren).gameObject.AddComponent<BoxCollider>();
					obj.center = new Vector3(0f, 0.65f, 0f);
					obj.size = new Vector3(2.2f, 1.6f, 2.2f);
					((Collider)obj).isTrigger = true;
					Broadcast(encounter, OceanAdventuresNetworkOp.EncounterUpdate);
				}
			}
			GameObject rewardChest = encounter.RewardChest;
			if ((Object)(object)rewardChest == (Object)null)
			{
				AbortUnavailableRewardChest(encounter, "Reward chest disappeared before it was looted.");
				return;
			}
			Container componentInChildren2 = rewardChest.GetComponentInChildren<Container>(true);
			if (!((Object)(object)componentInChildren2 == (Object)null) && ValheimReflection.TryEnsureContainerInventory(componentInChildren2, out object inventory))
			{
				Inventory val4 = (Inventory)((inventory is Inventory) ? inventory : null);
				if (val4 != null)
				{
					if (!encounter.RewardChestReady)
					{
						val4.RemoveAll();
						foreach (RewardGrant item in encounter.ChestRemaining)
						{
							if (ValheimReflection.Invoke(val4, "AddItem", item.Prefab, item.Amount, item.Quality, 0, 0L, "", false, false) == null)
							{
								if (++encounter.RewardChestAttempts >= 10)
								{
									AbortUnavailableRewardChest(encounter, "Native reward chest rejected an item.");
								}
								return;
							}
						}
						if (!ChestMatches(encounter.ChestRemaining, val4) || GhostHoldInteractionPatch.TryFindBadIcon(val4, out string _))
						{
							if (++encounter.RewardChestAttempts >= 10)
							{
								AbortUnavailableRewardChest(encounter, "Native reward chest cargo failed validation.");
							}
							return;
						}
						ValheimReflection.Invoke(componentInChildren2, "OnContainerChanged");
						encounter.RewardChestReady = true;
						ValheimReflection.SetNetworkBool(rewardChest, "OceanAdventuresChestReady", value: true);
						if (encounter.Type == EncounterType.Smugglers)
						{
							rewardChest.GetComponent<OceanAdventuresSmugglerBoat>()?.ApplyReady(ready: true);
						}
						OceanAdventuresPlugin.Log.LogInfo((object)(encounter.Type.ToString() + " native reward chest ready: " + string.Join(", ", encounter.ChestRemaining.Select((RewardGrant item) => item.Prefab + " x" + item.Amount)) + (((Object)(object)val == (Object)null) ? " (free)." : " (private contract chest).")));
						visuals.ApplyUpdate(Snapshot(encounter));
						Broadcast(encounter, OceanAdventuresNetworkOp.EncounterUpdate);
						return;
					}
					object obj2 = ValheimReflection.Invoke(componentInChildren2, "IsInUse");
					if (obj2 is bool && (bool)obj2)
					{
						return;
					}
					List<RewardGrant> list = ObserveChestBalance(encounter.ChestRemaining, val4);
					if (!SameChestBalance(list, encounter.ChestRemaining))
					{
						if (!string.IsNullOrEmpty(encounter.OwnerId) && !contracts.SaveChestRemaining(encounter.OwnerId, encounter.ContractId, list))
						{
							return;
						}
						encounter.ChestRemaining = list;
						OceanAdventuresPlugin.Log.LogInfo((object)(encounter.Type.ToString() + " chest balance updated: " + ((list.Count == 0) ? "empty." : (string.Join(", ", list.Select((RewardGrant item) => item.Prefab + " x" + item.Amount)) + "."))));
					}
					if (encounter.ChestRemaining.Count == 0 && val4.GetAllItems().Count == 0 && (string.IsNullOrEmpty(encounter.OwnerId) || (!((Object)(object)val == (Object)null) && contracts.CompleteChest(val, encounter.Type, encounter.ContractId))))
					{
						MarkChestLooted(encounter, val);
					}
					return;
				}
			}
			if (++encounter.RewardChestAttempts >= 10)
			{
				AbortUnavailableRewardChest(encounter, "Native reward chest inventory did not initialize.");
			}
		}

		private static bool ConfigureRewardChestAccess(GameObject chest, Container container, string ownerId)
		{
			FieldInfo fieldInfo = AccessTools.Field(typeof(Container), "m_privacy");
			if (fieldInfo == null)
			{
				return false;
			}
			fieldInfo.SetValue(container, Enum.ToObject(fieldInfo.FieldType, string.IsNullOrEmpty(ownerId) ? 2 : 0));
			AccessTools.Field(typeof(Container), "m_autoDestroyEmpty")?.SetValue(container, false);
			AccessTools.Field(typeof(Container), "m_checkGuardStone")?.SetValue(container, false);
			if (string.IsNullOrEmpty(ownerId))
			{
				return true;
			}
			if (!long.TryParse(ownerId, out var result) || result == 0L)
			{
				return false;
			}
			Piece componentInChildren = chest.GetComponentInChildren<Piece>(true);
			FieldInfo fieldInfo2 = AccessTools.Field(typeof(Piece), "m_creator");
			ZNetView component = chest.GetComponent<ZNetView>();
			if ((Object)(object)componentInChildren == (Object)null || fieldInfo2 == null || (Object)(object)component == (Object)null || !component.IsValid())
			{
				return false;
			}
			fieldInfo2.SetValue(componentInChildren, result);
			component.GetZDO().Set(ZDOVars.s_creator, result);
			return componentInChildren.GetCreator() == result;
		}

		private static List<RewardGrant> ObserveChestBalance(List<RewardGrant> expected, Inventory inventory)
		{
			Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.Ordinal);
			foreach (ItemData allItem in inventory.GetAllItems())
			{
				if (allItem == null || (Object)(object)allItem.m_dropPrefab == (Object)null)
				{
					return expected.Select((RewardGrant grant) => new RewardGrant
					{
						Prefab = grant.Prefab,
						Amount = grant.Amount,
						Quality = grant.Quality
					}).ToList();
				}
				GameObject dropPrefab = allItem.m_dropPrefab;
				string text = ((dropPrefab != null) ? ((Object)dropPrefab).name : null);
				if (!string.IsNullOrEmpty(text))
				{
					string key = text + "|" + allItem.m_quality;
					dictionary[key] = (dictionary.TryGetValue(key, out var value) ? (value + allItem.m_stack) : allItem.m_stack);
				}
			}
			List<RewardGrant> list = new List<RewardGrant>();
			foreach (RewardGrant item in expected)
			{
				string key2 = item.Prefab + "|" + item.Quality;
				int value2;
				int num = (dictionary.TryGetValue(key2, out value2) ? value2 : 0);
				int num2 = Math.Min(item.Amount, num);
				dictionary[key2] = Math.Max(0, num - num2);
				if (num2 > 0)
				{
					list.Add(new RewardGrant
					{
						Prefab = item.Prefab,
						Amount = num2,
						Quality = item.Quality
					});
				}
			}
			return list;
		}

		private static bool ChestMatches(List<RewardGrant> expected, Inventory inventory)
		{
			if (SameChestBalance(expected, ObserveChestBalance(expected, inventory)))
			{
				return inventory.GetAllItems().Count == expected.Count;
			}
			return false;
		}

		private static bool SameChestBalance(List<RewardGrant> left, List<RewardGrant> right)
		{
			if (left.Count == right.Count)
			{
				return left.Zip(right, (RewardGrant a, RewardGrant b) => a.Prefab == b.Prefab && a.Amount == b.Amount && a.Quality == b.Quality).All((bool match) => match);
			}
			return false;
		}

		private void AbortUnavailableRewardChest(ActiveEncounter encounter, string reason)
		{
			OceanAdventuresPlugin.Log.LogWarning((object)("Reward chest unavailable: " + reason));
			EndEncounter(encounter, EncounterState.Aborted);
		}

		private void MarkChestLooted(ActiveEncounter encounter, Player? owner)
		{
			encounter.State = EncounterState.Claimed;
			encounter.SalvageClaimedAt = Time.time;
			if ((Object)(object)owner != (Object)null)
			{
				network.SendNotice(owner, (encounter.Type == EncounterType.Shipwreck) ? OceanAdventuresNoticeCode.WreckRewardQueued : OceanAdventuresNoticeCode.SmugglerRewardQueued);
			}
			visuals.ApplyUpdate(Snapshot(encounter));
			Broadcast(encounter, OceanAdventuresNetworkOp.EncounterUpdate);
		}

		private void SpawnSmugglerCrew(ActiveEncounter encounter)
		{
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_0122: Unknown result type (might be due to invalid IL or missing references)
			SmugglerCrewState crew = new SmugglerCrewState(config.SmugglerCrewSize);
			encounter.SmugglerCrew = crew;
			encounter.SmugglerMissingSince = Enumerable.Repeat(-1f, crew.Count).ToArray();
			for (int i = 0; i < crew.Count; i++)
			{
				Vector3 position = SmugglerCrewSpawnPosition(encounter, i);
				GameObject val = ValheimReflection.SpawnNetworkedPrefab("Dverger", position, encounter.SceneRotation, null, "OceanAdventuresSmuggler") ?? throw new InvalidOperationException("Could not spawn the full native smuggler crew.");
				encounter.Combatants.Add(val);
				ZNetView component = val.GetComponent<ZNetView>();
				Humanoid component2 = val.GetComponent<Humanoid>();
				if ((Object)(ob