Decompiled source of FreshWorld v1.0.6

FreshWorld.dll

Decompiled 18 hours 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.ExceptionServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Xml;
using BepInEx;
using BepInEx.Configuration;
using FreshWorld.Backend;
using FreshWorld.Commands;
using FreshWorld.Configuration;
using FreshWorld.Core;
using FreshWorld.Engine;
using FreshWorld.Runtime;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using SoftReferenceableAssets;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("FreshWorld")]
[assembly: AssemblyProduct("FreshWorld")]
[assembly: AssemblyCompany("sighsorry")]
[assembly: AssemblyFileVersion("1.0.6")]
[assembly: AssemblyInformationalVersion("1.0.6")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyVersion("1.0.6.0")]
[module: RefSafetyRules(11)]
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;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace FreshWorld
{
	[BepInPlugin("sighsorry.FreshWorld", "FreshWorld", "1.0.6")]
	public sealed class FreshWorldPlugin : BaseUnityPlugin
	{
		private sealed class ManualRequest
		{
			internal readonly ScheduledRun Run;

			internal readonly CommandRequestContext Context;

			internal readonly RunOptions Options;

			internal string? LastWait;

			internal ManualRequest(ScheduledRun run, CommandRequestContext context, RunOptions options)
			{
				Run = run;
				Context = context;
				Options = options;
			}
		}

		public const string Author = "sighsorry";

		public const string ModName = "FreshWorld";

		public const string ModVersion = "1.0.6";

		public const string ModGUID = "sighsorry.FreshWorld";

		public const string PluginGuid = "sighsorry.FreshWorld";

		public const string PluginName = "FreshWorld";

		public const string PluginVersion = "1.0.6";

		private const float SchedulePollIntervalSeconds = 1f;

		internal static FreshWorldPlugin? Instance;

		private FreshWorldConfig configuration;

		private RuntimeSettings? settings;

		private Harmony? harmony;

		private FileSystemWatcher? watcher;

		private int reloadRequested;

		private bool supported;

		private bool sessionFaulted;

		private ZNet? host;

		private long? worldUid;

		private FreshWorldScheduler? scheduler;

		private GuardedCoroutine? runner;

		private IDisposable? backendLease;

		private ScheduleClock lastClock;

		private float attachedAt;

		private float nextPoll;

		private float nextCheckpoint;

		private ManualRequest? pendingManual;

		private string? activeRunId;

		private string progress = "Idle.";

		private void Awake()
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Expected O, but got Unknown
			Instance = this;
			try
			{
				configuration = new FreshWorldConfig(((BaseUnityPlugin)this).Config);
				harmony = new Harmony("sighsorry.FreshWorld");
				harmony.PatchAll(typeof(FreshWorldPlugin).Assembly);
				ReloadSettings(reloadFile: false);
				FreshWorldCommands.Register(HandleCommand, delegate(string message)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)message);
				});
				watcher = new FileSystemWatcher(Path.GetDirectoryName(((BaseUnityPlugin)this).Config.ConfigFilePath), Path.GetFileName(((BaseUnityPlugin)this).Config.ConfigFilePath));
				watcher.Changed += OnConfigChanged;
				watcher.Created += OnConfigChanged;
				watcher.Renamed += OnConfigChanged;
				watcher.EnableRaisingEvents = true;
				supported = true;
				((BaseUnityPlugin)this).Logger.LogInfo((object)("FreshWorld ready. Maintenance runs only on the world host; configure " + ((BaseUnityPlugin)this).Config.ConfigFilePath));
			}
			catch (Exception ex)
			{
				CleanupPlugin();
				((BaseUnityPlugin)this).Logger.LogError((object)("FreshWorld initialization failed; maintenance is disabled. " + ex));
			}
		}

		private void OnConfigChanged(object sender, FileSystemEventArgs args)
		{
			Interlocked.Exchange(ref reloadRequested, 1);
		}

		private void ReloadSettings(bool reloadFile)
		{
			try
			{
				if (reloadFile)
				{
					bool saveOnConfigSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet;
					try
					{
						((BaseUnityPlugin)this).Config.SaveOnConfigSet = false;
						((BaseUnityPlugin)this).Config.Reload();
					}
					finally
					{
						((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet;
					}
				}
				RuntimeSettings runtimeSettings = configuration.Capture();
				try
				{
					scheduler?.Reconfigure(runtimeSettings.Schedule, ReadClock());
				}
				catch (Exception ex)
				{
					sessionFaulted = true;
					CloseScheduler("The updated schedule could not be persisted.");
					((BaseUnityPlugin)this).Logger.LogError((object)("Could not update the recorded schedule; maintenance is disabled for this world session. " + ex));
					return;
				}
				settings = runtimeSettings;
				((BaseUnityPlugin)this).Logger.LogInfo((object)$"Configuration applied: automatic={runtimeSettings.AutomaticEnabled}, schedule={runtimeSettings.Schedule.Mode}, timezone={runtimeSettings.Schedule.TimeZone.Id}, safeZones={runtimeSettings.Options.ZoneSafeZones}/{runtimeSettings.Options.VegetationSafeZones}/{runtimeSettings.Options.LocationSafeZones} (zones/resources/locations).");
				nextPoll = 0f;
			}
			catch (Exception ex2)
			{
				settings = null;
				CloseScheduler("The host configuration is invalid.");
				((BaseUnityPlugin)this).Logger.LogError((object)("Invalid FreshWorld configuration; new maintenance is disabled until the cfg is corrected. " + ex2.Message));
			}
		}

		private static bool IsHost(ZNet? net)
		{
			if ((Object)(object)net != (Object)null && net.IsServer() && !net.HaveStopped)
			{
				return ZNet.World != null;
			}
			return false;
		}

		private static bool WorldReady()
		{
			if ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.LocationsGenerated && ZDOMan.instance != null && (Object)(object)ZNetScene.instance != (Object)null && WorldGenerator.instance != null && (Object)(object)DungeonDB.instance != (Object)null && (Object)(object)Game.instance != (Object)null && (Object)(object)EnvMan.instance != (Object)null)
			{
				return !ZNet.m_loadError;
			}
			return false;
		}

		private ScheduleClock ReadClock()
		{
			if (!IsHost(host) || (Object)(object)EnvMan.instance == (Object)null)
			{
				return new ScheduleClock(DateTimeOffset.UtcNow, lastClock.GameDay);
			}
			long dayLengthSec = EnvMan.instance.m_dayLengthSec;
			if (dayLengthSec <= 0)
			{
				throw new InvalidOperationException("The game day length must be positive.");
			}
			return new ScheduleClock(DateTimeOffset.UtcNow, Math.Max(0.0, host.GetTimeSeconds() / (double)dayLengthSec));
		}

		private void Update()
		{
			if (!supported)
			{
				return;
			}
			try
			{
				ZNet instance = ZNet.instance;
				long? num = (IsHost(instance) ? new long?(instance.GetWorldUID()) : ((long?)null));
				if (worldUid.HasValue && (host != instance || worldUid != num || !IsHost(instance)))
				{
					StopSession();
				}
				if (runner == null && Interlocked.Exchange(ref reloadRequested, 0) != 0)
				{
					ReloadSettings(reloadFile: true);
				}
				if (!IsHost(instance) || !WorldReady())
				{
					runner?.Dispose();
					runner = null;
					CancelPendingManual("The hosting world is no longer ready.");
					return;
				}
				AttachHost(instance);
				if (runner != null)
				{
					GuardedCoroutine guardedCoroutine = runner;
					if (!guardedCoroutine.MoveNext() && runner == guardedCoroutine)
					{
						runner = null;
					}
				}
				if (runner == null && Interlocked.Exchange(ref reloadRequested, 0) != 0)
				{
					ReloadSettings(reloadFile: true);
				}
				if (settings == null || sessionFaulted)
				{
					return;
				}
				lastClock = ReadClock();
				EnsureScheduler();
				if (pendingManual != null)
				{
					if (!IsCurrentRequest(pendingManual.Context))
					{
						CancelPendingManual("The requester disconnected, changed session, or lost administrator authority.");
					}
					else if (runner == null)
					{
						TryDispatch(pendingManual.Run, pendingManual.Options);
					}
				}
				float realtimeSinceStartup = Time.realtimeSinceStartup;
				if (!(realtimeSinceStartup < nextPoll))
				{
					nextPoll = realtimeSinceStartup + 1f;
					ScheduledRun due = scheduler.GetDue(lastClock);
					if (realtimeSinceStartup >= nextCheckpoint)
					{
						scheduler.Checkpoint(lastClock);
						nextCheckpoint = realtimeSinceStartup + 60f;
					}
					if (runner == null && due != null)
					{
						TryDispatch(due, settings.Options);
					}
				}
			}
			catch (Exception ex)
			{
				sessionFaulted = true;
				runner?.Dispose();
				runner = null;
				((BaseUnityPlugin)this).Logger.LogError((object)("FreshWorld stopped maintenance for this world session after an error. " + ex));
				CloseScheduler("The host stopped maintenance after an error.");
			}
		}

		private void AttachHost(ZNet current)
		{
			long worldUID = current.GetWorldUID();
			if (worldUid.HasValue && (host != current || worldUid != worldUID))
			{
				StopSession();
			}
			if (!((Object)(object)host != (Object)null))
			{
				host = current;
				worldUid = worldUID;
				attachedAt = Time.realtimeSinceStartup;
				nextCheckpoint = attachedAt + 60f;
				sessionFaulted = false;
			}
		}

		private void EnsureScheduler()
		{
			if (scheduler == null)
			{
				string text = worldUid.Value.ToString(CultureInfo.InvariantCulture);
				scheduler = FreshWorldScheduler.Open(settings.Schedule, new JsonWorldStateStore(Path.Combine(Paths.ConfigPath, "FreshWorld", "state")), text, lastClock);
				ScheduledRun pendingRun = scheduler.PendingRun;
				if (pendingRun != null && pendingRun.IsManual)
				{
					scheduler.CancelPendingManualRun(pendingRun.Id, lastClock, "The manual request belonged to an earlier host session.");
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Cancelled an earlier session's unstarted manual request: " + pendingRun.Id));
				}
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Scheduler attached to world " + host.GetWorldName() + " (" + text + ")."));
			}
		}

		private bool IsCurrentRequest(CommandRequestContext context)
		{
			if (context.IsAuthorizedNow && context.Network == ZNet.instance && IsHost(context.Network))
			{
				return context.Network.GetWorldUID() == context.WorldUid;
			}
			return false;
		}

		internal void HandleCommand(FreshWorldCommandAction action, CommandRequestContext context)
		{
			try
			{
				if (!supported || !IsCurrentRequest(context))
				{
					context.Reply("Request rejected: this host session or your authority is no longer valid.");
					return;
				}
				if (!Enum.IsDefined(typeof(FreshWorldCommandAction), action))
				{
					context.Reply("Invalid command action.");
					return;
				}
				if (!WorldReady())
				{
					context.Reply("Request rejected: the world is not ready.");
					return;
				}
				AttachHost(context.Network);
				if (action == FreshWorldCommandAction.Status)
				{
					context.Reply(StatusMessage());
					return;
				}
				if (runner != null || activeRunId != null)
				{
					context.Reply("Maintenance is already running: " + activeRunId + ". " + progress);
					return;
				}
				if (pendingManual != null)
				{
					if (IsCurrentRequest(pendingManual.Context))
					{
						context.Reply("A manual request is already pending: " + pendingManual.Run.Id);
						return;
					}
					CancelPendingManual("The earlier request lost its authority or session.");
				}
				Interlocked.Exchange(ref reloadRequested, 0);
				ReloadSettings(reloadFile: true);
				if (settings == null)
				{
					context.Reply("Request rejected: the host cfg is invalid; see the host log.");
					return;
				}
				if (sessionFaulted)
				{
					context.Reply("Request rejected: maintenance is faulted for this world session; see the host log.");
					return;
				}
				if (!IsCurrentRequest(context))
				{
					context.Reply("Request rejected: authority changed before admission.");
					return;
				}
				lastClock = ReadClock();
				EnsureScheduler();
				ScheduledRun due = scheduler.GetDue(lastClock);
				if (due != null)
				{
					context.Reply("A scheduled maintenance is already pending: " + due.Id);
					return;
				}
				ScheduledRun scheduledRun = scheduler.RequestManualRun(lastClock);
				pendingManual = new ManualRequest(scheduledRun, context, settings.Options);
				context.Reply("Accepted " + scheduledRun.Id + ". Host cfg: zones=" + settings.Options.ZonesEnabled + ", vegetation=" + settings.Options.VegetationEnabled + ", locations=" + settings.Options.LocationsEnabled + ", safeZones=" + settings.Options.ZoneSafeZones + "/" + settings.Options.VegetationSafeZones + "/" + settings.Options.LocationSafeZones + " (zones/resources/locations).");
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Manual request accepted from " + context.Actor + ": " + scheduledRun.Id));
				TryDispatch(scheduledRun, pendingManual.Options);
				nextPoll = 0f;
			}
			catch (Exception ex)
			{
				sessionFaulted = true;
				context.Reply("Request failed; see the host log. New maintenance is disabled for this session.");
				((BaseUnityPlugin)this).Logger.LogError((object)("FreshWorld command admission failed: " + ex));
				CloseScheduler("Command admission failed.");
			}
		}

		private string StatusMessage()
		{
			if (sessionFaulted)
			{
				return "Maintenance is faulted for this world session. " + progress;
			}
			if (runner != null)
			{
				return "Running " + activeRunId + ": " + progress;
			}
			if (pendingManual != null)
			{
				return "Pending " + pendingManual.Run.Id + ": " + (pendingManual.LastWait ?? "waiting to start");
			}
			if (settings == null)
			{
				return "The host cfg is invalid; new maintenance is disabled.";
			}
			ScheduledRun scheduledRun = scheduler?.PendingRun;
			if (scheduledRun != null)
			{
				return "Scheduled maintenance pending: " + scheduledRun.Id;
			}
			RunRecord runRecord = scheduler?.Attempts.LastOrDefault();
			return string.Concat("Automatic=" + settings.AutomaticEnabled + "; manual commands available; Mode=" + settings.Schedule.Mode.ToString() + "; zones=" + settings.Options.ZonesEnabled + ", resources=" + settings.Options.VegetationEnabled + ", locations=" + settings.Options.LocationsEnabled + "; safeZones=" + settings.Options.ZoneSafeZones + "/" + settings.Options.VegetationSafeZones + "/" + settings.Options.LocationSafeZones + " (zones/resources/locations). ", (runRecord == null) ? "Idle. No maintenance has run in this world's recorded history." : ("Idle. Last run: " + runRecord.Status.ToString() + " (" + runRecord.Run.Id + ")."));
		}

		private void TryDispatch(ScheduledRun run, RunOptions options)
		{
			if (runner != null || settings == null || sessionFaulted || (!run.IsManual && !settings.AutomaticEnabled))
			{
				return;
			}
			ManualRequest manualRequest = pendingManual;
			if (run.IsManual)
			{
				if (manualRequest == null || manualRequest.Run.Id != run.Id || !IsCurrentRequest(manualRequest.Context))
				{
					if (manualRequest != null && manualRequest.Run.Id == run.Id)
					{
						CancelPendingManual("The requester is no longer authorized in this session.");
					}
					else
					{
						scheduler.CancelPendingManualRun(run.Id, lastClock, "No authenticated request exists in this session.");
					}
					return;
				}
				options = manualRequest.Options;
			}
			string text = null;
			if (!WorldReady() || !IsHost(host))
			{
				text = "world readiness";
			}
			else if (Time.realtimeSinceStartup - attachedAt < settings.StartupDelaySeconds)
			{
				text = "startup delay";
			}
			else if (Time.timeScale <= 0f)
			{
				text = "the host to unpause";
			}
			else if (host.IsSaving())
			{
				text = "the current world save";
			}
			else if (!MaintenanceGate.IsAvailable)
			{
				text = "another maintenance operation";
			}
			if (text != null)
			{
				if (manualRequest != null && manualRequest.LastWait != text)
				{
					manualRequest.LastWait = text;
					manualRequest.Context.Reply("Waiting for " + text + ".");
				}
			}
			else
			{
				StartRun(run, options);
			}
		}

		private void StartRun(ScheduledRun run, RunOptions options)
		{
			FreshWorldScheduler activeScheduler = scheduler;
			ZNet activeHost = host;
			long? activeWorldUid = worldUid;
			CommandRequestContext requester = ((pendingManual?.Run.Id == run.Id) ? pendingManual.Context : null);
			backendLease = MaintenanceGate.Acquire();
			try
			{
				activeScheduler.BeginRun(run, lastClock);
				pendingManual = null;
				activeRunId = run.Id;
				progress = "Starting.";
				Exception failure = null;
				MaintenancePipeline maintenancePipeline = new MaintenancePipeline(options, run.IncludeVegetation, delegate(string message)
				{
					progress = message;
					((BaseUnityPlugin)this).Logger.LogInfo((object)message);
					requester?.Reply(message);
				}, delegate(string message)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)message);
					requester?.Reply(message);
				});
				runner = new GuardedCoroutine(maintenancePipeline.Run(), CanContinue, delegate(Exception error)
				{
					failure = failure ?? error;
					((BaseUnityPlugin)this).Logger.LogError((object)("Maintenance error: " + error));
				}, delegate(bool success)
				{
					try
					{
						if (success)
						{
							activeScheduler.CompleteRun(run.Id, ReadClock());
							((BaseUnityPlugin)this).Logger.LogInfo((object)("Maintenance completed: " + run.Id));
							progress = "Completed.";
							requester?.Reply("Completed " + run.Id + ". World changes use the game's normal saves; no extra save was requested.");
						}
						else
						{
							activeScheduler.FailRun(run.Id, ReadClock(), failure?.Message ?? "World session ended or maintenance was cancelled.");
							((BaseUnityPlugin)this).Logger.LogWarning((object)("Maintenance stopped; completed world changes remain. This run will not retry automatically: " + run.Id));
							progress = "Stopped: " + (failure?.Message ?? "world session ended");
							requester?.Reply("Failed or interrupted " + run.Id + ": " + (failure?.Message ?? "world session ended") + ". Applied changes remain; this run will not retry automatically.");
						}
					}
					catch (Exception ex)
					{
						sessionFaulted = true;
						((BaseUnityPlugin)this).Logger.LogError((object)("Could not persist the maintenance result; further runs disabled for this session. " + ex));
						requester?.Reply("Maintenance ended, but its result could not be recorded. Further runs are disabled; see the host log.");
					}
					finally
					{
						backendLease?.Dispose();
						backendLease = null;
						activeRunId = null;
					}
				});
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Maintenance starting: " + run.Id + "; vegetation=" + run.IncludeVegetation));
				requester?.Reply("Starting " + run.Id + ".");
				bool CanContinue()
				{
					if (requester != null && !IsCurrentRequest(requester))
					{
						if (failure == null)
						{
							failure = new InvalidOperationException("The requester's connection, session, or authority is no longer valid.");
						}
						return false;
					}
					if (ZNet.instance == activeHost && IsHost(activeHost) && activeHost.GetWorldUID() == activeWorldUid)
					{
						return WorldReady();
					}
					return false;
				}
			}
			catch
			{
				backendLease?.Dispose();
				backendLease = null;
				activeRunId = null;
				requester?.Reply("Maintenance could not start; see the host log.");
				throw;
			}
		}

		private void CancelPendingManual(string reason)
		{
			ManualRequest manualRequest = pendingManual;
			if (manualRequest != null)
			{
				pendingManual = null;
				try
				{
					scheduler?.CancelPendingManualRun(manualRequest.Run.Id, ReadClock(), reason);
				}
				catch (Exception ex)
				{
					sessionFaulted = true;
					((BaseUnityPlugin)this).Logger.LogError((object)("Could not persist cancellation of the manual request: " + ex));
				}
				manualRequest.Context.Reply("Cancelled " + manualRequest.Run.Id + " before execution: " + reason);
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Manual request cancelled before execution: " + manualRequest.Run.Id + "; " + reason));
			}
		}

		private void CloseScheduler(string reason = "The scheduler was closed.")
		{
			CancelPendingManual(reason);
			FreshWorldScheduler freshWorldScheduler = scheduler;
			scheduler = null;
			if (freshWorldScheduler == null)
			{
				return;
			}
			try
			{
				freshWorldScheduler.Dispose();
			}
			catch (Exception ex)
			{
				sessionFaulted = true;
				((BaseUnityPlugin)this).Logger.LogError((object)("Failed to close world state. " + ex));
			}
		}

		internal void StopSession()
		{
			runner?.Dispose();
			runner = null;
			backendLease?.Dispose();
			backendLease = null;
			CloseScheduler("The hosting world session ended.");
			host = null;
			worldUid = null;
			nextPoll = 0f;
			sessionFaulted = false;
			activeRunId = null;
			progress = "Idle.";
		}

		private void OnDestroy()
		{
			CleanupPlugin();
		}

		private void CleanupPlugin()
		{
			supported = false;
			try
			{
				TryCleanup(DisposeWatcher, "Could not stop configuration watching.");
				TryCleanup(StopSession, "Could not stop the active world session.");
				TryCleanup(FreshWorldCommands.Unregister, "Could not unregister the console command.");
				TryCleanup(delegate
				{
					Harmony? obj = harmony;
					if (obj != null)
					{
						obj.UnpatchSelf();
					}
				}, "Could not remove Harmony patches.");
			}
			finally
			{
				Instance = null;
			}
		}

		private void DisposeWatcher()
		{
			FileSystemWatcher fileSystemWatcher = watcher;
			watcher = null;
			if (fileSystemWatcher != null)
			{
				fileSystemWatcher.Changed -= OnConfigChanged;
				fileSystemWatcher.Created -= OnConfigChanged;
				fileSystemWatcher.Renamed -= OnConfigChanged;
				fileSystemWatcher.Dispose();
			}
		}

		private void TryCleanup(Action cleanup, string message)
		{
			try
			{
				cleanup();
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)(message + " " + ex));
			}
		}
	}
	[HarmonyPatch(typeof(ZNet), "Shutdown", new Type[] { typeof(bool) })]
	internal static class ShutdownPatch
	{
		[HarmonyPrefix]
		private static void Prefix()
		{
			FreshWorldPlugin.Instance?.StopSession();
		}
	}
	[HarmonyPatch(typeof(ZNet), "ShutdownWithoutSave", new Type[] { typeof(bool) })]
	internal static class ShutdownWithoutSavePatch
	{
		[HarmonyPrefix]
		private static void Prefix()
		{
			FreshWorldPlugin.Instance?.StopSession();
		}
	}
}
namespace FreshWorld.Core
{
	public sealed class FreshWorldScheduler : IDisposable
	{
		private readonly object gate = new object();

		private ScheduleSettings settings;

		private readonly JsonWorldStateStore.Lease lease;

		private WorldState state;

		private bool disposed;

		private bool faulted;

		public string WorldId => state.WorldId;

		public IReadOnlyList<RunRecord> Attempts
		{
			get
			{
				lock (gate)
				{
					return state.Attempts.Select((RunRecord a) => a.Copy()).ToArray();
				}
			}
		}

		public RunRecord? ActiveRun
		{
			get
			{
				lock (gate)
				{
					return state.Attempts.FirstOrDefault((RunRecord a) => a.Status == RunStatus.Running)?.Copy();
				}
			}
		}

		public ScheduledRun? PendingRun
		{
			get
			{
				lock (gate)
				{
					EnsureUsable();
					return state.Pending;
				}
			}
		}

		private FreshWorldScheduler(ScheduleSettings settings, JsonWorldStateStore.Lease lease, WorldState state)
		{
			this.settings = settings;
			this.lease = lease;
			this.state = state;
		}

		public static FreshWorldScheduler Open(ScheduleSettings settings, JsonWorldStateStore store, string worldId, ScheduleClock now)
		{
			if (settings == null)
			{
				throw new ArgumentNullException("settings");
			}
			if (store == null)
			{
				throw new ArgumentNullException("store");
			}
			JsonWorldStateStore.Lease lease = store.Acquire(worldId);
			try
			{
				WorldState worldState = lease.Read(worldId);
				if (worldState == null)
				{
					worldState = NewState(settings, worldId, now);
				}
				else
				{
					foreach (RunRecord item in worldState.Attempts.Where((RunRecord a) => a.Status == RunStatus.Running))
					{
						item.Finish(RunStatus.Interrupted, now.UtcNow, "The previous host session ended before completion. This run will not be retried automatically.");
					}
					if (worldState.Fingerprint != settings.Fingerprint)
					{
						ResetAutomaticSchedule(worldState, settings, now);
					}
					else if (!settings.RunMissedOnWorldStart)
					{
						Advance(worldState, now);
					}
				}
				if (!settings.AutomaticEnabled)
				{
					ScheduledRun? pending = worldState.Pending;
					if (pending == null || !pending.IsManual)
					{
						worldState.Pending = null;
					}
				}
				lease.Write(worldState);
				return new FreshWorldScheduler(settings, lease, worldState);
			}
			catch
			{
				lease.Dispose();
				throw;
			}
		}

		public void Reconfigure(ScheduleSettings nextSettings, ScheduleClock now)
		{
			if (nextSettings == null)
			{
				throw new ArgumentNullException("nextSettings");
			}
			lock (gate)
			{
				EnsureUsable();
				WorldState worldState = state.Copy();
				if (worldState.Fingerprint != nextSettings.Fingerprint)
				{
					ResetAutomaticSchedule(worldState, nextSettings, now);
				}
				if (!nextSettings.AutomaticEnabled)
				{
					ScheduledRun? pending = worldState.Pending;
					if (pending == null || !pending.IsManual)
					{
						worldState.Pending = null;
					}
				}
				if (worldState.Fingerprint != state.Fingerprint || worldState.Pending?.Id != state.Pending?.Id)
				{
					Persist(worldState);
				}
				settings = nextSettings;
			}
		}

		public ScheduledRun? GetDue(ScheduleClock now)
		{
			lock (gate)
			{
				EnsureUsable();
				WorldState worldState = state.Copy();
				ScheduledRun candidate = ((!settings.AutomaticEnabled) ? null : ((settings.Mode == ScheduleMode.DailyTimes) ? LatestDaily(worldState, now) : LatestGameDay(worldState, now)));
				if (!settings.AutomaticEnabled)
				{
					ScheduledRun? pending = worldState.Pending;
					if (pending == null || !pending.IsManual)
					{
						worldState.Pending = null;
					}
				}
				Advance(worldState, now);
				if (candidate != null)
				{
					ScheduledRun? pending2 = worldState.Pending;
					if ((pending2 == null || !pending2.IsManual) && !worldState.Attempts.Any((RunRecord a) => a.Run.Id == candidate.Id))
					{
						worldState.Pending = candidate;
					}
				}
				if (worldState.Pending?.Id != state.Pending?.Id)
				{
					Persist(worldState);
				}
				else
				{
					state = worldState;
				}
				return state.Attempts.Any((RunRecord a) => a.Status == RunStatus.Running) ? null : state.Pending;
			}
		}

		public void BeginRun(ScheduledRun run, ScheduleClock now)
		{
			if (run == null)
			{
				throw new ArgumentNullException("run");
			}
			lock (gate)
			{
				EnsureUsable();
				if (state.Attempts.Any((RunRecord a) => a.Status == RunStatus.Running))
				{
					throw new InvalidOperationException("A FreshWorld run is already active.");
				}
				if (state.Pending == null || state.Pending.Id != run.Id || state.Attempts.Any((RunRecord a) => a.Run.Id == run.Id))
				{
					throw new InvalidOperationException("The run is no longer pending or has already been attempted.");
				}
				WorldState worldState = state.Copy();
				worldState.Attempts.Add(new RunRecord(worldState.Pending, now.UtcNow));
				worldState.Pending = null;
				while (worldState.Attempts.Count > 128)
				{
					worldState.Attempts.RemoveAt(0);
				}
				Persist(worldState);
			}
		}

		public ScheduledRun RequestManualRun(ScheduleClock now, bool includeVegetation = true)
		{
			lock (gate)
			{
				EnsureUsable();
				if (state.Attempts.Any((RunRecord a) => a.Status == RunStatus.Running))
				{
					throw new InvalidOperationException("A FreshWorld run is already active.");
				}
				if (state.Pending != null)
				{
					throw new InvalidOperationException("A FreshWorld run is already pending; a manual request cannot replace it.");
				}
				WorldState worldState = state.Copy();
				ScheduledRun result = (worldState.Pending = new ScheduledRun("manual:" + Guid.NewGuid().ToString("N"), now.UtcNow, now.GameDay, "Manual", includeVegetation));
				Persist(worldState);
				return result;
			}
		}

		public void CompleteRun(string runId, ScheduleClock now)
		{
			FinishRun(runId, now, RunStatus.Completed, "");
		}

		public void FailRun(string runId, ScheduleClock now, string reason)
		{
			FinishRun(runId, now, RunStatus.Failed, reason);
		}

		public bool CancelPendingManualRun(string runId, ScheduleClock now, string reason)
		{
			lock (gate)
			{
				EnsureUsable();
				if (state.Pending == null || state.Pending.Id != runId || !state.Pending.IsManual)
				{
					return false;
				}
				WorldState worldState = state.Copy();
				RunRecord runRecord = new RunRecord(worldState.Pending, now.UtcNow);
				runRecord.Finish(RunStatus.Interrupted, now.UtcNow, reason);
				worldState.Attempts.Add(runRecord);
				worldState.Pending = null;
				while (worldState.Attempts.Count > 128)
				{
					worldState.Attempts.RemoveAt(0);
				}
				Persist(worldState);
				return true;
			}
		}

		public void Checkpoint(ScheduleClock now)
		{
			lock (gate)
			{
				EnsureUsable();
				GetDue(now);
				Persist(state.Copy());
			}
		}

		private void FinishRun(string runId, ScheduleClock now, RunStatus status, string reason)
		{
			lock (gate)
			{
				EnsureUsable();
				WorldState worldState = state.Copy();
				(worldState.Attempts.SingleOrDefault((RunRecord a) => a.Run.Id == runId && a.Status == RunStatus.Running) ?? throw new InvalidOperationException("No matching active FreshWorld run exists.")).Finish(status, now.UtcNow, reason);
				Persist(worldState);
			}
		}

		private ScheduledRun? LatestDaily(WorldState current, ScheduleClock now)
		{
			if (now.UtcNow.UtcDateTime.Ticks <= current.CursorUtcTicks)
			{
				return null;
			}
			DateTime date = TimeZoneInfo.ConvertTime(now.UtcNow, settings.TimeZone).Date;
			for (int i = 0; i < 3; i++)
			{
				DateTime dateTime = date.AddDays(-i);
				for (int num = settings.DailyTimes.Count - 1; num >= 0; num--)
				{
					TimeSpan timeSpan = settings.DailyTimes[num];
					DateTime dateTime2 = DateTime.SpecifyKind(dateTime.Add(timeSpan), DateTimeKind.Unspecified);
					if (!settings.TimeZone.IsInvalidTime(dateTime2))
					{
						TimeSpan offset = (settings.TimeZone.IsAmbiguousTime(dateTime2) ? settings.TimeZone.GetAmbiguousTimeOffsets(dateTime2).Max() : settings.TimeZone.GetUtcOffset(dateTime2));
						DateTimeOffset dateTimeOffset = new DateTimeOffset(dateTime2, offset).ToUniversalTime();
						if (!(dateTimeOffset > now.UtcNow))
						{
							if (dateTimeOffset.UtcDateTime.Ticks <= current.CursorUtcTicks)
							{
								return null;
							}
							string text = ScheduleSettings.FormatTime(timeSpan);
							return new ScheduledRun(current.Fingerprint + ":daily:" + dateTime.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture) + ":" + text, dateTimeOffset, now.GameDay, text, settings.IncludeVegetation(timeSpan));
						}
					}
				}
			}
			return null;
		}

		private ScheduledRun? LatestGameDay(WorldState current, ScheduleClock now)
		{
			if (now.GameDay <= current.CursorGameDay)
			{
				return null;
			}
			double num = Math.Floor((now.GameDay - current.GameDayAnchor) / settings.GameDayInterval);
			double num2 = Math.Floor((current.CursorGameDay - current.GameDayAnchor) / settings.GameDayInterval);
			if (num < 1.0 || num <= num2)
			{
				return null;
			}
			double gameDay = current.GameDayAnchor + num * settings.GameDayInterval;
			return new ScheduledRun(current.Fingerprint + ":days:" + current.GameDayAnchor.ToString("R", CultureInfo.InvariantCulture) + ":" + num.ToString("0", CultureInfo.InvariantCulture), now.UtcNow, gameDay, "GameDays", vegetation: true);
		}

		private void Persist(WorldState next)
		{
			try
			{
				lease.Write(next);
				state = next;
			}
			catch
			{
				faulted = true;
				throw;
			}
		}

		private static void Advance(WorldState target, ScheduleClock clock)
		{
			target.CursorUtcTicks = Math.Max(target.CursorUtcTicks, clock.UtcNow.UtcDateTime.Ticks);
			target.CursorGameDay = Math.Max(target.CursorGameDay, clock.GameDay);
		}

		private static void ResetAutomaticSchedule(WorldState target, ScheduleSettings settings, ScheduleClock now)
		{
			target.Fingerprint = settings.Fingerprint;
			ScheduledRun? pending = target.Pending;
			if (pending == null || !pending.IsManual)
			{
				target.Pending = null;
			}
			target.GameDayAnchor = now.GameDay;
			target.CursorGameDay = now.GameDay;
			target.CursorUtcTicks = now.UtcNow.UtcDateTime.Ticks;
		}

		private static WorldState NewState(ScheduleSettings settings, string worldId, ScheduleClock now)
		{
			return new WorldState
			{
				WorldId = worldId,
				Fingerprint = settings.Fingerprint,
				CursorUtcTicks = now.UtcNow.UtcDateTime.Ticks,
				CursorGameDay = now.GameDay,
				GameDayAnchor = now.GameDay
			};
		}

		private void EnsureUsable()
		{
			if (disposed)
			{
				throw new ObjectDisposedException("FreshWorldScheduler");
			}
			if (faulted)
			{
				throw new InvalidOperationException("State persistence failed; this scheduler is disabled for the session.");
			}
		}

		public void Dispose()
		{
			lock (gate)
			{
				if (disposed)
				{
					return;
				}
				try
				{
					if (!faulted)
					{
						lease.Write(state);
					}
				}
				finally
				{
					disposed = true;
					lease.Dispose();
				}
			}
		}
	}
	public sealed class StateCorruptionException : IOException
	{
		public StateCorruptionException(string message)
			: base(message)
		{
		}

		public StateCorruptionException(string message, Exception inner)
			: base(message, inner)
		{
		}
	}
	public sealed class JsonWorldStateStore
	{
		internal sealed class Lease : IDisposable
		{
			private readonly string path;

			private readonly FileStream fileLock;

			private readonly DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(WorldState));

			public Lease(string path, FileStream fileLock)
			{
				this.path = path;
				this.fileLock = fileLock;
			}

			public WorldState? Read(string worldId)
			{
				if (!File.Exists(path))
				{
					return null;
				}
				try
				{
					using FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
					if (fileStream.Length > 4194304)
					{
						throw new StateCorruptionException("FreshWorld state exceeds its size limit.");
					}
					WorldState obj = (serializer.ReadObject(fileStream) as WorldState) ?? throw new StateCorruptionException("FreshWorld state is empty.");
					obj.Validate(worldId);
					return obj;
				}
				catch (StateCorruptionException)
				{
					throw;
				}
				catch (Exception ex2) when (ex2 is SerializationException || ex2 is XmlException || ex2 is ArgumentException)
				{
					throw new StateCorruptionException("FreshWorld state is corrupt. Automatic reset is disabled; a stale backup is never restored automatically.", ex2);
				}
			}

			public void Write(WorldState state)
			{
				state.Validate(state.WorldId);
				string sourceFileName = path + "." + Guid.NewGuid().ToString("N") + ".tmp";
				try
				{
					using (FileStream fileStream = new FileStream(sourceFileName, FileMode.CreateNew, FileAccess.Write, FileShare.None))
					{
						serializer.WriteObject(fileStream, state);
						fileStream.Flush(flushToDisk: true);
					}
					if (File.Exists(path))
					{
						File.Replace(sourceFileName, path, null);
					}
					else
					{
						File.Move(sourceFileName, path);
					}
				}
				finally
				{
					if (File.Exists(sourceFileName))
					{
						File.Delete(sourceFileName);
					}
				}
			}

			public void Dispose()
			{
				fileLock.Dispose();
			}
		}

		private readonly string directory;

		public JsonWorldStateStore(string directory)
		{
			if (string.IsNullOrWhiteSpace(directory))
			{
				throw new ArgumentException("A state directory is required.", "directory");
			}
			this.directory = Path.GetFullPath(directory);
		}

		public string GetStatePath(string worldId)
		{
			ValidateWorldId(worldId);
			return Path.Combine(directory, ScheduleSettings.Hash(worldId) + ".json");
		}

		internal Lease Acquire(string worldId)
		{
			string statePath = GetStatePath(worldId);
			Directory.CreateDirectory(directory);
			return new Lease(statePath, new FileStream(statePath + ".lock", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None));
		}

		private static void ValidateWorldId(string worldId)
		{
			if (string.IsNullOrWhiteSpace(worldId))
			{
				throw new ArgumentException("Use the persistent world UID, not its display name.", "worldId");
			}
		}
	}
	public enum ScheduleMode
	{
		DailyTimes,
		GameDays
	}
	public sealed class ScheduleSettings
	{
		private readonly HashSet<TimeSpan> vegetationTimes;

		public ScheduleMode Mode { get; }

		public bool AutomaticEnabled { get; }

		public IReadOnlyList<TimeSpan> DailyTimes { get; }

		public TimeZoneInfo TimeZone { get; }

		public double GameDayInterval { get; }

		public bool RunMissedOnWorldStart { get; }

		internal string Fingerprint { get; }

		public ScheduleSettings(ScheduleMode mode, string dailyTimesCsv, string timeZoneId, double gameDayInterval, string vegetationDailyTimesCsv = "*", bool runMissedOnWorldStart = false, bool automaticEnabled = true)
		{
			if (!Enum.IsDefined(typeof(ScheduleMode), mode))
			{
				throw new ArgumentOutOfRangeException("mode");
			}
			Mode = mode;
			AutomaticEnabled = automaticEnabled;
			bool flag = automaticEnabled && mode == ScheduleMode.DailyTimes;
			bool flag2 = automaticEnabled && mode == ScheduleMode.GameDays;
			if (flag2)
			{
				ValidateInterval(gameDayInterval);
			}
			GameDayInterval = (flag2 ? gameDayInterval : 24.0);
			RunMissedOnWorldStart = automaticEnabled && runMissedOnWorldStart;
			DailyTimes = Array.AsReadOnly(flag ? ParseTimes(dailyTimesCsv, allowEmpty: false) : Array.Empty<TimeSpan>());
			TimeZone = (flag ? ResolveTimeZone(timeZoneId) : TimeZoneInfo.Utc);
			vegetationTimes = (flag ? ParseVegetationTimes(DailyTimes, vegetationDailyTimesCsv) : new HashSet<TimeSpan>());
			string text = mode switch
			{
				ScheduleMode.GameDays => gameDayInterval.ToString("R", CultureInfo.InvariantCulture), 
				ScheduleMode.DailyTimes => string.Join("|", TimeZone.Id, string.Join(",", DailyTimes.Select(FormatTime)), string.Join(",", vegetationTimes.OrderBy((TimeSpan t) => t).Select(FormatTime))), 
				_ => "", 
			};
			Fingerprint = Hash(automaticEnabled ? ("active-v2|" + mode.ToString() + "|" + text) : "automatic-disabled-v1").Substring(0, 16);
		}

		internal bool IncludeVegetation(TimeSpan slot)
		{
			return vegetationTimes.Contains(slot);
		}

		internal static string FormatTime(TimeSpan time)
		{
			return time.ToString("hh\\:mm", CultureInfo.InvariantCulture);
		}

		internal static string Hash(string value)
		{
			using SHA256 sHA = SHA256.Create();
			return string.Concat(from b in sHA.ComputeHash(Encoding.UTF8.GetBytes(value))
				select b.ToString("x2", CultureInfo.InvariantCulture));
		}

		private static void ValidateInterval(double interval)
		{
			if (double.IsNaN(interval) || double.IsInfinity(interval) || interval <= 0.0)
			{
				throw new ArgumentOutOfRangeException("interval", "Game day interval must be finite and greater than zero.");
			}
		}

		private static TimeZoneInfo ResolveTimeZone(string timeZoneId)
		{
			if (!string.Equals(timeZoneId, "Local", StringComparison.OrdinalIgnoreCase))
			{
				return TimeZoneInfo.FindSystemTimeZoneById(timeZoneId);
			}
			return TimeZoneInfo.Local;
		}

		private static HashSet<TimeSpan> ParseVegetationTimes(IReadOnlyList<TimeSpan> times, string text)
		{
			if (text == null)
			{
				throw new ArgumentNullException("text");
			}
			IReadOnlyList<TimeSpan> collection;
			if (!(text.Trim() == "*"))
			{
				IReadOnlyList<TimeSpan> readOnlyList = ParseTimes(text, allowEmpty: true);
				collection = readOnlyList;
			}
			else
			{
				collection = times;
			}
			HashSet<TimeSpan> hashSet = new HashSet<TimeSpan>(collection);
			if (hashSet.Any((TimeSpan t) => !times.Contains(t)))
			{
				throw new ArgumentException("Vegetation times must be a subset of daily run times, or '*'.", "text");
			}
			return hashSet;
		}

		private static TimeSpan[] ParseTimes(string text, bool allowEmpty)
		{
			if (text == null)
			{
				throw new ArgumentNullException("text");
			}
			if (allowEmpty && string.IsNullOrWhiteSpace(text))
			{
				return Array.Empty<TimeSpan>();
			}
			HashSet<TimeSpan> hashSet = new HashSet<TimeSpan>();
			string[] array = text.Split(',');
			for (int i = 0; i < array.Length; i++)
			{
				if (!TimeSpan.TryParseExact(array[i].Trim(), "hh\\:mm", CultureInfo.InvariantCulture, out var result) || result.TotalHours >= 24.0)
				{
					throw new ArgumentException("Daily times must contain HH:mm values separated by commas.", "text");
				}
				if (!hashSet.Add(result))
				{
					throw new ArgumentException("Daily times cannot contain duplicates.", "text");
				}
			}
			if (hashSet.Count == 0)
			{
				throw new ArgumentException("At least one daily run time is required.", "text");
			}
			return hashSet.OrderBy((TimeSpan t) => t).ToArray();
		}
	}
	public readonly struct ScheduleClock
	{
		public DateTimeOffset UtcNow { get; }

		public double GameDay { get; }

		public ScheduleClock(DateTimeOffset utcNow, double gameDay)
		{
			if (double.IsNaN(gameDay) || double.IsInfinity(gameDay) || gameDay < 0.0)
			{
				throw new ArgumentOutOfRangeException("gameDay");
			}
			UtcNow = utcNow.ToUniversalTime();
			GameDay = gameDay;
		}
	}
	[DataContract]
	public sealed class ScheduledRun
	{
		internal const string ManualSlot = "Manual";

		[DataMember(Order = 2)]
		private long dueUtcTicks;

		[DataMember(Order = 1)]
		public string Id { get; private set; } = "";

		[DataMember(Order = 3)]
		public double GameDay { get; private set; }

		[DataMember(Order = 4)]
		public string Slot { get; private set; } = "";

		[DataMember(Order = 5)]
		public bool IncludeVegetation { get; private set; }

		public DateTimeOffset DueUtc => new DateTimeOffset(dueUtcTicks, TimeSpan.Zero);

		internal bool IsManual => string.Equals(Slot, "Manual", StringComparison.Ordinal);

		internal bool Valid
		{
			get
			{
				if (!string.IsNullOrWhiteSpace(Id) && dueUtcTicks > 0 && dueUtcTicks <= DateTime.MaxValue.Ticks && GameDay >= 0.0 && !double.IsInfinity(GameDay) && !double.IsNaN(GameDay))
				{
					return Slot != null;
				}
				return false;
			}
		}

		internal ScheduledRun(string id, DateTimeOffset dueUtc, double gameDay, string slot, bool vegetation)
		{
			Id = id;
			dueUtcTicks = dueUtc.UtcDateTime.Ticks;
			GameDay = gameDay;
			Slot = slot;
			IncludeVegetation = vegetation;
		}
	}
	public enum RunStatus
	{
		Running,
		Completed,
		Failed,
		Interrupted
	}
	[DataContract]
	public sealed class RunRecord
	{
		[DataMember(Order = 3)]
		private long startedUtcTicks;

		[DataMember(Order = 4)]
		private long finishedUtcTicks;

		[DataMember(Order = 1)]
		public ScheduledRun Run { get; private set; }

		[DataMember(Order = 2)]
		public RunStatus Status { get; internal set; }

		[DataMember(Order = 5)]
		public string Message { get; internal set; } = "";

		public DateTimeOffset StartedUtc => new DateTimeOffset(startedUtcTicks, TimeSpan.Zero);

		public DateTimeOffset? FinishedUtc
		{
			get
			{
				if (finishedUtcTicks != 0L)
				{
					return new DateTimeOffset(finishedUtcTicks, TimeSpan.Zero);
				}
				return null;
			}
		}

		internal bool Valid
		{
			get
			{
				if (Run != null && Run.Valid && Enum.IsDefined(typeof(RunStatus), Status) && startedUtcTicks > 0 && startedUtcTicks <= DateTime.MaxValue.Ticks && finishedUtcTicks >= 0 && finishedUtcTicks <= DateTime.MaxValue.Ticks && ((Status == RunStatus.Running) ? (finishedUtcTicks == 0) : (finishedUtcTicks > 0)))
				{
					return Message != null;
				}
				return false;
			}
		}

		internal RunRecord(ScheduledRun run, DateTimeOffset start)
		{
			Run = run;
			startedUtcTicks = start.UtcDateTime.Ticks;
			Status = RunStatus.Running;
		}

		internal void Finish(RunStatus status, DateTimeOffset time, string message)
		{
			Status = status;
			finishedUtcTicks = time.UtcDateTime.Ticks;
			Message = message ?? "";
		}

		internal RunRecord Copy()
		{
			return (RunRecord)MemberwiseClone();
		}
	}
	[DataContract]
	internal sealed class WorldState
	{
		[DataMember(Order = 1)]
		public int SchemaVersion = 1;

		[DataMember(Order = 2)]
		public string WorldId = "";

		[DataMember(Order = 3)]
		public string Fingerprint = "";

		[DataMember(Order = 4)]
		public long CursorUtcTicks;

		[DataMember(Order = 5)]
		public double CursorGameDay;

		[DataMember(Order = 6)]
		public double GameDayAnchor;

		[DataMember(Order = 7)]
		public ScheduledRun? Pending;

		[DataMember(Order = 8)]
		public List<RunRecord> Attempts = new List<RunRecord>();

		public WorldState Copy()
		{
			WorldState obj = (WorldState)MemberwiseClone();
			obj.Attempts = Attempts.Select((RunRecord a) => a.Copy()).ToList();
			return obj;
		}

		public void Validate(string expectedWorldId)
		{
			if (SchemaVersion != 1 || WorldId != expectedWorldId || string.IsNullOrWhiteSpace(Fingerprint) || CursorUtcTicks <= 0 || CursorUtcTicks > DateTime.MaxValue.Ticks || !FiniteNonnegative(CursorGameDay) || !FiniteNonnegative(GameDayAnchor) || GameDayAnchor > CursorGameDay || Attempts == null || Attempts.Any((RunRecord a) => a == null || !a.Valid) || Attempts.Select((RunRecord a) => a.Run.Id).Distinct().Count() != Attempts.Count || Attempts.Count((RunRecord a) => a.Status == RunStatus.Running) > 1 || (Pending != null && (!Pending.Valid || Attempts.Any((RunRecord a) => a.Run.Id == Pending.Id))))
			{
				throw new StateCorruptionException("FreshWorld state is inconsistent. Automatic reset is disabled until the state file is recovered.");
			}
		}

		private static bool FiniteNonnegative(double x)
		{
			if (x >= 0.0 && !double.IsNaN(x))
			{
				return !double.IsInfinity(x);
			}
			return false;
		}
	}
}
namespace FreshWorld.Runtime
{
	public sealed class GuardedCoroutine : IEnumerator, IDisposable
	{
		private readonly Stack<IEnumerator> _stack = new Stack<IEnumerator>();

		private readonly Func<bool> _canContinue;

		private readonly Action<Exception> _onError;

		private readonly Action<bool> _onFinished;

		private bool _finished;

		private object? _current;

		public object? Current => _current;

		public GuardedCoroutine(IEnumerator root, Func<bool> canContinue, Action<Exception> onError, Action<bool> onFinished)
		{
			if (root == null)
			{
				throw new ArgumentNullException("root");
			}
			_canContinue = canContinue ?? throw new ArgumentNullException("canContinue");
			_onError = onError ?? throw new ArgumentNullException("onError");
			_onFinished = onFinished ?? throw new ArgumentNullException("onFinished");
			_stack.Push(root);
		}

		public bool MoveNext()
		{
			if (_finished)
			{
				return false;
			}
			_current = null;
			try
			{
				while (_stack.Count > 0)
				{
					if (!_canContinue())
					{
						Finish(success: false);
						return false;
					}
					IEnumerator enumerator = _stack.Peek();
					if (!enumerator.MoveNext())
					{
						_stack.Pop();
						(enumerator as IDisposable)?.Dispose();
						continue;
					}
					object current = enumerator.Current;
					if (current is IEnumerator enumerator2)
					{
						foreach (IEnumerator item in _stack)
						{
							if (item == enumerator2)
							{
								throw new InvalidOperationException("A coroutine yielded an already active enumerator.");
							}
						}
						_stack.Push(enumerator2);
						continue;
					}
					_current = current;
					return true;
				}
				Finish(success: true);
				return false;
			}
			catch (Exception error)
			{
				Report(error);
				Finish(success: false);
				return false;
			}
		}

		public void Reset()
		{
			throw new NotSupportedException("Coroutines cannot be reset.");
		}

		public void Dispose()
		{
			Finish(success: false);
		}

		private void Finish(bool success)
		{
			if (_finished)
			{
				return;
			}
			_finished = true;
			_current = null;
			while (_stack.Count > 0)
			{
				IEnumerator enumerator = _stack.Pop();
				try
				{
					(enumerator as IDisposable)?.Dispose();
				}
				catch (Exception error)
				{
					success = false;
					Report(error);
				}
			}
			try
			{
				_onFinished(success);
			}
			catch (Exception error2)
			{
				Report(error2);
			}
		}

		private void Report(Exception error)
		{
			try
			{
				_onError(error);
			}
			catch (Exception)
			{
			}
		}
	}
}
namespace FreshWorld.Engine
{
	internal static class BaseProtection
	{
		private static HashSet<int> playerObjects = new HashSet<int>();

		private static HashSet<int> unconditionalObjects = new HashSet<int>();

		private static HashSet<Vector2s> excluded = new HashSet<Vector2s>();

		private static DateTime calculatedAt = DateTime.MinValue;

		private static int lastSize = -1;

		public static void Configure(IEnumerable<string> placedObjects, IEnumerable<string> alwaysProtectedObjects)
		{
			playerObjects = new HashSet<int>(placedObjects.Select((string id) => StringExtensionMethods.GetStableHashCode(id)));
			unconditionalObjects = new HashSet<int>(alwaysProtectedObjects.Select((string id) => StringExtensionMethods.GetStableHashCode(id)));
			InvalidateCache();
		}

		public static HashSet<Vector2s> GetExcluded(int size)
		{
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: 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_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			if (size < 0)
			{
				throw new ArgumentOutOfRangeException("size");
			}
			if (lastSize == size && DateTime.UtcNow - calculatedAt < TimeSpan.FromSeconds(10.0))
			{
				return excluded;
			}
			HashSet<Vector2s> hashSet = new HashSet<Vector2s>();
			if (size > 0)
			{
				int num = size - 1;
				foreach (ZDO item in GameWorld.AllZDOs())
				{
					int prefab = item.GetPrefab();
					if (!unconditionalObjects.Contains(prefab) && (!playerObjects.Contains(prefab) || item.GetLong(ZDOVars.s_creator, 0L) == 0L))
					{
						continue;
					}
					Vector2s zone = ZoneSystem.GetZone(item.GetPosition());
					for (int i = Math.Max(-32768, zone.x - num); i <= Math.Min(32767, zone.x + num); i++)
					{
						for (int j = Math.Max(-32768, zone.y - num); j <= Math.Min(32767, zone.y + num); j++)
						{
							hashSet.Add(new Vector2s(i, j));
						}
					}
				}
			}
			excluded = hashSet;
			lastSize = size;
			calculatedAt = DateTime.UtcNow;
			return excluded;
		}

		public static void InvalidateCache()
		{
			calculatedAt = DateTime.MinValue;
			lastSize = -1;
			excluded = new HashSet<Vector2s>();
		}
	}
	[HarmonyPatch(typeof(ZDOMan), "GetSaveClonePerChunk", new Type[] { })]
	internal static class EmptyChunkSavePatch
	{
		private static readonly FieldInfo FileCount = FindCounter("m_numFiles");

		private static readonly FieldInfo DirtyFileCount = FindCounter("m_dirtyFiles");

		private static FieldInfo FindCounter(string name)
		{
			FieldInfo field = (typeof(ZDOMan).GetNestedType("SaveData", BindingFlags.NonPublic) ?? throw new TypeLoadException("ZDOMan.SaveData")).GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (field == null || field.FieldType != typeof(int))
			{
				throw new MissingFieldException("ZDOMan.SaveData", name);
			}
			return field;
		}

		[HarmonyPostfix]
		internal static void Postfix(List<Tuple<ChunkIndex, List<ZDO>>> __result, ChunkSaveMapping ___m_chunkSaveMapping, HashSet<ChunkIndex>[] ___m_dirtyChunks, List<ZDO>[] ___m_objectsBySector, object ___m_saveData)
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			ZNet instance = ZNet.instance;
			if (instance == null || !instance.IsServer())
			{
				return;
			}
			HashSet<ChunkIndex> scheduled = new HashSet<ChunkIndex>();
			HashSet<ChunkIndex> splitParents = new HashSet<ChunkIndex>();
			foreach (Tuple<ChunkIndex, List<ZDO>> item in __result)
			{
				Record(item.Item1, scheduled, splitParents);
			}
			int num = 0;
			foreach (KeyValuePair<ChunkIndex, ChunkInfo> chunk in ___m_chunkSaveMapping.Chunks)
			{
				ChunkIndex key = chunk.Key;
				ChunkInfo value = chunk.Value;
				if (value.SaveChunk && value.m_numZDOs > 0 && IsOrdinaryChunk(key) && ___m_dirtyChunks[0].Contains(key) && !Overlaps(key, scheduled, splitParents) && IsEmpty(key, ___m_objectsBySector))
				{
					__result.Add(Tuple.Create<ChunkIndex, List<ZDO>>(key, new List<ZDO>()));
					Record(key, scheduled, splitParents);
					num++;
				}
			}
			if (num != 0)
			{
				FileCount.SetValue(___m_saveData, (int)FileCount.GetValue(___m_saveData) + num);
				DirtyFileCount.SetValue(___m_saveData, (int)DirtyFileCount.GetValue(___m_saveData) + num);
			}
		}

		private static bool IsOrdinaryChunk(ChunkIndex chunk)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: 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_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			if (!((ChunkIndex)(ref chunk)).Equals(ZoneSystem.ChunkPortal) && chunk.m_chunkSize <= 3 && (chunk.Chunk & 0xFF) < 64 && chunk.Chunk >> 8 < 64)
			{
				ChunkIndex val = ZoneSystem.ChunkIndexFromIndexAndSize(chunk, chunk.m_chunkSize);
				return ((ChunkIndex)(ref val)).Equals(chunk);
			}
			return false;
		}

		private static void Record(ChunkIndex chunk, HashSet<ChunkIndex> scheduled, HashSet<ChunkIndex> splitParents)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			if (IsOrdinaryChunk(chunk))
			{
				scheduled.Add(chunk);
				for (int i = chunk.m_chunkSize + 1; i <= 3; i++)
				{
					splitParents.Add(ZoneSystem.ChunkIndexFromIndexAndSize(chunk, (byte)i));
				}
			}
		}

		private static bool Overlaps(ChunkIndex chunk, HashSet<ChunkIndex> scheduled, HashSet<ChunkIndex> splitParents)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			if (scheduled.Contains(chunk) || splitParents.Contains(chunk))
			{
				return true;
			}
			for (int i = chunk.m_chunkSize + 1; i <= 3; i++)
			{
				if (scheduled.Contains(ZoneSystem.ChunkIndexFromIndexAndSize(chunk, (byte)i)))
				{
					return true;
				}
			}
			return false;
		}

		private static bool IsEmpty(ChunkIndex chunk, List<ZDO>[] sectors)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			ValueTuple<int, int> zoneFromChunk = ZoneSystem.GetZoneFromChunk(chunk);
			int item = zoneFromChunk.Item1;
			int item2 = zoneFromChunk.Item2;
			int num = 8 << (int)chunk.m_chunkSize;
			for (int i = item2; i < item2 + num; i++)
			{
				for (int j = item; j < item + num; j++)
				{
					List<ZDO> list = sectors[ZoneSystem.SectorToIndex(j, i).Sector];
					if (list != null && list.Count != 0)
					{
						return false;
					}
				}
			}
			return true;
		}
	}
	internal static class GameWorld
	{
		[HarmonyPatch(typeof(ZDOMan), "SendDestroyed")]
		private static class DestroyBatchPatch
		{
			private const int MaxPerPacket = 10000;

			private static readonly FieldRef<ZDOMan, List<ZDOID>> Pending = AccessTools.FieldRefAccess<ZDOMan, List<ZDOID>>("m_destroySendList");

			[HarmonyPrefix]
			private static bool Prefix(ZDOMan __instance)
			{
				//IL_001c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0022: Expected O, but got Unknown
				//IL_0034: Unknown result type (might be due to invalid IL or missing references)
				List<ZDOID> list = Pending.Invoke(__instance);
				if (list.Count < 10000)
				{
					return true;
				}
				ZPackage val = new ZPackage();
				val.Write(10000);
				for (int i = 0; i < 10000; i++)
				{
					val.Write(list[i]);
				}
				list.RemoveRange(0, 10000);
				ZRoutedRpc.instance.InvokeRoutedRPC(0L, "DestroyZDO", new object[1] { val });
				return false;
			}
		}

		private static readonly FieldRef<ZoneSystem, HashSet<Vector2s>> GeneratedZones = AccessTools.FieldRefAccess<ZoneSystem, HashSet<Vector2s>>("m_generatedZones");

		private static readonly FieldRef<ZDOMan, Dictionary<ZDOID, ZDO>> ObjectsById = AccessTools.FieldRefAccess<ZDOMan, Dictionary<ZDOID, ZDO>>("m_objectsByID");

		private static readonly FieldRef<ZNetScene, Dictionary<ZDO, ZNetView>> SceneInstances = AccessTools.FieldRefAccess<ZNetScene, Dictionary<ZDO, ZNetView>>("m_instances");

		private static readonly Func<ZoneSystem, Vector2s, bool> PokeLocalZone = AccessTools.MethodDelegate<Func<ZoneSystem, Vector2s, bool>>(AccessTools.Method(typeof(ZoneSystem), "PokeLocalZone", (Type[])null, (Type[])null), (object)null, true);

		private static readonly FieldInfo ZoneRoots = AccessTools.Field(typeof(ZoneSystem), "m_zones") ?? throw new MissingFieldException(typeof(ZoneSystem).FullName, "m_zones");

		private static readonly FieldInfo RootObject = AccessTools.Field(ZoneRoots.FieldType.GetGenericArguments()[1], "m_root") ?? throw new MissingFieldException("ZoneSystem.ZoneData", "m_root");

		private static readonly FieldInfo Heightmaps = AccessTools.Field(typeof(Heightmap), "s_heightmaps") ?? throw new MissingFieldException(typeof(Heightmap).FullName, "s_heightmaps");

		private static readonly FieldInfo HeightmapBuildData = AccessTools.Field(typeof(Heightmap), "m_buildData") ?? throw new MissingFieldException(typeof(Heightmap).FullName, "m_buildData");

		private static readonly int PlayerPrefab = StringExtensionMethods.GetStableHashCode("Player");

		private static readonly HashSet<Vector2s> OwnedLoads = new HashSet<Vector2s>();

		private static ZoneSystem? _loadWorld;

		public static Vector2s[] GeneratedSnapshot(HashSet<Vector2s>? candidates = null)
		{
			IEnumerable<Vector2s> source = GeneratedZones.Invoke(ZoneSystem.instance);
			if (candidates != null)
			{
				source = source.Where(candidates.Contains);
			}
			return source.OrderBy((Vector2s zone) => (long)zone.x * (long)zone.x + (long)zone.y * (long)zone.y).ToArray();
		}

		public static HashSet<Vector2s> GeneratedSetSnapshot()
		{
			return new HashSet<Vector2s>(GeneratedZones.Invoke(ZoneSystem.instance));
		}

		public static bool IsGenerated(Vector2s zone)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			return GeneratedZones.Invoke(ZoneSystem.instance).Contains(zone);
		}

		public static List<ZDO> GetZDOs(Vector2s zone)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			List<ZDO> list = new List<ZDO>();
			ZDOMan.instance.FindSectorObjects(zone, new SimulationDistance(0, 0, false), list, (List<ZDO>)null);
			list.RemoveAll((ZDO zdo) => zdo == null || !zdo.IsValid() || ZoneSystem.GetZone(zdo.GetPosition()) != zone);
			return list;
		}

		public static IEnumerable<ZDO> AllZDOs()
		{
			return ObjectsById.Invoke(ZDOMan.instance).Values.ToArray();
		}

		public static void CollectPlayerZones(HashSet<Vector2s> zones)
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			if (zones == null)
			{
				throw new ArgumentNullException("zones");
			}
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null || !instance.IsServer() || instance.HaveStopped)
			{
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if (!instance.IsDedicated() && (Object)(object)localPlayer != (Object)null)
			{
				TryAddPlayerZone(zones, ((Component)localPlayer).transform.position);
			}
			ZDOMan instance2 = ZDOMan.instance;
			Dictionary<ZDOID, ZDO> dictionary = ((instance2 == null) ? null : ObjectsById.Invoke(instance2));
			foreach (ZNetPeer peer in instance.GetPeers())
			{
				if (peer != null && !peer.m_server && peer.IsReady() && peer.m_socket != null && peer.m_socket.IsConnected() && (!(peer.m_characterID != ZDOID.None) || dictionary == null || !dictionary.TryGetValue(peer.m_characterID, out var value) || value == null || !value.IsValid() || !TryAddPlayerZone(zones, value.GetPosition())))
				{
					TryAddPlayerZone(zones, peer.GetRefPos());
				}
			}
		}

		private static bool TryAddPlayerZone(HashSet<Vector2s> zones, Vector3 position)
		{
			//IL_0000: 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_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			if (float.IsNaN(position.x) || float.IsInfinity(position.x) || float.IsNaN(position.y) || float.IsInfinity(position.y) || float.IsNaN(position.z) || float.IsInfinity(position.z))
			{
				return false;
			}
			float num = (float)(((double)position.x + 32.0) / 64.0);
			float num2 = (float)(((double)position.z + 32.0) / 64.0);
			if (Math.Floor(num) < -32768.0 || Math.Floor(num) > 32767.0 || Math.Floor(num2) < -32768.0 || Math.Floor(num2) > 32767.0)
			{
				return false;
			}
			zones.Add(ZoneSystem.GetZone(position));
			return true;
		}

		public static void RemoveZDO(ZDO zdo)
		{
			RemoveZDO(zdo, null);
		}

		private static void RemoveZDO(ZDO zdo, HashSet<ZDOID>? visited)
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			if (zdo == null || !zdo.IsValid() || IsPlayer(zdo) || (visited != null && !visited.Add(zdo.m_uid)))
			{
				return;
			}
			ZDOMan instance = ZDOMan.instance;
			zdo.SetOwner(ZDOMan.GetSessionID());
			ZDOID connectionZDOID = zdo.GetConnectionZDOID((ConnectionType)3);
			if (connectionZDOID != ZDOID.None && ObjectsById.Invoke(instance).TryGetValue(connectionZDOID, out var value) && value != zdo)
			{
				if (visited == null)
				{
					visited = new HashSet<ZDOID> { zdo.m_uid };
				}
				RemoveZDO(value, visited);
			}
			if (zdo.IsValid())
			{
				ZNetScene instance2 = ZNetScene.instance;
				if (SceneInstances.Invoke(instance2).TryGetValue(zdo, out var value2) && (Object)(object)value2 != (Object)null)
				{
					instance2.Destroy(((Component)value2).gameObject);
				}
				else
				{
					instance.DestroyZDO(zdo);
				}
			}
		}

		private static bool IsPlayer(ZDO zdo)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			if (zdo.GetPrefab() == PlayerPrefab)
			{
				return true;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer != (Object)null && ((Character)localPlayer).GetZDOID() == zdo.m_uid)
			{
				return true;
			}
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return false;
			}
			ZDOID uid = zdo.m_uid;
			foreach (PlayerInfo player in instance.GetPlayerList())
			{
				if (player.m_characterID == uid)
				{
					return true;
				}
			}
			foreach (ZNetPeer peer in instance.GetPeers())
			{
				if (peer.m_characterID == uid)
				{
					return true;
				}
			}
			return false;
		}

		private static void EnsureLoadWorld()
		{
			ZoneSystem instance = ZoneSystem.instance;
			if (instance != _loadWorld)
			{
				OwnedLoads.Clear();
				_loadWorld = instance;
			}
		}

		public static void PokeZone(Vector2s zone)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: 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)
			EnsureLoadWorld();
			ZoneSystem instance = ZoneSystem.instance;
			if (!instance.IsZoneLoaded(zone))
			{
				if (!TryGetRoot(zone, out GameObject _))
				{
					OwnedLoads.Add(zone);
				}
				PokeLocalZone(instance, zone);
			}
		}

		public static void ReleaseZone(Vector2s zone)
		{
			//IL_000a: 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)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			EnsureLoadWorld();
			if (!OwnedLoads.Remove(zone))
			{
				return;
			}
			List<ZDO> zDOs = GetZDOs(zone);
			if (zDOs.Any((ZDO zdo) => zdo != null && zdo.IsValid() && IsPlayer(zdo)))
			{
				return;
			}
			ZNetScene instance = ZNetScene.instance;
			Dictionary<ZDO, ZNetView> dictionary = SceneInstances.Invoke(instance);
			foreach (ZDO item in zDOs)
			{
				if (!dictionary.TryGetValue(item, out var value))
				{
					continue;
				}
				if ((Object)(object)value != (Object)null)
				{
					GameObject gameObject = ((Component)value).gameObject;
					if (value.GetZDO() != null)
					{
						value.ResetZDO();
					}
					Object.Destroy((Object)(object)gameObject);
				}
				dictionary.Remove(item);
			}
			RemoveRoot(zone);
		}

		public static bool TryGetRoot(Vector2s zone, out GameObject root)
		{
			//IL_0025: 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)
			IDictionary dictionary = (IDictionary)(ZoneRoots.GetValue(ZoneSystem.instance) ?? throw new InvalidOperationException("The zone root registry is not ready."));
			if (dictionary.Contains(zone))
			{
				object? value = RootObject.GetValue(dictionary[zone]);
				GameObject val = (GameObject)((value is GameObject) ? value : null);
				if (val != null && (Object)(object)val != (Object)null)
				{
					root = val;
					return true;
				}
			}
			root = null;
			return false;
		}

		public static void RemoveGeneratedZone(Vector2s zone)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			EnsureLoadWorld();
			GeneratedZones.Invoke(ZoneSystem.instance).Remove(zone);
			OwnedLoads.Remove(zone);
			RemoveRoot(zone);
		}

		private static void RemoveRoot(Vector2s zone)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			ZoneSystem instance = ZoneSystem.instance;
			IDictionary obj = (IDictionary)(ZoneRoots.GetValue(instance) ?? throw new InvalidOperationException("The zone root registry is not ready."));
			if (TryGetRoot(zone, out GameObject root))
			{
				Object.Destroy((Object)(object)root);
			}
			obj.Remove(zone);
		}

		public static void RecalculateTerrain()
		{
			Heightmap[] array = ((List<Heightmap>)(Heightmaps.GetValue(null) ?? throw new InvalidOperationException("The heightmap registry is not ready."))).ToArray();
			foreach (Heightmap val in array)
			{
				if (!((Object)(object)val == (Object)null))
				{
					HeightmapBuildData.SetValue(val, null);
					val.Poke(1, false);
				}
			}
		}
	}
	internal static class NativePlacement
	{
		private sealed class LocationTemplateState
		{
			private sealed class TemplateTransform
			{
				private readonly Transform _transform;

				private readonly Vector3 _position;

				private readonly Quaternion _rotation;

				private readonly Vector3 _scale;

				private readonly bool _active;

				public TemplateTransform(Transform transform)
				{
					//IL_000f: Unknown result type (might be due to invalid IL or missing references)
					//IL_0014: Unknown result type (might be due to invalid IL or missing references)
					//IL_001b: Unknown result type (might be due to invalid IL or missing references)
					//IL_0020: Unknown result type (might be due to invalid IL or missing references)
					//IL_0027: Unknown result type (might be due to invalid IL or missing references)
					//IL_002c: Unknown result type (might be due to invalid IL or missing references)
					_transform = transform;
					_position = transform.localPosition;
					_rotation = transform.localRotation;
					_scale = transform.localScale;
					_active = ((Component)transform).gameObject.activeSelf;
				}

				public void RestoreTransform()
				{
					//IL_0016: Unknown result type (might be due to invalid IL or missing references)
					//IL_0027: 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)
					if (!((Object)(object)_transform == (Object)null))
					{
						_transform.localPosition = _position;
						_transform.localRotation = _rotation;
						_transform.localScale = _scale;
					}
				}

				public void RestoreActive()
				{
					if ((Object)(object)_transform != (Object)null && ((Component)_transform).gameObject.activeSelf != _active)
					{
						((Component)_transform).gameObject.SetActive(_active);
					}
				}
			}

			private readonly Transform _root;

			private readonly Vector3 _position;

			private readonly Quaternion _rotation;

			private readonly TemplateTransform[] _transforms;

			private LocationTemplateState(GameObject prefab)
			{
				//IL_0019: Unknown result type (might be due to invalid IL or missing references)
				//IL_001e: Unknown result type (might be due to invalid IL or missing references)
				//IL_002a: Unknown result type (might be due to invalid IL or missing references)
				//IL_002f: Unknown result type (might be due to invalid IL or missing references)
				_root = prefab.transform;
				_position = _root.position;
				_rotation = _root.rotation;
				_transforms = (from transform in prefab.GetComponentsInChildren<Transform>(true)
					select new TemplateTransform(transform)).ToArray();
			}

			public static LocationTemplateState? Capture(ZoneSystem zones, Vector2s zone)
			{
				//IL_0006: Unknown result type (might be due to invalid IL or missing references)
				//IL_0010: Unknown result type (might be due to invalid IL or missing references)
				//IL_001a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0020: Unknown result type (might be due to invalid IL or missing references)
				//IL_0025: Unknown result type (might be due to invalid IL or missing references)
				if (!zones.m_locationInstances.TryGetValue(zone, out var value) || value.m_location == null)
				{
					return null;
				}
				SoftReference<GameObject> prefab = value.m_location.m_prefab;
				if (!prefab.IsValid || !prefab.IsLoaded)
				{
					return null;
				}
				GameObject asset = prefab.Asset;
				if (!((Object)(object)asset != (Object)null))
				{
					return null;
				}
				return new LocationTemplateState(asset);
			}

			public void Restore()
			{
				//IL_0032: Unknown result type (might be due to invalid IL or missing references)
				//IL_0043: Unknown result type (might be due to invalid IL or missing references)
				TemplateTransform[] transforms = _transforms;
				for (int i = 0; i < transforms.Length; i++)
				{
					transforms[i].RestoreTransform();
				}
				if ((Object)(object)_root != (Object)null)
				{
					_root.position = _position;
					_root.rotation = _rotation;
				}
				transforms = _transforms;
				for (int i = 0; i < transforms.Length; i++)
				{
					transforms[i].RestoreActive();
				}
			}
		}

		private static readonly Type ClearAreaType = AccessTools.Inner(typeof(ZoneSystem), "ClearArea") ?? throw new TypeLoadException("ZoneSystem.ClearArea");

		private static readonly Type ClearAreasType = typeof(List<>).MakeGenericType(ClearAreaType);

		private static readonly ConstructorInfo ClearAreaConstructor = AccessTools.Constructor(ClearAreaType, new Type[2]
		{
			typeof(Vector3),
			typeof(float)
		}, false) ?? throw new MissingMethodException("ZoneSystem.ClearArea constructor");

		private static readonly MethodInfo Vegetation = AccessTools.Method(typeof(ZoneSystem), "PlaceVegetation", (Type[])null, (Type[])null) ?? throw new MissingMethodException("ZoneSystem.PlaceVegetation");

		private static readonly MethodInfo Locations = AccessTools.Method(typeof(ZoneSystem), "PlaceLocations", (Type[])null, (Type[])null) ?? throw new MissingMethodException("ZoneSystem.PlaceLocations");

		private static readonly Func<ZoneSystem, ZoneLocation, bool, bool> PokeLocation = AccessTools.MethodDelegate<Func<ZoneSystem, ZoneLocation, bool, bool>>(AccessTools.Method(typeof(ZoneSystem), "PokeCanSpawnLocation", (Type[])null, (Type[])null) ?? throw new MissingMethodException("ZoneSystem.PokeCanSpawnLocation"), (object)null, true);

		private static readonly FieldRef<ZoneSystem, List<GameObject>> Temporary = AccessTools.FieldRefAccess<ZoneSystem, List<GameObject>>("m_tempSpawnedObjects");

		private static readonly FieldInfo GhostInit = AccessTools.Field(typeof(ZNetView), "m_ghostInit") ?? throw new MissingFieldException("ZNetView.m_ghostInit");

		private static readonly FieldInfo RandomInitialDamage = AccessTools.Field(typeof(WearNTear), "m_randomInitialDamage") ?? throw new MissingFieldException("WearNTear.m_randomInitialDamage");

		private static readonly FieldInfo CachedPrefabName = AccessTools.Field(AccessTools.Field(typeof(ZoneLocation), "m_prefab").FieldType, "m_name") ?? throw new MissingFieldException("SoftReference.m_name");

		public static List<GameObject> TemporaryObjects(ZoneSystem zones)
		{
			return Temporary.Invoke(zones);
		}

		public static IList CreateClearAreas()
		{
			return (IList)Activator.CreateInstance(ClearAreasType);
		}

		public static object CreateClearArea(Vector3 center, float radius)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			return ClearAreaConstructor.Invoke(new object[2] { center, radius });
		}

		public static bool IsValidLocationPrefab(ZoneLocation location)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			if (location != null)
			{
				if (!location.m_prefab.IsValid)
				{
					if (CachedPrefabName.GetValue(location.m_prefab) is string value)
					{
						return !string.IsNullOrWhiteSpace(value);
					}
					return false;
				}
				return true;
			}
			return false;
		}

		public static bool CanSpawnLocation(ZoneSystem zones, ZoneLocation location)
		{
			return PokeLocation(zones, location, arg3: true);
		}

		public static void PlaceVegetation(ZoneSystem zones, Vector2s zone, Transform parent, Heightmap heightmap, IList clearAreas, List<GameObject> objects)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			Run(delegate
			{
				//IL_0014: Unknown result type (might be due to invalid IL or missing references)
				//IL_0022: Unknown result type (might be due to invalid IL or missing references)
				//IL_0027: Unknown result type (might be due to invalid IL or missing references)
				Vegetation.Invoke(zones, new object[7]
				{
					zone,
					ZoneSystem.GetZonePos(zone),
					parent,
					heightmap,
					clearAreas,
					(object)(SpawnMode)2,
					objects
				});
			});
		}

		public static void PlaceLocations(ZoneSystem zones, Vector2s zone, Transform parent, Heightmap heightmap, IList clearAreas, List<GameObject> objects)
		{
			//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)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			LocationTemplateState locationTemplateState = LocationTemplateState.Capture(zones, zone);
			try
			{
				Run(delegate
				{
					//IL_0014: Unknown result type (might be due to invalid IL or missing references)
					//IL_0022: Unknown result type (might be due to invalid IL or missing references)
					//IL_0027: Unknown result type (might be due to invalid IL or missing references)
					Locations.Invoke(zones, new object[7]
					{
						zone,
						ZoneSystem.GetZonePos(zone),
						parent,
						heightmap,
						clearAreas,
						(object)(SpawnMode)2,
						objects
					});
				});
			}
			finally
			{
				locationTemplateState?.Restore();
			}
		}

		private static void Run(Action placement)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			State state = Random.state;
			bool flag = (bool)GhostInit.GetValue(null);
			bool flag2 = (bool)RandomInitialDamage.GetValue(null);
			try
			{
				placement();
			}
			catch (TargetInvocationException ex) when (ex.InnerException != null)
			{
				ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
				throw;
			}
			finally
			{
				Random.state = state;
				GhostInit.SetValue(null, flag);
				RandomInitialDamage.SetValue(null, flag2);
			}
		}

		public static HashSet<string> RequireIds(HashSet<string> ids)
		{
			if (ids == null || ids.Count == 0 || ids.Any(string.IsNullOrWhiteSpace))
			{
				throw new ArgumentException("At least one exact, nonempty prefab ID is required.", "ids");
			}
			return new HashSet<string>(ids, StringComparer.Ordinal);
		}

		public static void DestroyCreatedObjects(List<GameObject> temporary, HashSet<GameObject> original)
		{
			GameObject[] array = temporary.Where((GameObject obj) => !original.Contains(obj)).ToArray();
			foreach (GameObject val in array)
			{
				if ((Object)(object)val != (Object)null)
				{
					Object.Destroy((Object)(object)val);
				}
				temporary.Remove(val);
			}
		}
	}
	internal sealed class OperationParameters
	{
		public int SafeZones { get; set; }

		public float TerrainReset { get; set; }
	}
	internal abstract class ExecutedOperation
	{
		protected readonly Action<string> Log;

		protected int Failed;

		protected ExecutedOperation(Action<string> log)
		{
			Log = log;
		}

		public void Init()
		{
			Log(OnInit());
		}

		public IEnumerator Execute()
		{
			OnStart();
			yield return OnExecute();
			OnEnd();
		}

		protected abstract string OnInit();

		protected abstract IEnumerator OnExecute();

		protected virtual void OnStart()
		{
		}

		protected virtual void OnEnd()
		{
		}
	}
	internal abstract class ZoneOperation : ExecutedOperation
	{
		protected readonly OperationParameters Args;

		protected Vector2s[] ZonesToUpgrade;

		protected ZoneOperation(Action<string> log, OperationParameters args, HashSet<Vector2s>? candidates = null)
			: base(log)
		{
			Args = args;
			ZonesToUpgrade = GameWorld.GeneratedSnapshot(candidates);
		}

		protected override string OnInit()
		{
			HashSet<Vector2s> protectedZones = BaseProtection.GetExcluded(Args.SafeZones);
			ZonesToUpgrade = ZonesToUpgrade.Where((Vector2s zone) => !protectedZones.Contains(zone)).ToArray();
			return GetType().Name + ": " + ZonesToUpgrade.Length + " zones selected.";
		}

		protected abstract bool ExecuteZone(Vector2s zone);

		protected override IEnumerator OnExecute()
		{
			throw new InvalidOperationException("A zone operation must be executed through its tracking wrapper.");
		}
	}
	internal class RegenerateLocations : ZoneOperation
	{
		private static readonly int ZoneControlHash = StringExtensionMethods.GetStableHashCode("_ZoneCtrl");

		private static readonly int TerrainCompilerHash = StringExtensionMethods.GetStableHashCode("_TerrainCompiler");

		private readonly HashSet<string> _ids;

		private int _reset;

		private bool _terrainTouched;

		public RegenerateLocations(Action<string> log, HashSet<string> ids, OperationParameters args, HashSet<Vector2s>? candidates = null)
			: base(log, args, candidates)
		{
			_ids = NativePlacement.RequireIds(ids);
		}

		protected override string OnInit()
		{
			Dictionary<Vector2s, LocationInstance> locations = ZoneSystem.instance.m_locationInstances;
			ZonesToUpgrade = ZonesToUpgrade.Where((Vector2s zone) => locations.TryGetValue(zone, out var value) && IsSelected(value)).ToArray();
			return base.OnInit();
		}

		private bool IsSelected(LocationInstance location)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			if (location.m_placed && NativePlacement.IsValidLocationPrefab(location.m_location))
			{
				return _ids.Contains(location.m_location.m_prefab.Name);
			}
			return false;
		}

		protected override bool ExecuteZone(Vector2s zone)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			ZoneSystem instance = ZoneSystem.instance;
			if (!instance.m_locationInstances.TryGetValue(zone, out var value) || !IsSelected(value))
			{
				return true;
			}
			if (!instance.IsZoneLoaded(zone) || !GameWorld.TryGetRoot(zone, out GameObject _) || !NativePlacement.CanSpawnLocation(instance, value.m_location))
			{
				GameWorld.PokeZone(zone);
				return false;
			}
			if (ExecuteLocation(zone, value))
			{
				_reset++;
			}
			return true;
		}

		protected unsafe virtual bool ExecuteLocation(Vector2s zone, LocationInstance location)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: 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_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: 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_00ff: Unknown result type (might be due to invalid IL or missing references)
			if (!IsSelected(location))
			{
				return false;
			}
			if (!GameWorld.TryGetRoot(zone, out GameObject root))
			{
				Vector2s val = zone;
				throw new InvalidOperationException("Location zone became unavailable before reset: " + ((object)(*(Vector2s*)(&val))/*cast due to .constrained prefix*/).ToString());
			}
			Heightmap componentInChildren = root.GetComponentInChildren<Heightmap>();
			if ((Object)(object)componentInChildren == (Object)null)
			{
				Vector2s val = zone;
				throw new InvalidOperationException("Loaded location zone has no heightmap: " + ((object)(*(Vector2s*)(&val))/*cast due to .constrained prefix*/).ToString());
			}
			ZoneSystem instance = ZoneSystem.instance;
			float exteriorRadius = location.m_location.m_exteriorRadius;
			ClearLocationObjects(zone, location.m_position, exteriorRadius);
			float num = ((Args.TerrainReset > 0f) ? Args.TerrainReset : exteriorRadius);
			if (num > 0f)
			{
				_terrainTouched = true;
				TerrainResetter.Execute(location.m_position, num);
			}
			location.m_placed = false;
			instance.m_locationInstances[zone] = location;
			List<GameObject> list = NativePlacement.TemporaryObjects(instance);
			HashSet<GameObject> original = new HashSet<GameObject>(list);
			bool active = TerrainResetter.Active;
			try
			{
				TerrainResetter.Active = num > 0f;
				NativePlacement.PlaceLocations(instance, zone, root.transform, componentInChildren, NativePlacement.CreateClearAreas(), list);
				NativePlacement.DestroyCreatedObjects(list, original);
			}
			finally
			{
				TerrainResetter.Active = active;
			}
			return true;
		}

		private static void ClearLocationObjects(Vector2s zone, Vector3 center, float radius)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			if (radius <= 0f)
			{
				return;
			}
			float num = radius * radius;
			foreach (ZDO zDO in GameWorld.GetZDOs(zone))
			{
				int prefab = zDO.GetPrefab();
				if (prefab != ZoneControlHash && prefab != TerrainCompilerHash)
				{
					Vector3 position = zDO.GetPosition();
					float num2 = position.x - center.x;
					float num3 = position.z - center.z;
					if (position.y > 4000f || num2 * num2 + num3 * num3 < num)
					{
						GameWorld.RemoveZDO(zDO);
					}
				}
			}
		}

		protected override void OnEnd()
		{
			base.OnEnd();
			if (_terrainTouched)
			{
				GameWorld.RecalculateTerrain();
			}
			Log($"Location reset: {_reset} locations regenerated.");
		}
	}
	internal class ResetVegetation : ZoneOperation
	{
		private readonly HashSet<string> _ids;

		private readonly HashSet<int> _hashes;

		private List<ZoneVegetation> _vegetation = new List<ZoneVegetation>();

		private int _removed;

		private int _spawned;

		private bool _terrainTouched;

		public ResetVegetation(Action<string> log, HashSet<string> ids, OperationParameters args, HashSet<Vector2s>? candidates = null)
			: base(log, args, candidates)
		{
			_ids = NativePlacement.RequireIds(ids);
			_hashes = new HashSet<int>(_ids.Select((string id) => StringExtensionMethods.GetStableHashCode(id)));
			foreach (string id in _ids)
			{
				string text = id + "_frac";
				if ((Object)(object)ZNetScene.instance.GetPrefab(text) != (Object)null)
				{
					_hashes.Add(StringExtensionMethods.GetStableHashCode(text));
				}
			}
		}

		protected override void OnStart()
		{
			base.OnStart();
			_vegetation = ZoneSystem.instance.m_vegetation.Select(delegate(ZoneVegetation vegetation)
			{
				ZoneVegetation val = vegetation.Clone();
				val.m_enable = (Object)(object)val.m_prefab != (Object)null && _ids.Contains(((Object)val.m_prefab).name);
				return val;
			}).ToList();
		}

		protected unsafe override bool ExecuteZone(Vector2s zone)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_0185: Unknown result type (might be due to invalid IL or missing references)
			if (!ZoneSystem.instance.IsZoneLoaded(zone) || !GameWorld.TryGetRoot(zone, out GameObject root))
			{
				GameWorld.PokeZone(zone);
				return false;
			}
			Heightmap componentInChildren = root.GetComponentInChildren<Heightmap>();
			if ((Object)(object)componentInChildren == (Object)null)
			{
				Vector2s val = zone;
				throw new InvalidOperationException("Loaded vegetation zone has no heightmap: " + ((object)(*(Vector2s*)(&val))/*cast due to .constrained prefix*/).ToString());
			}
			foreach (ZDO zDO in GameWorld.GetZDOs(zone))
			{
				if (_hashes.Contains(zDO.GetPrefab()))
				{
					GameWorld.RemoveZDO(zDO);
					_removed++;
				}
			}
			ZoneSystem instance = ZoneSystem.instance;
			List<ZoneVegetation> vegetation = instance.m_vegetation;
			bool active = TerrainResetter.Active;
			List<GameObject> list = NativePlacement.TemporaryObjects(instance);
			HashSet<GameObject> originalObjects = new HashSet<GameObject>(list);
			try
			{
				instance.m_vegetation = _vegetation;
				TerrainResetter.Active = Args.TerrainReset > 0f;
				NativePlacement.PlaceVegetation(instance, zone, root.transform, componentInChildren, GetClearAreas(zone), list);
				GameObject[] array = list.Where((GameObject obj) => !originalObjects.Contains(obj)).ToArray();
				foreach (GameObject val2 in array)
				{
					if (!((Object)(object)val2 == (Object)null))
					{
						_spawned++;
						if (Args.TerrainReset > 0f)
						{
							_terrainTouched = true;
							TerrainResetter.Execute(val2.transform.position, Args.TerrainReset);
						}
					}
				}
				NativePlacement.DestroyCreatedObjects(list, originalObjects);
			}
			finally
			{
				instance.m_vegetation = vegetation;
				TerrainResetter.Active = active;
			}
			return true;
		}

		private static IList GetClearAreas(Vector2s zone)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			IList list = NativePlacement.CreateClearAreas();
			if (ZoneSystem.instance.m_locationInstances.TryGetValue(zone, out var value) && value.m_location != null && value.m_location.m_clearArea)
			{
				list.Add(NativePlacement.CreateClearArea(value.m_position, value.m_location.m_exteriorRadius));
			}
			return list;
		}

		protected override void OnEnd()
		{
			base.OnEnd();
			if (_terrainTouched)
			{
				GameWorld.RecalculateTerrain();
			}
			Log($"Vegetation reset: {_removed} objects removed, {_spawned} vegetation objects created.");
		}
	}
	internal class ResetZones : ZoneOperation
	{
		private static readonly Action<Minimap, float> UpdateLocationPins = AccessTools.MethodDelegate<Action<Minimap, float>>(AccessTools.Method(typeof(Minimap), "UpdateLocationPins", (Type[])null, (Type[])null), (object)null, true);

		private readonly Dictionary<Vector2s, BorderDirection> _borders = new Dictionary<Vector2s, BorderDirection>();

		private int _reset;

		public ResetZones(Action<string> log, OperationParameters args, HashSet<Vector2s>? candidates = null)
			: base(log, args, candidates)
		{
		}

		protected override bool ExecuteZone(Vector2s zone)
		{
			//IL_0007: 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_001c: 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_0031: 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_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: 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_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: 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_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0165: Unknown result type (might be due to invalid IL or missing references)
			//IL_0128: 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_012e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0137: Unknown result type (might be due to invalid IL or missing references)
			//IL_013e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0151: 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_015e: Unknown result type (might be due to invalid IL or missing references)
			//IL_015f: Unknown result type (might be due to invalid IL or missing references)
			ZoneSystem instance = ZoneSystem.instance;
			AddBorder(zone.x, zone.y - 1, BorderDirection.North);
			AddBorder(zone.x - 1, zone.y, BorderDirection.East);
			AddBorder(zone.x, zone.y + 1, BorderDirection.South);
			AddBorder(zone.x + 1, zone.y, BorderDirection.West);
			AddBorder(zone.x + 1, zone.y - 1, BorderDirection.NorthWest);
			AddBorder(zone.x - 1, zone.y - 1, BorderDirection.NorthEast);
			AddBorder(zone.x + 1, zone.y + 1, BorderDirection.SouthWest);
			AddBorder(zone.x - 1, zone.y + 1, BorderDirection.SouthEast);
			foreach (ZDO zDO in GameWorld.GetZDOs(zone))
			{
				if (zDO != null && zDO.IsValid() && ZoneSystem.GetZone(zDO.GetPosition()) == zone)
				{
					GameWorld.RemoveZDO(zDO);
				}
			}
			if (instance.m_locationInstances.TryGetValue(zone, out var value))
			{
				value.m_placed = false;
				Vector3 position = value.m_position;
				position.y = WorldGenerator.instance.GetHeight(position.x, position.z);
				value.m_position = position;
				instance.m_locationInstances[zone] = value;
			}
			GameWorld.RemoveGeneratedZone(zone);
			_reset++;
			return true;
		}

		private void AddBorder(int x, int y, BorderDirection direction)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			if (x >= -32768 && x <= 32767 && y >= -32768 && y <= 32767)
			{
				Vector2s key = default(Vector2s);
				((Vector2s)(ref key))..ctor(x, y);
				if