Decompiled source of WormtownTerrainSettings v0.5.0

WormtownTerrainSettings.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Unity.Collections;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("WormtownTerrainSettings")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("WormtownTerrainSettings")]
[assembly: AssemblyTitle("WormtownTerrainSettings")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[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 WormtownTerrainSettings
{
	[HarmonyPatch]
	public static class GroundClutterPatches
	{
		private sealed class RuntimeBaseline
		{
			public MapBuilder builder;

			public int fixed_crates;

			public int random_crates;

			public int random_trunks;

			public int diggables;
		}

		private sealed class LoaderBaseline
		{
			public MapLayoutPrefabLoader loader;

			public int spawns_per_frame;
		}

		private struct RankedIndex
		{
			public int index;

			public uint rank;

			public RankedIndex(int value, uint value_rank)
			{
				index = value;
				rank = value_rank;
			}
		}

		private sealed class OccupancyGrid
		{
			private readonly float cell_size;

			private readonly Dictionary<long, List<Vector2>> cells = new Dictionary<long, List<Vector2>>();

			public OccupancyGrid(float size)
			{
				cell_size = size;
			}

			public void Add(Vector3 position)
			{
				//IL_0000: Unknown result type (might be due to invalid IL or missing references)
				//IL_0012: 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_0056: Unknown result type (might be due to invalid IL or missing references)
				//IL_005c: Unknown result type (might be due to invalid IL or missing references)
				int x = Mathf.FloorToInt(position.x / cell_size);
				int z = Mathf.FloorToInt(position.z / cell_size);
				long key = CellKey(x, z);
				if (!cells.TryGetValue(key, out var value))
				{
					value = new List<Vector2>();
					cells.Add(key, value);
				}
				value.Add(new Vector2(position.x, position.z));
			}

			public bool IsFree(Vector3 position, float distance)
			{
				//IL_0000: 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_003a: 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_0080: Unknown result type (might be due to invalid IL or missing references)
				//IL_0085: Unknown result type (might be due to invalid IL or missing references)
				//IL_0087: Unknown result type (might be due to invalid IL or missing references)
				//IL_008c: Unknown result type (might be due to invalid IL or missing references)
				int num = Mathf.FloorToInt(position.x / cell_size);
				int num2 = Mathf.FloorToInt(position.z / cell_size);
				int num3 = Mathf.CeilToInt(distance / cell_size);
				float num4 = distance * distance;
				Vector2 val = default(Vector2);
				((Vector2)(ref val))..ctor(position.x, position.z);
				for (int i = -num3; i <= num3; i++)
				{
					for (int j = -num3; j <= num3; j++)
					{
						if (!cells.TryGetValue(CellKey(num + j, num2 + i), out var value))
						{
							continue;
						}
						foreach (Vector2 item in value)
						{
							Vector2 val2 = item - val;
							if (((Vector2)(ref val2)).sqrMagnitude < num4)
							{
								return false;
							}
						}
					}
				}
				return true;
			}

			private static long CellKey(int x, int z)
			{
				return ((long)x << 32) ^ (uint)z;
			}
		}

		private const int MAX_STATIC_OBJECTS = 8000;

		private const int MAX_BUILDINGS = 256;

		private const int MAX_NETWORKED_ITEMS = 1000;

		private const int MAX_STATIC_SPAWNS_PER_FRAME = 100;

		private const int PLACEMENT_ATTEMPTS = 32;

		private const float TRAIN_CLEARANCE = 30f;

		private const float TOWN_CLEARANCE = 54f;

		private const float QUARRY_CLEARANCE = 44f;

		private const float CHURCH_CLEARANCE = 30f;

		private const float MESA_CLEARANCE = 26f;

		private const float STATIC_SPACING = 3f;

		private const float BUILDING_SPACING = 18f;

		private const int BOULDER_SEED = 101;

		private const int CACTUS_SEED = 211;

		private const int BUILDING_SEED = 307;

		private const int TRUNK_SEED = 401;

		private static readonly FieldInfo diggable_count_field = AccessTools.Field(typeof(MapBuilder), "numOfRandoDiggablesToSpawn");

		private static readonly FieldInfo scatter_limit_field = AccessTools.Field(typeof(MapBuilder), "scatterObjectSetItemSpawnLimit");

		private static readonly FieldInfo building_color_field = AccessTools.Field(typeof(CollapsibleBuilding), "dontRandomizeBuildingColors");

		private static readonly Dictionary<int, RuntimeBaseline> runtime_baselines = new Dictionary<int, RuntimeBaseline>();

		private static readonly Dictionary<int, LoaderBaseline> loader_baselines = new Dictionary<int, LoaderBaseline>();

		private static int processed_layout_id;

		private static int layout_seed;

		private static int baseline_priority_trunks;

		private static int loader_spawns_per_frame;

		private static bool loading_tutorial;

		private static bool layout_loaded_without_snapshot;

		[HarmonyPatch(typeof(MapBuilder), "Start")]
		[HarmonyPostfix]
		public static void MapBuilder_Start_Postfix(MapBuilder __instance)
		{
			CaptureRuntimeBaseline(__instance);
		}

		[HarmonyPatch(typeof(MapLayoutPrefabLoader), "LoadMapLayout")]
		[HarmonyPrefix]
		public static void MapLayoutPrefabLoader_LoadMapLayout_Prefix(MapLayoutPrefabLoader __instance, bool _pardnerTutorialMap, bool _wormTutorialMap)
		{
			processed_layout_id = 0;
			baseline_priority_trunks = 0;
			layout_loaded_without_snapshot = false;
			loading_tutorial = _pardnerTutorialMap || _wormTutorialMap;
			loader_spawns_per_frame = GetLoaderBaseline(__instance);
			if ((Object)(object)__instance != (Object)null)
			{
				__instance.numberOfBuilderSetObjectsToSpawnPerFrame = loader_spawns_per_frame;
			}
		}

		[HarmonyPatch(typeof(MapLayoutPrefabLoader), "ChangeMapLoadState")]
		[HarmonyPrefix]
		public static void MapLayoutPrefabLoader_ChangeMapLoadState_Prefix(MapLayoutPrefabLoader __instance, int _stateIndex)
		{
			if (_stateIndex != 4 || (Object)(object)__instance == (Object)null || (Object)(object)__instance.activeMapLayout == (Object)null)
			{
				return;
			}
			MapLayout activeMapLayout = __instance.activeMapLayout;
			int instanceID = ((Object)activeMapLayout).GetInstanceID();
			if (processed_layout_id == instanceID)
			{
				return;
			}
			processed_layout_id = instanceID;
			layout_seed = GetLayoutSeed(__instance);
			CapturePriorityTrunkBaseline();
			NetworkManager singleton = NetworkManager.Singleton;
			if ((Object)(object)singleton != (Object)null && singleton.IsListening && !singleton.IsHost && !TerrainSettingsSync.CanApplyGameplayPatches)
			{
				layout_loaded_without_snapshot = true;
			}
			if (loading_tutorial || IsTutorialActive() || !TerrainSettingsSync.CanApplyGameplayPatches || !TerrainSettings.HasGroundClutterEffect)
			{
				return;
			}
			try
			{
				ScaleBuildings(activeMapLayout);
				ScaleStaticSpawners(activeMapLayout);
				__instance.numberOfBuilderSetObjectsToSpawnPerFrame = Mathf.Max(loader_spawns_per_frame, ScaleCount(loader_spawns_per_frame, TerrainSettings.ground_clutter, 100));
			}
			catch (Exception ex)
			{
				Plugin.log.LogError((object)$"ground clutter layout scaling failed: {ex}");
				TerrainSettingsSync.ReportTerrainApplicationFailure(ex.Message);
			}
		}

		[HarmonyPatch(typeof(MapBuilder), "ChangeBuilderState")]
		[HarmonyPrefix]
		public static void MapBuilder_ChangeBuilderState_Prefix(MapBuilder __instance, int _stateIndex)
		{
			if (!ShouldScaleHostRuntime(__instance))
			{
				return;
			}
			RuntimeBaseline runtimeBaseline = CaptureRuntimeBaseline(__instance);
			byte units = EffectiveClutterUnits();
			switch (_stateIndex)
			{
			case 4:
			{
				int num = ScaleCount(runtimeBaseline.fixed_crates + runtimeBaseline.random_crates, units, 1000);
				__instance.numberOfCratesToSpawn = Mathf.Min(runtimeBaseline.fixed_crates, num);
				__instance.numberOfRandomWorldCratesToSpawn = Mathf.Max(0, num - __instance.numberOfCratesToSpawn);
				break;
			}
			case 13:
				if (diggable_count_field != null)
				{
					diggable_count_field.SetValue(__instance, ScaleCount(runtimeBaseline.diggables, units, 1000));
				}
				break;
			}
		}

		[HarmonyPatch(typeof(MapBuilder), "ChangeBuilderState")]
		[HarmonyPostfix]
		public static void MapBuilder_ChangeBuilderState_Postfix(MapBuilder __instance, int _stateIndex)
		{
			if (_stateIndex == 5 && ShouldScaleHostRuntime(__instance) && __instance.priorityTrunkSpawnPoints != null)
			{
				RuntimeBaseline runtimeBaseline = CaptureRuntimeBaseline(__instance);
				int num = ScaleCount(baseline_priority_trunks + runtimeBaseline.random_trunks, EffectiveClutterUnits(), 1000);
				__instance.numberOfRandomWorldTrunksToSpawn = Mathf.Max(0, num - (__instance.priorityTrunkSpawnPoints = SelectPriorityTrunks(__instance.priorityTrunkSpawnPoints, Mathf.Min(num, __instance.priorityTrunkSpawnPoints.Count))).Count);
			}
		}

		[HarmonyPatch(typeof(MapBuilder), "SetupNextScatterObjectSet")]
		[HarmonyPostfix]
		public static void MapBuilder_SetupNextScatterObjectSet_Postfix(MapBuilder __instance)
		{
			if (ShouldScaleHostRuntime(__instance) && !(scatter_limit_field == null))
			{
				int vanilla = (int)scatter_limit_field.GetValue(__instance);
				scatter_limit_field.SetValue(__instance, ScaleCount(vanilla, EffectiveClutterUnits(), 1000));
			}
		}

		public static bool ValidateSynchronizedSnapshot()
		{
			if (layout_loaded_without_snapshot)
			{
				return !TerrainSettings.HasGroundClutterEffect;
			}
			return true;
		}

		public static void ResetRuntimeState()
		{
			foreach (RuntimeBaseline value in runtime_baselines.Values)
			{
				if (!((Object)(object)value.builder == (Object)null))
				{
					value.builder.numberOfCratesToSpawn = value.fixed_crates;
					value.builder.numberOfRandomWorldCratesToSpawn = value.random_crates;
					value.builder.numberOfRandomWorldTrunksToSpawn = value.random_trunks;
					if (diggable_count_field != null)
					{
						diggable_count_field.SetValue(value.builder, value.diggables);
					}
				}
			}
			foreach (LoaderBaseline value2 in loader_baselines.Values)
			{
				if ((Object)(object)value2.loader != (Object)null)
				{
					value2.loader.numberOfBuilderSetObjectsToSpawnPerFrame = value2.spawns_per_frame;
				}
			}
			runtime_baselines.Clear();
			loader_baselines.Clear();
			processed_layout_id = 0;
			layout_seed = 0;
			baseline_priority_trunks = 0;
			loader_spawns_per_frame = 0;
			loading_tutorial = false;
			layout_loaded_without_snapshot = false;
		}

		private static void ScaleBuildings(MapLayout layout)
		{
			//IL_01ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_020b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0215: Unknown result type (might be due to invalid IL or missing references)
			//IL_021a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0223: Unknown result type (might be due to invalid IL or missing references)
			//IL_0225: Unknown result type (might be due to invalid IL or missing references)
			//IL_025f: Unknown result type (might be due to invalid IL or missing references)
			//IL_02af: Unknown result type (might be due to invalid IL or missing references)
			if (layout.buildings == null || layout.buildings.Count == 0)
			{
				return;
			}
			List<int> list = new List<int>();
			for (int i = 0; i < layout.buildings.Count; i++)
			{
				if (IsScalableBuilding(layout.buildings[i]))
				{
					list.Add(i);
				}
			}
			if (list.Count == 0)
			{
				return;
			}
			int num = ScaleCount(list.Count, TerrainSettings.ground_clutter, 256);
			int num2 = RequiredPlayerSpawnBuilding(layout, list);
			if (num2 >= 0)
			{
				num = Mathf.Max(1, num);
			}
			HashSet<int> hashSet = SelectIndices(list, Mathf.Min(num, list.Count), 307, num2);
			List<CollapsibleBuilding> list2 = new List<CollapsibleBuilding>();
			List<CollapsibleBuilding> list3 = new List<CollapsibleBuilding>();
			for (int j = 0; j < layout.buildings.Count; j++)
			{
				CollapsibleBuilding val = layout.buildings[j];
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				if (!list.Contains(j) || hashSet.Contains(j))
				{
					list2.Add(val);
					if (list.Contains(j))
					{
						list3.Add(val);
					}
				}
				else
				{
					((Component)val).gameObject.SetActive(false);
					Object.Destroy((Object)(object)((Component)val).gameObject);
				}
			}
			int num3 = Mathf.Max(0, num - list.Count);
			int num4 = 0;
			if (num3 > 0 && list3.Count > 0)
			{
				OccupancyGrid occupancyGrid = BuildBuildingOccupancy(layout, list2);
				Terrain terrain = (((Object)(object)MapBuilder.Singleton != (Object)null) ? MapBuilder.Singleton.GetTerrain() : null);
				for (int k = 0; k < num3; k++)
				{
					if (list2.Count >= 256)
					{
						break;
					}
					CollapsibleBuilding val2 = list3[(int)(StableHash(layout_seed, 307, k, 0) % (uint)list3.Count)];
					if (!TryFindPlacement(terrain, ((Component)val2).transform.position, 18f, 36f, 7f, 2.5f, 307, k, occupancyGrid, out var position))
					{
						continue;
					}
					Quaternion val3 = Quaternion.Euler(((Component)val2).transform.eulerAngles.x, UnitHash(layout_seed, 307, k, 91) * 360f, ((Component)val2).transform.eulerAngles.z);
					GameObject val4 = Object.Instantiate<GameObject>(((Component)val2).gameObject, position, val3, ((Component)layout).transform);
					((Object)val4).name = ((Object)((Component)val2).gameObject).name + " Clutter Copy";
					val4.transform.localScale = ((Component)val2).transform.localScale;
					CollapsibleBuilding component = val4.GetComponent<CollapsibleBuilding>();
					if ((Object)(object)component == (Object)null)
					{
						Object.Destroy((Object)(object)val4);
						continue;
					}
					if (building_color_field != null)
					{
						building_color_field.SetValue(component, true);
					}
					list2.Add(component);
					occupancyGrid.Add(position);
					num4++;
				}
			}
			RebuildBuildingLists(layout, list2);
			Plugin.log.LogInfo((object)($"ground clutter buildings: vanilla={list.Count}, " + $"target={num}, active={list3.Count + num4}"));
		}

		private static void ScaleStaticSpawners(MapLayout layout)
		{
			if (layout.builderSetObjectSpawners == null || (Object)(object)MapBuilder.Singleton == (Object)null)
			{
				return;
			}
			List<MapLayout_BuilderSetObject_Spawner> list = new List<MapLayout_BuilderSetObject_Spawner>(layout.builderSetObjectSpawners);
			List<int> list2 = new List<int>();
			List<int> list3 = new List<int>();
			Dictionary<int, int> dictionary = new Dictionary<int, int>();
			for (int i = 0; i < list.Count; i++)
			{
				if (TryClassifyStaticSpawner(list[i], out var cactus, out var cactus_count))
				{
					if (cactus)
					{
						list3.Add(i);
						dictionary[i] = cactus_count;
					}
					else
					{
						list2.Add(i);
					}
				}
			}
			int num = ScaleCount(list2.Count, TerrainSettings.ground_clutter, 8000);
			int num2 = 0;
			foreach (int item in list3)
			{
				num2 += dictionary[item];
			}
			int num3 = ScaleCount(num2, TerrainSettings.ground_clutter, 8000);
			if (TerrainSettings.ground_clutter <= 20)
			{
				num3 = num2;
			}
			HashSet<int> hashSet = SelectIndices(list2, Mathf.Min(num, list2.Count), 101, -1);
			HashSet<int> hashSet2 = SelectCactusIndices(list3, dictionary, Mathf.Min(num3, num2));
			List<MapLayout_BuilderSetObject_Spawner> list4 = new List<MapLayout_BuilderSetObject_Spawner>();
			for (int j = 0; j < list.Count; j++)
			{
				if ((!list2.Contains(j) && !list3.Contains(j)) || hashSet.Contains(j) || hashSet2.Contains(j))
				{
					list4.Add(list[j]);
				}
			}
			OccupancyGrid occupancy = BuildStaticOccupancy(layout, list4);
			int num4 = AddSpawnerCopies(layout, list, list2, Mathf.Max(0, num - list2.Count), 101, occupancy, list4);
			int num5 = AddCactusCopies(layout, list, list3, dictionary, Mathf.Max(0, num3 - num2), occupancy, list4);
			layout.builderSetObjectSpawners = list4;
			Plugin.log.LogInfo((object)($"ground clutter static: boulders={hashSet.Count + num4}/" + $"{num}, cactuses={CountSelectedCactuses(hashSet2, dictionary) + num5}/" + $"{num3}, total={list4.Count}"));
		}

		private static int AddSpawnerCopies(MapLayout layout, List<MapLayout_BuilderSetObject_Spawner> originals, List<int> source_indices, int desired, int category_seed, OccupancyGrid occupancy, List<MapLayout_BuilderSetObject_Spawner> output)
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			if (desired <= 0 || source_indices.Count == 0)
			{
				return 0;
			}
			Terrain terrain = MapBuilder.Singleton.GetTerrain();
			int num = 0;
			for (int i = 0; i < desired; i++)
			{
				if (output.Count >= 8000)
				{
					break;
				}
				int index = source_indices[(int)(StableHash(layout_seed, category_seed, i, 0) % (uint)source_indices.Count)];
				MapLayout_BuilderSetObject_Spawner val = originals[index];
				if (TryFindPlacement(terrain, val.pos, 3f, 8f, 0f, float.MaxValue, category_seed, i, occupancy, out var position))
				{
					MapLayout_BuilderSetObject_Spawner item = CreateSpawnerCopy(layout, val, position, category_seed, i);
					output.Add(item);
					occupancy.Add(position);
					num++;
				}
			}
			return num;
		}

		private static int AddCactusCopies(MapLayout layout, List<MapLayout_BuilderSetObject_Spawner> originals, List<int> source_indices, Dictionary<int, int> cactus_counts, int desired_components, OccupancyGrid occupancy, List<MapLayout_BuilderSetObject_Spawner> output)
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			if (desired_components <= 0 || source_indices.Count == 0)
			{
				return 0;
			}
			Terrain terrain = MapBuilder.Singleton.GetTerrain();
			int num = 0;
			int num2 = 0;
			while (num < desired_components && output.Count < 8000 && num2 < desired_components * 4)
			{
				int num3 = source_indices[(int)(StableHash(layout_seed, 211, num2, 0) % (uint)source_indices.Count)];
				MapLayout_BuilderSetObject_Spawner val = originals[num3];
				if (TryFindPlacement(terrain, val.pos, 3f, 8f, 0f, float.MaxValue, 211, num2, occupancy, out var position))
				{
					output.Add(CreateSpawnerCopy(layout, val, position, 211, num2));
					occupancy.Add(position);
					num += cactus_counts[num3];
				}
				num2++;
			}
			return num;
		}

		private static MapLayout_BuilderSetObject_Spawner CreateSpawnerCopy(MapLayout layout, MapLayout_BuilderSetObject_Spawner source, Vector3 position, int category_seed, int copy)
		{
			//IL_0015: 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_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject(((Object)((Component)source).gameObject).name + " Clutter Copy");
			val.transform.SetParent(((Component)layout).transform, false);
			val.transform.position = position;
			MapLayout_BuilderSetObject_Spawner obj = val.AddComponent<MapLayout_BuilderSetObject_Spawner>();
			obj.SetIndex = source.SetIndex;
			obj.PrefabIndexWithinSet = source.PrefabIndexWithinSet;
			obj.pos = position;
			obj.rot = source.rot;
			obj.rot.y = UnitHash(layout_seed, category_seed, copy, 47) * 360f;
			obj.scale = source.scale;
			return obj;
		}

		private static bool TryClassifyStaticSpawner(MapLayout_BuilderSetObject_Spawner spawner, out bool cactus, out int cactus_count)
		{
			cactus = false;
			cactus_count = 0;
			GameObject val = ResolveBasePrefab(spawner);
			GameObject val2 = ResolveActivePrefab(spawner, val);
			if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null || ContainsForbiddenStaticContent(val2))
			{
				return false;
			}
			switch (((Object)val).name)
			{
			case "Barrel Cactus":
				return true;
			case "Cactus 1":
			case "Cactus 2":
				cactus = true;
				cactus_count = val2.GetComponentsInChildren<Cactus>(true).Length;
				return cactus_count > 0;
			case "Boulder 1":
			case "Boulder 2":
			case "Boulder 3":
			case "Boulder 4":
			case "Boulder 5":
				return true;
			default:
				return false;
			}
		}

		private static GameObject ResolveBasePrefab(MapLayout_BuilderSetObject_Spawner spawner)
		{
			MapBuilder singleton = MapBuilder.Singleton;
			if ((Object)(object)spawner == (Object)null || (Object)(object)singleton == (Object)null || singleton.builderSets == null || spawner.SetIndex < 0 || spawner.SetIndex >= singleton.builderSets.Length)
			{
				return null;
			}
			BuilderSet val = singleton.builderSets[spawner.SetIndex];
			if ((Object)(object)val == (Object)null || val.prefabs == null || spawner.PrefabIndexWithinSet < 0 || spawner.PrefabIndexWithinSet >= val.prefabs.Length)
			{
				return null;
			}
			return val.prefabs[spawner.PrefabIndexWithinSet];
		}

		private static GameObject ResolveActivePrefab(MapLayout_BuilderSetObject_Spawner spawner, GameObject fallback)
		{
			//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_001f: 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_0033: Expected I4, but got Unknown
			BuilderSet val = MapBuilder.Singleton.builderSets[spawner.SetIndex];
			GameObject[] array = null;
			ThemeOverride themeOverride = GameManager.Singleton.themeOverride;
			switch (themeOverride - 1)
			{
			case 0:
				array = val.themedPrefabs_Halloween;
				break;
			case 1:
				array = val.themedPrefabs_WinterHoliday;
				break;
			case 2:
				array = val.themedPrefabs_Aquarium;
				break;
			}
			int prefabIndexWithinSet = spawner.PrefabIndexWithinSet;
			if (array == null || prefabIndexWithinSet < 0 || prefabIndexWithinSet >= array.Length || !((Object)(object)array[prefabIndexWithinSet] != (Object)null))
			{
				return fallback;
			}
			return array[prefabIndexWithinSet];
		}

		private static bool ContainsForbiddenStaticContent(GameObject prefab)
		{
			Component[] componentsInChildren = prefab.GetComponentsInChildren<Component>(true);
			foreach (Component val in componentsInChildren)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				if (val is NetworkObject || val is NetworkBehaviour || val is CollapsibleBuilding || val is PropSpawner || val is TrunkSpawner || val is FarmSpawnPoint || val is PardnerObjective)
				{
					return true;
				}
				switch (((object)val).GetType().Name)
				{
				case "DestructibleTrain":
				case "BuildingInteriorSet":
				case "PardnerObjective":
				case "Diggable":
				case "PickUppable":
				case "LootTrunk":
				case "BrokenTrainTrackSegment":
					return true;
				}
				Type[] interfaces = ((object)val).GetType().GetInterfaces();
				for (int j = 0; j < interfaces.Length; j++)
				{
					if (interfaces[j].Name == "IWorldUsable")
					{
						return true;
					}
				}
			}
			Transform[] componentsInChildren2 = prefab.GetComponentsInChildren<Transform>(true);
			for (int i = 0; i < componentsInChildren2.Length; i++)
			{
				if (IsEssentialTag(((Component)componentsInChildren2[i]).gameObject.tag))
				{
					return true;
				}
			}
			return false;
		}

		private static bool IsScalableBuilding(CollapsibleBuilding building)
		{
			if ((Object)(object)building == (Object)null || building.isChurch || building.isWaterTower || building.isMiscStructure || building.isFarmHouse || (Object)(object)((Component)building).GetComponentInChildren<NetworkObject>(true) != (Object)null)
			{
				return false;
			}
			Component[] componentsInChildren = ((Component)building).GetComponentsInChildren<Component>(true);
			foreach (Component val in componentsInChildren)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				string name = ((object)val).GetType().Name;
				if (!(val is NetworkBehaviour) && !(val is FarmSpawnPoint) && !(val is PardnerObjective))
				{
					switch (name)
					{
					case "DestructibleTrain":
					case "PardnerObjective":
					case "Diggable":
					case "PickUppable":
					case "LootTrunk":
					case "BrokenTrainTrackSegment":
					case "LadderSpawn":
						break;
					default:
					{
						Type[] interfaces = ((object)val).GetType().GetInterfaces();
						for (int j = 0; j < interfaces.Length; j++)
						{
							if (interfaces[j].Name == "IWorldUsable")
							{
								return false;
							}
						}
						TrunkSpawner val2 = (TrunkSpawner)(object)((val is TrunkSpawner) ? val : null);
						if (val2 == null || !val2.onlySpawnEquipment_NoNormalLoot)
						{
							continue;
						}
						return false;
					}
					}
				}
				return false;
			}
			Transform[] componentsInChildren2 = ((Component)building).GetComponentsInChildren<Transform>(true);
			for (int i = 0; i < componentsInChildren2.Length; i++)
			{
				switch (((Component)componentsInChildren2[i]).gameObject.tag)
				{
				case "PlayerSpawnPoint":
				case "SafeSpawnPoint":
				case "CrateSpawnPoint":
				case "LadderSpawnPoint":
				case "PriorityLadderSpawnPoint":
				case "Train":
				case "DestructibleTrain":
				case "Dandylion":
					return false;
				}
			}
			return true;
		}

		private static int RequiredPlayerSpawnBuilding(MapLayout layout, List<int> scalable_indices)
		{
			int num = 0;
			int num2 = -1;
			foreach (int scalable_index in scalable_indices)
			{
				int num3 = CountTag(((Component)layout.buildings[scalable_index]).gameObject, "PlayerSpawnPoint");
				if (num3 > 0 && num2 < 0)
				{
					num2 = scalable_index;
				}
				num += num3;
			}
			if (GameObject.FindGameObjectsWithTag("PlayerSpawnPoint").Length - num <= 0)
			{
				return num2;
			}
			return -1;
		}

		private static void RebuildBuildingLists(MapLayout layout, List<CollapsibleBuilding> buildings)
		{
			layout.buildings = buildings;
			MapBuilder.Singleton.spawnedBuildings.Clear();
			MapBuilder.Singleton.spawnedBuildings.AddRange(buildings);
			if ((Object)(object)MapLayoutPrefabCreator.Singleton != (Object)null)
			{
				MapLayoutPrefabCreator.Singleton.spawnedBuildings.Clear();
				MapLayoutPrefabCreator.Singleton.spawnedBuildings.AddRange(buildings);
			}
			for (int i = 0; i < buildings.Count; i++)
			{
				buildings[i].buildingID = i;
			}
		}

		private static OccupancyGrid BuildBuildingOccupancy(MapLayout layout, List<CollapsibleBuilding> buildings)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			OccupancyGrid occupancyGrid = new OccupancyGrid(3f);
			foreach (CollapsibleBuilding building in buildings)
			{
				occupancyGrid.Add(((Component)building).transform.position);
			}
			if (layout.builderSetObjectSpawners != null)
			{
				foreach (MapLayout_BuilderSetObject_Spawner builderSetObjectSpawner in layout.builderSetObjectSpawners)
				{
					if ((Object)(object)builderSetObjectSpawner != (Object)null)
					{
						occupancyGrid.Add(builderSetObjectSpawner.pos);
					}
				}
			}
			return occupancyGrid;
		}

		private static OccupancyGrid BuildStaticOccupancy(MapLayout layout, List<MapLayout_BuilderSetObject_Spawner> spawners)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			OccupancyGrid occupancyGrid = new OccupancyGrid(3f);
			foreach (MapLayout_BuilderSetObject_Spawner spawner in spawners)
			{
				if ((Object)(object)spawner != (Object)null)
				{
					occupancyGrid.Add(spawner.pos);
				}
			}
			if (layout.buildings != null)
			{
				foreach (CollapsibleBuilding building in layout.buildings)
				{
					if ((Object)(object)building != (Object)null)
					{
						occupancyGrid.Add(((Component)building).transform.position);
					}
				}
			}
			return occupancyGrid;
		}

		private static bool TryFindPlacement(Terrain terrain, Vector3 source_position, float spacing, float margin, float slope_radius, float maximum_slope, int category_seed, int copy, OccupancyGrid occupancy, out Vector3 position)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0003: 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)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: 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_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: 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_00f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: Unknown result type (might be due to invalid IL or missing references)
			position = source_position;
			if ((Object)(object)terrain == (Object)null || (Object)(object)terrain.terrainData == (Object)null)
			{
				return false;
			}
			Vector3 position2 = ((Component)terrain).transform.position;
			Vector3 size = terrain.terrainData.size;
			float num = GroundY(terrain, source_position.x, source_position.z);
			float num2 = Mathf.Clamp(source_position.y - num, -2f, 5f);
			Vector3 val = default(Vector3);
			for (int i = 0; i < 32; i++)
			{
				float num3 = Mathf.Lerp(position2.x + margin, position2.x + size.x - margin, UnitHash(layout_seed, category_seed, copy, i * 2));
				float num4 = Mathf.Lerp(position2.z + margin, position2.z + size.z - margin, UnitHash(layout_seed, category_seed, copy, i * 2 + 1));
				((Vector3)(ref val))..ctor(num3, GroundY(terrain, num3, num4) + num2, num4);
				if (!IsProtected(val) && occupancy.IsFree(val, spacing) && (!(slope_radius > 0f) || !(TerrainRelief(terrain, val, slope_radius) > maximum_slope)))
				{
					position = val;
					return true;
				}
			}
			return false;
		}

		private static bool IsProtected(Vector3 position)
		{
			//IL_0000: 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_0038: 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_0077: Unknown result type (might be due to invalid IL or missing references)
			if (Mathf.Abs(position.z) < 30f)
			{
				return true;
			}
			MapBuilder singleton = MapBuilder.Singleton;
			if ((Object)(object)singleton == (Object)null)
			{
				return false;
			}
			if (Near(position, singleton.town, 54f) || Near(position, singleton.quarry, 44f) || Near(position, singleton.church, 30f))
			{
				return true;
			}
			if (singleton.mesas != null)
			{
				GameObject[] mesas = singleton.mesas;
				foreach (GameObject target in mesas)
				{
					if (Near(position, target, 26f))
					{
						return true;
					}
				}
			}
			return false;
		}

		private static bool Near(Vector3 position, GameObject target, float radius)
		{
			//IL_000d: 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_0024: 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)
			if ((Object)(object)target == (Object)null)
			{
				return false;
			}
			Vector2 val = default(Vector2);
			((Vector2)(ref val))..ctor(position.x - target.transform.position.x, position.z - target.transform.position.z);
			return ((Vector2)(ref val)).sqrMagnitude < radius * radius;
		}

		private static float TerrainRelief(Terrain terrain, Vector3 position, float radius)
		{
			//IL_0015: 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)
			float num = float.MaxValue;
			float num2 = float.MinValue;
			for (int i = -1; i <= 1; i += 2)
			{
				for (int j = -1; j <= 1; j += 2)
				{
					float num3 = GroundY(terrain, position.x + (float)j * radius, position.z + (float)i * radius);
					num = Mathf.Min(num, num3);
					num2 = Mathf.Max(num2, num3);
				}
			}
			return num2 - num;
		}

		private static float GroundY(Terrain terrain, float x, float z)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			return ((Component)terrain).transform.position.y + terrain.SampleHeight(new Vector3(x, 0f, z));
		}

		private static HashSet<int> SelectIndices(List<int> indices, int target, int category_seed, int forced_index)
		{
			HashSet<int> hashSet = new HashSet<int>();
			if (target <= 0)
			{
				return hashSet;
			}
			if (forced_index >= 0)
			{
				hashSet.Add(forced_index);
			}
			List<RankedIndex> list = new List<RankedIndex>();
			foreach (int index in indices)
			{
				if (index != forced_index)
				{
					list.Add(new RankedIndex(index, StableHash(layout_seed, category_seed, index, 0)));
				}
			}
			list.Sort(delegate(RankedIndex left, RankedIndex right)
			{
				int num = left.rank.CompareTo(right.rank);
				return (num == 0) ? left.index.CompareTo(right.index) : num;
			});
			foreach (RankedIndex item in list)
			{
				if (hashSet.Count >= target)
				{
					break;
				}
				hashSet.Add(item.index);
			}
			return hashSet;
		}

		private static HashSet<int> SelectCactusIndices(List<int> indices, Dictionary<int, int> counts, int target_components)
		{
			HashSet<int> hashSet = new HashSet<int>();
			if (target_components <= 0)
			{
				return hashSet;
			}
			List<RankedIndex> list = new List<RankedIndex>();
			foreach (int index in indices)
			{
				list.Add(new RankedIndex(index, StableHash(layout_seed, 211, index, 0)));
			}
			list.Sort(delegate(RankedIndex left, RankedIndex right)
			{
				int num2 = left.rank.CompareTo(right.rank);
				return (num2 == 0) ? left.index.CompareTo(right.index) : num2;
			});
			int num = 0;
			foreach (RankedIndex item in list)
			{
				hashSet.Add(item.index);
				num += counts[item.index];
				if (num >= target_components)
				{
					break;
				}
			}
			return hashSet;
		}

		private static int CountSelectedCactuses(HashSet<int> selected, Dictionary<int, int> counts)
		{
			int num = 0;
			foreach (int item in selected)
			{
				num += counts[item];
			}
			return num;
		}

		private static List<GameObject> SelectPriorityTrunks(List<GameObject> source, int target)
		{
			HashSet<int> hashSet = new HashSet<int>();
			List<int> list = new List<int>();
			for (int i = 0; i < source.Count; i++)
			{
				TrunkSpawner val = (((Object)(object)source[i] != (Object)null) ? source[i].GetComponent<TrunkSpawner>() : null);
				if ((Object)(object)val != (Object)null && val.onlySpawnEquipment_NoNormalLoot)
				{
					hashSet.Add(i);
				}
				else
				{
					list.Add(i);
				}
			}
			target = Mathf.Max(target, hashSet.Count);
			List<RankedIndex> list2 = new List<RankedIndex>();
			foreach (int item in list)
			{
				list2.Add(new RankedIndex(item, StableHash(layout_seed, 401, item, 0)));
			}
			list2.Sort(delegate(RankedIndex left, RankedIndex right)
			{
				int num2 = left.rank.CompareTo(right.rank);
				return (num2 == 0) ? left.index.CompareTo(right.index) : num2;
			});
			foreach (RankedIndex item2 in list2)
			{
				if (hashSet.Count >= target)
				{
					break;
				}
				hashSet.Add(item2.index);
			}
			List<GameObject> list3 = new List<GameObject>();
			for (int num = 0; num < source.Count; num++)
			{
				if (hashSet.Contains(num))
				{
					list3.Add(source[num]);
				}
			}
			return list3;
		}

		private static RuntimeBaseline CaptureRuntimeBaseline(MapBuilder builder)
		{
			int instanceID = ((Object)builder).GetInstanceID();
			if (!runtime_baselines.TryGetValue(instanceID, out var value))
			{
				value = new RuntimeBaseline
				{
					builder = builder,
					fixed_crates = builder.numberOfCratesToSpawn,
					random_crates = builder.numberOfRandomWorldCratesToSpawn,
					random_trunks = builder.numberOfRandomWorldTrunksToSpawn,
					diggables = ((diggable_count_field != null) ? ((int)diggable_count_field.GetValue(builder)) : 0)
				};
				runtime_baselines.Add(instanceID, value);
			}
			return value;
		}

		private static int GetLoaderBaseline(MapLayoutPrefabLoader loader)
		{
			if ((Object)(object)loader == (Object)null)
			{
				return 1;
			}
			int instanceID = ((Object)loader).GetInstanceID();
			if (!loader_baselines.TryGetValue(instanceID, out var value))
			{
				value = new LoaderBaseline
				{
					loader = loader,
					spawns_per_frame = Mathf.Max(1, loader.numberOfBuilderSetObjectsToSpawnPerFrame)
				};
				loader_baselines.Add(instanceID, value);
			}
			return value.spawns_per_frame;
		}

		private static void CapturePriorityTrunkBaseline()
		{
			baseline_priority_trunks = GameObject.FindGameObjectsWithTag("PriorityTrunkSpawnPoint").Length;
		}

		private static bool ShouldScaleHostRuntime(MapBuilder builder)
		{
			if ((Object)(object)builder != (Object)null && ((NetworkBehaviour)builder).IsHost && !IsTutorialActive())
			{
				return TerrainSettingsSync.CanApplyGameplayPatches;
			}
			return false;
		}

		private static byte EffectiveClutterUnits()
		{
			if (!TerrainSettings.enabled)
			{
				return 20;
			}
			return TerrainSettings.ground_clutter;
		}

		private static int GetLayoutSeed(MapLayoutPrefabLoader loader)
		{
			MapBuilder singleton = MapBuilder.Singleton;
			if ((Object)(object)singleton != (Object)null && singleton.chosenMapLayoutIndexForJoinInProgPlayers != null && singleton.chosenMapLayoutIndexForJoinInProgPlayers.Value >= 0)
			{
				return singleton.chosenMapLayoutIndexForJoinInProgPlayers.Value;
			}
			return loader.mapLayoutIndexChosenByServer;
		}

		private static bool IsTutorialActive()
		{
			MenuToGameBridger singleton = MenuToGameBridger.Singleton;
			if ((Object)(object)singleton != (Object)null)
			{
				if (!singleton.isPardnerTutorial)
				{
					return singleton.isWormTutorial;
				}
				return true;
			}
			return false;
		}

		private static int ScaleCount(int vanilla, byte units, int maximum)
		{
			long num = (long)Mathf.Max(0, vanilla) * (long)units + 10;
			return Mathf.Min(maximum, (int)(num / 20));
		}

		private static int CountTag(GameObject root, string tag)
		{
			int num = 0;
			Transform[] componentsInChildren = root.GetComponentsInChildren<Transform>(true);
			for (int i = 0; i < componentsInChildren.Length; i++)
			{
				if (((Component)componentsInChildren[i]).gameObject.tag == tag)
				{
					num++;
				}
			}
			return num;
		}

		private static bool IsEssentialTag(string tag)
		{
			switch (tag)
			{
			default:
				return tag == "Dandylion";
			case "PlayerSpawnPoint":
			case "CrateSpawnPoint":
			case "PriorityTrunkSpawnPoint":
			case "TrunkSpawnPoint":
			case "LadderSpawnPoint":
			case "PriorityLadderSpawnPoint":
			case "PropSpawner":
			case "SafeSpawnPoint":
			case "SpawnBlocker":
			case "NonOverlap":
			case "WorldUsable":
			case "ClimbableLadder":
			case "Zipline":
			case "Train":
			case "CollapsibleBuilding":
			case "DestructibleTrain":
			case "PickUp":
				return true;
			}
		}

		private static uint StableHash(int a, int b, int c, int d)
		{
			return Mix(Mix(Mix(Mix((uint)(-2128831035 ^ a)) ^ (uint)b) ^ (uint)c) ^ (uint)d);
		}

		private static uint Mix(uint value)
		{
			value ^= value >> 16;
			value *= 2146121005;
			value ^= value >> 15;
			value *= 2221713035u;
			value ^= value >> 16;
			return value;
		}

		private static float UnitHash(int a, int b, int c, int d)
		{
			return (float)(StableHash(a, b, c, d) & 0xFFFFFF) / 16777215f;
		}
	}
	[BepInPlugin("com.wormtown.terrainsettings", "WormtownTerrainSettings", "0.5.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string GUID = "com.wormtown.terrainsettings";

		public const string NAME = "WormtownTerrainSettings";

		public const string VERSION = "0.5.0";

		public const byte VERSION_MAJOR = 0;

		public const byte VERSION_MINOR = 5;

		public const byte VERSION_PATCH = 0;

		public static Plugin instance;

		public static ManualLogSource log;

		public static bool terrain_patch_ready;

		public static bool ground_clutter_patch_ready;

		public static bool shovel_patch_ready;

		public static bool start_guard_patch_ready;

		private ConfigEntry<bool> enabled_config;

		private ConfigEntry<string> hilliness_config;

		private ConfigEntry<string> smoothness_config;

		private ConfigEntry<string> elevation_config;

		private ConfigEntry<string> ground_clutter_config;

		public static bool CriticalPatchesReady
		{
			get
			{
				if (terrain_patch_ready && ground_clutter_patch_ready && shovel_patch_ready)
				{
					return start_guard_patch_ready;
				}
				return false;
			}
		}

		private void Awake()
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Expected O, but got Unknown
			instance = this;
			log = ((BaseUnityPlugin)this).Logger;
			BindConfig();
			Harmony val = new Harmony("com.wormtown.terrainsettings");
			Type[] types = Assembly.GetExecutingAssembly().GetTypes();
			foreach (Type type in types)
			{
				try
				{
					val.CreateClassProcessor(type).Patch();
					if (type == typeof(TerrainPatches))
					{
						terrain_patch_ready = true;
					}
					else if (type == typeof(GroundClutterPatches))
					{
						ground_clutter_patch_ready = true;
					}
					else if (type == typeof(ShovelPatches))
					{
						shovel_patch_ready = true;
					}
					else if (type == typeof(TerrainSettingsSyncPatches))
					{
						start_guard_patch_ready = true;
					}
				}
				catch (Exception ex)
				{
					log.LogWarning((object)("patch class " + type.Name + " failed: " + ex.Message));
				}
			}
			RestoreHostPreferences();
			TerrainSettingsSync.EnsureAvailable();
			if (!CriticalPatchesReady)
			{
				log.LogError((object)"critical patches failed; terrain modifications have been disabled");
			}
			log.LogInfo((object)"WormtownTerrainSettings 0.5.0 loaded");
		}

		public static void RestoreHostPreferences()
		{
			if (!((Object)(object)instance == (Object)null))
			{
				if (!CriticalPatchesReady)
				{
					TerrainSettings.ResetToVanilla();
				}
				else
				{
					TerrainSettings.Apply(instance.enabled_config.Value, ParseHilliness(instance.hilliness_config.Value), ParseSmoothness(instance.smoothness_config.Value), ParseElevation(instance.elevation_config.Value), ParseGroundClutter(instance.ground_clutter_config.Value));
				}
			}
		}

		public static void SaveHostPreferences()
		{
			if (!((Object)(object)instance == (Object)null))
			{
				instance.enabled_config.Value = TerrainSettings.enabled;
				instance.hilliness_config.Value = TerrainSettingsRules.HillinessMultiplier(TerrainSettings.hilliness).ToString("0.00", CultureInfo.InvariantCulture);
				instance.smoothness_config.Value = TerrainSettings.smoothness.ToString(CultureInfo.InvariantCulture);
				instance.elevation_config.Value = TerrainSettingsRules.ElevationMultiplier(TerrainSettings.elevation).ToString("0.00", CultureInfo.InvariantCulture);
				instance.ground_clutter_config.Value = TerrainSettingsRules.GroundClutterMultiplier(TerrainSettings.ground_clutter).ToString("0.00", CultureInfo.InvariantCulture);
			}
		}

		private void BindConfig()
		{
			enabled_config = ((BaseUnityPlugin)this).Config.Bind<bool>("Terrain", "Enabled", false, "Enable synchronized custom terrain and ground clutter for hosted matches.");
			hilliness_config = ((BaseUnityPlugin)this).Config.Bind<string>("Terrain", "Hilliness", "1.00", "Terrain relief multiplier from 0.00 to 10.00 in 0.05 steps.");
			smoothness_config = ((BaseUnityPlugin)this).Config.Bind<string>("Terrain", "Smoothness", "0", "Additional deterministic smoothing passes from 0 to 16.");
			elevation_config = ((BaseUnityPlugin)this).Config.Bind<string>("Terrain", "Elevation", "1.00", "Overall terrain elevation multiplier from 0.00 to 10.00 in 0.05 steps.");
			ground_clutter_config = ((BaseUnityPlugin)this).Config.Bind<string>("Terrain", "GroundClutter", "1.00", "Ground clutter multiplier from 0.00 to 10.00 in 0.05 steps.");
		}

		private static byte ParseHilliness(string value)
		{
			if (TryParseMultiplier(value, out var result))
			{
				return result;
			}
			return (value ?? string.Empty).Trim() switch
			{
				"Flat" => TerrainSettingsRules.MultiplierToUnits(0.5f), 
				"Gentle" => TerrainSettingsRules.MultiplierToUnits(0.75f), 
				"Hilly" => TerrainSettingsRules.MultiplierToUnits(1.25f), 
				"VeryHilly" => TerrainSettingsRules.MultiplierToUnits(1.5f), 
				"Extreme" => TerrainSettingsRules.MultiplierToUnits(1.8f), 
				"Mountainous" => TerrainSettingsRules.MultiplierToUnits(2.1f), 
				"Towering" => TerrainSettingsRules.MultiplierToUnits(2.4f), 
				"Absurd" => TerrainSettingsRules.MultiplierToUnits(2.7f), 
				"Maximum" => TerrainSettingsRules.MultiplierToUnits(3f), 
				_ => 20, 
			};
		}

		private static byte ParseSmoothness(string value)
		{
			if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
			{
				return TerrainSettingsRules.ClampSmoothness(result);
			}
			return (value ?? string.Empty).Trim() switch
			{
				"Smooth" => 1, 
				"Smoother" => 2, 
				"Smoothest" => 4, 
				"UltraSmooth" => 5, 
				"Extreme" => 6, 
				"NearFlat" => 7, 
				"Maximum" => 8, 
				_ => 0, 
			};
		}

		private static byte ParseElevation(string value)
		{
			if (TryParseMultiplier(value, out var result))
			{
				return result;
			}
			return (value ?? string.Empty).Trim() switch
			{
				"Low" => TerrainSettingsRules.MultiplierToUnits(0.85f), 
				"High" => TerrainSettingsRules.MultiplierToUnits(1.1f), 
				"VeryHigh" => TerrainSettingsRules.MultiplierToUnits(1.2f), 
				"Extreme" => TerrainSettingsRules.MultiplierToUnits(1.5f), 
				"Towering" => TerrainSettingsRules.MultiplierToUnits(1.8f), 
				"SkyHigh" => TerrainSettingsRules.MultiplierToUnits(2.1f), 
				"Maximum" => TerrainSettingsRules.MultiplierToUnits(2.4f), 
				_ => 20, 
			};
		}

		private static byte ParseGroundClutter(string value)
		{
			if (!TryParseMultiplier(value, out var result))
			{
				return 20;
			}
			return TerrainSettingsRules.ClampGroundClutter(result);
		}

		private static bool TryParseMultiplier(string value, out byte result)
		{
			if (float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var result2) && !float.IsNaN(result2) && !float.IsInfinity(result2))
			{
				result = TerrainSettingsRules.MultiplierToUnits(result2);
				return true;
			}
			result = 0;
			return false;
		}
	}
	[HarmonyPatch]
	public static class ShovelPatches
	{
		private struct EquipmentTypeScope
		{
			public PardnerEquipment_Shovel shovel;

			public EquipmentType previous_type;
		}

		private struct AimedUseScope
		{
			public bool active;

			public PardnerEquipment_Shovel shovel;

			public EquipmentType previous_type;

			public bool previous_requirement;

			public PardnerEquipment_Shovel previous_aimed_shovel;

			public Vector3 previous_aimed_position;

			public Rigidbody body;

			public PlayerMovement movement;

			public Vector3 velocity;

			public bool restore_velocity;

			public bool air_animation_registered;
		}

		private const float AIM_PLAYER_RANGE = 6f;

		private const float AIM_RAY_LENGTH = 15f;

		private const int VISIBLE_TERRAIN_MASK = 128;

		private const float SHOVEL_DIG_STRENGTH = 0.24f;

		private const int SHOVEL_DIG_RADIUS = 10;

		private const float SHOVEL_DIG_FALLOFF = 0.25f;

		private static readonly FieldInfo specific_use_field = AccessTools.Field(typeof(PardnerEquipment), "specificUseRequirementsMet_ThisFrame");

		private static readonly FieldInfo use_buffer_field = AccessTools.Field(typeof(PardnerEquipment_Shovel), "useBuffer");

		private static readonly HashSet<PlayerMovement> airborne_shovel_animations = new HashSet<PlayerMovement>();

		[ThreadStatic]
		private static bool inside_shovel_deform;

		[ThreadStatic]
		private static PardnerEquipment_Shovel aimed_shovel;

		[ThreadStatic]
		private static Vector3 aimed_position;

		[HarmonyPatch(typeof(PardnerEquipment_Shovel), "DeformTerrain", new Type[]
		{
			typeof(Vector3),
			typeof(ulong)
		})]
		[HarmonyPrefix]
		private static void PardnerEquipment_Shovel_DeformTerrain_Prefix(out bool __state)
		{
			__state = inside_shovel_deform;
			inside_shovel_deform = true;
		}

		[HarmonyPatch(typeof(PardnerEquipment_Shovel), "DeformTerrain", new Type[]
		{
			typeof(Vector3),
			typeof(ulong)
		})]
		[HarmonyFinalizer]
		private static Exception PardnerEquipment_Shovel_DeformTerrain_Finalizer(Exception __exception, bool __state)
		{
			inside_shovel_deform = __state;
			return __exception;
		}

		[HarmonyPatch(typeof(TerrainDeformerManager), "LowerTerrain")]
		[HarmonyPrefix]
		public static bool TerrainDeformerManager_LowerTerrain_Prefix(TerrainDeformerManager __instance, Vector3 worldPosition)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			if (!inside_shovel_deform)
			{
				return true;
			}
			if (!TerrainSettingsSync.CanApplyGameplayPatches)
			{
				return !TerrainSettingsSync.ShouldDeferGameplayPatches;
			}
			__instance.LowerTerrainInCircle(worldPosition, 0.24f, 10, 0.25f);
			return false;
		}

		[HarmonyPatch(typeof(PlayerGrabber), "HandleUsingEquipment")]
		[HarmonyPrefix]
		private static void PlayerGrabber_HandleUsingEquipment_Prefix(PlayerGrabber __instance, out EquipmentTypeScope __state)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Invalid comparison between Unknown and I4
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			__state = default(EquipmentTypeScope);
			if (!((NetworkBehaviour)__instance).IsOwner || !TerrainSettingsSync.CanApplyGameplayPatches || (Object)(object)InputManager.Singleton == (Object)null)
			{
				return;
			}
			PardnerActionMapActions pardnerActionMap = InputManager.Singleton.inputActions.PardnerActionMap;
			if (((PardnerActionMapActions)(ref pardnerActionMap)).AimCameraMode.IsPressed())
			{
				PardnerEquipment clientsideEquippedEquipment = __instance.clientsideEquippedEquipment;
				PardnerEquipment_Shovel val = (PardnerEquipment_Shovel)(object)((clientsideEquippedEquipment is PardnerEquipment_Shovel) ? clientsideEquippedEquipment : null);
				if (!((Object)(object)val == (Object)null) && (int)((PardnerEquipment)val).equipmentType == 3)
				{
					__state.shovel = val;
					__state.previous_type = ((PardnerEquipment)val).equipmentType;
					((PardnerEquipment)val).equipmentType = (EquipmentType)1;
				}
			}
		}

		[HarmonyPatch(typeof(PlayerGrabber), "HandleUsingEquipment")]
		[HarmonyFinalizer]
		private static Exception PlayerGrabber_HandleUsingEquipment_Finalizer(Exception __exception, EquipmentTypeScope __state)
		{
			//IL_0015: 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)
			if ((Object)(object)__state.shovel != (Object)null)
			{
				((PardnerEquipment)__state.shovel).equipmentType = __state.previous_type;
			}
			return __exception;
		}

		[HarmonyPatch(typeof(PardnerEquipment_Shovel), "OnUse")]
		[HarmonyPrefix]
		private static bool PardnerEquipment_Shovel_OnUse_Prefix(PardnerEquipment_Shovel __instance, out AimedUseScope __state)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Invalid comparison between Unknown and I4
			//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)
			__state = default(AimedUseScope);
			if ((int)((PardnerEquipment)__instance).equipmentType == 1 && (Object)(object)InputManager.Singleton != (Object)null)
			{
				PardnerActionMapActions pardnerActionMap = InputManager.Singleton.inputActions.PardnerActionMap;
				if (((PardnerActionMapActions)(ref pardnerActionMap)).AimCameraMode.IsPressed())
				{
					if (!TryPrepareAimedUse(__instance, ref __state))
					{
						return false;
					}
					return true;
				}
			}
			return true;
		}

		[HarmonyPatch(typeof(PardnerEquipment_Shovel), "OnUse")]
		[HarmonyFinalizer]
		private static Exception PardnerEquipment_Shovel_OnUse_Finalizer(Exception __exception, AimedUseScope __state)
		{
			//IL_002c: 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_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			if (!__state.active)
			{
				return __exception;
			}
			specific_use_field.SetValue(__state.shovel, __state.previous_requirement);
			((PardnerEquipment)__state.shovel).equipmentType = __state.previous_type;
			aimed_shovel = __state.previous_aimed_shovel;
			aimed_position = __state.previous_aimed_position;
			if (__state.restore_velocity && (Object)(object)__state.body != (Object)null)
			{
				__state.body.velocity = __state.velocity;
			}
			if (__exception != null && __state.air_animation_registered)
			{
				airborne_shovel_animations.Remove(__state.movement);
			}
			return __exception;
		}

		[HarmonyPatch(typeof(PardnerEquipment_Shovel), "WaitASecondAndThenDigAHole")]
		[HarmonyPrefix]
		public static void PardnerEquipment_Shovel_WaitForDig_Prefix(PardnerEquipment_Shovel __instance, ref Vector3 _digPos)
		{
			//IL_000e: 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)
			if ((Object)(object)aimed_shovel == (Object)(object)__instance)
			{
				_digPos = aimed_position;
			}
		}

		[HarmonyPatch(typeof(PlayerMovement), "ChangeAwayFromStaticAnimationState")]
		[HarmonyPostfix]
		public static void PlayerMovement_ChangeAwayFromStaticAnimationState_Postfix(PlayerMovement __instance)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			if (airborne_shovel_animations.Remove(__instance) && !__instance.grounded)
			{
				__instance.movementState = (MovementState)1;
			}
		}

		[HarmonyPatch(typeof(PlayerMovement), "AttemptToSurfaceClingInFrontOfUs")]
		[HarmonyPrefix]
		public static bool PlayerMovement_AttemptSurfaceCling_Prefix(PlayerMovement __instance)
		{
			return !IsAimingShovel(__instance);
		}

		[HarmonyPatch(typeof(PlayerMovement), "AttemptToGrabZipline")]
		[HarmonyPrefix]
		public static bool PlayerMovement_AttemptGrabZipline_Prefix(PlayerMovement __instance)
		{
			return !IsAimingShovel(__instance);
		}

		public static void ResetRuntimeState()
		{
			//IL_000c: 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)
			inside_shovel_deform = false;
			aimed_shovel = null;
			aimed_position = Vector3.zero;
			airborne_shovel_animations.Clear();
		}

		private static bool TryPrepareAimedUse(PardnerEquipment_Shovel shovel, ref AimedUseScope state)
		{
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Invalid comparison between Unknown and I4
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_0138: Unknown result type (might be due to invalid IL or missing references)
			//IL_013d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0164: Unknown result type (might be due to invalid IL or missing references)
			//IL_0169: Unknown result type (might be due to invalid IL or missing references)
			//IL_0183: Unknown result type (might be due to invalid IL or missing references)
			//IL_0189: Invalid comparison between Unknown and I4
			//IL_01b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0207: Unknown result type (might be due to invalid IL or missing references)
			//IL_020c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0218: Unknown result type (might be due to invalid IL or missing references)
			//IL_021d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0229: Unknown result type (might be due to invalid IL or missing references)
			//IL_022e: Unknown result type (might be due to invalid IL or missing references)
			if (!TerrainSettingsSync.CanApplyGameplayPatches || specific_use_field == null || use_buffer_field == null || (bool)use_buffer_field.GetValue(shovel) || (Object)(object)((PardnerEquipment)shovel).grabberScriptOfWhoHasUsEquipped == (Object)null || !((NetworkBehaviour)((PardnerEquipment)shovel).grabberScriptOfWhoHasUsEquipped).IsOwner || ((PardnerEquipment)shovel).grabberScriptOfWhoHasUsEquipped.isHoldingAnObject || !((PardnerEquipment)shovel).GetIsCooldownReady())
			{
				return false;
			}
			PlayerGrabber grabberScriptOfWhoHasUsEquipped = ((PardnerEquipment)shovel).grabberScriptOfWhoHasUsEquipped;
			Player component = ((Component)grabberScriptOfWhoHasUsEquipped).GetComponent<Player>();
			PlayerMovement component2 = ((Component)grabberScriptOfWhoHasUsEquipped).GetComponent<PlayerMovement>();
			if ((Object)(object)component == (Object)null || !component.isAlive || (Object)(object)component2 == (Object)null || ((int)component2.movementState != 0 && (int)component2.movementState != 1))
			{
				return false;
			}
			Camera val = (((Object)(object)GameManager.Singleton != (Object)null) ? GameManager.Singleton.activeCamera : null);
			RaycastHit val2 = default(RaycastHit);
			if ((Object)(object)val == (Object)null || !Physics.Raycast(new Ray(((Component)val).transform.position, ((Component)val).transform.forward), ref val2, 15f, 128, (QueryTriggerInteraction)1) || Vector3.Distance(((Component)grabberScriptOfWhoHasUsEquipped).transform.position, ((RaycastHit)(ref val2)).point) > 6f || !(((RaycastHit)(ref val2)).collider is TerrainCollider))
			{
				return false;
			}
			state.active = true;
			state.shovel = shovel;
			state.previous_type = ((PardnerEquipment)shovel).equipmentType;
			state.previous_requirement = (bool)specific_use_field.GetValue(shovel);
			state.previous_aimed_shovel = aimed_shovel;
			state.previous_aimed_position = aimed_position;
			state.body = ((Component)component2).GetComponent<Rigidbody>();
			state.movement = component2;
			state.restore_velocity = (int)component2.movementState == 1 && (Object)(object)state.body != (Object)null;
			state.velocity = (state.restore_velocity ? state.body.velocity : Vector3.zero);
			if (state.restore_velocity)
			{
				airborne_shovel_animations.Add(component2);
				state.air_animation_registered = true;
			}
			specific_use_field.SetValue(shovel, true);
			((PardnerEquipment)shovel).equipmentType = (EquipmentType)3;
			aimed_shovel = shovel;
			aimed_position = ((RaycastHit)(ref val2)).point;
			((PardnerEquipment)shovel).aimSpotOnUse = ((RaycastHit)(ref val2)).point;
			((PardnerEquipment)shovel).cameraPositionOnUse = ((Component)val).transform.position;
			((PardnerEquipment)shovel).playerPositionOnUse = ((Component)grabberScriptOfWhoHasUsEquipped).transform.position;
			return true;
		}

		private static bool IsAimingShovel(PlayerMovement movement)
		{
			//IL_002f: 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)
			if (TerrainSettingsSync.CanApplyGameplayPatches && !((Object)(object)movement == (Object)null) && ((NetworkBehaviour)movement).IsOwner && !((Object)(object)InputManager.Singleton == (Object)null))
			{
				PardnerActionMapActions pardnerActionMap = InputManager.Singleton.inputActions.PardnerActionMap;
				if (((PardnerActionMapActions)(ref pardnerActionMap)).AimCameraMode.IsPressed())
				{
					PlayerGrabber component = ((Component)movement).GetComponent<PlayerGrabber>();
					if ((Object)(object)component != (Object)null)
					{
						return component.clientsideEquippedEquipment is PardnerEquipment_Shovel;
					}
					return false;
				}
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(TerrainManager), "SetTerrainHeights")]
	public static class TerrainPatches
	{
		private struct ProtectedArea
		{
			public Vector3 position;

			public float radius;

			public ProtectedArea(Vector3 area_position, float area_radius)
			{
				//IL_0001: Unknown result type (might be due to invalid IL or missing references)
				//IL_0002: Unknown result type (might be due to invalid IL or missing references)
				position = area_position;
				radius = area_radius;
			}
		}

		private const int EDGE_BLEND_SAMPLES = 16;

		private const float TOWN_PROTECTION_RADIUS = 48f;

		private const float QUARRY_PROTECTION_RADIUS = 38f;

		private const float CHURCH_PROTECTION_RADIUS = 24f;

		private const float MESA_PROTECTION_RADIUS = 20f;

		private const float BUILDING_PROTECTION_RADIUS = 12f;

		private const float PROTECTION_BLEND_WIDTH = 12f;

		private const float TRAIN_CORRIDOR_HALF_WIDTH = 24f;

		private const float TRAIN_CORRIDOR_BLEND_WIDTH = 4f;

		private static TerrainManager captured_manager;

		private static float[,] vanilla_heights;

		private static float[,] applied_base_heights;

		private static ushort applied_revision;

		private static bool terrain_generation_seen;

		[HarmonyPostfix]
		public static void SetTerrainHeights_Postfix(TerrainManager __instance)
		{
			if (IsTutorial() || IsMapAuthoring())
			{
				return;
			}
			terrain_generation_seen = true;
			try
			{
				if (!CaptureVanillaTerrain(__instance))
				{
					if (TerrainSettings.HasNonVanillaEffect)
					{
						TerrainSettingsSync.ReportTerrainApplicationFailure("terrain data was unavailable after generation");
					}
				}
				else
				{
					if (!TerrainSettingsSync.CanApplyTerrain)
					{
						return;
					}
					if (TerrainSettings.HasNonVanillaEffect)
					{
						if (!ApplyCapturedTerrain(TerrainSettingsSync.ActiveRevision, update_duplicate: false))
						{
							TerrainSettingsSync.ReportTerrainApplicationFailure("the generated heightmap could not be committed");
						}
					}
					else
					{
						applied_revision = TerrainSettingsSync.ActiveRevision;
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.log.LogWarning((object)$"terrain settings failed; vanilla terrain retained: {ex}");
				if (TerrainSettings.HasNonVanillaEffect)
				{
					TerrainSettingsSync.ReportTerrainApplicationFailure(ex.Message);
				}
			}
		}

		public static bool ApplySynchronizedSnapshot(ushort revision)
		{
			if (!terrain_generation_seen || applied_revision == revision || IsTutorial())
			{
				return true;
			}
			if ((Object)(object)captured_manager == (Object)null || vanilla_heights == null)
			{
				return false;
			}
			try
			{
				bool num = ApplyCapturedTerrain(revision, update_duplicate: true);
				if (!num)
				{
					TerrainSettingsSync.ReportTerrainApplicationFailure("the late synchronized heightmap could not update worm collision");
				}
				return num;
			}
			catch (Exception ex)
			{
				Plugin.log.LogWarning((object)$"late terrain synchronization failed: {ex}");
				TerrainSettingsSync.ReportTerrainApplicationFailure(ex.Message);
				return false;
			}
		}

		public static void ResetSessionState()
		{
			ShovelPatches.ResetRuntimeState();
			GroundClutterPatches.ResetRuntimeState();
			captured_manager = null;
			vanilla_heights = null;
			applied_base_heights = null;
			applied_revision = 0;
			terrain_generation_seen = false;
		}

		private static bool CaptureVanillaTerrain(TerrainManager manager)
		{
			Terrain val = (((Object)(object)manager != (Object)null) ? manager.GetTerrain() : null);
			TerrainData val2 = (((Object)(object)val != (Object)null) ? val.terrainData : null);
			if ((Object)(object)val2 == (Object)null)
			{
				Plugin.log.LogWarning((object)"terrain settings skipped: terrain data unavailable");
				return false;
			}
			int heightmapResolution = val2.heightmapResolution;
			captured_manager = manager;
			vanilla_heights = val2.GetHeights(0, 0, heightmapResolution, heightmapResolution);
			applied_base_heights = null;
			applied_revision = 0;
			return true;
		}

		private static bool ApplyCapturedTerrain(ushort revision, bool update_duplicate)
		{
			Terrain val = (((Object)(object)captured_manager != (Object)null) ? captured_manager.GetTerrain() : null);
			TerrainData val2 = (((Object)(object)val != (Object)null) ? val.terrainData : null);
			if ((Object)(object)val2 == (Object)null || vanilla_heights == null)
			{
				return false;
			}
			float[,] previous_base = applied_base_heights ?? vanilla_heights;
			float[,] array = (float[,])vanilla_heights.Clone();
			if (TerrainSettings.HasNonVanillaEffect)
			{
				int num = TerrainSettingsRules.AdditionalSmoothingPasses(TerrainSettings.smoothness);
				for (int i = 0; i < num; i++)
				{
					array = SmoothHeightmap(array);
				}
				ApplyShape(array, TerrainSettingsRules.HillinessMultiplier(TerrainSettings.hilliness), TerrainSettingsRules.ElevationMultiplier(TerrainSettings.elevation));
				ApplySafetyBlend(val, vanilla_heights, array, BuildProtectedAreas());
			}
			float[,] array2 = (update_duplicate ? PreserveRuntimeDeformations(val2, previous_base, array) : array);
			val2.SetHeights(0, 0, array2);
			applied_base_heights = array;
			AlignFutureLayoutSpawns(val, previous_base, array);
			if (update_duplicate && !UpdateDuplicateTerrain(val2, array))
			{
				return false;
			}
			applied_revision = revision;
			Plugin.log.LogInfo((object)(TerrainSettings.HasNonVanillaEffect ? ("terrain applied: hilliness=" + TerrainSettingsRules.HillinessName(TerrainSettings.hilliness) + ", smoothness=" + TerrainSettingsRules.SmoothnessName(TerrainSettings.smoothness) + ", elevation=" + TerrainSettingsRules.ElevationName(TerrainSettings.elevation)) : "terrain restored to the synchronized vanilla baseline"));
			return true;
		}

		private static float[,] PreserveRuntimeDeformations(TerrainData terrain_data, float[,] previous_base, float[,] new_base)
		{
			int length = new_base.GetLength(0);
			int length2 = new_base.GetLength(1);
			if (previous_base == null || previous_base.GetLength(0) != length || previous_base.GetLength(1) != length2)
			{
				return new_base;
			}
			float[,] heights = terrain_data.GetHeights(0, 0, length2, length);
			float[,] array = new float[length, length2];
			for (int i = 0; i < length; i++)
			{
				for (int j = 0; j < length2; j++)
				{
					float num = heights[i, j] - previous_base[i, j];
					array[i, j] = Mathf.Clamp01(new_base[i, j] + num);
				}
			}
			return array;
		}

		private static bool UpdateDuplicateTerrain(TerrainData visible_data, float[,] heights)
		{
			GameManager singleton = GameManager.Singleton;
			GameObject val = (((Object)(object)singleton != (Object)null) ? singleton.duplicateWormOnlyTerrain : null);
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			Terrain component = val.GetComponent<Terrain>();
			TerrainCollider component2 = val.GetComponent<TerrainCollider>();
			TerrainData val2 = (((Object)(object)component != (Object)null) ? component.terrainData : null);
			bool flag = (Object)(object)val2 == (Object)(object)visible_data;
			if (!flag && (Object)(object)val2 != (Object)null && val2.heightmapResolution == heights.GetLength(0))
			{
				val2.SetHeights(0, 0, heights);
				flag = true;
			}
			TerrainData val3 = (((Object)(object)component2 != (Object)null) ? component2.terrainData : null);
			bool flag2 = (Object)(object)component2 != (Object)null && ((Object)(object)val3 == (Object)(object)visible_data || (flag && (Object)(object)val3 == (Object)(object)val2));
			if (!flag2 && (Object)(object)val3 != (Object)null && (Object)(object)val3 != (Object)(object)val2 && val3.heightmapResolution == heights.GetLength(0))
			{
				val3.SetHeights(0, 0, heights);
				flag2 = true;
			}
			return flag && flag2;
		}

		private static void ApplyShape(float[,] modified, float hilliness, float elevation)
		{
			int length = modified.GetLength(0);
			int length2 = modified.GetLength(1);
			double num = 0.0;
			for (int i = 0; i < length; i++)
			{
				for (int j = 0; j < length2; j++)
				{
					num += (double)modified[i, j];
				}
			}
			float mean = (float)(num / (double)(length2 * length));
			for (int k = 0; k < length; k++)
			{
				for (int l = 0; l < length2; l++)
				{
					modified[k, l] = RoundedHeight(modified[k, l], mean, hilliness, elevation);
				}
			}
		}

		private static void ApplySafetyBlend(Terrain terrain, float[,] original, float[,] modified, List<ProtectedArea> protected_areas)
		{
			//IL_0016: 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_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)
			//IL_0030: 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)
			int length = modified.GetLength(0);
			int length2 = modified.GetLength(1);
			Vector3 position = ((Component)terrain).transform.position;
			Vector3 size = terrain.terrainData.size;
			for (int i = 0; i < length; i++)
			{
				float num = TrainCorridorBlend(position.z + (float)i / (float)(length - 1) * size.z);
				for (int j = 0; j < length2; j++)
				{
					float num2 = Mathf.Min(BorderBlend(j, i, length2, length), ProtectedAreaBlend(terrain, protected_areas, j, i, length2, length));
					float num3 = ((modified[i, j] < original[i, j]) ? num2 : Mathf.Min(num, num2));
					modified[i, j] = Mathf.Clamp01(Mathf.Lerp(original[i, j], modified[i, j], num3));
				}
			}
		}

		private static float TrainCorridorBlend(float world_z)
		{
			float num = Mathf.Abs(world_z);
			float num2 = Mathf.InverseLerp(24f, 28f, num);
			return num2 * num2 * (3f - 2f * num2);
		}

		private static float RoundedHeight(float height, float mean, float hilliness, float elevation)
		{
			if (elevation <= 0f)
			{
				return 0f;
			}
			if (Mathf.Approximately(hilliness, 1f) && Mathf.Approximately(elevation, 1f))
			{
				return Mathf.Clamp01(height);
			}
			float num = SoftElevationCenter(mean * elevation);
			float num2 = (height - mean) * hilliness * elevation;
			if (num2 >= 0f)
			{
				float num3 = 1f - num;
				if (!(num3 <= Mathf.Epsilon))
				{
					return num + num3 * num2 / (num3 + num2);
				}
				return num;
			}
			float num4 = 0f - num2;
			if (!(num <= Mathf.Epsilon))
			{
				return num - num * num4 / (num + num4);
			}
			return num;
		}

		private static float SoftElevationCenter(float value)
		{
			value = Mathf.Max(0f, value);
			if (value <= 0.8f)
			{
				return value;
			}
			float num = value - 0.8f;
			return 0.8f + 0.19999999f * num / (0.19999999f + num);
		}

		private static List<ProtectedArea> BuildProtectedAreas()
		{
			//IL_002d: 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)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: 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)
			List<ProtectedArea> list = new List<ProtectedArea>();
			MapLayoutPrefabLoader singleton = MapLayoutPrefabLoader.Singleton;
			MapLayout val = (((Object)(object)singleton != (Object)null) ? singleton.activeMapLayout : null);
			if ((Object)(object)val != (Object)null)
			{
				list.Add(new ProtectedArea(val.town_POS, 48f));
				list.Add(new ProtectedArea(val.quarry_POS, 38f));
				list.Add(new ProtectedArea(val.church_POS, 24f));
				if (val.mesa_Positions != null)
				{
					foreach (Vector3 mesa_Position in val.mesa_Positions)
					{
						list.Add(new ProtectedArea(mesa_Position, 20f));
					}
				}
				if (val.buildings != null)
				{
					foreach (CollapsibleBuilding building in val.buildings)
					{
						AddProtectedBuilding(list, building);
					}
				}
			}
			MapBuilder singleton2 = MapBuilder.Singleton;
			if ((Object)(object)singleton2 == (Object)null)
			{
				return list;
			}
			AddProtectedObject(list, singleton2.town, 48f);
			AddProtectedObject(list, singleton2.quarry, 38f);
			AddProtectedObject(list, singleton2.church, 24f);
			AddProtectedObject(list, singleton2.waterTower, 12f);
			if (singleton2.mesas != null)
			{
				GameObject[] mesas = singleton2.mesas;
				foreach (GameObject scene_object in mesas)
				{
					AddProtectedObject(list, scene_object, 20f);
				}
			}
			if (singleton2.spawnedBuildings != null)
			{
				foreach (CollapsibleBuilding spawnedBuilding in singleton2.spawnedBuildings)
				{
					AddProtectedBuilding(list, spawnedBuilding);
				}
			}
			return list;
		}

		private static void AddProtectedObject(List<ProtectedArea> areas, GameObject scene_object, float radius)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)scene_object != (Object)null)
			{
				areas.Add(new ProtectedArea(scene_object.transform.position, radius));
			}
		}

		private static void AddProtectedBuilding(List<ProtectedArea> areas, CollapsibleBuilding building)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)building != (Object)null)
			{
				areas.Add(new ProtectedArea(((Component)building).transform.position, 12f));
			}
		}

		private static float ProtectedAreaBlend(Terrain terrain, List<ProtectedArea> areas, int x, int y, int width, int height)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_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_003f: 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_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)terrain == (Object)null || areas == null || areas.Count == 0)
			{
				return 1f;
			}
			Vector3 position = ((Component)terrain).transform.position;
			Vector3 size = terrain.terrainData.size;
			float num = position.x + (float)x / (float)(width - 1) * size.x;
			float num2 = position.z + (float)y / (float)(height - 1) * size.z;
			float num3 = 1f;
			foreach (ProtectedArea area in areas)
			{
				float num4 = num - area.position.x;
				float num5 = num2 - area.position.z;
				float num6 = area.radius + 12f;
				float num7 = num4 * num4 + num5 * num5;
				if (!(num7 >= num6 * num6))
				{
					float num8 = Mathf.Sqrt(num7);
					float num9 = Mathf.InverseLerp(area.radius, num6, num8);
					num9 = num9 * num9 * (3f - 2f * num9);
					num3 = Mathf.Min(num3, num9);
				}
			}
			return num3;
		}

		private static void AlignFutureLayoutSpawns(Terrain terrain, float[,] previous_base, float[,] new_base)
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: 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_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: 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_0133: Unknown result type (might be due to invalid IL or missing references)
			//IL_0138: Unknown result type (might be due to invalid IL or missing references)
			//IL_013d: Unknown result type (might be due to invalid IL or missing references)
			MapLayoutPrefabLoader singleton = MapLayoutPrefabLoader.Singleton;
			MapLayout val = (((Object)(object)singleton != (Object)null) ? singleton.activeMapLayout : null);
			if ((Object)(object)terrain == (Object)null || (Object)(object)val == (Object)null || previous_base == null || new_base == null)
			{
				return;
			}
			Vector3 bones_POS = val.bones_POS;
			val.bones_POS = ShiftPosition(terrain, previous_base, new_base, val.bones_POS);
			MapBuilder singleton2 = MapBuilder.Singleton;
			if ((Object)(object)singleton2 != (Object)null && (Object)(object)singleton2.conductorBones != (Object)null && Mathf.Abs(singleton2.conductorBones.transform.position.x - bones_POS.x) < 0.1f && Mathf.Abs(singleton2.conductorBones.transform.position.z - bones_POS.z) < 0.1f)
			{
				Vector3 position = singleton2.conductorBones.transform.position;
				position.y += val.bones_POS.y - bones_POS.y;
				singleton2.conductorBones.transform.position = position;
			}
			if (val.builderSetObjectSpawners == null)
			{
				return;
			}
			foreach (MapLayout_BuilderSetObject_Spawner builderSetObjectSpawner in val.builderSetObjectSpawners)
			{
				if ((Object)(object)builderSetObjectSpawner != (Object)null)
				{
					builderSetObjectSpawner.pos = ShiftPosition(terrain, previous_base, new_base, builderSetObjectSpawner.pos);
				}
			}
		}

		private static Vector3 ShiftPosition(Terrain terrain, float[,] previous_base, float[,] new_base, Vector3 position)
		{
			//IL_0002: 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_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)
			float num = SampleNormalizedHeight(terrain, previous_base, position);
			float num2 = SampleNormalizedHeight(terrain, new_base, position);
			position.y += (num2 - num) * terrain.terrainData.size.y;
			return position;
		}

		private static float SampleNormalizedHeight(Terrain terrain, float[,] heights, Vector3 world_position)
		{
			//IL_0016: 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_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)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: 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)
			//IL_0049: 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)
			int length = heights.GetLength(0);
			int length2 = heights.GetLength(1);
			Vector3 position = ((Component)terrain).transform.position;
			Vector3 size = terrain.terrainData.size;
			float num = Mathf.Clamp01((world_position.x - position.x) / size.x);
			float num2 = Mathf.Clamp01((world_position.z - position.z) / size.z);
			float num3 = num * (float)(length2 - 1);
			float num4 = num2 * (float)(length - 1);
			int num5 = Mathf.FloorToInt(num3);
			int num6 = Mathf.FloorToInt(num4);
			int num7 = Mathf.Min(length2 - 1, num5 + 1);
			int num8 = Mathf.Min(length - 1, num6 + 1);
			float num9 = num3 - (float)num5;
			float num10 = num4 - (float)num6;
			float num11 = Mathf.Lerp(heights[num6, num5], heights[num6, num7], num9);
			float num12 = Mathf.Lerp(heights[num8, num5], heights[num8, num7], num9);
			return Mathf.Lerp(num11, num12, num10);
		}

		private static float[,] SmoothHeightmap(float[,] source)
		{
			int length = source.GetLength(0);
			int length2 = source.GetLength(1);
			float[,] array = new float[length, length2];
			float[,] array2 = new float[length, length2];
			for (int i = 0; i < length; i++)
			{
				for (int j = 0; j < length2; j++)
				{
					int num = Mathf.Max(0, j - 2);
					int num2 = Mathf.Max(0, j - 1);
					int num3 = Mathf.Min(length2 - 1, j + 1);
					int num4 = Mathf.Min(length2 - 1, j + 2);
					array[i, j] = (source[i, num] + source[i, num2] * 4f + source[i, j] * 6f + source[i, num3] * 4f + source[i, num4]) / 16f;
				}
			}
			for (int k = 0; k < length; k++)
			{
				int num5 = Mathf.Max(0, k - 2);
				int num6 = Mathf.Max(0, k - 1);
				int num7 = Mathf.Min(length - 1, k + 1);
				int num8 = Mathf.Min(length - 1, k + 2);
				for (int l = 0; l < length2; l++)
				{
					array2[k, l] = (array[num5, l] + array[num6, l] * 4f + array[k, l] * 6f + array[num7, l] * 4f + array[num8, l]) / 16f;
				}
			}
			return array2;
		}

		private static float BorderBlend(int x, int y, int width, int height)
		{
			float num = Mathf.Clamp01((float)Math.Min(Math.Min(x, width - 1 - x), Math.Min(y, height - 1 - y)) / 16f);
			return num * num * (3f - 2f * num);
		}

		private static bool IsTutorial()
		{
			MenuToGameBridger singleton = MenuToGameBridger.Singleton;
			if ((Object)(object)singleton != (Object)null)
			{
				if (!singleton.isWormTutorial)
				{
					return singleton.isPardnerTutorial;
				}
				return true;
			}
			return false;
		}

		private static bool IsMapAuthoring()
		{
			MapLayoutPrefabCreator singleton = MapLayoutPrefabCreator.Singleton;
			MapLayoutPrefabLoader singleton2 = MapLayoutPrefabLoader.Singleton;
			if ((Object)(object)singleton != (Object)null && ((Component)singleton).gameObject.activeInHierarchy)
			{
				if (!((Object)(object)singleton2 == (Object)null))
				{
					return (Object)(object)singleton2.activeMapLayout == (Object)null;
				}
				return true;
			}
			return false;
		}
	}
	public static class TerrainSettings
	{
		public const byte DEFAULT_HILLINESS = 20;

		public const byte DEFAULT_SMOOTHNESS = 0;

		public const byte DEFAULT_ELEVATION = 20;

		public const byte DEFAULT_GROUND_CLUTTER = 20;

		public static bool enabled;

		public static byte hilliness = 20;

		public static byte smoothness = 0;

		public static byte elevation = 20;

		public static byte ground_clutter = 20;

		public static bool HasNonVanillaEffect
		{
			get
			{
				if (enabled)
				{
					if (hilliness == 20 && smoothness == 0)
					{
						return elevation != 20;
					}
					return true;
				}
				return false;
			}
		}

		public static bool HasGroundClutterEffect
		{
			get
			{
				if (enabled)
				{
					return ground_clutter != 20;
				}
				return false;
			}
		}

		public static bool Apply(bool new_enabled, byte new_hilliness, byte new_smoothness, byte new_elevation, byte new_ground_clutter)
		{
			new_hilliness = TerrainSettingsRules.ClampHilliness(new_hilliness);
			new_smoothness = TerrainSettingsRules.ClampSmoothness(new_smoothness);
			new_elevation = TerrainSettingsRules.ClampElevation(new_elevation);
			new_ground_clutter = TerrainSettingsRules.ClampGroundClutter(new_ground_clutter);
			bool result = enabled != new_enabled || hilliness != new_hilliness || smoothness != new_smoothness || elevation != new_elevation || ground_clutter != new_ground_clutter;
			enabled = new_enabled;
			hilliness = new_hilliness;
			smoothness = new_smoothness;
			elevation = new_elevation;
			ground_clutter = new_ground_clutter;
			return result;
		}

		public static bool ResetToVanilla()
		{
			return Apply(new_enabled: false, 20, 0, 20, 20);
		}
	}
	public static class TerrainSettingsRules
	{
		public const byte MULTIPLIER_UNITS_PER_ONE = 20;

		public const byte MAX_HILLINESS = 200;

		public const byte MAX_SMOOTHNESS = 16;

		public const byte MAX_ELEVATION = 200;

		public const byte MAX_GROUND_CLUTTER = 200;

		public static byte ClampHilliness(int value)
		{
			return ClampByte(value, 200);
		}

		public static byte ClampSmoothness(int value)
		{
			return ClampByte(value, 16);
		}

		public static byte ClampElevation(int value)
		{
			return ClampByte(value, 200);
		}

		public static byte ClampGroundClutter(int value)
		{
			return ClampByte(value, 200);
		}

		public static float HillinessMultiplier(byte value)
		{
			return (float)(int)ClampHilliness(value) / 20f;
		}

		public static int AdditionalSmoothingPasses(byte value)
		{
			return ClampSmoothness(value);
		}

		public static float ElevationMultiplier(byte value)
		{
			return (float)(int)ClampElevation(value) / 20f;
		}

		public static float GroundClutterMultiplier(byte value)
		{
			return (float)(int)ClampGroundClutter(value) / 20f;
		}

		public static string HillinessName(byte value)
		{
			return MultiplierName(ClampHilliness(value));
		}

		public static string SmoothnessName(byte value)
		{
			byte b = ClampSmoothness(value);
			if (b != 1)
			{
				return $"{b} passes";
			}
			return "1 pass";
		}

		public static string ElevationName(byte value)
		{
			return MultiplierName(ClampElevation(value));
		}

		public static string GroundClutterName(byte value)
		{
			return MultiplierName(ClampGroundClutter(value));
		}

		public static byte MultiplierToUnits(float value)
		{
			return ClampByte((int)Math.Round(value * 20f, MidpointRounding.AwayFromZero), 200);
		}

		private static string MultiplierName(byte value)
		{
			return ((float)(int)value / 20f).ToString("0.00", CultureInfo.InvariantCulture) + "x";
		}

		private static byte ClampByte(int value, byte maximum)
		{
			if (value <= 0)
			{
				return 0;
			}
			if (value >= maximum)
			{
				return maximum;
			}
			return (byte)value;
		}
	}
	public sealed class TerrainSettingsSync : MonoBehaviour
	{
		private const string REQUEST_MESSAGE = "com.wormtown.terrainsettings.request.v6";

		private const string SNAPSHOT_MESSAGE = "com.wormtown.terrainsettings.snapshot.v6";

		private const string ACK_MESSAGE = "com.wormtown.terrainsettings.ack.v6";

		private const string SERVICE_OBJECT_NAME = "Wormtown Terrain Settings Service";

		private const byte PROTOCOL_VERSION = 6;

		private const byte TERRAIN_ALGORITHM_VERSION = 10;

		private const int COMPATIBILITY_HEADER_SIZE = 5;

		private const int REQUEST_SIZE = 5;

		private const int SNAPSHOT_SIZE = 12;

		private const int ACK_SIZE = 7;

		private const float REQUEST_RETRY_INITIAL_SECONDS = 0.5f;

		private const float REQUEST_RETRY_MAX_SECONDS = 5f;

		private const float SNAPSHOT_RETRY_SECONDS = 1f;

		private const float LATE_JOIN_MAP_WAIT_SECONDS = 5f;

		private const float LATE_JOIN_COMPATIBILITY_GRACE_SECONDS = 3f;

		private static TerrainSettingsSync instance;

		private static int ui_revision;

		private readonly HashSet<ulong> modded_clients = new HashSet<ulong>();

		private readonly Dictionary<ulong, ushort> acknowledged_revisions = new Dictionary<ulong, ushort>();

		private readonly Dictionary<ulong, float> compatibility_deadlines = new Dictionary<ulong, float>();

		private NetworkManager network_manager;

		private bool registered_as_host;

		private bool has_host_snapshot;

		private bool terrain_application_failed;

		private ushort active_revision;

		private float next_request_time;

		private float request_retry_seconds;

		private float next_snapshot_retry_time;

		private float allow_vanilla_late_join_time;

		public static int UiRevision => ui_revision;

		public static ushort ActiveRevision
		{
			get
			{
				if (!((Object)(object)instance != (Object)null))
				{
					return 0;
				}
				return instance.active_revision;
			}
		}

		public static bool IsLocalHost
		{
			get
			{
				NetworkManager singleton = NetworkManager.Singleton;
				if ((Object)(object)singleton != (Object)null)
				{
					return singleton.IsHost;
				}
				return false;
			}
		}

		public static bool CanEdit
		{
			get
			{
				//IL_0020: Unknown result type (might be due to invalid IL or missing references)
				//IL_0026: Invalid comparison between Unknown and I4
				if (!IsLocalHost || !Plugin.CriticalPatchesReady)
				{
					return false;
				}
				GameManager singleton = GameManager.Singleton;
				if (!((Object)(object)singleton == (Object)null))
				{
					return (int)singleton.gameState == 0;
				}
				return true;
			}
		}

		public static bool CanApplyTerrain
		{
			get
			{
				if (!Plugin.CriticalPatchesReady)
				{
					return false;
				}
				NetworkManager singleton = NetworkManager.Singleton;
				if ((Object)(object)singleton == (Object)null || !singleton.IsListening || singleton.IsHost)
				{
					return true;
				}
				if ((Object)(object)instance != (Object)null)
				{
					return instance.has_host_snapshot;
				}
				return false;
			}
		}

		public static bool CanApplyGameplayPatches
		{
			get
			{
				if (!Plugin.CriticalPatchesReady)
				{
					return false;
				}
				NetworkManager singleton = NetworkManager.Singleton;
				if ((Object)(object)singleton == (Object)null || !singleton.IsListening || (Object)(object)instance == (Object)null || (Object)(object)instance.network_manager != (Object)(object)singleton)
				{
					return false;
				}
				if (!singleton.IsHost)
				{
					return instance.has_host_snapshot;
				}
				if (instance.registered_as_host)
				{
					return !instance.terrain_application_failed;
				}
				return false;
			}
		}

		public static bool ShouldDeferGameplayPatches
		{
			get
			{
				NetworkManager singleton = NetworkManager.Singleton;
				if (Plugin.CriticalPatchesReady && (Object)(object)singleton != (Object)null && singleton.IsListening && !singleton.IsHost && (Object)(object)instance != (Object)null && (Object)(object)instance.network_manager == (Object)(object)singleton && !instance.has_host_snapshot)
				{
					return Time.unscaledTime < instance.allow_vanilla_late_join_time;
				}
				return false;
			}
		}

		public static int MissingClientCount
		{
			get
			{
				if ((Object)(object)instance == (Object)null || !instance.registered_as_host)
				{
					return 0;
				}
				return instance.CountMissingClients();
			}
		}

		public static bool AreAllConnectedClientsReady
		{
			get
			{
				if ((Object)(object)instance == (Object)null || !instance.registered_as_host)
				{
					return false;
				}
				return instance.CountMissingClients() == 0;
			}
		}

		public static bool CanLoadLateJoinMap
		{
			get
			{
				if (!Plugin.CriticalPatchesReady)
				{
					return true;
				}
				NetworkManager singleton = NetworkManager.Singleton;
				GameManager singleton2 = GameManager.Singleton;
				if ((Object)(object)singleton == (Object)null || singleton.IsHost || (Object)(object)singleton2 == (Object)null || !singleton2.weJoinedInProg)
				{
					return true;
				}
				if ((Object)(object)instance == (Object)null || (Object)(object)instance.network_manager != (Object)(object)singleton)
				{
					return false;
				}
				if (!instance.has_host_snapshot)
				{
					return Time.unscaledTime >= instance.allow_vanilla_late_join_time;
				}
				return true;
			}
		}

		public static string PanelTitle
		{
			get
			{
				if (!Plugin.CriticalPatchesReady)
				{
					return "Terrain Settings (Unavailable)";
				}
				if ((Object)(object)instance != (Object)null && instance.terrain_application_failed)
				{
					return "Terrain Settings (Failed)";
				}
				if (!IsLocalHost && (Object)(object)instance != (Object)null && !instance.has_host_snapshot)
				{
					return "Terrain Settings (Syncing)";
				}
				int missingClientCount = MissingClientCount;
				if (missingClientCount <= 0)
				{
					return "Terrain Settings";
				}
				return $"Terrain Settings ({missingClientCount} Missing)";
			}
		}

		public static bool IsSoloTestLobby(LobbyManager lobby_manager)
		{
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			NetworkManager singleton = NetworkManager.Singleton;
			MenuToGameBridger singleton2 = MenuToGameBridger.Singleton;
			GameManager singleton3 = GameManager.Singleton;
			int num = (((Object)(object)lobby_manager != (Object)null) ? ((lobby_manager.humanTeam?.Count ?? 0) + (lobby_manager.wormTeam?.Count ?? 0)) : 0);
			if ((Object)(object)singleton != (Object)null && singleton.IsHost && singleton.ConnectedClients.Count == 1 && (Object)(object)singleton2 != (Object)null && !singleton2.isQuickplayMatch && (Object)(object)singleton3 != (Object)null && (int)singleton3.gameState == 0)
			{
				return num == 1;
			}
			return false;
		}

		public static bool EnsureAvailable()
		{
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Expected O, but got Unknown
			if ((Object)(object)instance != (Object)null)
			{
				return true;
			}
			try
			{
				TerrainSettingsSync terrainSettingsSync = Object.FindObjectOfType<TerrainSettingsSync>();
				GameObject val = (((Object)(object)terrainSettingsSync != (Object)null) ? ((Component)terrainSettingsSync).gameObject : GameObject.Find("Wormtown Terrain Settings Service"));
				if ((Object)(object)val == (Object)null)
				{
					val = new GameObject("Wormtown Terrain Settings Service");
					Object.DontDestroyOnLoad((Object)(object)val);
				}
				terrainSettingsSync = val.GetComponent<TerrainSettingsSync>();
				if ((Object)(object)terrainSettingsSync == (Object)null)
				{
					terrainSettingsSync = val.AddComponent<TerrainSettingsSync>();
				}
				instance = terrainSettingsSync;
				return true;
			}
			catch (Exception ex)
			{
				Plugin.log.LogWarning((object)("terrain sync creation failed: " + ex.Message));
				return false;
			}
		}

		private void Awake()
		{
			if ((Object)(object)instance != (Object)null && (Object)(object)instance != (Object)(object)this)
			{
				Object.Destroy((Object)(object)this);
			}
			else
			{
				instance = this;
			}
		}

		public static void SetEnabled(bool value)
		{
			if (CanEdit && TerrainSettings.Apply(value, TerrainSettings.hilliness, TerrainSettings.smoothness, TerrainSettings.elevation, TerrainSettings.ground_clutter))
			{
				CommitHostChange();
			}
		}

		public static void SetHilliness(byte value)
		{
			if (CanEdit && TerrainSettings.Apply(TerrainSettings.enabled, value, TerrainSettings.smoothness, TerrainSettings.elevation, TerrainSettings.ground_clutter))
			{
				CommitHostChange();
			}
		}

		public static void SetSmoothness(byte value)
		{
			if (CanEdit && TerrainSettings.Apply(TerrainSettings.enabled, TerrainSettings.hilliness, value, TerrainSettings.elevation, TerrainSettings.ground_clutter))
			{
				CommitHostChange();
			}
		}

		public static void SetElevation(byte value)
		{
			if (CanEdit && TerrainSettings.Apply(TerrainSettings.enabled, TerrainSettings.hilliness, TerrainSettings.smoothness, value, TerrainSettings.ground_clutter))
			{
				CommitHostChange();
			}
		}

		public static void SetGroundClutter(byte value)
		{
			if (CanEdit && TerrainSettings.Apply(TerrainSettings.enabled, TerrainSettings.hilliness, TerrainSettings.smoothness, TerrainSettings.elevation, value))
			{
				CommitHostChange();
			}
		}

		public static void ResendCurrentSnapshot()
		{
			if ((Object)(object)instance != (Object)null && instance.registered_as_host)
			{
				instance.SendSnapshotToModdedClients();
			}
		}

		public static void ReportTerrainApplicationFailure(string reason)
		{
			Plugin.log.LogError((object)("synchronized terrain application failed: " + reason));
			NetworkManager singleton = NetworkManager.Singleton;
			if ((Object)(object)instance != (Object)null)
			{
				instance.terrain_application_failed = true;
				TouchUi();
			}
			if ((Object)(object)singleton == (Object)null || !singleton.IsListening)
			{
				return;
			}
			try
			{
				if (singleton.IsHost)
				{
					List<ulong> list = new List<ulong>();
					foreach (ulong key in singleton.ConnectedClients.Keys)
					{
						if (key != singleton.LocalClientId)
						{
							list.Add(key);
						}
					}
					{
						foreach (ulong item in list)
						{
							singleton.DisconnectClient(item);
						}
						return;
					}
				}
				if (singleton.IsConnectedClient)
				{
					singleton.Shutdown(false);
				}
			}
			catch (Exception ex)
			{
				Plugin.log.LogError((object)("terrain failure disconnect failed: " + ex.Message));
			}
		}

		private void Update()
		{
			NetworkManager singleton = NetworkManager.Singleton;
			if ((Object)(object)network_manager != (Object)null && ((Object)(object)network_manager != (Object)(object)singleton || !network_manager.IsListening))
			{
				UnregisterNetworkManager();
			}
			if ((Object)(object)network_manager == (Object)null && (Object)(object)singleton != (Object)null && singleton.IsListening)
			{
				RegisterNetworkManager(singleton);
			}
			if ((Object)(object)network_manager == (Object)null)
			{
				return;
			}
			if (registered_as_host)
			{
				if (Time.unscaledTime >= next_snapshot_retry_ti