Decompiled source of DeepNorthSnowfall v1.0.8

BepInEx/plugins/DeepNorthSnowfall.dll

Decompiled 3 weeks ago
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace DeepNorthSnowRestore;

[BepInPlugin("com.teamgrimgoren.valheim.deepnorthsnowfall", "Deep North Snowfall", "1.0.8")]
public sealed class DeepNorthSnowRestorePlugin : BaseUnityPlugin
{
	[HarmonyPatch(typeof(EnvMan), "UpdateTimeSkip")]
	private static class EnvManUpdateTimeSkipPatch
	{
		private static void Prefix(EnvMan __instance, out bool __state)
		{
			__state = (Object)(object)__instance != (Object)null && __instance.IsTimeSkipping();
		}

		private static void Postfix(EnvMan __instance, bool __state)
		{
			if (__state && !((Object)(object)__instance == (Object)null) && !__instance.IsTimeSkipping())
			{
				ZNet instance = ZNet.instance;
				if (!((Object)(object)instance == (Object)null) && instance.IsServer())
				{
					RestoreAfterCompletedTimeSkip();
				}
			}
		}
	}

	[HarmonyPatch(typeof(TerrainComp), "Awake")]
	private static class TerrainCompAwakePatch
	{
		private static void Postfix(TerrainComp __instance)
		{
			SnowRestorer.RestoreLoadedTerrainCompIfPending(__instance, "pending sleep restore");
		}
	}

	public const string ModGuid = "com.teamgrimgoren.valheim.deepnorthsnowfall";

	public const string ModName = "Deep North Snowfall";

	public const string ModVersion = "1.0.8";

	private const string RpcRestoreEpoch = "com.teamgrimgoren.valheim.deepnorthsnowfall.RestoreEpoch";

	private const string RpcRequestState = "com.teamgrimgoren.valheim.deepnorthsnowfall.RequestState";

	private static ManualLogSource _log;

	private static ConfigEntry<bool> _restoreAfterSleep;

	private static ConfigEntry<float> _postEventScanSeconds;

	private static ConfigEntry<float> _postEventScanIntervalSeconds;

	private static ConfigEntry<float> _minimumSnowDelta;

	private static ConfigEntry<bool> _claimTerrainOwnership;

	private static ConfigEntry<bool> _verboseZoneLogs;

	private static Harmony _harmony;

	private static int _scheduledScanEpoch;

	private static float _scheduledScanUntil;

	private static float _nextScheduledScan;

	private static bool _rpcRegistered;

	private static bool _requestedServerState;

	private static readonly MethodInfo ZRoutedRpcGetServerPeerIDMethod = typeof(ZRoutedRpc).GetMethod("GetServerPeerID", BindingFlags.Instance | BindingFlags.NonPublic);

	private void Awake()
	{
		//IL_0104: Unknown result type (might be due to invalid IL or missing references)
		//IL_010e: Expected O, but got Unknown
		_log = ((BaseUnityPlugin)this).Logger;
		_restoreAfterSleep = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "RestoreAfterSleep", true, "Restore Deep North ground snow once after a successful sleep/night skip.");
		_postEventScanSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("General", "PostEventScanSeconds", 30f, "After sleep or snowrestore, keep checking loaded terrain for this many seconds to catch zones that become ready shortly after the event.");
		_postEventScanIntervalSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("General", "PostEventScanIntervalSeconds", 2f, "Seconds between post-event loaded terrain checks.");
		_minimumSnowDelta = ((BaseUnityPlugin)this).Config.Bind<float>("Safety", "MinimumSnowDelta", 0.001f, "Minimum missing snow value required before a paint sample is changed.");
		_claimTerrainOwnership = ((BaseUnityPlugin)this).Config.Bind<bool>("Safety", "ClaimTerrainOwnership", true, "On the server, claim ownership of loaded TerrainComp ZNetViews before saving if needed.");
		_verboseZoneLogs = ((BaseUnityPlugin)this).Config.Bind<bool>("Logging", "VerboseZoneLogs", true, "Log each touched zone and changed snow sample count.");
		SnowRestoreState.Load(_log);
		SnowRestorer.Initialize(_log, _minimumSnowDelta, _claimTerrainOwnership, _verboseZoneLogs);
		RegisterConsoleCommand();
		_harmony = new Harmony("com.teamgrimgoren.valheim.deepnorthsnowfall");
		_harmony.PatchAll();
		_log.LogInfo((object)"Deep North Snowfall 1.0.8 loaded. Command: snowrestore.");
	}

	private void OnDestroy()
	{
		if (_harmony != null)
		{
			_harmony.UnpatchSelf();
		}
	}

	private void Update()
	{
		EnsureRpcRegistered();
		RequestServerStateIfNeeded();
		RunScheduledPostEventScan();
	}

	private static void RegisterConsoleCommand()
	{
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		//IL_0025: Expected O, but got Unknown
		//IL_0020: Unknown result type (might be due to invalid IL or missing references)
		new ConsoleCommand("snowrestore", "Queue a Deep North ground snow refresh and process currently loaded Deep North terrain.", new ConsoleEvent(RunSnowRestoreCommand), false, true, true, false, false, false, (ConsoleOptionsFetcher)null, false, true, true);
	}

	private static void RunSnowRestoreCommand(ConsoleEventArgs args)
	{
		int num = SnowRestoreState.BeginNewEpoch();
		RestoreReport restoreReport = SnowRestorer.RestoreLoadedDeepNorthTerrainComps("manual command", markCurrentEpoch: true, "Started pending Deep North restore epoch " + num + "; loaded Deep North zones were processed now, and unloaded Deep North zones will restore when loaded.");
		BroadcastRestoreEpoch(num, "manual command");
		SchedulePostEventScans();
		string text = restoreReport.ToConsoleString();
		if (args != null && (Object)(object)args.Context != (Object)null)
		{
			args.Context.AddString(text);
		}
	}

	private static void RestoreAfterCompletedTimeSkip()
	{
		if (_restoreAfterSleep.Value)
		{
			int num = SnowRestoreState.BeginNewEpoch();
			_log.LogInfo((object)("snowrestore sleep/night skip: started pending Deep North restore epoch " + num + "."));
			SnowRestorer.RestoreLoadedDeepNorthTerrainComps("sleep/night skip", markCurrentEpoch: true, "Started pending Deep North restore epoch " + num + "; loaded Deep North zones were processed now, and unloaded Deep North zones will restore when loaded.");
			BroadcastRestoreEpoch(num, "sleep/night skip");
			SchedulePostEventScans();
		}
	}

	private static void EnsureRpcRegistered()
	{
		if (!_rpcRegistered && ZRoutedRpc.instance != null)
		{
			ZRoutedRpc.instance.Register<int>("com.teamgrimgoren.valheim.deepnorthsnowfall.RestoreEpoch", (Action<long, int>)RPC_RestoreEpoch);
			ZRoutedRpc.instance.Register("com.teamgrimgoren.valheim.deepnorthsnowfall.RequestState", (Action<long>)RPC_RequestState);
			_rpcRegistered = true;
			_log.LogInfo((object)"snowrestore: registered routed RPC synchronization.");
		}
	}

	private static void RequestServerStateIfNeeded()
	{
		if (_requestedServerState || !_rpcRegistered)
		{
			return;
		}
		ZNet instance = ZNet.instance;
		ZRoutedRpc instance2 = ZRoutedRpc.instance;
		if (!((Object)(object)instance == (Object)null) && instance2 != null && !instance.IsServer())
		{
			long serverPeerID = GetServerPeerID(instance2);
			if (serverPeerID != 0)
			{
				instance2.InvokeRoutedRPC(serverPeerID, "com.teamgrimgoren.valheim.deepnorthsnowfall.RequestState", new object[0]);
				_requestedServerState = true;
				_log.LogInfo((object)"snowrestore: requested pending restore state from server.");
			}
		}
	}

	private static void BroadcastRestoreEpoch(int epoch, string reason)
	{
		EnsureRpcRegistered();
		ZNet instance = ZNet.instance;
		ZRoutedRpc instance2 = ZRoutedRpc.instance;
		if (epoch > 0 && !((Object)(object)instance == (Object)null) && instance2 != null && instance.IsServer())
		{
			instance2.InvokeRoutedRPC(0L, "com.teamgrimgoren.valheim.deepnorthsnowfall.RestoreEpoch", new object[1] { epoch });
			_log.LogInfo((object)("snowrestore: broadcast restore epoch " + epoch + " to modded clients (" + reason + ")."));
		}
	}

	private static void RPC_RequestState(long sender)
	{
		ZNet instance = ZNet.instance;
		ZRoutedRpc instance2 = ZRoutedRpc.instance;
		if (!((Object)(object)instance == (Object)null) && instance2 != null && instance.IsServer() && SnowRestoreState.HasPendingEpoch())
		{
			instance2.InvokeRoutedRPC(sender, "com.teamgrimgoren.valheim.deepnorthsnowfall.RestoreEpoch", new object[1] { SnowRestoreState.CurrentEpoch });
			_log.LogInfo((object)("snowrestore: sent restore epoch " + SnowRestoreState.CurrentEpoch + " to peer " + sender + "."));
		}
	}

	private static void RPC_RestoreEpoch(long sender, int epoch)
	{
		ZNet instance = ZNet.instance;
		ZRoutedRpc instance2 = ZRoutedRpc.instance;
		if (!((Object)(object)instance == (Object)null) && instance2 != null && epoch > 0 && !instance.IsServer())
		{
			long serverPeerID = GetServerPeerID(instance2);
			if (serverPeerID != 0 && sender != serverPeerID)
			{
				_log.LogWarning((object)("snowrestore: ignored restore epoch " + epoch + " from non-server peer " + sender + "."));
			}
			else
			{
				SnowRestoreState.SetEpoch(epoch);
				_log.LogInfo((object)("snowrestore: received restore epoch " + epoch + " from server."));
				SnowRestorer.RestoreLoadedDeepNorthTerrainComps("server restore epoch " + epoch, markCurrentEpoch: true, "Server requested Deep North snow restore; loaded Deep North zones were processed now, and unloaded Deep North zones will restore when loaded.");
				SchedulePostEventScans();
			}
		}
	}

	private static long GetServerPeerID(ZRoutedRpc rpc)
	{
		if (rpc == null || ZRoutedRpcGetServerPeerIDMethod == null)
		{
			return 0L;
		}
		object obj = ZRoutedRpcGetServerPeerIDMethod.Invoke(rpc, null);
		if (obj is long)
		{
			return (long)obj;
		}
		return 0L;
	}

	private static void SchedulePostEventScans()
	{
		float num = Mathf.Max(0f, _postEventScanSeconds.Value);
		if (!(num <= 0f) && SnowRestoreState.HasPendingEpoch())
		{
			float num2 = Mathf.Max(0.5f, _postEventScanIntervalSeconds.Value);
			_scheduledScanEpoch = SnowRestoreState.CurrentEpoch;
			_scheduledScanUntil = Time.realtimeSinceStartup + num;
			_nextScheduledScan = Time.realtimeSinceStartup + num2;
			_log.LogInfo((object)("snowrestore: scheduled post-event Deep North loaded-zone scans for " + num + " seconds."));
		}
	}

	private static void RunScheduledPostEventScan()
	{
		if (_scheduledScanEpoch <= 0 || SnowRestoreState.CurrentEpoch != _scheduledScanEpoch)
		{
			return;
		}
		float realtimeSinceStartup = Time.realtimeSinceStartup;
		if (realtimeSinceStartup > _scheduledScanUntil)
		{
			_scheduledScanEpoch = 0;
		}
		else if (!(realtimeSinceStartup < _nextScheduledScan))
		{
			ZNet instance = ZNet.instance;
			if (!((Object)(object)instance == (Object)null))
			{
				float num = Mathf.Max(0.5f, _postEventScanIntervalSeconds.Value);
				_nextScheduledScan = realtimeSinceStartup + num;
				SnowRestorer.RestoreLoadedDeepNorthTerrainComps("post-event retry", markCurrentEpoch: true, "Retry scan for loaded Deep North terrain after a pending restore was started.", logAlways: false);
			}
		}
	}
}
internal static class SnowRestoreState
{
	private static readonly Dictionary<string, int> RestoredZones = new Dictionary<string, int>();

	private static ManualLogSource _log;

	private static string _statePath;

	public static int CurrentEpoch { get; private set; }

	public static void Load(ManualLogSource log)
	{
		_log = log;
		_statePath = Path.Combine(Paths.ConfigPath, "com.teamgrimgoren.valheim.deepnorthsnowfall.state");
		CurrentEpoch = 0;
		RestoredZones.Clear();
		if (!File.Exists(_statePath))
		{
			return;
		}
		try
		{
			string[] array = File.ReadAllLines(_statePath);
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				if (text.Length == 0 || text.StartsWith("#"))
				{
					continue;
				}
				if (text.StartsWith("epoch=", StringComparison.OrdinalIgnoreCase))
				{
					if (int.TryParse(text.Substring("epoch=".Length), out var result))
					{
						CurrentEpoch = Math.Max(0, result);
					}
				}
				else if (text.StartsWith("zone=", StringComparison.OrdinalIgnoreCase))
				{
					ParseZoneLine(text);
				}
			}
			_log.LogInfo((object)("Loaded snowrestore state: epoch=" + CurrentEpoch + ", restoredZones=" + RestoredZones.Count + "."));
		}
		catch (Exception ex)
		{
			_log.LogWarning((object)("Could not load snowrestore state file: " + ex.Message));
			CurrentEpoch = 0;
			RestoredZones.Clear();
		}
	}

	public static int BeginNewEpoch()
	{
		CurrentEpoch = ((CurrentEpoch == int.MaxValue) ? 1 : (CurrentEpoch + 1));
		RestoredZones.Clear();
		Save();
		return CurrentEpoch;
	}

	public static void SetEpoch(int epoch)
	{
		if (epoch > 0 && CurrentEpoch != epoch)
		{
			CurrentEpoch = epoch;
			RestoredZones.Clear();
			Save();
		}
	}

	public static bool HasPendingEpoch()
	{
		return CurrentEpoch > 0;
	}

	public static bool IsRestoredThisEpoch(string zoneKey)
	{
		if (CurrentEpoch > 0 && RestoredZones.TryGetValue(zoneKey, out var value))
		{
			return value >= CurrentEpoch;
		}
		return false;
	}

	public static void MarkRestoredThisEpoch(string zoneKey)
	{
		if (CurrentEpoch > 0 && !string.IsNullOrEmpty(zoneKey))
		{
			RestoredZones[zoneKey] = CurrentEpoch;
			Save();
		}
	}

	private static void ParseZoneLine(string line)
	{
		string text = line.Substring("zone=".Length);
		string[] array = text.Split(new char[1] { ';' });
		if (array.Length == 0)
		{
			return;
		}
		int result = 0;
		for (int i = 1; i < array.Length; i++)
		{
			string text2 = array[i].Trim();
			if (text2.StartsWith("epoch=", StringComparison.OrdinalIgnoreCase))
			{
				int.TryParse(text2.Substring("epoch=".Length), out result);
			}
		}
		string text3 = array[0].Trim();
		if (text3.Length > 0 && result > 0)
		{
			RestoredZones[text3] = result;
		}
	}

	private static void Save()
	{
		if (string.IsNullOrEmpty(_statePath))
		{
			return;
		}
		try
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine("# Deep North Snowfall state");
			stringBuilder.AppendLine("epoch=" + CurrentEpoch);
			foreach (KeyValuePair<string, int> restoredZone in RestoredZones)
			{
				stringBuilder.Append("zone=");
				stringBuilder.Append(restoredZone.Key);
				stringBuilder.Append(";epoch=");
				stringBuilder.Append(restoredZone.Value);
				stringBuilder.AppendLine();
			}
			File.WriteAllText(_statePath, stringBuilder.ToString());
		}
		catch (Exception ex)
		{
			if (_log != null)
			{
				_log.LogWarning((object)("Could not save snowrestore state file: " + ex.Message));
			}
		}
	}
}
internal static class SnowRestorer
{
	private const BindingFlags PrivateInstance = BindingFlags.Instance | BindingFlags.NonPublic;

	private const BindingFlags PrivateStatic = BindingFlags.Static | BindingFlags.NonPublic;

	private static readonly FieldInfo TerrainCompInstancesField = typeof(TerrainComp).GetField("s_instances", BindingFlags.Static | BindingFlags.NonPublic);

	private static readonly FieldInfo TerrainCompHeightmapField = typeof(TerrainComp).GetField("m_hmap", BindingFlags.Instance | BindingFlags.NonPublic);

	private static readonly FieldInfo TerrainCompNetViewField = typeof(TerrainComp).GetField("m_nview", BindingFlags.Instance | BindingFlags.NonPublic);

	private static readonly FieldInfo TerrainCompPitchField = typeof(TerrainComp).GetField("m_pitch", BindingFlags.Instance | BindingFlags.NonPublic);

	private static readonly FieldInfo TerrainCompModifiedPaintField = typeof(TerrainComp).GetField("m_modifiedPaint", BindingFlags.Instance | BindingFlags.NonPublic);

	private static readonly FieldInfo TerrainCompPaintMaskField = typeof(TerrainComp).GetField("m_paintMask", BindingFlags.Instance | BindingFlags.NonPublic);

	private static readonly MethodInfo TerrainCompSaveMethod = typeof(TerrainComp).GetMethod("Save", BindingFlags.Instance | BindingFlags.NonPublic);

	private static ManualLogSource _log;

	private static ConfigEntry<float> _minimumSnowDelta;

	private static ConfigEntry<bool> _claimTerrainOwnership;

	private static ConfigEntry<bool> _verboseZoneLogs;

	public static void Initialize(ManualLogSource log, ConfigEntry<float> minimumSnowDelta, ConfigEntry<bool> claimTerrainOwnership, ConfigEntry<bool> verboseZoneLogs)
	{
		_log = log;
		_minimumSnowDelta = minimumSnowDelta;
		_claimTerrainOwnership = claimTerrainOwnership;
		_verboseZoneLogs = verboseZoneLogs;
	}

	public static RestoreReport RestoreLoadedDeepNorthTerrainComps(string reason, bool markCurrentEpoch, string note)
	{
		return RestoreLoadedDeepNorthTerrainComps(reason, markCurrentEpoch, note, logAlways: true);
	}

	public static RestoreReport RestoreLoadedDeepNorthTerrainComps(string reason, bool markCurrentEpoch, string note, bool logAlways)
	{
		//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
		RestoreReport restoreReport = new RestoreReport(reason, "loadedDeepNorth");
		restoreReport.DeepNorthPlayerCount = -1;
		restoreReport.Note = note;
		if (!ReflectionReady(restoreReport))
		{
			return restoreReport;
		}
		ZNet instance = ZNet.instance;
		if ((Object)(object)instance == (Object)null)
		{
			restoreReport.Refuse("Refusing to run: network is not ready.");
			LogRefusal(restoreReport);
			return restoreReport;
		}
		WorldGenerator instance2 = WorldGenerator.instance;
		if (instance2 == null)
		{
			restoreReport.Refuse("Refusing to run: WorldGenerator is not ready.");
			LogRefusal(restoreReport);
			return restoreReport;
		}
		List<TerrainComp> loadedTerrainComps = GetLoadedTerrainComps();
		restoreReport.LoadedTerrainComps = loadedTerrainComps.Count;
		HashSet<string> hashSet = new HashSet<string>();
		for (int i = 0; i < loadedTerrainComps.Count; i++)
		{
			TerrainComp val = loadedTerrainComps[i];
			if ((Object)(object)val == (Object)null)
			{
				continue;
			}
			object? value = TerrainCompHeightmapField.GetValue(val);
			Heightmap val2 = (Heightmap)((value is Heightmap) ? value : null);
			if (!((Object)(object)val2 == (Object)null) && !val2.IsDistantLod)
			{
				Vector2s zone = ZoneSystem.GetZone(((Component)val2).transform.position);
				if ((!markCurrentEpoch || !SnowRestoreState.IsRestoredThisEpoch(ZoneKey(zone))) && RestoreTerrainComp(val, val2, zone, instance2, restoreReport, markCurrentEpoch))
				{
					hashSet.Add(ZoneKey(zone));
				}
			}
		}
		restoreReport.InspectedZoneCount = hashSet.Count;
		if (logAlways || restoreReport.ZoneResults.Count > 0 || restoreReport.ChangedSnowValues > 0)
		{
			LogReport(restoreReport);
		}
		return restoreReport;
	}

	public static void RestoreLoadedTerrainCompIfPending(TerrainComp terrainComp, string reason)
	{
		//IL_0069: Unknown result type (might be due to invalid IL or missing references)
		//IL_006e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0073: 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_00bb: Unknown result type (might be due to invalid IL or missing references)
		if (!SnowRestoreState.HasPendingEpoch() || (Object)(object)terrainComp == (Object)null)
		{
			return;
		}
		RestoreReport report = new RestoreReport(reason, "pendingLoadedZone");
		if (!ReflectionReady(report))
		{
			return;
		}
		ZNet instance = ZNet.instance;
		if ((Object)(object)instance == (Object)null)
		{
			return;
		}
		WorldGenerator instance2 = WorldGenerator.instance;
		if (instance2 == null)
		{
			return;
		}
		object? value = TerrainCompHeightmapField.GetValue(terrainComp);
		Heightmap val = (Heightmap)((value is Heightmap) ? value : null);
		if ((Object)(object)val == (Object)null || val.IsDistantLod)
		{
			return;
		}
		Vector2s zone = ZoneSystem.GetZone(((Component)val).transform.position);
		string zoneKey = ZoneKey(zone);
		if (!SnowRestoreState.IsRestoredThisEpoch(zoneKey))
		{
			RestoreReport restoreReport = new RestoreReport(reason + " epoch " + SnowRestoreState.CurrentEpoch, "pendingLoadedZone");
			restoreReport.LoadedTerrainComps = 1;
			restoreReport.InspectedZoneCount = 1;
			if (RestoreTerrainComp(terrainComp, val, zone, instance2, restoreReport, markCurrentEpoch: true) && restoreReport.ChangedSnowValues > 0)
			{
				LogReport(restoreReport);
			}
		}
	}

	private static bool ReflectionReady(RestoreReport report)
	{
		if (TerrainCompInstancesField == null || TerrainCompHeightmapField == null || TerrainCompNetViewField == null || TerrainCompPitchField == null || TerrainCompModifiedPaintField == null || TerrainCompPaintMaskField == null || TerrainCompSaveMethod == null)
		{
			report.Refuse("Refusing to run: current Valheim TerrainComp internals do not match the inspected layout.");
			LogRefusal(report);
			return false;
		}
		return true;
	}

	private static List<TerrainComp> GetLoadedTerrainComps()
	{
		if (!(TerrainCompInstancesField.GetValue(null) is List<TerrainComp> collection))
		{
			return new List<TerrainComp>();
		}
		return new List<TerrainComp>(collection);
	}

	private static bool RestoreTerrainComp(TerrainComp terrainComp, Heightmap heightmap, Vector2s zone, WorldGenerator worldGenerator, RestoreReport report, bool markCurrentEpoch)
	{
		//IL_00f9: 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_0101: Unknown result type (might be due to invalid IL or missing references)
		//IL_0103: Unknown result type (might be due to invalid IL or missing references)
		//IL_010a: Invalid comparison between Unknown and I4
		//IL_01bd: Unknown result type (might be due to invalid IL or missing references)
		//IL_014f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0154: Unknown result type (might be due to invalid IL or missing references)
		//IL_017e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0180: Unknown result type (might be due to invalid IL or missing references)
		//IL_019a: Unknown result type (might be due to invalid IL or missing references)
		object? value = TerrainCompNetViewField.GetValue(terrainComp);
		ZNetView val = (ZNetView)((value is ZNetView) ? value : null);
		if ((Object)(object)val == (Object)null || !val.IsValid())
		{
			report.SkippedInvalidNetView++;
			return false;
		}
		if (!val.IsOwner())
		{
			if (_claimTerrainOwnership.Value)
			{
				val.ClaimOwnership();
			}
			if (!val.IsOwner())
			{
				report.SkippedNotOwner++;
				return false;
			}
		}
		int num = (int)TerrainCompPitchField.GetValue(terrainComp);
		bool[] array = TerrainCompModifiedPaintField.GetValue(terrainComp) as bool[];
		Color[] array2 = TerrainCompPaintMaskField.GetValue(terrainComp) as Color[];
		if (num <= 0 || array == null || array2 == null || array.Length != array2.Length)
		{
			report.SkippedBadPaintData++;
			return false;
		}
		Texture2D paintMask = heightmap.GetPaintMask();
		int num2 = 0;
		int num3 = 0;
		float num4 = Mathf.Max(0f, _minimumSnowDelta.Value);
		Color val3 = default(Color);
		for (int i = 0; i < array2.Length; i++)
		{
			int num5 = i % num;
			int num6 = i / num;
			Vector3 val2 = heightmap.VertexMaskToWorld(num5, num6);
			if ((int)worldGenerator.GetBiome(val2) != 64)
			{
				continue;
			}
			num3++;
			worldGenerator.GetBiomeHeight((Biome)64, val2.x, val2.z, ref val3, false, true);
			float num7 = Mathf.Clamp01(val3.g);
			if (num7 <= 0f)
			{
				continue;
			}
			Color val4 = array2[i];
			float num8 = Mathf.Clamp01(val4.g);
			if (!(num8 + num4 >= num7))
			{
				val4.g = num7;
				array2[i] = val4;
				array[i] = true;
				if ((Object)(object)paintMask != (Object)null)
				{
					paintMask.SetPixel(num5, num6, val4);
				}
				num2++;
			}
		}
		if (num3 == 0)
		{
			return false;
		}
		string zoneKey = ZoneKey(zone);
		report.AddTouchedZone(zoneKey, num2);
		if (num2 <= 0)
		{
			if (markCurrentEpoch)
			{
				SnowRestoreState.MarkRestoredThisEpoch(zoneKey);
			}
			return true;
		}
		if ((Object)(object)paintMask != (Object)null)
		{
			paintMask.Apply();
		}
		TerrainCompSaveMethod.Invoke(terrainComp, new object[1] { false });
		heightmap.Poke(1, true);
		report.ChangedSnowValues += num2;
		if (markCurrentEpoch)
		{
			SnowRestoreState.MarkRestoredThisEpoch(zoneKey);
		}
		return true;
	}

	private static void LogRefusal(RestoreReport report)
	{
		if (_log != null)
		{
			_log.LogWarning((object)report.ToLogString());
		}
	}

	private static void LogReport(RestoreReport report)
	{
		if (_log == null)
		{
			return;
		}
		_log.LogInfo((object)report.ToLogString());
		if (_verboseZoneLogs.Value)
		{
			for (int i = 0; i < report.ZoneResults.Count; i++)
			{
				ZoneRestoreResult zoneRestoreResult = report.ZoneResults[i];
				_log.LogInfo((object)("snowrestore zone " + zoneRestoreResult.Zone + ": changed " + zoneRestoreResult.ChangedSnowValues + " snow values"));
			}
		}
	}

	private static string ZoneKey(Vector2s zone)
	{
		return zone.x + "," + zone.y;
	}
}
internal sealed class RestoreReport
{
	private readonly Dictionary<string, ZoneRestoreResult> _zonesByKey = new Dictionary<string, ZoneRestoreResult>();

	public string Reason { get; private set; }

	public string Scope { get; private set; }

	public bool Refused { get; private set; }

	public string RefusalReason { get; private set; }

	public string Note { get; set; }

	public int DeepNorthPlayerCount { get; set; }

	public int LoadedTerrainComps { get; set; }

	public int InspectedZoneCount { get; set; }

	public int ChangedSnowValues { get; set; }

	public int SkippedNotOwner { get; set; }

	public int SkippedInvalidNetView { get; set; }

	public int SkippedBadPaintData { get; set; }

	public List<ZoneRestoreResult> ZoneResults { get; private set; }

	public RestoreReport(string reason, string scope)
	{
		Reason = reason;
		Scope = scope;
		ZoneResults = new List<ZoneRestoreResult>();
	}

	public void Refuse(string reason)
	{
		Refused = true;
		RefusalReason = reason;
	}

	public void AddTouchedZone(string zoneKey, int changedSnowValues)
	{
		if (_zonesByKey.TryGetValue(zoneKey, out var value))
		{
			value.ChangedSnowValues += changedSnowValues;
			return;
		}
		ZoneRestoreResult zoneRestoreResult = new ZoneRestoreResult(zoneKey, changedSnowValues);
		_zonesByKey.Add(zoneKey, zoneRestoreResult);
		ZoneResults.Add(zoneRestoreResult);
	}

	public string ToConsoleString()
	{
		return ToSummaryString();
	}

	public string ToLogString()
	{
		return ToSummaryString();
	}

	private string ToSummaryString()
	{
		if (Refused)
		{
			return "snowrestore refused (" + Reason + "): " + RefusalReason;
		}
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.Append("snowrestore ");
		stringBuilder.Append(Reason);
		stringBuilder.Append(": playersInDeepNorth=");
		if (DeepNorthPlayerCount < 0)
		{
			stringBuilder.Append("notChecked");
		}
		else
		{
			stringBuilder.Append(DeepNorthPlayerCount);
		}
		stringBuilder.Append(", scope=");
		stringBuilder.Append(Scope);
		stringBuilder.Append(", loadedTerrainComps=");
		stringBuilder.Append(LoadedTerrainComps);
		stringBuilder.Append(", inspectedZones=");
		stringBuilder.Append(InspectedZoneCount);
		stringBuilder.Append(", touchedZones=");
		stringBuilder.Append(ZoneResults.Count);
		stringBuilder.Append(", changedSnowValues=");
		stringBuilder.Append(ChangedSnowValues);
		if (!string.IsNullOrEmpty(Note))
		{
			stringBuilder.Append(", note=");
			stringBuilder.Append(Note);
		}
		if (SkippedNotOwner > 0 || SkippedInvalidNetView > 0 || SkippedBadPaintData > 0)
		{
			stringBuilder.Append(", skippedNotOwner=");
			stringBuilder.Append(SkippedNotOwner);
			stringBuilder.Append(", skippedInvalidNetView=");
			stringBuilder.Append(SkippedInvalidNetView);
			stringBuilder.Append(", skippedBadPaintData=");
			stringBuilder.Append(SkippedBadPaintData);
		}
		return stringBuilder.ToString();
	}
}
internal sealed class ZoneRestoreResult
{
	public string Zone { get; private set; }

	public int ChangedSnowValues { get; set; }

	public ZoneRestoreResult(string zone, int changedSnowValues)
	{
		Zone = zone;
		ChangedSnowValues = changedSnowValues;
	}
}