Decompiled source of CityPlanner v1.1.0

CityPlanner.dll

Decompiled 16 hours ago
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Core.Logging.Interpolation;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using HarmonyLib;
using Il2CppInterop.Runtime;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppSystem;
using Il2CppSystem.Collections.Generic;
using Il2CppSystem.Reflection;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("CityPlanner")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.0+babed5d3f1ebe844f936f6925836e8052d0d437e")]
[assembly: AssemblyProduct("CityPlanner")]
[assembly: AssemblyTitle("CityPlanner")]
[assembly: AssemblyVersion("1.1.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

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

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

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace CityPlanner
{
	internal class PlannedDistrict
	{
		public char Key;

		public string Name;

		public string Type;

		public LandValue LandValue;

		public Density Density;
	}
	internal enum StreetKind
	{
		Alley = 1,
		Back,
		Main
	}
	internal readonly record struct Gap(bool Vertical, int X, int Y);
	internal class PlannedTile
	{
		public PlannedDistrict District;

		public string Building;

		public Direction? Facing;
	}
	internal class Plan
	{
		public int Width;

		public int Height;

		public float Population = 1f;

		public readonly Dictionary<char, PlannedDistrict> Districts = new Dictionary<char, PlannedDistrict>();

		public readonly Dictionary<(int x, int y), PlannedTile> Tiles = new Dictionary<(int, int), PlannedTile>();

		public readonly Dictionary<Gap, StreetKind> Streets = new Dictionary<Gap, StreetKind>();

		public static Plan Load(string path, out string error)
		{
			error = null;
			if (!File.Exists(path))
			{
				error = "no plan file at " + path;
				return null;
			}
			return Parse(File.ReadAllLines(path), out error);
		}

		public static Plan Parse(IEnumerable<string> lines, out string error)
		{
			//IL_0219: Unknown result type (might be due to invalid IL or missing references)
			//IL_021e: Unknown result type (might be due to invalid IL or missing references)
			//IL_022e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0233: Unknown result type (might be due to invalid IL or missing references)
			error = null;
			Plan plan = new Plan();
			Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
			List<string[]> list = new List<string[]>();
			List<string> list2 = new List<string>();
			string text = "";
			foreach (string line in lines)
			{
				string text2 = line.Trim();
				if (text2.Length == 0 || text2.StartsWith("#"))
				{
					continue;
				}
				if (text2.StartsWith("[") && text2.EndsWith("]"))
				{
					string text3 = text2;
					text = text3.Substring(1, text3.Length - 1 - 1).Trim().ToLowerInvariant();
					continue;
				}
				switch (text)
				{
				case "city":
					var (text4, text5) = KeyValue(text2);
					if (text4.Equals("Size", StringComparison.OrdinalIgnoreCase))
					{
						string[] array2 = text5.ToLowerInvariant().Split('x');
						int.TryParse(array2[0], out plan.Width);
						if (array2.Length > 1)
						{
							int.TryParse(array2[1], out plan.Height);
						}
					}
					else if (text4.Equals("Population", StringComparison.OrdinalIgnoreCase))
					{
						float.TryParse(text5, NumberStyles.Float, CultureInfo.InvariantCulture, out plan.Population);
					}
					break;
				case "districts":
				{
					(string, string) tuple2 = KeyValue(text2);
					string item3 = tuple2.Item1;
					string[] array = tuple2.Item2.Split('|');
					if (item3.Length != 1 || array.Length < 4)
					{
						throw new FormatException("bad district line: " + text2);
					}
					plan.Districts[item3[0]] = new PlannedDistrict
					{
						Key = item3[0],
						Name = array[0].Trim(),
						Type = array[1].Trim(),
						LandValue = Enum.Parse<LandValue>(array[2].Trim(), ignoreCase: true),
						Density = Enum.Parse<Density>(array[3].Trim(), ignoreCase: true)
					};
					break;
				}
				case "buildings":
				{
					(string, string) tuple = KeyValue(text2);
					string item = tuple.Item1;
					string item2 = tuple.Item2;
					dictionary[item] = item2;
					break;
				}
				case "map":
					list.Add(text2.Split(new char[2] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries));
					break;
				case "streets":
					list2.Add(text2);
					break;
				}
			}
			if (plan.Width < 5 || plan.Height < 5)
			{
				error = "missing or bad Size";
				return null;
			}
			if (list.Count != plan.Height - 2)
			{
				error = $"map has {list.Count} rows, a {plan.Width}x{plan.Height} city needs {plan.Height - 2}";
				return null;
			}
			for (int i = 0; i < list.Count; i++)
			{
				if (list[i].Length != plan.Width - 2)
				{
					error = $"map row {i + 1} has {list[i].Length} cells, needs {plan.Width - 2}";
					return null;
				}
				for (int j = 0; j < list[i].Length; j++)
				{
					string[] array3 = list[i][j].Split(':');
					if (array3.Length != 2 || array3[0].Length != 1 || !plan.Districts.TryGetValue(array3[0][0], out var value))
					{
						error = $"map row {i + 1}, column {j + 1}: '{list[i][j]}' isn't District:Building with a known district";
						return null;
					}
					string text6 = array3[1];
					Direction? facing = null;
					int num = text6.IndexOf('/');
					if (num >= 0)
					{
						string text7 = text6;
						int num2 = num + 1;
						facing = text7.Substring(num2, text7.Length - num2).ToUpperInvariant() switch
						{
							"N" => (Direction)2, 
							"E" => (Direction)3, 
							"S" => (Direction)0, 
							"W" => (Direction)1, 
							_ => null, 
						};
						if (!facing.HasValue)
						{
							error = $"map row {i + 1}, column {j + 1}: facing must be /N, /E, /S or /W in '{list[i][j]}'";
							return null;
						}
						text6 = text6.Substring(0, num);
					}
					string value2 = null;
					if (text6 != "?" && !dictionary.TryGetValue(text6, out value2))
					{
						error = $"map row {i + 1}, column {j + 1}: unknown building code '{text6}'";
						return null;
					}
					plan.Tiles[(j + 1, plan.Height - 2 - i)] = new PlannedTile
					{
						District = value,
						Building = value2,
						Facing = facing
					};
				}
			}
			foreach (string item4 in list2)
			{
				if (!plan.ParseStreetLine(item4, out error))
				{
					error = "[Streets] '" + item4 + "': " + error;
					return null;
				}
			}
			return plan;
		}

		private bool ParseStreetLine(string line, out string error)
		{
			error = null;
			(string, string) tuple = KeyValue(line);
			var (text, _) = tuple;
			StreetKind value;
			switch (tuple.Item2.ToLowerInvariant())
			{
			case "main":
			case "road":
			case "street":
				value = StreetKind.Main;
				break;
			case "back":
			case "backstreet":
			case "back street":
				value = StreetKind.Back;
				break;
			case "alley":
				value = StreetKind.Alley;
				break;
			default:
				error = "the type must be main, back or alley";
				return false;
			}
			(int, int)? tuple3 = null;
			(int, int)? tuple4 = null;
			string text2 = null;
			string[] array = text.Split(new char[2] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
			foreach (string text3 in array)
			{
				string text4 = text3.ToUpperInvariant();
				if (text4.StartsWith("R") && TryRange(text4, 'R', out var range))
				{
					tuple3 = range;
					continue;
				}
				if (text4.StartsWith("C") && TryRange(text4, 'C', out var range2))
				{
					tuple4 = range2;
					continue;
				}
				bool flag;
				switch (text4)
				{
				case "N":
				case "E":
				case "S":
				case "W":
				case "EDGE":
				case "INSIDE":
					flag = true;
					break;
				default:
					flag = false;
					break;
				}
				if (flag)
				{
					text2 = text4;
					continue;
				}
				error = "don't understand '" + text3 + "' (use R<row>, C<column> and N, E, S, W, edge or inside)";
				return false;
			}
			if (!tuple3.HasValue || !tuple4.HasValue || text2 == null)
			{
				error = "needs rows (R3 or R3-R5), columns (C1 or C1-C4) and a side (N, E, S, W, edge or inside)";
				return false;
			}
			var (num, num2) = tuple3.Value;
			var (num3, num4) = tuple4.Value;
			if (num < 1 || num2 > Height - 2 || num3 < 1 || num4 > Width - 2)
			{
				error = $"outside the map (rows 1-{Height - 2}, columns 1-{Width - 2})";
				return false;
			}
			for (int j = num; j <= num2; j++)
			{
				for (int k = num3; k <= num4; k++)
				{
					array = ((text2 == "EDGE") ? new string[4]
					{
						(j == num) ? "N" : null,
						(j == num2) ? "S" : null,
						(k == num3) ? "W" : null,
						(k == num4) ? "E" : null
					} : ((!(text2 == "INSIDE")) ? new string[1] { text2 } : new string[2]
					{
						(j < num2) ? "S" : null,
						(k < num4) ? "E" : null
					}));
					array = array;
					foreach (string text5 in array)
					{
						if (text5 != null)
						{
							Streets[GapBeside(j, k, text5)] = value;
						}
					}
				}
			}
			return true;
		}

		public Gap GapBeside(int row, int col, string side)
		{
			int num = Height - 1 - row;
			return side switch
			{
				"N" => new Gap(Vertical: false, col, num), 
				"S" => new Gap(Vertical: false, col, num - 1), 
				"E" => new Gap(Vertical: true, col, num), 
				_ => new Gap(Vertical: true, col - 1, num), 
			};
		}

		private static bool TryRange(string token, char letter, out (int, int) range)
		{
			range = default((int, int));
			string[] array = token.Split('-');
			if (array.Length > 2)
			{
				return false;
			}
			if (!int.TryParse(array[0].TrimStart(letter), out var result))
			{
				return false;
			}
			int result2 = result;
			if (array.Length == 2 && !int.TryParse(array[1].TrimStart(letter), out result2))
			{
				return false;
			}
			range = (Math.Min(result, result2), Math.Max(result, result2));
			return true;
		}

		private static (string, string) KeyValue(string line)
		{
			int num = line.IndexOf('=');
			if (num >= 0)
			{
				string item = line.Substring(0, num).Trim();
				int num2 = num + 1;
				return (item, line.Substring(num2, line.Length - num2).Trim());
			}
			return (line.Trim(), "");
		}

		public PlannedTile Nearest(int x, int y)
		{
			return Tiles[(Math.Clamp(x, 1, Width - 2), Math.Clamp(y, 1, Height - 2))];
		}
	}
	internal static class PlannerLink
	{
		private static int port;

		private static string pagePath;

		private static readonly ConcurrentQueue<Action> mainThread = new ConcurrentQueue<Action>();

		internal static volatile string Status = "{\"ok\":true,\"state\":\"starting\"}";

		internal static volatile bool Generating;

		internal static string PageUrl => $"http://127.0.0.1:{port}/";

		public static void Start(int listenPort)
		{
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Expected O, but got Unknown
			port = listenPort;
			pagePath = FindPage();
			try
			{
				TcpListener listener = new TcpListener(IPAddress.Loopback, port);
				listener.Start();
				Thread thread = new Thread((ThreadStart)delegate
				{
					Listen(listener);
				});
				thread.IsBackground = true;
				thread.Name = "CityPlannerLink";
				thread.Start();
				Plugin.Logger.LogInfo((object)("Planner page link ready at " + PageUrl + " (this PC only)" + ((pagePath == null) ? "; city-planner.html not found, so the page can't be opened from the game" : "")));
			}
			catch (Exception ex)
			{
				ManualLogSource logger = Plugin.Logger;
				bool flag = default(bool);
				BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(133, 2, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Planner page link couldn't use port ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(port);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" (");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("). Set another port under [Planner] in the config. Plans can still be pasted into cityplan.txt.");
				}
				logger.LogWarning(val);
			}
		}

		private static string FindPage()
		{
			try
			{
				string text = Directory.GetFiles(Paths.PluginPath, "CityPlanner.dll", SearchOption.AllDirectories).FirstOrDefault();
				return Directory.GetFiles((text == null) ? Paths.PluginPath : Path.GetDirectoryName(text), "city-planner.html", SearchOption.AllDirectories).FirstOrDefault() ?? Directory.GetFiles(Paths.PluginPath, "city-planner.html", SearchOption.AllDirectories).FirstOrDefault();
			}
			catch
			{
				return null;
			}
		}

		public static void OpenPage()
		{
			Plugin.Logger.LogInfo((object)("Opening the planner page: " + PageUrl));
			Application.OpenURL(PageUrl);
		}

		private static void Listen(TcpListener listener)
		{
			while (true)
			{
				try
				{
					TcpClient client = listener.AcceptTcpClient();
					ThreadPool.QueueUserWorkItem(delegate
					{
						Serve(client);
					});
				}
				catch (Exception ex)
				{
					Plugin.Logger.LogWarning((object)("Planner page link stopped: " + ex.Message));
					break;
				}
			}
		}

		private static void Serve(TcpClient client)
		{
			using (client)
			{
				try
				{
					int receiveTimeout = (client.SendTimeout = 5000);
					client.ReceiveTimeout = receiveTimeout;
					NetworkStream stream = client.GetStream();
					if (!ReadRequest(stream, out var method, out var target, out var headers, out var body))
					{
						return;
					}
					string value;
					string host = (headers.TryGetValue("host", out value) ? value : "");
					string value2;
					string text = (headers.TryGetValue("origin", out value2) ? value2 : null);
					string[] source = new string[2]
					{
						$"http://127.0.0.1:{port}",
						$"http://localhost:{port}"
					};
					if (!source.Any((string u) => u == "http://" + host) || (text != null && text != "null" && !source.Contains(text)))
					{
						Respond(stream, 403, "text/plain", "Only the City Planner page can talk to the game.", null);
						return;
					}
					string cors = text ?? "null";
					string text2 = target.Split('?')[0];
					if (method == "OPTIONS")
					{
						Respond(stream, 204, "text/plain", "", cors);
					}
					else if (method == "GET" && (text2 == "/" || text2 == "/city-planner.html"))
					{
						if (pagePath != null && File.Exists(pagePath))
						{
							Respond(stream, 200, "text/html; charset=utf-8", File.ReadAllText(pagePath), cors);
						}
						else
						{
							Respond(stream, 404, "text/plain", "city-planner.html wasn't found in the City Planner mod folder. Reinstall the mod.", cors);
						}
					}
					else if (method == "GET" && text2 == "/plan")
					{
						string content = (File.Exists(Plugin.PlanPath) ? File.ReadAllText(Plugin.PlanPath) : "");
						Respond(stream, 200, "text/plain; charset=utf-8", content, cors);
					}
					else if (method == "GET" && text2 == "/status")
					{
						Respond(stream, 200, "application/json", Status, cors);
					}
					else if (method == "POST" && text2 == "/plan")
					{
						Respond(stream, 200, "application/json", ReceivePlan(body), cors);
					}
					else
					{
						Respond(stream, 404, "text/plain", "Not found", cors);
					}
				}
				catch (Exception ex)
				{
					Plugin.Logger.LogWarning((object)("Planner page request failed: " + ex.Message));
				}
			}
		}

		private static string ReceivePlan(string text)
		{
			Plan plan;
			string error;
			try
			{
				plan = Plan.Parse(text.Replace("\r", "").Split('\n'), out error);
			}
			catch (Exception ex)
			{
				plan = null;
				error = ex.Message;
			}
			if (plan == null)
			{
				return "{\"ok\":false,\"error\":" + Json(error ?? "the plan couldn't be read") + "}";
			}
			string text2 = Plugin.PlanPath + ".tmp";
			File.WriteAllText(text2, text);
			File.Move(text2, Plugin.PlanPath, overwrite: true);
			mainThread.Enqueue(delegate
			{
				Plugin.NoteFileWritten();
				Plugin.Use(plan, "sent from the planner page");
			});
			string value = (Generating ? "after" : "now");
			return $"{{\"ok\":true,\"size\":\"{plan.Width}x{plan.Height}\",\"blocks\":{Json(Blocks(plan))},\"label\":{Json(Sizes.Label(plan.Width, plan.Height))},\"streets\":{plan.Streets.Count},\"when\":\"{value}\"}}";
		}

		private static bool ReadRequest(NetworkStream stream, out string method, out string target, out Dictionary<string, string> headers, out string body)
		{
			method = (target = (body = null));
			headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
			MemoryStream memoryStream = new MemoryStream();
			byte[] array = new byte[4096];
			int num;
			while ((num = IndexOfBlankLine(memoryStream)) < 0)
			{
				int num2 = stream.Read(array, 0, array.Length);
				if (num2 <= 0 || memoryStream.Length > 32768)
				{
					return false;
				}
				memoryStream.Write(array, 0, num2);
			}
			byte[] array2 = memoryStream.ToArray();
			string[] array3 = Encoding.ASCII.GetString(array2, 0, num).Split("\r\n");
			string[] array4 = array3[0].Split(' ');
			if (array4.Length < 2)
			{
				return false;
			}
			method = array4[0].ToUpperInvariant();
			target = array4[1];
			foreach (string item in array3.Skip(1))
			{
				int num3 = item.IndexOf(':');
				if (num3 > 0)
				{
					Dictionary<string, string> obj = headers;
					string key = item.Substring(0, num3).Trim();
					string text = item;
					int num4 = num3 + 1;
					obj[key] = text.Substring(num4, text.Length - num4).Trim();
				}
			}
			string value;
			int result;
			int num5 = ((headers.TryGetValue("content-length", out value) && int.TryParse(value, out result)) ? result : 0);
			if (num5 < 0 || num5 > 524288)
			{
				return false;
			}
			MemoryStream memoryStream2 = new MemoryStream();
			memoryStream2.Write(array2, num + 4, array2.Length - num - 4);
			while (memoryStream2.Length < num5)
			{
				int num6 = stream.Read(array, 0, array.Length);
				if (num6 <= 0)
				{
					return false;
				}
				memoryStream2.Write(array, 0, num6);
			}
			body = Encoding.UTF8.GetString(memoryStream2.ToArray(), 0, num5);
			return true;
		}

		private static int IndexOfBlankLine(MemoryStream ms)
		{
			byte[] buffer = ms.GetBuffer();
			for (int i = 0; i + 3 < ms.Length; i++)
			{
				if (buffer[i] == 13 && buffer[i + 1] == 10 && buffer[i + 2] == 13 && buffer[i + 3] == 10)
				{
					return i;
				}
			}
			return -1;
		}

		private static void Respond(NetworkStream stream, int code, string type, string content, string cors)
		{
			byte[] bytes = Encoding.UTF8.GetBytes(content);
			string value = code switch
			{
				200 => "OK", 
				204 => "No Content", 
				403 => "Forbidden", 
				_ => "Not Found", 
			};
			string text = $"HTTP/1.1 {code} {value}\r\nContent-Type: {type}\r\nContent-Length: {bytes.Length}\r\nCache-Control: no-store\r\nConnection: close\r\n";
			if (cors != null)
			{
				text = text + "Access-Control-Allow-Origin: " + cors + "\r\nAccess-Control-Allow-Methods: GET, POST\r\nAccess-Control-Allow-Headers: Content-Type\r\nAccess-Control-Allow-Private-Network: true\r\n";
			}
			byte[] bytes2 = Encoding.ASCII.GetBytes(text + "\r\n");
			stream.Write(bytes2, 0, bytes2.Length);
			stream.Write(bytes, 0, bytes.Length);
		}

		internal static string Blocks(Plan plan)
		{
			return $"{plan.Width - 2} × {plan.Height - 2} blocks";
		}

		internal static string Json(string s)
		{
			return "\"" + s.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\n", " ")
				.Replace("\r", "") + "\"";
		}

		internal static bool TryDequeue(out Action action)
		{
			return mainThread.TryDequeue(out action);
		}
	}
	public class PlannerPump : MonoBehaviour
	{
		private static float nextCheck;

		private static GameObject button;

		public PlannerPump(IntPtr ptr)
			: base(ptr)
		{
		}

		public void Update()
		{
			UpdateGenerating();
			Action action;
			while (!PlannerLink.Generating && PlannerLink.TryDequeue(out action))
			{
				try
				{
					action();
				}
				catch (Exception ex)
				{
					Plugin.Logger.LogError((object)("Applying a plan from the planner page failed: " + ex));
				}
			}
			if (!(Time.unscaledTime < nextCheck))
			{
				nextCheck = Time.unscaledTime + 0.5f;
				UpdateStatus();
				if ((Object)(object)button == (Object)null)
				{
					button = MenuButton.TryCreate();
				}
			}
		}

		private static void UpdateGenerating()
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Invalid comparison between Unknown and I4
			if (PlannerLink.Generating)
			{
				CityConstructor instance = CityConstructor.Instance;
				if ((Object)(object)instance == (Object)null || (int)instance.loadState >= 19 || ((Object)(object)SessionData.Instance != (Object)null && SessionData.Instance.startedGame))
				{
					PlannerLink.Generating = false;
				}
			}
		}

		private static void UpdateStatus()
		{
			bool flag = (Object)(object)SessionData.Instance != (Object)null && SessionData.Instance.startedGame;
			string value = (PlannerLink.Generating ? "generating" : (flag ? "playing" : "menu"));
			Plan plan = Plugin.Plan;
			string value2 = ((plan == null) ? "null" : $"{{\"size\":\"{plan.Width}x{plan.Height}\",\"blocks\":{PlannerLink.Json(PlannerLink.Blocks(plan))},\"streets\":{plan.Streets.Count}}}");
			PlannerLink.Status = $"{{\"ok\":true,\"state\":\"{value}\",\"plan\":{value2}}}";
		}
	}
	[HarmonyPatch(typeof(CityConstructor), "GenerateNewCity")]
	internal static class GeneratingFlag
	{
		private static void Prefix()
		{
			PlannerLink.Generating = true;
		}
	}
	internal static class MenuButton
	{
		public static GameObject TryCreate()
		{
			//IL_0303: Unknown result type (might be due to invalid IL or missing references)
			//IL_030a: Expected O, but got Unknown
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			//IL_012b: 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_014e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0153: Unknown result type (might be due to invalid IL or missing references)
			//IL_0162: Unknown result type (might be due to invalid IL or missing references)
			//IL_0167: Unknown result type (might be due to invalid IL or missing references)
			//IL_0170: Unknown result type (might be due to invalid IL or missing references)
			//IL_017d: 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_0188: Unknown result type (might be due to invalid IL or missing references)
			//IL_0197: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ef: Expected O, but got Unknown
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Expected O, but got Unknown
			//IL_024d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0252: Unknown result type (might be due to invalid IL or missing references)
			//IL_0274: Unknown result type (might be due to invalid IL or missing references)
			//IL_0279: Unknown result type (might be due to invalid IL or missing references)
			//IL_029b: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				MainMenuController instance = MainMenuController.Instance;
				DropdownController val = ((instance != null) ? instance.citySizeDropdown : null);
				ButtonController val2 = ((instance != null) ? instance.pasteShareCodeButton : null) ?? ((instance != null) ? instance.changeCityNameButton : null);
				if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null)
				{
					return null;
				}
				RectTransform component = ((Component)val).GetComponent<RectTransform>();
				GameObject obj = Object.Instantiate<GameObject>(((Component)val2).gameObject, ((Transform)component).parent);
				((Object)obj).name = "CityPlannerButton";
				ButtonController component2 = obj.GetComponent<ButtonController>();
				if ((Object)(object)component2 != (Object)null)
				{
					component2.SetInteractable(true);
					component2.useAutomaticText = false;
					if ((Object)(object)component2.text != (Object)null)
					{
						((TMP_Text)component2.text).text = "City Planner";
					}
					if ((Object)(object)component2.tooltip != (Object)null)
					{
						((Behaviour)component2.tooltip).enabled = false;
					}
				}
				Button component3 = obj.GetComponent<Button>();
				if ((Object)(object)component3 != (Object)null)
				{
					component3.onClick = new ButtonClickedEvent();
					((UnityEvent)component3.onClick).AddListener(DelegateSupport.ConvertDelegate<UnityAction>((Delegate)new Action(PlannerLink.OpenPage)));
				}
				RectTransform component4 = obj.GetComponent<RectTransform>();
				component4.anchorMin = component.anchorMin;
				component4.anchorMax = component.anchorMax;
				component4.pivot = component.pivot;
				Rect rect = component.rect;
				float num = ((Rect)(ref rect)).width * 0.55f;
				rect = component4.rect;
				float num2 = Mathf.Max(num, ((Rect)(ref rect)).width);
				rect = component.rect;
				component4.sizeDelta = new Vector2(num2, ((Rect)(ref rect)).height);
				Vector2 anchoredPosition = component.anchoredPosition;
				rect = component.rect;
				component4.anchoredPosition = anchoredPosition + new Vector2(((Rect)(ref rect)).width * (1f - component.pivot.x) + component4.sizeDelta.x * component4.pivot.x + 12f, 0f);
				obj.SetActive(true);
				ManualLogSource logger = Plugin.Logger;
				bool flag = default(bool);
				BepInExInfoLogInterpolatedStringHandler val3 = new BepInExInfoLogInterpolatedStringHandler(88, 6, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("City Planner button (");
					((BepInExLogInterpolatedStringHandler)val3).AppendFormatted<string>(((component2 != null) ? ((MemberInfo)((Object)component2).GetIl2CppType()).Name : null) ?? "no controller");
					((BepInExLogInterpolatedStringHandler)val3).AppendLiteral(", copied from ");
					((BepInExLogInterpolatedStringHandler)val3).AppendFormatted<string>(((Object)val2).name);
					((BepInExLogInterpolatedStringHandler)val3).AppendLiteral(") added beside the city size (size list ");
					rect = component.rect;
					((BepInExLogInterpolatedStringHandler)val3).AppendFormatted<float>(((Rect)(ref rect)).width, "0");
					((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("x");
					rect = component.rect;
					((BepInExLogInterpolatedStringHandler)val3).AppendFormatted<float>(((Rect)(ref rect)).height, "0");
					((BepInExLogInterpolatedStringHandler)val3).AppendLiteral(" at ");
					((BepInExLogInterpolatedStringHandler)val3).AppendFormatted<Vector2>(component.anchoredPosition);
					((BepInExLogInterpolatedStringHandler)val3).AppendLiteral(", in '");
					Transform parent = ((Transform)component).parent;
					((BepInExLogInterpolatedStringHandler)val3).AppendFormatted<string>((parent != null) ? ((Object)parent).name : null);
					((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("')");
				}
				logger.LogInfo(val3);
				return obj;
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogWarning((object)("Couldn't add the City Planner button: " + ex.Message));
				return new GameObject("CityPlannerButtonFailed");
			}
		}
	}
	[HarmonyPatch(typeof(ButtonController), "OnPointerClick")]
	internal static class MenuButtonClick
	{
		private static bool Prefix(ButtonController __instance, PointerEventData eventData)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)__instance == (Object)null || ((Object)((Component)__instance).gameObject).name != "CityPlannerButton")
			{
				return true;
			}
			if (eventData == null || (int)eventData.button == 0)
			{
				PlannerLink.OpenPage();
			}
			return false;
		}
	}
	[BepInPlugin("sodmods.cityplanner", "City Planner", "1.1.0")]
	public class Plugin : BasePlugin
	{
		internal static ManualLogSource Logger;

		internal static Plan Plan;

		internal static ConfigEntry<bool> HomeWealth;

		internal static ConfigEntry<string> KeepSizes;

		internal static ConfigEntry<bool> LogPerformance;

		internal static ConfigEntry<bool> StreetWealth;

		internal static ConfigEntry<string> GritClusters;

		internal static ConfigEntry<bool> StreetThemes;

		internal static ConfigEntry<bool> EdgeGates;

		internal static ConfigEntry<string> OpenAlleysIn;

		internal static ConfigEntry<int> EdgeGateReach;

		internal static ConfigEntry<LandValue> GritFreeFrom;

		internal static ConfigEntry<int> PlannerPort;

		private static DateTime planFileTime;

		internal static string PlanPath => Path.Combine(Paths.ConfigPath, "cityplan.txt");

		internal static bool Active
		{
			get
			{
				if (Plan != null && (Object)(object)CityConstructor.Instance != (Object)null && CityConstructor.Instance.generateNew && RestartSafeController.Instance.cityX == Plan.Width)
				{
					return RestartSafeController.Instance.cityY == Plan.Height;
				}
				return false;
			}
		}

		public override void Load()
		{
			//IL_01af: Unknown result type (might be due to invalid IL or missing references)
			Logger = ((BasePlugin)this).Log;
			KeepSizes = ((BasePlugin)this).Config.Bind<string>("Sizes", "Keep", "10x10,9x10", "City sizes planned so far, so cities made at those sizes still load. Planned sizes are added automatically. Never remove or reorder entries.");
			PlannerPort = ((BasePlugin)this).Config.Bind<int>("Planner", "Port", 47811, "The planner page sends plans to the game through this port, on this PC only. Change it only if another program uses it; the page opened from the game's City Planner button follows the change.");
			HomeWealth = ((BasePlugin)this).Config.Bind<bool>("Wealth", "DistrictHomeWealth", true, "Homes take their wealth mainly from their district (otherwise mostly from floor height, as in vanilla).");
			StreetWealth = ((BasePlugin)this).Config.Bind<bool>("Wealth", "AverageStreetWealth", true, "Streets take the average wealth of the tiles they cover (vanilla: their first tile), for street decoration.");
			StreetThemes = ((BasePlugin)this).Config.Bind<bool>("Wealth", "StreetThemes", true, "District-themed street decoration (Chinatown gates etc.) follows the districts a street actually runs through.");
			OpenAlleysIn = ((BasePlugin)this).Config.Bind<string>("Streets", "OpenAlleysIn", "Chinatown,Red Light,The Bricks,Brickrow", "Districts (planned names) where alleys become back streets, so no dead-end walls split them. Empty = vanilla.");
			EdgeGates = ((BasePlugin)this).Config.Bind<bool>("Wealth", "ChinatownGatesAtEdges", true, "Paifang gates only where a street enters or leaves Chinatown.");
			EdgeGateReach = ((BasePlugin)this).Config.Bind<int>("Wealth", "EdgeGateReach", 8, "How close (in path nodes; a city block is about 19) a gate must be to Chinatown's edge.");
			GritClusters = ((BasePlugin)this).Config.Bind<string>("Wealth", "GritDecorations", "Shanty,BurningBarrel,StreetJunk,StaticLitter", "Street decorations (name fragments) kept out of wealthy districts in planned cities.");
			GritFreeFrom = ((BasePlugin)this).Config.Bind<LandValue>("Wealth", "GritFreeFrom", (LandValue)3, "Districts at this land value or above get none of the grit decorations.");
			LogPerformance = ((BasePlugin)this).Config.Bind<bool>("Debug", "LogPerformance", false, "Write frame rate and memory use to the log every minute.");
			InstallBundledCities();
			ReloadPlanFile(force: true);
			if (Plan == null && !File.Exists(PlanPath))
			{
				((BasePlugin)this).Log.LogInfo((object)"No city plan in the config folder: city generation is unchanged.");
			}
			new Harmony("sodmods.cityplanner").PatchAll(typeof(Plugin).Assembly);
			((BasePlugin)this).AddComponent<PlannerPump>();
			PlannerLink.Start(PlannerPort.Value);
			((BasePlugin)this).Log.LogInfo((object)"City Planner loaded");
		}

		internal static void ReloadPlanFile(bool force = false)
		{
			try
			{
				if (!File.Exists(PlanPath))
				{
					return;
				}
				DateTime lastWriteTimeUtc = File.GetLastWriteTimeUtc(PlanPath);
				if (force || !(lastWriteTimeUtc == planFileTime))
				{
					planFileTime = lastWriteTimeUtc;
					string error;
					Plan plan = Plan.Load(PlanPath, out error);
					if (plan == null)
					{
						Logger.LogError((object)("City plan not loaded: " + error));
					}
					else
					{
						Use(plan, "loaded from cityplan.txt");
					}
				}
			}
			catch (Exception ex)
			{
				Logger.LogError((object)("City plan not loaded: " + ex.Message));
			}
		}

		internal static void Use(Plan plan, string how)
		{
			Plan = plan;
			Sizes.Remember(plan.Width, plan.Height);
			Sizes.RefreshDropdown();
			Logger.LogInfo((object)($"City plan {how}: {plan.Width}x{plan.Height}, {plan.Districts.Count} districts, population x{plan.Population}" + ((plan.Streets.Count > 0) ? $", {plan.Streets.Count} painted streets" : "")));
		}

		internal static void NoteFileWritten()
		{
			try
			{
				planFileTime = File.GetLastWriteTimeUtc(PlanPath);
			}
			catch
			{
			}
		}

		private void InstallBundledCities()
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Expected O, but got Unknown
			try
			{
				string[] files = Directory.GetFiles(Paths.PluginPath, "*.citb", SearchOption.AllDirectories);
				ManualLogSource log = ((BasePlugin)this).Log;
				bool flag = default(bool);
				BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(22, 2, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("City files found in ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(Paths.PluginPath);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(files.Length);
				}
				log.LogInfo(val);
				if (files.Length == 0)
				{
					return;
				}
				string text = Path.Combine(Application.persistentDataPath, "Cities");
				Directory.CreateDirectory(text);
				string[] array = files;
				foreach (string text2 in array)
				{
					string text3 = Path.ChangeExtension(text2, ".txt");
					string[] array2 = ((!File.Exists(text3)) ? new string[1] { text2 } : new string[2] { text2, text3 });
					foreach (string text4 in array2)
					{
						string text5 = Path.Combine(text, Path.GetFileName(text4));
						if (!File.Exists(text5) || new FileInfo(text5).Length != new FileInfo(text4).Length)
						{
							File.Copy(text4, text5, overwrite: true);
							((BasePlugin)this).Log.LogInfo((object)("Installed city file " + Path.GetFileName(text4) + " into " + text));
						}
					}
				}
			}
			catch (Exception ex)
			{
				((BasePlugin)this).Log.LogError((object)("Couldn't install the bundled cities: " + ex.Message));
			}
		}
	}
	internal static class Sizes
	{
		internal static int VanillaCount = -1;

		public static void Ensure()
		{
			CityControls instance = CityControls.Instance;
			List<CitySize> val = ((instance != null) ? instance.citySizes : null);
			if (val == null || VanillaCount >= 0)
			{
				return;
			}
			if (Plugin.Plan != null)
			{
				Remember(Plugin.Plan.Width, Plugin.Plan.Height);
			}
			VanillaCount = val.Count;
			string[] array = Plugin.KeepSizes.Value.Split(',', StringSplitOptions.RemoveEmptyEntries);
			for (int i = 0; i < array.Length; i++)
			{
				string[] array2 = array[i].Trim().ToLowerInvariant().Split('x');
				if (array2.Length == 2 && int.TryParse(array2[0], out var result) && int.TryParse(array2[1], out var result2))
				{
					Add(val, result, result2);
				}
			}
		}

		public static void Remember(int w, int h)
		{
			List<string> list = (from e in Plugin.KeepSizes.Value.Split(',', StringSplitOptions.RemoveEmptyEntries)
				select e.Trim().ToLowerInvariant()).ToList();
			if (!list.Contains($"{w}x{h}"))
			{
				list.Add($"{w}x{h}");
				Plugin.KeepSizes.Value = string.Join(",", list);
			}
			CityControls instance = CityControls.Instance;
			List<CitySize> val = ((instance != null) ? instance.citySizes : null);
			if (val != null && VanillaCount >= 0)
			{
				Add(val, w, h);
			}
		}

		public static string Label(int w, int h)
		{
			return $"Planned city ({w - 2} × {h - 2} blocks)";
		}

		public static void RefreshDropdown()
		{
			//IL_008b: 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_0130: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Expected O, but got Unknown
			try
			{
				MainMenuController instance = MainMenuController.Instance;
				object obj;
				if (instance == null)
				{
					obj = null;
				}
				else
				{
					DropdownController citySizeDropdown = instance.citySizeDropdown;
					obj = ((citySizeDropdown != null) ? citySizeDropdown.dropdown : null);
				}
				TMP_Dropdown val = (TMP_Dropdown)obj;
				CityControls instance2 = CityControls.Instance;
				List<CitySize> val2 = ((instance2 != null) ? instance2.citySizes : null);
				if (!((Object)(object)val == (Object)null) && val2 != null && VanillaCount >= 0)
				{
					while (val.options.Count > VanillaCount)
					{
						val.options.RemoveAt(val.options.Count - 1);
					}
					Plan plan = Plugin.Plan;
					for (int i = VanillaCount; i < val2.Count; i++)
					{
						int num = Mathf.RoundToInt(val2[i].v2.x);
						int num2 = Mathf.RoundToInt(val2[i].v2.y);
						bool flag = plan != null && num == plan.Width && num2 == plan.Height;
						val.options.Add(new OptionData(flag ? Label(num, num2) : $"Extra large ({num - 2} × {num2 - 2} blocks)"));
					}
					val.RefreshShownValue();
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogError((object)ex);
			}
		}

		private static void Add(List<CitySize> list, int w, int h)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: 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_0065: Expected O, but got Unknown
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Expected O, but got Unknown
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			for (int i = 0; i < list.Count; i++)
			{
				if (Mathf.RoundToInt(list[i].v2.x) == w && Mathf.RoundToInt(list[i].v2.y) == h)
				{
					return;
				}
			}
			list.Add(new CitySize
			{
				size = (Size)3,
				v2 = new Vector2((float)w, (float)h)
			});
			ManualLogSource logger = Plugin.Logger;
			bool flag = default(bool);
			BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(18, 3, ref flag);
			if (flag)
			{
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("City size slot ");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(list.Count - 1);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": ");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(w);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("x");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(h);
			}
			logger.LogInfo(val);
		}
	}
	[HarmonyPatch(typeof(MainMenuController), "LoadDropdownContent")]
	internal static class DropdownPatch
	{
		private static void Prefix()
		{
			if (!PlannerLink.Generating)
			{
				Plugin.ReloadPlanFile();
			}
			Sizes.Ensure();
		}

		private static void Postfix()
		{
			Sizes.RefreshDropdown();
		}
	}
	[HarmonyPatch(typeof(Toolbox), "GetCitySizeFromValue")]
	internal static class SizeLookupPatch
	{
		private static void Prefix()
		{
			Sizes.Ensure();
		}
	}
	[HarmonyPatch(typeof(CitizenCreator), "Populate")]
	internal static class PopulationPatch
	{
		private static void Prefix()
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Expected O, but got Unknown
			if (Plugin.Active)
			{
				CityData.Instance.populationMultiplier = Mathf.Clamp(Plugin.Plan.Population, 0.1f, 2f);
				ManualLogSource logger = Plugin.Logger;
				bool flag = default(bool);
				BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(27, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Populating with multiplier ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<float>(CityData.Instance.populationMultiplier);
				}
				logger.LogInfo(val);
			}
		}
	}
	internal static class DistrictsPatch
	{
		internal static readonly Dictionary<int, PlannedDistrict> Created = new Dictionary<int, PlannedDistrict>();

		public static void Run()
		{
			try
			{
				Apply(Plugin.Plan);
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogError((object)("Applying districts failed: " + ex));
			}
		}

		private static void Apply(Plan plan)
		{
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Expected O, but got Unknown
			//IL_0172: Unknown result type (might be due to invalid IL or missing references)
			//IL_0177: Unknown result type (might be due to invalid IL or missing references)
			//IL_0221: Unknown result type (might be due to invalid IL or missing references)
			//IL_0228: Expected O, but got Unknown
			Dictionary<string, DistrictPreset> dictionary = new Dictionary<string, DistrictPreset>(StringComparer.OrdinalIgnoreCase);
			Enumerator<DistrictPreset> enumerator = Toolbox.Instance.allDistricts.GetEnumerator();
			while (enumerator.MoveNext())
			{
				DistrictPreset current = enumerator.Current;
				dictionary[((Object)current).name] = current;
			}
			CityDistricts instance = HighlanderSingleton<CityDistricts>.Instance;
			List<DistrictController> list = ((IEnumerable<DistrictController>)instance.districtDirectory.ToArray()).ToList();
			Dictionary<char, DistrictController> dictionary2 = new Dictionary<char, DistrictController>();
			Created.Clear();
			bool flag = default(bool);
			foreach (PlannedDistrict value3 in plan.Districts.Values)
			{
				if (!dictionary.TryGetValue(value3.Type, out var value))
				{
					ManualLogSource logger = Plugin.Logger;
					BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(29, 2, ref flag);
					if (flag)
					{
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Unknown district type '");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(value3.Type);
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("' for ");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(value3.Name);
					}
					logger.LogError(val);
				}
				else
				{
					DistrictController component = Object.Instantiate<GameObject>(instance.districtPrefab, PrefabControls.Instance.cityContainer.transform).GetComponent<DistrictController>();
					component.Setup(value);
					dictionary2[value3.Key] = component;
					Created[component.districtID] = value3;
				}
			}
			int num = 0;
			Enumerator<Vector2Int, CityTile> enumerator3 = HighlanderSingleton<CityBoundaryAndTiles>.Instance.cityTiles.GetEnumerator();
			while (enumerator3.MoveNext())
			{
				KeyValuePair<Vector2Int, CityTile> current3 = enumerator3.Current;
				Vector2Int key = current3.Key;
				PlannedTile plannedTile = plan.Nearest(((Vector2Int)(ref key)).x, ((Vector2Int)(ref key)).y);
				if (dictionary2.TryGetValue(plannedTile.District.Key, out var value2))
				{
					value2.AddCityTile(current3.Value);
					num++;
				}
			}
			foreach (DistrictController item in list)
			{
				if (item.cityTiles.Count <= 0)
				{
					instance.districtDirectory.Remove(item);
					Object.Destroy((Object)(object)((Component)item).gameObject);
				}
			}
			ManualLogSource logger2 = Plugin.Logger;
			BepInExInfoLogInterpolatedStringHandler val2 = new BepInExInfoLogInterpolatedStringHandler(75, 3, ref flag);
			if (flag)
			{
				((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Districts applied: ");
				((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<int>(dictionary2.Count);
				((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" planned districts, ");
				((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<int>(num);
				((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" tiles, removed ");
				((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<int>(list.Count);
				((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" generated districts");
			}
			logger2.LogInfo(val2);
		}
	}
	internal static class BuildingsPatch
	{
		public static void Run()
		{
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: 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_01c1: Expected O, but got Unknown
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			//IL_011a: Expected O, but got Unknown
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			CityBuildings instance = HighlanderSingleton<CityBuildings>.Instance;
			try
			{
				Plan plan = Plugin.Plan;
				Dictionary<string, BuildingPreset> dictionary = new Dictionary<string, BuildingPreset>(StringComparer.OrdinalIgnoreCase);
				Enumerator<BuildingPreset> enumerator = AssetLoader.Instance.GetAllBuildingPresets().GetEnumerator();
				while (enumerator.MoveNext())
				{
					BuildingPreset current = enumerator.Current;
					dictionary[((Object)current).name] = current;
				}
				Dictionary<Vector2Int, CityTile> cityTiles = HighlanderSingleton<CityBoundaryAndTiles>.Instance.cityTiles;
				int num = 0;
				Enumerator<Vector2Int, CityTile> enumerator2 = cityTiles.GetEnumerator();
				bool flag = default(bool);
				while (enumerator2.MoveNext())
				{
					KeyValuePair<Vector2Int, CityTile> current2 = enumerator2.Current;
					Vector2Int key = current2.Key;
					CityTile value = current2.Value;
					PlannedTile plannedTile = plan.Nearest(((Vector2Int)(ref key)).x, ((Vector2Int)(ref key)).y);
					value.landValue = plannedTile.District.LandValue;
					value.density = plannedTile.District.Density;
					if (!plan.Tiles.TryGetValue((((Vector2Int)(ref key)).x, ((Vector2Int)(ref key)).y), out var value2) || value2.Building == null || (Object)(object)value.building != (Object)null)
					{
						continue;
					}
					if (!dictionary.TryGetValue(value2.Building, out var value3))
					{
						ManualLogSource logger = Plugin.Logger;
						BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(23, 2, ref flag);
						if (flag)
						{
							((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Unknown building '");
							((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(value2.Building);
							((BepInExLogInterpolatedStringHandler)val).AppendLiteral("' at ");
							((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Vector2Int>(key);
						}
						logger.LogError(val);
					}
					else
					{
						Object.Instantiate<GameObject>(instance.buildingPrefab, ((Component)value).transform).GetComponent<NewBuilding>().Setup(value, value3, false, 0);
						num++;
					}
				}
				Enumerator<BlockController> enumerator3 = HighlanderSingleton<CityBlocks>.Instance.blocksDirectory.GetEnumerator();
				while (enumerator3.MoveNext())
				{
					enumerator3.Current.UpdateAverageDensity();
				}
				ManualLogSource logger2 = Plugin.Logger;
				BepInExInfoLogInterpolatedStringHandler val2 = new BepInExInfoLogInterpolatedStringHandler(18, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Buildings placed: ");
					((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<int>(num);
				}
				logger2.LogInfo(val2);
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogError((object)("Placing buildings failed: " + ex));
			}
		}
	}
	[HarmonyPatch(typeof(CityConstructor), "Update")]
	internal static class StagePatch
	{
		internal static bool DistrictsDone;

		internal static bool BuildingsDone;

		internal static bool AlleysDone;

		private static void Prefix(CityConstructor __instance)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Invalid comparison between Unknown and I4
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Invalid comparison between Unknown and I4
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Invalid comparison between Unknown and I4
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Invalid comparison between Unknown and I4
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Invalid comparison between Unknown and I4
			if (!__instance.loadingOperationActive && Plugin.Active)
			{
				if ((int)__instance.loadState == 3 && !DistrictsDone)
				{
					DistrictsDone = true;
					DistrictsPatch.Run();
				}
				else if ((int)__instance.loadState == 5 && !BuildingsDone)
				{
					BuildingsDone = true;
					BuildingsPatch.Run();
				}
				else if ((int)__instance.loadState == 7 && !AlleysDone)
				{
					AlleysDone = true;
					OpenAlleys.Run();
				}
				else if ((int)__instance.loadState == 15 && !StreetThemes.Prepared)
				{
					StreetThemes.Prepare();
				}
				else if ((int)__instance.loadState >= 19 && StreetThemes.Prepared)
				{
					StreetThemes.Restore();
				}
			}
		}
	}
	internal static class OpenAlleys
	{
		public static void Run()
		{
			//IL_0195: Unknown result type (might be due to invalid IL or missing references)
			//IL_019c: Expected O, but got Unknown
			if (Plugin.Plan.Streets.Count > 0)
			{
				return;
			}
			try
			{
				HashSet<string> hashSet = new HashSet<string>(from d in Plugin.OpenAlleysIn.Value.Split(',', StringSplitOptions.RemoveEmptyEntries)
					select d.Trim(), StringComparer.OrdinalIgnoreCase);
				if (hashSet.Count == 0)
				{
					return;
				}
				int num = 0;
				Enumerator<StreetController> enumerator = CityData.Instance.streetDirectory.GetEnumerator();
				while (enumerator.MoveNext())
				{
					StreetController current = enumerator.Current;
					if (!current.isAlley || current.tiles.Count == 0)
					{
						continue;
					}
					string text = (from n in ((IEnumerable<NewTile>)current.tiles.ToArray()).Select(delegate(NewTile t)
						{
							object obj;
							if (t == null)
							{
								obj = null;
							}
							else
							{
								CityTile cityTile = t.cityTile;
								obj = ((cityTile != null) ? cityTile.district : null);
							}
							DistrictController val2 = (DistrictController)obj;
							PlannedDistrict value;
							return (val2 == null || !DistrictsPatch.Created.TryGetValue(val2.districtID, out value)) ? null : value.Name;
						})
						where n != null
						group n by n into g
						orderby g.Count() descending
						select g.Key).FirstOrDefault();
					if (text != null && hashSet.Contains(text))
					{
						current.isAlley = false;
						current.SetAsBackstreet();
						num++;
					}
				}
				ManualLogSource logger = Plugin.Logger;
				bool flag = default(bool);
				BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(36, 2, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Opened ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(num);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" alleys into back streets in ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(string.Join(", ", hashSet));
				}
				logger.LogInfo(val);
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogError((object)("Opening alleys failed: " + ex));
			}
		}
	}
	[HarmonyPatch(typeof(NewBuilding), "CalculateFacing")]
	internal static class FacingPatch
	{
		internal static int Count;

		private static bool Prefix(NewBuilding __instance)
		{
			//IL_001d: 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_005f: 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_0086: Expected O, but got Unknown
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			if (!Plugin.Active || (Object)(object)__instance.cityTile == (Object)null)
			{
				return true;
			}
			Vector2Int cityCoord = __instance.cityTile.cityCoord;
			if (!Plugin.Plan.Tiles.TryGetValue((((Vector2Int)(ref cityCoord)).x, ((Vector2Int)(ref cityCoord)).y), out var value) || !value.Facing.HasValue)
			{
				return true;
			}
			__instance.SetFacing(value.Facing.Value, true);
			Count++;
			ManualLogSource logger = Plugin.Logger;
			bool flag = default(bool);
			BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(21, 3, ref flag);
			if (flag)
			{
				BuildingPreset preset = __instance.preset;
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>((preset != null) ? ((Object)preset).name : null);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" at ");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Vector2Int>(cityCoord);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" faces ");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Direction>(value.Facing.Value);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" (planned)");
			}
			logger.LogInfo(val);
			return false;
		}
	}
	[HarmonyPatch(typeof(DistrictController), "UpdateName")]
	internal static class DistrictNamePatch
	{
		private static void Postfix(DistrictController __instance)
		{
			if (DistrictsPatch.Created.TryGetValue(__instance.districtID, out var value))
			{
				((Object)__instance).name = value.Name;
				((Object)((Component)__instance).transform).name = value.Name;
			}
		}
	}
	[HarmonyPatch(typeof(NewAddress), "CalculateLandValue")]
	internal static class AddressWealthPatch
	{
		private static readonly float[] BandStart = new float[5] { 0.05f, 0.15f, 0.28f, 0.45f, 0.6f };

		private static readonly float[] BandEnd = new float[5] { 0.18f, 0.3f, 0.45f, 0.62f, 0.78f };

		internal static int Count;

		private static void Postfix(NewAddress __instance)
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Expected I4, but got Unknown
			if (!Plugin.Active || !Plugin.HomeWealth.Value)
			{
				return;
			}
			NewBuilding building = ((NewGameLocation)__instance).building;
			if (!((Object)(object)((building != null) ? building.cityTile : null) == (Object)null) && !((Object)(object)((NewGameLocation)__instance).floor == (Object)null))
			{
				int num = Mathf.Clamp((int)((NewGameLocation)__instance).building.cityTile.landValue, 0, 4);
				int num2 = 0;
				Enumerator<NewRoom> enumerator = ((NewGameLocation)__instance).rooms.GetEnumerator();
				while (enumerator.MoveNext())
				{
					NewRoom current = enumerator.Current;
					num2 += current.nodes.Count;
				}
				float num3 = Mathf.Clamp01((Mathf.Min((float)((NewGameLocation)__instance).floor.floor / 4f, 5f) + (float)num2 * 0.05f) / 11f);
				float num4 = Mathf.Lerp(BandStart[num], BandEnd[num], num3);
				if ((SoCustomComparison)(object)__instance.addressPreset != (SoCustomComparison)null)
				{
					num4 = Mathf.Clamp(num4, __instance.addressPreset.minimumLandValue, __instance.addressPreset.maximumLandValue);
				}
				__instance.normalizedLandValue = num4;
				Count++;
			}
		}
	}
	[HarmonyPatch(typeof(Toolbox), "GetNormalizedLandValue", new Type[]
	{
		typeof(NewGameLocation),
		typeof(bool)
	})]
	internal static class StreetWealthPatch
	{
		private static readonly ConcurrentDictionary<IntPtr, float> Cache = new ConcurrentDictionary<IntPtr, float>();

		internal static void Reset()
		{
			Cache.Clear();
		}

		private static void Postfix(NewGameLocation location, ref float __result)
		{
			if (!Plugin.Active || !Plugin.StreetWealth.Value || (Object)(object)location == (Object)null || (Object)(object)location.thisAsAddress != (Object)null || location.nodes == null || location.nodes.Count == 0)
			{
				return;
			}
			float fallback = __result;
			__result = Cache.GetOrAdd(((Il2CppObjectBase)location).Pointer, delegate
			{
				//IL_0045: Unknown result type (might be due to invalid IL or missing references)
				float num = 0f;
				int num2 = 0;
				Enumerator<NewNode> enumerator = location.nodes.GetEnumerator();
				while (enumerator.MoveNext())
				{
					NewNode current = enumerator.Current;
					object obj;
					if (current == null)
					{
						obj = null;
					}
					else
					{
						NewTile tile = current.tile;
						obj = ((tile != null) ? tile.cityTile : null);
					}
					CityTile val = (CityTile)obj;
					if (!((Object)(object)val == (Object)null))
					{
						num += (float)val.landValue / 4f;
						num2++;
					}
				}
				return (num2 <= 0) ? fallback : (num / (float)num2);
			});
		}
	}
	internal static class StreetThemes
	{
		internal sealed class Rule
		{
			public HashSet<string> Limit;

			public HashSet<string> Ban;

			public bool Gate;

			public FurnitureCluster Cluster;

			public bool OriginalLimit;

			public bool OriginalBan;
		}

		internal static readonly Dictionary<IntPtr, Rule> Rules = new Dictionary<IntPtr, Rule>();

		internal static readonly HashSet<IntPtr> Grit = new HashSet<IntPtr>();

		internal static bool Prepared;

		internal static int Placed;

		internal static int Refused;

		private static bool listed;

		public static void Prepare()
		{
			//IL_031d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0324: Expected O, but got Unknown
			Restore();
			Prepared = true;
			try
			{
				ListThemes();
				string[] source = (from g in Plugin.GritClusters.Value.Split(',', StringSplitOptions.RemoveEmptyEntries)
					select g.Trim()).ToArray();
				Enumerator<FurnitureCluster> enumerator = Toolbox.Instance.allFurnitureClusters.GetEnumerator();
				while (enumerator.MoveNext())
				{
					FurnitureCluster current = enumerator.Current;
					if ((SoCustomComparison)(object)current == (SoCustomComparison)null || !current.allowOnStreets)
					{
						continue;
					}
					string name = ((Object)current).name;
					if (source.Any((string g) => name.IndexOf(g, StringComparison.OrdinalIgnoreCase) >= 0))
					{
						Grit.Add(((Il2CppObjectBase)current).Pointer);
					}
					if (Plugin.StreetThemes.Value && (current.limitToDistricts || current.banFromDistricts))
					{
						Rules[((Il2CppObjectBase)current).Pointer] = new Rule
						{
							Limit = (current.limitToDistricts ? new HashSet<string>(((IEnumerable<DistrictPreset>)current.allowedInDistricts.ToArray()).Select((DistrictPreset d) => ((Object)d).name)) : null),
							Ban = (current.banFromDistricts ? new HashSet<string>(((IEnumerable<DistrictPreset>)current.notAllowedInDistricts.ToArray()).Select((DistrictPreset d) => ((Object)d).name)) : null),
							Gate = (name.IndexOf("Paifang", StringComparison.OrdinalIgnoreCase) >= 0),
							Cluster = current,
							OriginalLimit = current.limitToDistricts,
							OriginalBan = current.banFromDistricts
						};
						current.limitToDistricts = false;
						current.banFromDistricts = false;
					}
				}
				int num = 0;
				if (Plugin.StreetThemes.Value)
				{
					Enumerator<StreetController> enumerator2 = CityData.Instance.streetDirectory.GetEnumerator();
					while (enumerator2.MoveNext())
					{
						StreetController current2 = enumerator2.Current;
						Dictionary<int, (DistrictController, int)> dictionary = new Dictionary<int, (DistrictController, int)>();
						Enumerator<NewRoom> enumerator3 = ((NewGameLocation)current2).rooms.GetEnumerator();
						while (enumerator3.MoveNext())
						{
							Enumerator<NewNode> enumerator4 = enumerator3.Current.nodes.GetEnumerator();
							while (enumerator4.MoveNext())
							{
								NewNode current3 = enumerator4.Current;
								object obj;
								if (current3 == null)
								{
									obj = null;
								}
								else
								{
									NewTile tile = current3.tile;
									if (tile == null)
									{
										obj = null;
									}
									else
									{
										CityTile cityTile = tile.cityTile;
										obj = ((cityTile != null) ? cityTile.district : null);
									}
								}
								DistrictController val = (DistrictController)obj;
								if (!((Object)(object)val == (Object)null))
								{
									dictionary.TryGetValue(val.districtID, out var value);
									dictionary[val.districtID] = (val, value.Item2 + 1);
								}
							}
						}
						if (dictionary.Count != 0)
						{
							DistrictController item = dictionary.Values.OrderByDescending<(DistrictController, int), int>(((DistrictController d, int n) c) => c.n).First().Item1;
							if ((Object)(object)((NewGameLocation)current2).district != (Object)(object)item)
							{
								((NewGameLocation)current2).district = item;
								num++;
							}
						}
					}
				}
				ManualLogSource logger = Plugin.Logger;
				bool flag = default(bool);
				BepInExInfoLogInterpolatedStringHandler val2 = new BepInExInfoLogInterpolatedStringHandler(86, 3, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Street themes ready: ");
					((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<int>(Rules.Count);
					((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" district rules lifted, ");
					((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<int>(Grit.Count);
					((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" grit decorations, ");
					((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<int>(num);
					((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" streets re-districted");
				}
				logger.LogInfo(val2);
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogError((object)("Street themes: " + ex));
			}
		}

		public static void Restore()
		{
			foreach (Rule value in Rules.Values)
			{
				if (!((SoCustomComparison)(object)value.Cluster == (SoCustomComparison)null))
				{
					value.Cluster.limitToDistricts = value.OriginalLimit;
					value.Cluster.banFromDistricts = value.OriginalBan;
				}
			}
			Rules.Clear();
			Grit.Clear();
			Prepared = false;
		}

		private static void ListThemes()
		{
			//IL_0104: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Expected O, but got Unknown
			if (listed)
			{
				return;
			}
			listed = true;
			Enumerator<FurnitureCluster> enumerator = Toolbox.Instance.allFurnitureClusters.GetEnumerator();
			bool flag = default(bool);
			while (enumerator.MoveNext())
			{
				FurnitureCluster current = enumerator.Current;
				if (!((SoCustomComparison)(object)current == (SoCustomComparison)null) && current.allowOnStreets && (current.limitToDistricts || current.banFromDistricts))
				{
					string text = (current.limitToDistricts ? ("only " + string.Join("+", ((IEnumerable<DistrictPreset>)current.allowedInDistricts.ToArray()).Select((DistrictPreset d) => ((Object)d).name))) : "");
					string text2 = (current.banFromDistricts ? (" not " + string.Join("+", ((IEnumerable<DistrictPreset>)current.notAllowedInDistricts.ToArray()).Select((DistrictPreset d) => ((Object)d).name))) : "");
					ManualLogSource logger = Plugin.Logger;
					BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(47, 5, ref flag);
					if (flag)
					{
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Themed decoration ");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(((Object)current).name);
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": ");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(text);
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(text2);
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" (max ");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(current.maximumPerRoom);
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" per street, chance ");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<float>(current.placementChance);
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral(")");
					}
					logger.LogInfo(val);
				}
			}
		}
	}
	[HarmonyPatch(typeof(GenerationController), "GetBestFurnitureClusterLocation")]
	internal static class PlacementPatch
	{
		private static void Postfix(NewRoom room, FurnitureCluster cluster, ref FurnitureClusterLocation __result)
		{
			//IL_0170: Unknown result type (might be due to invalid IL or missing references)
			//IL_017a: Unknown result type (might be due to invalid IL or missing references)
			if (__result == null || !StreetThemes.Prepared || (SoCustomComparison)(object)cluster == (SoCustomComparison)null || (Object)(object)((room != null) ? room.gameLocation : null) == (Object)null)
			{
				return;
			}
			bool flag = (Object)(object)room.gameLocation.thisAsStreet != (Object)null;
			if (StreetThemes.Rules.TryGetValue(((Il2CppObjectBase)cluster).Pointer, out var value))
			{
				object obj;
				if (!flag)
				{
					obj = room.gameLocation.district;
				}
				else
				{
					NewNode anchorNode = __result.anchorNode;
					if (anchorNode == null)
					{
						obj = null;
					}
					else
					{
						NewTile tile = anchorNode.tile;
						if (tile == null)
						{
							obj = null;
						}
						else
						{
							CityTile cityTile = tile.cityTile;
							obj = ((cityTile != null) ? cityTile.district : null);
						}
					}
				}
				DistrictController val = (DistrictController)obj;
				DistrictPreset val2 = ((val != null) ? val.preset : null);
				bool flag2 = (SoCustomComparison)(object)val2 != (SoCustomComparison)null && (value.Limit == null || value.Limit.Contains(((Object)val2).name)) && (value.Ban == null || !value.Ban.Contains(((Object)val2).name));
				if (flag2 && flag && value.Gate && Plugin.EdgeGates.Value)
				{
					flag2 = NearDistrictEdge(__result.anchorNode, val, Plugin.EdgeGateReach.Value);
				}
				if (!flag2)
				{
					__result = null;
					return;
				}
				if (flag)
				{
					Interlocked.Increment(ref StreetThemes.Placed);
				}
			}
			if (flag && StreetThemes.Grit.Contains(((Il2CppObjectBase)cluster).Pointer))
			{
				NewNode anchorNode2 = __result.anchorNode;
				object obj2;
				if (anchorNode2 == null)
				{
					obj2 = null;
				}
				else
				{
					NewTile tile2 = anchorNode2.tile;
					obj2 = ((tile2 != null) ? tile2.cityTile : null);
				}
				CityTile val3 = (CityTile)obj2;
				if (!((Object)(object)val3 == (Object)null) && val3.landValue >= Plugin.GritFreeFrom.Value)
				{
					__result = null;
					Interlocked.Increment(ref StreetThemes.Refused);
				}
			}
		}

		private static bool NearDistrictEdge(NewNode node, DistrictController district, int reach)
		{
			//IL_001a: 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_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			if (node == null || (Object)(object)district == (Object)null)
			{
				return false;
			}
			Dictionary<Vector3, NewNode> nodeMap = PathFinder.Instance.nodeMap;
			Vector3Int nodeCoord = node.nodeCoord;
			NewNode val = default(NewNode);
			for (int i = 1; i <= reach; i++)
			{
				(int, int)[] array = new(int, int)[4]
				{
					(i, 0),
					(-i, 0),
					(0, i),
					(0, -i)
				};
				for (int j = 0; j < array.Length; j++)
				{
					var (num, num2) = array[j];
					if (!nodeMap.TryGetValue(Vector3Int.op_Implicit(new Vector3Int(((Vector3Int)(ref nodeCoord)).x + num, ((Vector3Int)(ref nodeCoord)).y + num2, ((Vector3Int)(ref nodeCoord)).z)), ref val))
					{
						continue;
					}
					object obj;
					if (val == null)
					{
						obj = null;
					}
					else
					{
						NewTile tile = val.tile;
						if (tile == null)
						{
							obj = null;
						}
						else
						{
							CityTile cityTile = tile.cityTile;
							obj = ((cityTile != null) ? cityTile.district : null);
						}
					}
					DistrictController val2 = (DistrictController)obj;
					if ((Object)(object)val2 != (Object)null && val2.districtID != district.districtID)
					{
						return true;
					}
				}
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(CityConstructor), "GenerateNewCity")]
	internal static class GenerateTimer
	{
		internal static Stopwatch Timer;

		private static void Prefix()
		{
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Expected O, but got Unknown
			StagePatch.DistrictsDone = (StagePatch.BuildingsDone = (StagePatch.AlleysDone = false));
			AddressWealthPatch.Count = 0;
			StreetThemes.Restore();
			StreetThemes.Refused = 0;
			StreetThemes.Placed = 0;
			StreetWealthPatch.Reset();
			Timer = Stopwatch.StartNew();
			RestartSafeController instance = RestartSafeController.Instance;
			ManualLogSource logger = Plugin.Logger;
			bool flag = default(bool);
			BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(16, 4, ref flag);
			if (flag)
			{
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Generating ");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(instance.cityName);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" at ");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(instance.cityX);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("x");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(instance.cityY);
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(Plugin.Active ? " from the plan" : "");
			}
			logger.LogInfo(val);
		}
	}
	[HarmonyPatch(typeof(Player), "Update")]
	internal static class Telemetry
	{
		private static bool started;

		private static float windowStart;

		private static float lastFrame;

		private static int frames;

		private static float minFps = float.MaxValue;

		private static long MemoryMb()
		{
			return Process.GetCurrentProcess().WorkingSet64 / 1048576;
		}

		private static void Postfix()
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0219: Unknown result type (might be due to invalid IL or missing references)
			//IL_0220: Expected O, but got Unknown
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			if (!Plugin.LogPerformance.Value || (Object)(object)SessionData.Instance == (Object)null || !SessionData.Instance.startedGame)
			{
				return;
			}
			float unscaledTime = Time.unscaledTime;
			if (!started)
			{
				started = true;
				windowStart = unscaledTime;
				Vector2 citySize = CityData.Instance.citySize;
				string value = ((GenerateTimer.Timer != null) ? $"{GenerateTimer.Timer.Elapsed.TotalSeconds:0}s since generation started" : "loaded from file");
				Plugin.Logger.LogInfo((object)($"In game: city {citySize.x}x{citySize.y}, {CityData.Instance.citizenDirectory.Count} citizens, {value}, memory {MemoryMb()} MB" + ((AddressWealthPatch.Count > 0) ? $", {AddressWealthPatch.Count} addresses re-valued, {StreetThemes.Refused} grit decorations kept out of wealthy streets, {StreetThemes.Placed} themed decorations placed" : "")));
				GenerateTimer.Timer = null;
			}
			frames++;
			if (lastFrame > 0f && unscaledTime > lastFrame)
			{
				minFps = Mathf.Min(minFps, 1f / (unscaledTime - lastFrame));
			}
			lastFrame = unscaledTime;
			if (!(unscaledTime - windowStart < 60f))
			{
				ManualLogSource logger = Plugin.Logger;
				bool flag = default(bool);
				BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(43, 3, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Perf: avg ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<float>((float)frames / (unscaledTime - windowStart), "0");
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" fps, worst frame ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<float>(1000f / Mathf.Max(minFps, 0.1f), "0");
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" ms, memory ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<long>(MemoryMb());
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" MB");
				}
				logger.LogInfo(val);
				windowStart = unscaledTime;
				frames = 0;
				minFps = float.MaxValue;
			}
		}
	}
	[HarmonyPatch(typeof(PathFinder), "CreateStreets")]
	internal static class StreetsPatch
	{
		private class Piece
		{
			public StreetChunk Chunk;

			public bool Junction;

			public Gap Gap;

			public int CornerX;

			public int CornerY;

			public StreetKind Kind;

			public bool Painted;

			public bool OwnedNorthSouth;

			public List<Piece> Group;
		}

		private const float MainMin = 0.6f;

		private const float BackMin = 0.25f;

		private const float BackMax = 0.45f;

		private const float AlleyMax = 0.2f;

		private const float AutoMain = 0.5f;

		private const float AutoAlley = 0.15f;

		private static bool Prefix()
		{
			if (!Plugin.Active || Plugin.Plan.Streets.Count == 0)
			{
				return true;
			}
			try
			{
				Build();
				return false;
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogError((object)("Painted streets failed, using the game's own streets: " + ex));
				foreach (StreetController item in CityData.Instance.streetDirectory.ToArray())
				{
					Object.Destroy((Object)(object)((Component)item).gameObject);
				}
				CityData.Instance.streetDirectory.Clear();
				return true;
			}
		}

		private static int FloorDiv(int a, int b)
		{
			return (int)Math.Floor((double)a / (double)b);
		}

		private static void Build()
		{
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_04ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_04d1: Expected O, but got Unknown
			//IL_07ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_07b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_07bc: Unknown result type (might be due to invalid IL or missing references)
			Plan plan = Plugin.Plan;
			int tileMultiplier = CityControls.Instance.tileMultiplier;
			Dictionary<Gap, Piece> gaps = new Dictionary<Gap, Piece>();
			Dictionary<(int, int), Piece> dictionary = new Dictionary<(int, int), Piece>();
			List<Piece> list = new List<Piece>();
			List<StreetChunk> streetChunks = PathFinder.Instance.streetChunks;
			for (int i = 0; i < streetChunks.Count; i++)
			{
				StreetChunk val = streetChunks[i];
				Piece piece = new Piece
				{
					Chunk = val,
					Junction = val.isJunction
				};
				if (val.allTiles.Count == 0)
				{
					list.Add(piece);
					continue;
				}
				int num = int.MaxValue;
				int num2 = int.MaxValue;
				bool flag = true;
				for (int j = 0; j < val.allTiles.Count; j++)
				{
					Vector3Int globalTileCoord = val.allTiles[j].globalTileCoord;
					num = Math.Min(num, ((Vector3Int)(ref globalTileCoord)).x);
					num2 = Math.Min(num2, ((Vector3Int)(ref globalTileCoord)).y);
					int num3 = (((Vector3Int)(ref globalTileCoord)).x % tileMultiplier + tileMultiplier) % tileMultiplier;
					if (num3 != 0 && num3 != tileMultiplier - 1)
					{
						flag = false;
					}
				}
				if (piece.Junction)
				{
					piece.CornerX = FloorDiv(num - (tileMultiplier - 1), tileMultiplier);
					piece.CornerY = FloorDiv(num2 - (tileMultiplier - 1), tileMultiplier);
					if (!dictionary.TryAdd((piece.CornerX, piece.CornerY), piece))
					{
						list.Add(piece);
					}
				}
				else
				{
					piece.Gap = (flag ? new Gap(Vertical: true, FloorDiv(num - (tileMultiplier - 1), tileMultiplier), FloorDiv(num2, tileMultiplier)) : new Gap(Vertical: false, FloorDiv(num, tileMultiplier), FloorDiv(num2 - (tileMultiplier - 1), tileMultiplier)));
					if (!gaps.TryAdd(piece.Gap, piece))
					{
						list.Add(piece);
					}
				}
			}
			foreach (Piece value2 in gaps.Values)
			{
				value2.Painted = plan.Streets.TryGetValue(value2.Gap, out value2.Kind);
				if (!value2.Painted)
				{
					value2.Kind = Auto(value2.Chunk);
				}
			}
			foreach (Piece item in list)
			{
				item.Kind = Auto(item.Chunk);
			}
			foreach (Piece value3 in dictionary.Values)
			{
				List<Piece> list2 = (from piece4 in Around(value3, gaps)
					where piece4 != null
					select piece4).ToList();
				value3.Kind = ((list2.Count > 0) ? list2.Max((Piece piece4) => piece4.Kind) : Auto(value3.Chunk));
				bool flag2 = Same(value3, Get(gaps, new Gap(Vertical: true, value3.CornerX, value3.CornerY))) && Same(value3, Get(gaps, new Gap(Vertical: true, value3.CornerX, value3.CornerY + 1)));
				bool flag3 = Same(value3, Get(gaps, new Gap(Vertical: false, value3.CornerX, value3.CornerY))) && Same(value3, Get(gaps, new Gap(Vertical: false, value3.CornerX + 1, value3.CornerY)));
				if (flag2 && flag3)
				{
					value3.OwnedNorthSouth = LineLength(gaps, northSouth: true, value3.CornerX, value3.CornerY, value3.Kind) >= LineLength(gaps, northSouth: false, value3.CornerY, value3.CornerX, value3.Kind);
				}
				else if (flag2 || flag3)
				{
					value3.OwnedNorthSouth = flag2;
				}
				else
				{
					value3.OwnedNorthSouth = Same(value3, Get(gaps, new Gap(Vertical: true, value3.CornerX, value3.CornerY))) || Same(value3, Get(gaps, new Gap(Vertical: true, value3.CornerX, value3.CornerY + 1)));
				}
			}
			int num4 = plan.Streets.Keys.Count((Gap k) => !gaps.ContainsKey(k));
			if (num4 > 0)
			{
				ManualLogSource logger = Plugin.Logger;
				bool flag4 = default(bool);
				BepInExWarningLogInterpolatedStringHandler val2 = new BepInExWarningLogInterpolatedStringHandler(86, 1, ref flag4);
				if (flag4)
				{
					((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<int>(num4);
					((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" painted gaps have no road in this city (the coastline ring has none) and were skipped");
				}
				logger.LogWarning(val2);
			}
			List<List<Piece>> list3 = new List<List<Piece>>();
			int num5 = -1;
			int num6 = Math.Max(plan.Width, plan.Height) + 1;
			bool[] array = new bool[2] { true, false };
			foreach (bool flag5 in array)
			{
				for (int num8 = num5; num8 <= num6; num8++)
				{
					List<Piece> run = null;
					for (int num9 = num5; num9 <= num6; num9++)
					{
						Piece p = Get(gaps, new Gap(flag5, flag5 ? num8 : num9, flag5 ? num9 : num8));
						Step(ref run, p, list3);
						Piece piece2 = Get(dictionary, flag5 ? (num8, num9) : (num9, num8));
						Step(ref run, (piece2 != null && piece2.OwnedNorthSouth == flag5) ? piece2 : null, list3);
					}
				}
			}
			foreach (Piece item2 in from piece4 in gaps.Values.Concat(dictionary.Values)
				where piece4.Kind == StreetKind.Alley && piece4.Group == null
				select piece4)
			{
				List<Piece> list4 = new List<Piece>();
				Queue<Piece> queue = new Queue<Piece>();
				item2.Group = list4;
				queue.Enqueue(item2);
				while (queue.Count > 0)
				{
					Piece piece3 = queue.Dequeue();
					list4.Add(piece3);
					foreach (Piece item3 in Neighbours(piece3, gaps, dictionary))
					{
						if (item3 != null && item3.Group == null && item3.Kind == StreetKind.Alley)
						{
							item3.Group = list4;
							queue.Enqueue(item3);
						}
					}
				}
				list3.Add(list4);
			}
			foreach (Piece item4 in from piece4 in gaps.Values.Concat(dictionary.Values).Concat(list)
				where piece4.Group == null
				select piece4)
			{
				item4.Group = new List<Piece> { item4 };
				list3.Add(item4.Group);
			}
			Dictionary<StreetKind, int> dictionary2 = new Dictionary<StreetKind, int>();
			foreach (List<Piece> item5 in list3)
			{
				StreetKind kind = item5[0].Kind;
				Vector3 anchorTile = item5[0].Chunk.anchorTile;
				DistrictController district = PathFinder.Instance.tileMap[anchorTile].cityTile.district;
				StreetController component = Object.Instantiate<GameObject>(PrefabControls.Instance.street, PrefabControls.Instance.cityContainer.transform).GetComponent<StreetController>();
				component.Setup(district);
				if (!CityData.Instance.streetDirectory.Contains(component))
				{
					CityData.Instance.streetDirectory.Add(component);
				}
				float num10 = 0f;
				foreach (Piece item6 in item5)
				{
					for (int num11 = 0; num11 < item6.Chunk.allTiles.Count; num11++)
					{
						component.AddTile(item6.Chunk.allTiles[num11]);
					}
					component.AddChunk(item6.Chunk);
					int num7 = component.chunkSize;
					component.chunkSize = num7 + 1;
					num10 = Mathf.Max(num10, item6.Chunk.footfallNormalized);
				}
				switch (kind)
				{
				case StreetKind.Main:
					component.normalizedFootfall = Mathf.Clamp(num10, 0.6f, 1f);
					component.SetAsStreet();
					break;
				case StreetKind.Back:
					component.normalizedFootfall = Mathf.Clamp(num10, 0.25f, 0.45f);
					component.SetAsBackstreet();
					break;
				default:
					component.normalizedFootfall = Mathf.Min(num10, 0.2f);
					component.SetAsAlley();
					break;
				}
				dictionary2[kind] = dictionary2.GetValueOrDefault(kind) + 1;
			}
			Enumerator<StreetController> enumerator7 = CityData.Instance.streetDirectory.GetEnumerator();
			while (enumerator7.MoveNext())
			{
				enumerator7.Current.LoadStreetTiles();
			}
			int value = gaps.Values.Count((Piece piece4) => piece4.Painted);
			Plugin.Logger.LogInfo((object)($"Painted streets: {value} of {gaps.Count} gaps painted, {dictionary.Count} crossings; {dictionary2.GetValueOrDefault(StreetKind.Main)} main streets, {dictionary2.GetValueOrDefault(StreetKind.Back)} back streets, {dictionary2.GetValueOrDefault(StreetKind.Alley)} alleys" + ((list.Count > 0) ? $", {list.Count} unrecognised pieces" : "")));
		}

		private static StreetKind Auto(StreetChunk chunk)
		{
			if (!(chunk.footfallNormalized >= 0.5f))
			{
				if (!(chunk.footfallNormalized < 0.15f))
				{
					return StreetKind.Back;
				}
				return StreetKind.Alley;
			}
			return StreetKind.Main;
		}

		private static T Get<TK, T>(Dictionary<TK, T> d, TK key) where T : class
		{
			if (!d.TryGetValue(key, out var value))
			{
				return null;
			}
			return value;
		}

		private static bool Same(Piece a, Piece b)
		{
			if (b != null)
			{
				return b.Kind == a.Kind;
			}
			return false;
		}

		private static IEnumerable<Piece> Around(Piece j, Dictionary<Gap, Piece> gaps)
		{
			yield return Get(gaps, new Gap(Vertical: true, j.CornerX, j.CornerY));
			yield return Get(gaps, new Gap(Vertical: true, j.CornerX, j.CornerY + 1));
			yield return Get(gaps, new Gap(Vertical: false, j.CornerX, j.CornerY));
			yield return Get(gaps, new Gap(Vertical: false, j.CornerX + 1, j.CornerY));
		}

		private static IEnumerable<Piece> Neighbours(Piece p, Dictionary<Gap, Piece> gaps, Dictionary<(int, int), Piece> junctions)
		{
			if (p.Junction)
			{
				return Around(p, gaps);
			}
			Gap gap = p.Gap;
			if (gap.Vertical)
			{
				return new Piece[2]
				{
					Get(junctions, (gap.X, gap.Y - 1)),
					Get(junctions, (gap.X, gap.Y))
				};
			}
			return new Piece[2]
			{
				Get(junctions, (gap.X - 1, gap.Y)),
				Get(junctions, (gap.X, gap.Y))
			};
		}

		private static int LineLength(Dictionary<Gap, Piece> gaps, bool northSouth, int line, int pos, StreetKind kind)
		{
			int num = 0;
			int num2 = pos;
			while (true)
			{
				Piece piece = Get(gaps, new Gap(northSouth, northSouth ? line : num2, northSouth ? num2 : line));
				if (piece == null || piece.Kind != kind)
				{
					break;
				}
				num++;
				num2--;
			}
			int num3 = pos + 1;
			while (true)
			{
				Piece piece2 = Get(gaps, new Gap(northSouth, northSouth ? line : num3, northSouth ? num3 : line));
				if (piece2 == null || piece2.Kind != kind)
				{
					break;
				}
				num++;
				num3++;
			}
			return num;
		}

		private static void Step(ref List<Piece> run, Piece p, List<List<Piece>> groups)
		{
			if (p == null || p.Kind == StreetKind.Alley || p.Group != null)
			{
				run = null;
				return;
			}
			if (run == null || run[0].Kind != p.Kind)
			{
				run = new List<Piece>();
				groups.Add(run);
			}
			run.Add(p);
			p.Group = run;
		}
	}
}