Decompiled source of TABS Bannerlord v0.1.1

plugins/TabsBannerlord.Core.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using TabsBannerlord.Core.Battle;
using TabsBannerlord.Core.Campaign;
using TabsBannerlord.Core.Common;
using TabsBannerlord.Core.Content;
using TabsBannerlord.Core.Diagnostics;
using TabsBannerlord.Core.Serialization;
using TabsBannerlord.Core.State;
using TabsBannerlord.Core.World;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("TabsBannerlord.Core")]
[assembly: InternalsVisibleTo("TabsBannerlord.Core.Tests")]
[assembly: AssemblyVersion("0.1.0.0")]
namespace TabsBannerlord.Core.World
{
	public sealed class PathResult
	{
		public bool Success;

		public List<Vec2> Points = new List<Vec2>();

		public Vec2 Goal;

		public bool GoalAdjusted;

		public string Message = "";

		public double Cost;
	}
	public sealed class NavGrid
	{
		private sealed class MinHeap
		{
			private int[] _items = new int[1024];

			private double[] _keys = new double[1024];

			private int _count;

			public int Count => _count;

			public void Clear()
			{
				_count = 0;
			}

			public void Push(int item, double key)
			{
				if (_count == _items.Length)
				{
					Array.Resize(ref _items, _count * 2);
					Array.Resize(ref _keys, _count * 2);
				}
				int num = _count++;
				_items[num] = item;
				_keys[num] = key;
				while (num > 0)
				{
					int num2 = (num - 1) / 2;
					if (!(_keys[num2] <= _keys[num]))
					{
						Swap(num2, num);
						num = num2;
						continue;
					}
					break;
				}
			}

			public int Pop()
			{
				int result = _items[0];
				_count--;
				_items[0] = _items[_count];
				_keys[0] = _keys[_count];
				int num = 0;
				while (true)
				{
					int num2 = 2 * num + 1;
					int num3 = num2 + 1;
					int num4 = num;
					if (num2 < _count && _keys[num2] < _keys[num4])
					{
						num4 = num2;
					}
					if (num3 < _count && _keys[num3] < _keys[num4])
					{
						num4 = num3;
					}
					if (num4 == num)
					{
						break;
					}
					Swap(num4, num);
					num = num4;
				}
				return result;
			}

			private void Swap(int a, int b)
			{
				int num = _items[a];
				_items[a] = _items[b];
				_items[b] = num;
				double num2 = _keys[a];
				_keys[a] = _keys[b];
				_keys[b] = num2;
			}
		}

		public readonly int Width;

		public readonly int Height;

		public readonly double CellSize;

		private readonly TerrainDef[] _cells;

		private readonly int[] _component;

		private readonly bool[] _passable;

		private readonly double[] _inverseSpeed;

		private double[] _g;

		private int[] _came;

		private int[] _seen;

		private int[] _closed;

		private int _search;

		private readonly MinHeap _open = new MinHeap();

		private static readonly int[] Dx = new int[8] { 1, -1, 0, 0, 1, 1, -1, -1 };

		private static readonly int[] Dy = new int[8] { 0, 0, 1, -1, 1, -1, 1, -1 };

		private double _maxSpeed = -1.0;

		public double WorldWidth => (double)Width * CellSize;

		public double WorldHeight => (double)Height * CellSize;

		private double MaxTerrainSpeed
		{
			get
			{
				if (_maxSpeed < 0.0)
				{
					_maxSpeed = 1.0;
					TerrainDef[] cells = _cells;
					foreach (TerrainDef terrainDef in cells)
					{
						if (terrainDef.Passable)
						{
							_maxSpeed = Math.Max(_maxSpeed, terrainDef.Speed);
						}
					}
				}
				return _maxSpeed;
			}
		}

		public NavGrid(WorldDef world)
		{
			Height = world.Rows.Count;
			Width = world.Rows[0].Length;
			CellSize = world.CellSize;
			Dictionary<char, TerrainDef> dictionary = new Dictionary<char, TerrainDef>();
			foreach (TerrainDef item in world.Terrain)
			{
				dictionary[item.Symbol[0]] = item;
			}
			_cells = new TerrainDef[Width * Height];
			for (int i = 0; i < Height; i++)
			{
				for (int j = 0; j < Width; j++)
				{
					_cells[i * Width + j] = dictionary[world.Rows[i][j]];
				}
			}
			_component = new int[Width * Height];
			_passable = new bool[_cells.Length];
			_inverseSpeed = new double[_cells.Length];
			for (int k = 0; k < _cells.Length; k++)
			{
				_passable[k] = _cells[k].Passable;
				_inverseSpeed[k] = 1.0 / _cells[k].Speed;
			}
			LabelComponents();
		}

		public bool InBounds(int x, int y)
		{
			if (x >= 0 && y >= 0 && x < Width)
			{
				return y < Height;
			}
			return false;
		}

		public TerrainDef Cell(int x, int y)
		{
			return _cells[y * Width + x];
		}

		public (int x, int y) ToCell(Vec2 p)
		{
			int val = (int)Math.Floor(p.X / CellSize);
			int val2 = (int)Math.Floor(p.Y / CellSize);
			return (x: Math.Max(0, Math.Min(Width - 1, val)), y: Math.Max(0, Math.Min(Height - 1, val2)));
		}

		public Vec2 CellCenter(int x, int y)
		{
			return new Vec2(((double)x + 0.5) * CellSize, ((double)y + 0.5) * CellSize);
		}

		public TerrainDef TerrainAt(Vec2 p)
		{
			var (x, y) = ToCell(p);
			return Cell(x, y);
		}

		public bool IsPassable(Vec2 p)
		{
			if (p.X < 0.0 || p.Y < 0.0 || p.X >= WorldWidth || p.Y >= WorldHeight)
			{
				return false;
			}
			return TerrainAt(p).Passable;
		}

		public int ComponentAt(Vec2 p)
		{
			var (num, num2) = ToCell(p);
			return _component[num2 * Width + num];
		}

		public bool SameComponent(Vec2 a, Vec2 b)
		{
			int num = ComponentAt(a);
			int num2 = ComponentAt(b);
			if (num > 0)
			{
				return num == num2;
			}
			return false;
		}

		private void LabelComponents()
		{
			int num = 0;
			Queue<int> queue = new Queue<int>();
			for (int i = 0; i < _cells.Length; i++)
			{
				if (!_cells[i].Passable || _component[i] != 0)
				{
					continue;
				}
				num++;
				_component[i] = num;
				queue.Enqueue(i);
				while (queue.Count > 0)
				{
					int num2 = queue.Dequeue();
					int num3 = num2 % Width;
					int num4 = num2 / Width;
					for (int j = 0; j < 8; j++)
					{
						int num5 = num3 + Dx[j];
						int num6 = num4 + Dy[j];
						if (InBounds(num5, num6))
						{
							int num7 = num6 * Width + num5;
							if (_cells[num7].Passable && _component[num7] == 0 && (j < 4 || DiagonalOpen(num3, num4, num5, num6)))
							{
								_component[num7] = num;
								queue.Enqueue(num7);
							}
						}
					}
				}
			}
		}

		private bool DiagonalOpen(int x, int y, int nx, int ny)
		{
			if (Cell(nx, y).Passable)
			{
				return Cell(x, ny).Passable;
			}
			return false;
		}

		public bool TryNearestReachable(Vec2 from, Vec2 target, out Vec2 result)
		{
			int num = ComponentAt(from);
			result = target;
			if (num == 0)
			{
				return false;
			}
			if (IsPassable(target) && ComponentAt(target) == num)
			{
				return true;
			}
			(int x, int y) tuple = ToCell(target);
			int item = tuple.x;
			int item2 = tuple.y;
			int num2 = Math.Max(Width, Height);
			for (int i = 1; i <= num2; i++)
			{
				double num3 = double.MaxValue;
				bool flag = false;
				for (int j = item2 - i; j <= item2 + i; j++)
				{
					for (int k = item - i; k <= item + i; k++)
					{
						if (Math.Max(Math.Abs(k - item), Math.Abs(j - item2)) == i && InBounds(k, j) && _component[j * Width + k] == num)
						{
							Vec2 vec = CellCenter(k, j);
							double lengthSquared = (vec - target).LengthSquared;
							if (lengthSquared < num3)
							{
								num3 = lengthSquared;
								result = vec;
								flag = true;
							}
						}
					}
				}
				if (flag)
				{
					return true;
				}
			}
			return false;
		}

		public bool LineWalkable(Vec2 a, Vec2 b, out double maxCost)
		{
			maxCost = 0.0;
			if (!IsPassable(a) || !IsPassable(b))
			{
				return false;
			}
			(int x, int y) tuple = ToCell(a);
			int num = tuple.x;
			int num2 = tuple.y;
			(int x, int y) tuple2 = ToCell(b);
			int item = tuple2.x;
			int item2 = tuple2.y;
			double num3 = b.X - a.X;
			double num4 = b.Y - a.Y;
			int num5 = ((num3 > 0.0) ? 1 : ((num3 < 0.0) ? (-1) : 0));
			int num6 = ((num4 > 0.0) ? 1 : ((num4 < 0.0) ? (-1) : 0));
			double num7 = ((num5 == 0) ? double.PositiveInfinity : (((double)(num + ((num5 > 0) ? 1 : 0)) * CellSize - a.X) / num3));
			double num8 = ((num6 == 0) ? double.PositiveInfinity : (((double)(num2 + ((num6 > 0) ? 1 : 0)) * CellSize - a.Y) / num4));
			double num9 = ((num5 == 0) ? double.PositiveInfinity : (CellSize / Math.Abs(num3)));
			double num10 = ((num6 == 0) ? double.PositiveInfinity : (CellSize / Math.Abs(num4)));
			int num11 = Width + Height + 4;
			while (num11-- > 0)
			{
				if (!InBounds(num, num2))
				{
					return false;
				}
				TerrainDef terrainDef = Cell(num, num2);
				if (!terrainDef.Passable)
				{
					return false;
				}
				maxCost = Math.Max(maxCost, 1.0 / terrainDef.Speed);
				if (num == item && num2 == item2)
				{
					return true;
				}
				if (Math.Abs(num7 - num8) < 1E-12)
				{
					if (!InBounds(num + num5, num2) || !InBounds(num, num2 + num6) || !Cell(num + num5, num2).Passable || !Cell(num, num2 + num6).Passable)
					{
						return false;
					}
					num += num5;
					num2 += num6;
					num7 += num9;
					num8 += num10;
				}
				else if (num7 < num8)
				{
					num += num5;
					num7 += num9;
				}
				else
				{
					num2 += num6;
					num8 += num10;
				}
			}
			return false;
		}

		public bool LineWalkable(Vec2 a, Vec2 b)
		{
			double maxCost;
			return LineWalkable(a, b, out maxCost);
		}

		public PathResult FindPath(Vec2 start, Vec2 goal)
		{
			PathResult pathResult = new PathResult
			{
				Goal = goal
			};
			if (!IsPassable(start) && !TryNearestPassable(start, out start))
			{
				pathResult.Message = "Start position is not on traversable terrain.";
				return pathResult;
			}
			if (!IsPassable(goal) || !SameComponent(start, goal))
			{
				if (!TryNearestReachable(start, goal, out var result))
				{
					pathResult.Message = "No reachable position near the destination.";
					return pathResult;
				}
				pathResult.GoalAdjusted = true;
				pathResult.Message = (IsPassable(goal) ? "Can't get there by land; heading as close as you can." : "Can't get there; heading as close as you can.");
				goal = result;
				pathResult.Goal = goal;
			}
			var (num, num2) = ToCell(start);
			var (num3, num4) = ToCell(goal);
			if (num == num3 && num2 == num4)
			{
				pathResult.Success = true;
				pathResult.Points.Add(goal);
				return pathResult;
			}
			if (LineWalkable(start, goal, out var maxCost) && maxCost <= 1.0 / MinSpeedOnLine(start, goal) + 1E-09)
			{
				pathResult.Success = true;
				pathResult.Points.Add(goal);
				pathResult.Cost = (goal - start).Length * maxCost;
				return pathResult;
			}
			int num5 = Width * Height;
			if (_g == null)
			{
				_g = new double[num5];
				_came = new int[num5];
				_seen = new int[num5];
				_closed = new int[num5];
			}
			if (++_search == int.MaxValue)
			{
				Array.Clear(_seen, 0, num5);
				Array.Clear(_closed, 0, num5);
				_search = 1;
			}
			int search = _search;
			double[] g = _g;
			int[] came = _came;
			int[] seen = _seen;
			int[] closed = _closed;
			int num6 = num2 * Width + num;
			int num7 = num4 * Width + num3;
			g[num6] = 0.0;
			came[num6] = -1;
			seen[num6] = search;
			MinHeap open = _open;
			open.Clear();
			open.Push(num6, Heuristic(num, num2, num3, num4));
			long num8 = 0L;
			while (open.Count > 0)
			{
				int num9 = open.Pop();
				if (closed[num9] == search)
				{
					continue;
				}
				closed[num9] = search;
				if (num9 == num7)
				{
					break;
				}
				int num10 = num9 % Width;
				int num11 = num9 / Width;
				double num12 = _inverseSpeed[num9];
				for (int i = 0; i < 8; i++)
				{
					int num13 = num10 + Dx[i];
					int num14 = num11 + Dy[i];
					if (num13 < 0 || num14 < 0 || num13 >= Width || num14 >= Height)
					{
						continue;
					}
					int num15 = num14 * Width + num13;
					if (_passable[num15] && closed[num15] != search && (i < 4 || (_passable[num11 * Width + num13] && _passable[num14 * Width + num10])))
					{
						double num16 = ((i >= 4) ? 1.41421356237 : 1.0) * CellSize * 0.5 * (_inverseSpeed[num15] + num12);
						double num17 = g[num9] + num16;
						double num18 = ((seen[num15] == search) ? g[num15] : double.MaxValue);
						if (num17 < num18 - 1E-12)
						{
							g[num15] = num17;
							came[num15] = num9;
							seen[num15] = search;
							open.Push(num15, num17 + Heuristic(num13, num14, num3, num4) + (double)(++num8) * 1E-09);
						}
					}
				}
			}
			if (seen[num7] != search || came[num7] == -1)
			{
				pathResult.Message = "No route found.";
				return pathResult;
			}
			List<Vec2> list = new List<Vec2>();
			for (int num19 = num7; num19 != num6; num19 = came[num19])
			{
				list.Add(CellCenter(num19 % Width, num19 / Width));
			}
			list.Reverse();
			list[list.Count - 1] = goal;
			pathResult.Points = Smooth(start, list);
			pathResult.Cost = g[num7];
			pathResult.Success = true;
			return pathResult;
		}

		public bool Reaches(Vec2 start, Vec2 goal, double tolerance)
		{
			if (!IsPassable(start) && !TryNearestPassable(start, out start))
			{
				return false;
			}
			if (IsPassable(goal) && SameComponent(start, goal))
			{
				return true;
			}
			if (TryNearestReachable(start, goal, out var result))
			{
				return Vec2.Distance(result, goal) <= tolerance;
			}
			return false;
		}

		private double MinSpeedOnLine(Vec2 a, Vec2 b)
		{
			return Math.Max(TerrainAt(a).Speed, TerrainAt(b).Speed);
		}

		private bool TryNearestPassable(Vec2 p, out Vec2 result)
		{
			(int x, int y) tuple = ToCell(p);
			int item = tuple.x;
			int item2 = tuple.y;
			result = p;
			for (int i = 1; i < Math.Max(Width, Height); i++)
			{
				for (int j = item2 - i; j <= item2 + i; j++)
				{
					for (int k = item - i; k <= item + i; k++)
					{
						if (InBounds(k, j) && Cell(k, j).Passable)
						{
							result = CellCenter(k, j);
							return true;
						}
					}
				}
			}
			return false;
		}

		private double Heuristic(int x, int y, int gx, int gy)
		{
			int num = Math.Abs(x - gx);
			int num2 = Math.Abs(y - gy);
			return ((double)(num + num2) + -0.5857864376299999 * (double)Math.Min(num, num2)) * CellSize / MaxTerrainSpeed;
		}

		private List<Vec2> Smooth(Vec2 start, List<Vec2> cells)
		{
			List<Vec2> list = new List<Vec2>();
			Vec2 a = start;
			int num = 0;
			while (num < cells.Count)
			{
				int num2 = num;
				double num3 = 0.0;
				for (int i = num; i < cells.Count; i++)
				{
					Vec2 a2 = ((i == 0) ? start : cells[i - 1]);
					num3 = Math.Max(num3, 1.0 / TerrainAt(Vec2.Lerp(a2, cells[i], 0.5)).Speed);
					if (!LineWalkable(a, cells[i], out var maxCost) || maxCost > num3 + 1E-09)
					{
						break;
					}
					num2 = i;
				}
				list.Add(cells[num2]);
				a = cells[num2];
				num = num2 + 1;
			}
			return list;
		}
	}
	public static class ProceduralWorld
	{
		private sealed class IslandRoadHeap
		{
			private readonly List<KeyValuePair<int, double>> _items = new List<KeyValuePair<int, double>>();

			public int Count => _items.Count;

			public void Push(int cell, double cost)
			{
				KeyValuePair<int, double> keyValuePair = new KeyValuePair<int, double>(cell, cost);
				int num = _items.Count;
				_items.Add(keyValuePair);
				while (num > 0)
				{
					int num2 = (num - 1) / 2;
					if (_items[num2].Value <= cost)
					{
						break;
					}
					_items[num] = _items[num2];
					num = num2;
				}
				_items[num] = keyValuePair;
			}

			public int Pop()
			{
				int key = _items[0].Key;
				int num = _items.Count - 1;
				KeyValuePair<int, double> value = _items[num];
				_items.RemoveAt(num);
				if (num == 0)
				{
					return key;
				}
				int num2 = 0;
				while (num2 * 2 + 1 < num)
				{
					int num3 = num2 * 2 + 1;
					if (num3 + 1 < num && _items[num3 + 1].Value < _items[num3].Value)
					{
						num3++;
					}
					if (value.Value <= _items[num3].Value)
					{
						break;
					}
					_items[num2] = _items[num3];
					num2 = num3;
				}
				_items[num2] = value;
				return key;
			}
		}

		public const string WorldId = "world.continent";

		public const string ScenarioId = "scenario.continent";

		public const int Version = 4;

		public const int FirstRealmsVersion = 5;

		public const int RealmsVersion = 6;

		public const int MinRealms = 2;

		public const int MaxRealms = 20;

		public const int Width = 300;

		public const int Height = 200;

		private const int LegacyWidth = 240;

		private const int LegacyHeight = 160;

		public static readonly string[] Cultures = new string[12]
		{
			"viking", "spooky", "fantasygood", "dynasty", "tribal", "medieval", "farmer", "fantasyevil", "pirate", "renaissance",
			"ancient", "wildwest"
		};

		private static readonly string[] Names = new string[12]
		{
			"Frostheim", "Hollowmere", "Dawnspire", "Jade Reach", "Bonewood", "Wobblemark", "Harvest League", "Ember Dominion", "Tidebound Ports", "Gilded Republic",
			"Marble League", "Dustfall Frontier"
		};

		private static readonly string[] Biomes = new string[12]
		{
			"snow", "swamp", "golden", "sakura", "autumn", "grassland", "grassland", "volcanic", "coast", "grassland",
			"grassland", "desert"
		};

		private static readonly string[] Capitals = new string[12]
		{
			"Skaldhavn", "Lantern Hollow", "Dawnspire", "Jade Pavilion", "Bonewood Hearth", "Wobbleton", "Golden Acres", "Emberwatch", "Tumbledown Port", "Gilded Square",
			"Marblehaven", "Dustfall"
		};

		private static readonly string[] CountWords = new string[21]
		{
			"", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine",
			"Ten", "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen",
			"Twenty"
		};

		private static readonly Vec2[] IslandOutline = new Vec2[34]
		{
			new Vec2(15.0, 70.0),
			new Vec2(23.0, 54.0),
			new Vec2(18.0, 39.0),
			new Vec2(32.0, 21.0),
			new Vec2(48.0, 15.0),
			new Vec2(67.0, 25.0),
			new Vec2(82.0, 20.0),
			new Vec2(102.0, 29.0),
			new Vec2(122.0, 17.0),
			new Vec2(151.0, 12.0),
			new Vec2(173.0, 21.0),
			new Vec2(181.0, 34.0),
			new Vec2(199.0, 28.0),
			new Vec2(216.0, 36.0),
			new Vec2(229.0, 53.0),
			new Vec2(218.0, 68.0),
			new Vec2(223.0, 86.0),
			new Vec2(228.0, 105.0),
			new Vec2(215.0, 122.0),
			new Vec2(211.0, 137.0),
			new Vec2(195.0, 146.0),
			new Vec2(177.0, 141.0),
			new Vec2(163.0, 128.0),
			new Vec2(147.0, 140.0),
			new Vec2(129.0, 145.0),
			new Vec2(105.0, 135.0),
			new Vec2(87.0, 143.0),
			new Vec2(67.0, 140.0),
			new Vec2(53.0, 129.0),
			new Vec2(38.0, 136.0),
			new Vec2(24.0, 126.0),
			new Vec2(19.0, 109.0),
			new Vec2(27.0, 94.0),
			new Vec2(14.0, 85.0)
		};

		private static readonly Vec2[] IslandRealms = new Vec2[12]
		{
			new Vec2(44.5, 38.5),
			new Vec2(89.5, 45.5),
			new Vec2(142.5, 35.5),
			new Vec2(196.5, 49.5),
			new Vec2(42.5, 80.5),
			new Vec2(95.5, 82.5),
			new Vec2(147.5, 78.5),
			new Vec2(197.5, 87.5),
			new Vec2(47.5, 115.5),
			new Vec2(94.5, 116.5),
			new Vec2(140.5, 116.5),
			new Vec2(191.5, 118.5)
		};

		private static readonly string[] MarketTowns = new string[12]
		{
			"Ravenfjord", "Gloamwick", "Lumenhall", "Lotus Gate", "Drumrock", "Fumblebury", "Turnip Cross", "Ashfall", "Barnacle Bay", "Florin Bridge",
			"Columnae", "Tumbleweed Gulch"
		};

		private static readonly string[] VillageSlots = new string[8] { "fields", "hollow", "brook", "crossing", "heath", "meadow", "orchard", "landing" };

		private static readonly string[] FoodSlots = new string[3] { "fields", "hollow", "meadow" };

		private static readonly Dictionary<string, string[]> CultureProducts = new Dictionary<string, string[]>(StringComparer.Ordinal)
		{
			{
				"viking",
				new string[6] { "good.fish", "good.fur", "good.hides", "good.timber", "good.ore", "good.meat" }
			},
			{
				"spooky",
				new string[6] { "good.fish", "good.flax", "good.clay", "good.meat", "good.timber", "good.salt" }
			},
			{
				"fantasygood",
				new string[5] { "good.grain", "good.grapes", "good.horses", "good.wool", "good.silver" }
			},
			{
				"dynasty",
				new string[5] { "good.grain", "good.cotton", "good.fish", "good.silver", "good.clay" }
			},
			{
				"tribal",
				new string[5] { "good.meat", "good.hides", "good.fur", "good.timber", "good.wool" }
			},
			{
				"medieval",
				new string[6] { "good.grain", "good.wool", "good.horses", "good.ore", "good.hides", "good.timber" }
			},
			{
				"farmer",
				new string[6] { "good.grain", "good.meat", "good.wool", "good.flax", "good.hides", "good.grapes" }
			},
			{
				"fantasyevil",
				new string[5] { "good.meat", "good.ore", "good.silver", "good.salt", "good.clay" }
			},
			{
				"pirate",
				new string[5] { "good.fish", "good.salt", "good.olives", "good.grapes", "good.timber" }
			},
			{
				"renaissance",
				new string[6] { "good.grapes", "good.olives", "good.cotton", "good.silver", "good.clay", "good.grain" }
			},
			{
				"ancient",
				new string[6] { "good.olives", "good.grapes", "good.fish", "good.clay", "good.salt", "good.grain" }
			},
			{
				"wildwest",
				new string[6] { "good.dates", "good.meat", "good.horses", "good.hides", "good.silver", "good.salt" }
			}
		};

		public static WorldDef Resolve(ContentDatabase content, CampaignState state)
		{
			string text = StateError(state);
			if (text.Length > 0)
			{
				throw new ArgumentException(text);
			}
			if (state.WorldId != "world.continent")
			{
				return content.World(state.WorldId);
			}
			if (state.WorldGeneration.Version < 5)
			{
				return Generate(state.WorldGeneration.Seed, state.WorldGeneration.Version);
			}
			return Generate(state.WorldGeneration.Seed, state.WorldGeneration.Realms, state.WorldGeneration.Version);
		}

		public static string StateError(CampaignState state)
		{
			if (state.WorldId == "world.continent")
			{
				if (state.WorldGeneration == null)
				{
					return "The procedural world is missing its saved generation identity.";
				}
				int version = state.WorldGeneration.Version;
				switch (version)
				{
				default:
					return "Unsupported procedural world version " + version + ".";
				case 5:
				case 6:
				{
					string text = RealmsError(state.WorldGeneration.Realms);
					if (text.Length > 0)
					{
						return text;
					}
					break;
				}
				case 1:
				case 2:
				case 3:
				case 4:
					if (state.WorldGeneration.Realms != null && state.WorldGeneration.Realms.Count > 0)
					{
						return "Only a version " + 5 + " or later world has a chosen list of realms.";
					}
					break;
				}
			}
			else if (state.WorldGeneration != null)
			{
				return "An authored world cannot use procedural generation metadata.";
			}
			return "";
		}

		public static void Register(ContentDatabase content)
		{
			if (!Cultures.Any((string id) => !content.Cultures.Contains(id)))
			{
				if (!content.Worlds.ContainsKey("world.continent"))
				{
					content.Worlds.Add("world.continent", Generate(1uL));
				}
				if (!content.Scenarios.Contains("scenario.continent"))
				{
					content.Scenarios.Add(CreateScenario(content, DefaultRealms()));
				}
			}
		}

		private static List<RosterEntryDef> Garrison(List<WeightedUnit> line, int count, string basic)
		{
			List<RosterEntryDef> list = (from e in DefenderTroops.Split(line, count)
				select new RosterEntryDef
				{
					Unit = e.Key,
					Count = e.Value
				}).ToList();
			if (list.Count == 0)
			{
				list.Add(new RosterEntryDef
				{
					Unit = basic,
					Count = count
				});
			}
			return list;
		}

		public static WorldDef Generate(ulong seed)
		{
			return Generate(seed, 4);
		}

		public static WorldDef Generate(ulong seed, int version)
		{
			return version switch
			{
				1 => GenerateVersion1(seed), 
				2 => GenerateVersion2(seed), 
				3 => GenerateVersion3(seed), 
				4 => GenerateVersion4(seed), 
				_ => throw new ArgumentOutOfRangeException("version", version, "Unsupported procedural world version."), 
			};
		}

		public static List<RealmSpec> DefaultRealms()
		{
			List<RealmSpec> list = new List<RealmSpec>();
			for (int i = 0; i < Cultures.Length; i++)
			{
				list.Add(new RealmSpec
				{
					Culture = Cultures[i],
					Name = Names[i],
					Capital = Capitals[i],
					MarketTown = MarketTowns[i],
					Biome = Biomes[i]
				});
			}
			return list;
		}

		public static RealmSpec DefaultRealm(string culture)
		{
			int num = Array.IndexOf(Cultures, culture);
			if (num >= 0)
			{
				return DefaultRealms()[num];
			}
			return null;
		}

		public static bool IsDefaultSet(IList<RealmSpec> realms)
		{
			if (realms != null && realms.Count == Cultures.Length)
			{
				return realms.Select((RealmSpec r) => r?.Culture).SequenceEqual(Cultures);
			}
			return false;
		}

		public static string RealmsError(IList<RealmSpec> realms)
		{
			if (realms == null || realms.Count < 2)
			{
				return "A campaign needs at least " + 2 + " realms.";
			}
			if (realms.Count > 20)
			{
				return "A campaign can have at most " + 20 + " realms.";
			}
			if (realms.Any((RealmSpec r) => r == null || string.IsNullOrWhiteSpace(r.Culture) || string.IsNullOrWhiteSpace(r.Name) || string.IsNullOrWhiteSpace(r.Capital) || string.IsNullOrWhiteSpace(r.MarketTown)))
			{
				return "Every realm needs a culture and names for its places.";
			}
			if (realms.Select((RealmSpec r) => r.Culture).Distinct<string>(StringComparer.Ordinal).Count() != realms.Count)
			{
				return "Each faction can rule only one realm.";
			}
			if (realms.Any((RealmSpec r) => r.Culture.Any((char c) => char.IsWhiteSpace(c) || char.IsControl(c))))
			{
				return "A realm's culture id is malformed.";
			}
			return "";
		}

		public static WorldDef Generate(ulong seed, IList<RealmSpec> realms)
		{
			return Generate(seed, realms, 6);
		}

		public static WorldDef Generate(ulong seed, IList<RealmSpec> realms, int version)
		{
			string text = RealmsError(realms);
			if (text.Length > 0)
			{
				throw new ArgumentException(text);
			}
			if (version < 5 || version > 6)
			{
				throw new ArgumentOutOfRangeException("version", version, "Unsupported procedural world version.");
			}
			return GenerateIsland(seed, version, realms.ToList());
		}

		private static WorldDef GenerateVersion1(ulong seed)
		{
			WorldDef worldDef = new WorldDef
			{
				Id = "world.continent",
				Name = "The Wobbling Continent",
				CellSize = 1.0,
				GenerationVersion = 1,
				GenerationSeed = seed
			};
			worldDef.Terrain = Legend();
			for (int i = 0; i < Cultures.Length; i++)
			{
				worldDef.Regions.Add(new WorldRegionDef
				{
					Id = "region." + Cultures[i],
					Culture = Cultures[i],
					Name = Names[i],
					Biome = Biomes[i],
					X = 33.5 + (double)(i % 4 * 57) + (double)(int)(Noise(seed, i, 7) * 7.0) - 3.0,
					Y = 30.5 + (double)(i / 4 * 49) + (double)(int)(Noise(seed, i, 13) * 7.0) - 3.0
				});
			}
			char[,] array = new char[240, 160];
			for (int j = 0; j < 160; j++)
			{
				for (int k = 0; k < 240; k++)
				{
					WorldRegionDef worldRegionDef = RegionAt(worldDef, k, j);
					double num = ((double)k - 120.0) / 114.0;
					double num2 = ((double)j - 80.0) / 72.0;
					double num3 = Math.Atan2(num2, num);
					double num4 = Math.Sqrt(num * num + num2 * num2);
					double num5 = 0.91 + 0.075 * Math.Sin(num3 * 5.0 + Noise(seed, 6, 2) * 6.0) + 0.045 * Math.Sin(num3 * 9.0 + Noise(seed, 8, 2) * 6.0);
					double num6 = SmoothNoise(seed + 5, (double)k * 0.085, (double)j * 0.085);
					bool flag = Math.Sqrt(((double)k - worldRegionDef.X) * ((double)k - worldRegionDef.X) + ((double)j - worldRegionDef.Y) * ((double)j - worldRegionDef.Y)) < 23.0 + num6 * 3.0;
					bool flag2 = num4 < num5 + num6 * 0.035;
					if (k < 4 || j < 4 || k >= 236 || j >= 156 || (!flag && !flag2))
					{
						array[k, j] = '~';
						continue;
					}
					if (!flag && num4 > num5 - 0.025)
					{
						array[k, j] = ':';
						continue;
					}
					double num7 = SmoothNoise(seed + 17, (double)k * 0.12, (double)j * 0.12);
					char c = ((worldRegionDef.Biome == "snow") ? 'S' : ((worldRegionDef.Biome == "desert") ? 'D' : ((worldRegionDef.Biome == "swamp") ? 'W' : '.')));
					if (num7 > 0.6)
					{
						c = 'F';
					}
					if (num7 < 0.23)
					{
						c = '^';
					}
					double num8 = 61.0 + 7.0 * Math.Sin((double)j * 0.055 + Noise(seed, 31, 9) * 5.0);
					double num9 = 175.0 + 9.0 * Math.Sin((double)j * 0.043 + Noise(seed, 51, 9) * 5.0);
					if ((Math.Abs((double)k - num8) < 2.7 || Math.Abs((double)k - num9) < 3.0) && num7 > 0.3)
					{
						c = 'M';
					}
					double num10 = 116.0 + 10.0 * Math.Sin((double)j * 0.061 + Noise(seed, 83, 19) * 5.0);
					if (Math.Abs((double)k - num10) < 1.4)
					{
						c = '=';
					}
					array[k, j] = c;
				}
			}
			for (int l = 0; l < worldDef.Regions.Count; l++)
			{
				WorldRegionDef worldRegionDef2 = worldDef.Regions[l];
				string culture = worldRegionDef2.Culture;
				AddLocation(worldDef, array, "town." + culture, Capitals[l], SettlementType.Town, worldRegionDef2.X, worldRegionDef2.Y, culture, "", 8, 900.0);
				AddLocation(worldDef, array, "castle." + culture, Names[l] + " Keep", SettlementType.Castle, worldRegionDef2.X + 14.0, worldRegionDef2.Y - 9.0, culture, "", 5, 550.0);
				AddLocation(worldDef, array, "village." + culture + ".fields", Names[l] + " Fields", SettlementType.Village, worldRegionDef2.X - 11.0, worldRegionDef2.Y + 8.0, culture, "town." + culture, 6, 500.0);
				AddLocation(worldDef, array, "village." + culture + ".hollow", Names[l] + " Hollow", SettlementType.Village, worldRegionDef2.X + 13.0, worldRegionDef2.Y + 10.0, culture, "castle." + culture, 5, 400.0);
				foreach (LocationDef item in worldDef.Locations.Where((LocationDef locationDef) => locationDef.Culture == culture && locationDef.Type != SettlementType.Town))
				{
					Road(array, worldRegionDef2.X, worldRegionDef2.Y, item.X, item.Y);
				}
				if (l % 4 > 0)
				{
					Road(array, worldDef.Regions[l - 1].X, worldDef.Regions[l - 1].Y, worldRegionDef2.X, worldRegionDef2.Y);
				}
				if (l >= 4)
				{
					Road(array, worldDef.Regions[l - 4].X, worldDef.Regions[l - 4].Y, worldRegionDef2.X, worldRegionDef2.Y);
				}
			}
			int[] array2 = new int[5] { 0, 4, 7, 8, 11 };
			foreach (int num12 in array2)
			{
				WorldRegionDef worldRegionDef3 = worldDef.Regions[num12];
				AddLocation(worldDef, array, "hideout.continent." + worldRegionDef3.Culture, Names[num12] + " Outlaw Camp", SettlementType.Hideout, worldRegionDef3.X - 17.0, worldRegionDef3.Y - 12.0, worldRegionDef3.Culture, "", 0, 100.0);
				worldDef.Locations.Last().BanditClan = "clan.continent.outlaws." + worldRegionDef3.Culture;
				Road(array, worldRegionDef3.X - 17.0, worldRegionDef3.Y - 12.0, worldRegionDef3.X, worldRegionDef3.Y);
			}
			for (int num13 = 0; num13 < 160; num13++)
			{
				char[] array3 = new char[240];
				for (int num14 = 0; num14 < 240; num14++)
				{
					array3[num14] = array[num14, num13];
				}
				worldDef.Rows.Add(new string(array3));
			}
			return worldDef;
		}

		private static void AddLocation(WorldDef world, char[,] cells, string id, string name, SettlementType type, double x, double y, string culture, string bound, int recruits, double prosperity)
		{
			for (int i = -2; i <= 2; i++)
			{
				for (int j = -2; j <= 2; j++)
				{
					cells[(int)x + j, (int)y + i] = '.';
				}
			}
			world.Locations.Add(new LocationDef
			{
				Id = id,
				Name = name,
				Type = type,
				X = x,
				Y = y,
				Culture = culture,
				BoundTo = bound,
				RecruitSlots = recruits,
				Prosperity = prosperity
			});
		}

		private static void Road(char[,] cells, double ax, double ay, double bx, double by)
		{
			int num = Math.Max(1, (int)Math.Ceiling(Math.Max(Math.Abs(bx - ax), Math.Abs(by - ay)) * 2.0));
			int num2 = (int)ax;
			int num3 = (int)ay;
			for (int i = 0; i <= num; i++)
			{
				int num4 = (int)(ax + (bx - ax) * (double)i / (double)num);
				int num5 = (int)(ay + (by - ay) * (double)i / (double)num);
				if (num4 != num2 && num5 != num3)
				{
					RoadCell(cells, num4, num3);
				}
				RoadCell(cells, num4, num5);
				num2 = num4;
				num3 = num5;
			}
		}

		private static void RoadCell(char[,] cells, int x, int y)
		{
			if (x <= 1 || y <= 1 || x >= cells.GetLength(0) - 2 || y >= cells.GetLength(1) - 2)
			{
				return;
			}
			for (int i = -1; i <= 1; i++)
			{
				for (int j = -1; j <= 1; j++)
				{
					if (cells[x + j, y + i] == 'M')
					{
						cells[x + j, y + i] = '.';
					}
				}
			}
			char c = cells[x, y];
			cells[x, y] = ((c == '~' || c == '=' || c == '+') ? '+' : '#');
		}

		public static WorldRegionDef RegionAt(WorldDef world, double x, double y)
		{
			WorldRegionDef result = null;
			double num = double.MaxValue;
			foreach (WorldRegionDef region in world.Regions)
			{
				double num2 = (x - region.X) * (x - region.X) + (y - region.Y) * (y - region.Y);
				if (num2 < num)
				{
					num = num2;
					result = region;
				}
			}
			return result;
		}

		public static double HeightAt(WorldDef world, double x, double y)
		{
			if (world.Rows.Count == 0)
			{
				return 1.5;
			}
			double cellSize = world.CellSize;
			int index = Math.Max(0, Math.Min(world.Rows[0].Length - 1, (int)Math.Floor(x / cellSize)));
			int index2 = Math.Max(0, Math.Min(world.Rows.Count - 1, (int)Math.Floor(y / cellSize)));
			char symbol = world.Rows[index2][index];
			TerrainType terrainType = world.Terrain.FirstOrDefault((TerrainDef t) => t.Symbol.Length > 0 && t.Symbol[0] == symbol)?.Type ?? TerrainType.Plains;
			switch (terrainType)
			{
			case TerrainType.Water:
				return -0.25 * cellSize;
			case TerrainType.River:
				return -0.15 * cellSize;
			case TerrainType.Ford:
			case TerrainType.Bridge:
				return 1.5 * cellSize;
			default:
			{
				double num = SmoothNoise(world.GenerationSeed + 41, x / cellSize * 0.045, y / cellSize * 0.045);
				double num2 = 1.5 + num * 0.8;
				if (terrainType == TerrainType.Hills)
				{
					num2 += 1.0 + SmoothNoise(world.GenerationSeed + 59, x / cellSize * 0.17, y / cellSize * 0.17) * 2.6;
				}
				if (terrainType == TerrainType.Mountains)
				{
					num2 += 6.0 + SmoothNoise(world.GenerationSeed + 61, x / cellSize * 0.12, y / cellSize * 0.12) * 6.0;
				}
				if (terrainType == TerrainType.Beach)
				{
					num2 = 1.3 + num * 0.2;
				}
				return num2 * cellSize;
			}
			}
		}

		private static double Noise(ulong seed, int x, int y)
		{
			ulong b = (ulong)((x * -7046029254386353131L) ^ (y * -4658895280553007687L));
			return (double)(StableHash.Combine(seed, b) >> 11) * 1.1102230246251565E-16;
		}

		private static double SmoothNoise(ulong seed, double x, double y)
		{
			int num = (int)Math.Floor(x);
			int num2 = (int)Math.Floor(y);
			double num3 = x - (double)num;
			double num4 = y - (double)num2;
			num3 = num3 * num3 * (3.0 - 2.0 * num3);
			num4 = num4 * num4 * (3.0 - 2.0 * num4);
			double num5 = Noise(seed, num, num2) * (1.0 - num3) + Noise(seed, num + 1, num2) * num3;
			double num6 = Noise(seed, num, num2 + 1) * (1.0 - num3) + Noise(seed, num + 1, num2 + 1) * num3;
			return num5 * (1.0 - num4) + num6 * num4;
		}

		private static List<TerrainDef> Legend()
		{
			return new List<TerrainDef>
			{
				Terrain(".", TerrainType.Plains, passable: true, 1.0, "grassland"),
				Terrain("F", TerrainType.Forest, passable: true, 0.72, "forest", 0.75),
				Terrain("^", TerrainType.Hills, passable: true, 0.8, "hills"),
				Terrain("M", TerrainType.Mountains, passable: false, 0.3, "mountains"),
				Terrain("~", TerrainType.Water, passable: false, 0.2, "water"),
				Terrain("=", TerrainType.River, passable: false, 0.2, "river"),
				Terrain("#", TerrainType.Road, passable: true, 1.35, "grassland"),
				Terrain("+", TerrainType.Bridge, passable: true, 1.15, "grassland"),
				Terrain("S", TerrainType.Snow, passable: true, 0.8, "snow"),
				Terrain("D", TerrainType.Desert, passable: true, 0.85, "desert"),
				Terrain("W", TerrainType.Swamp, passable: true, 0.6, "swamp", 0.8),
				Terrain(":", TerrainType.Beach, passable: true, 0.9, "coast")
			};
		}

		private static TerrainDef Terrain(string symbol, TerrainType type, bool passable, double speed, string biome, double sight = 1.0)
		{
			return new TerrainDef
			{
				Symbol = symbol,
				Type = type,
				Passable = passable,
				Speed = speed,
				Biome = biome,
				Sight = sight
			};
		}

		public static List<string> SettlementIds(IList<RealmSpec> realms)
		{
			List<string> list = new List<string>();
			foreach (RealmSpec realm in realms)
			{
				string culture = realm.Culture;
				list.Add("town." + culture);
				list.Add("castle." + culture);
				list.Add(MarketTownId(culture));
				string[] villageSlots = VillageSlots;
				foreach (string text in villageSlots)
				{
					list.Add("village." + culture + "." + text);
				}
			}
			return list;
		}

		public static ScenarioDef CreateScenario(ContentDatabase content, IList<RealmSpec> realms)
		{
			int count = realms.Count;
			ScenarioDef scenarioDef = new ScenarioDef
			{
				Id = "scenario.continent",
				World = "world.continent",
				Name = "The Wobbling Continent",
				Description = ((count < CountWords.Length) ? CountWords[count] : count.ToString(CultureInfo.InvariantCulture)) + " kingdoms of two towns each across a seed-generated continent of mountain passes, forest roads and river crossings."
			};
			bool flag = realms.Any((RealmSpec r) => r.Culture == "medieval");
			for (int num = 0; num < count; num++)
			{
				string culture = realms[num].Culture;
				string name = realms[num].Name;
				string marketTown = realms[num].MarketTown;
				CultureDef cultureDef = content.Culture(culture);
				string text = "kingdom.continent." + culture;
				string text2 = "clan.continent." + culture;
				string id = text2 + ".wardens";
				string id2 = text2 + ".stewards";
				scenarioDef.Kingdoms.Add(new KingdomStartDef
				{
					Id = text,
					Name = name,
					Culture = culture,
					RulerClan = text2,
					Color = cultureDef.Color,
					Symbol = cultureDef.Symbol
				});
				List<string> list = (from t in cultureDef.TownRecruits
					where content.Unit(t.Unit).Tier <= 3 && content.Unit(t.Unit).Slots == 1
					select t.Unit).Distinct().ToList();
				if (list.Count == 0)
				{
					list.Add(cultureDef.BasicTroop);
				}
				scenarioDef.Clans.Add(Clan(text2, name + " Council", text, cultureDef, "", new string[2]
				{
					"town." + culture,
					"village." + culture + ".fields"
				}, list, 900 + Jitter(text2, 500), 30, 14));
				scenarioDef.Clans.Add(Clan(id, name + " Wardens", text, cultureDef, "", new string[2]
				{
					"castle." + culture,
					"village." + culture + ".hollow"
				}, list, 180 + Jitter(id, 500), 18, 8));
				scenarioDef.Clans.Add(Clan(id2, marketTown + " Stewards", text, cultureDef, "", new string[1] { MarketTownId(culture) }, list, 350 + Jitter(id2, 500), 22, 10));
				List<WeightedUnit> line = DefenderTroops.Line(content, culture);
				string[] array = new string[2]
				{
					"town." + culture,
					"castle." + culture
				};
				foreach (string settlement in array)
				{
					scenarioDef.Garrisons.Add(new GarrisonStartDef
					{
						Settlement = settlement,
						Troops = Garrison(line, 26, cultureDef.BasicTroop)
					});
				}
				scenarioDef.Garrisons.Add(new GarrisonStartDef
				{
					Settlement = MarketTownId(culture),
					Troops = Garrison(line, 22, cultureDef.BasicTroop)
				});
				PlayerStartDef item = Start("start." + culture, cultureDef, "Independent " + cultureDef.Name + " warband");
				scenarioDef.PlayerStarts.Add(item);
				if (culture == "medieval" || (!flag && num == 0))
				{
					if (culture != "medieval")
					{
						scenarioDef.PlayerStarts.Insert(0, Start("start.warband", cultureDef, "Small warband"));
						continue;
					}
					scenarioDef.PlayerStarts.Insert(0, Start("start.warband", cultureDef, "Small warband"));
					PlayerStartDef playerStartDef = Start("start.commander", cultureDef, "Commander of Wobblemark");
					playerStartDef.Kind = StartKind.Commander;
					playerStartDef.HeroUnit = cultureDef.HeroUnit;
					playerStartDef.Gold = 4000;
					playerStartDef.Renown = 250.0;
					playerStartDef.Kingdom = text;
					playerStartDef.StartLocation = "castle." + culture;
					playerStartDef.Food = 30.0;
					playerStartDef.OwnedSettlements.Add("castle." + culture);
					playerStartDef.OwnedSettlements.Add("village." + culture + ".hollow");
					playerStartDef.Troops.Add(new RosterEntryDef
					{
						Unit = list.Last(),
						Count = 10
					});
					playerStartDef.Garrisons.Add(new GarrisonStartDef
					{
						Settlement = "castle." + culture,
						Troops = new List<RosterEntryDef>
						{
							new RosterEntryDef
							{
								Unit = cultureDef.BasicTroop,
								Count = 15
							}
						}
					});
					scenarioDef.PlayerStarts.Insert(1, playerStartDef);
				}
			}
			foreach (string item2 in from i in HideoutRealms(count)
				select realms[i].Culture)
			{
				CultureDef cultureDef2 = content.Culture(item2);
				scenarioDef.Clans.Add(new ClanStartDef
				{
					Id = "clan.continent.outlaws." + item2,
					Name = cultureDef2.Name + " Outlaws",
					Culture = item2,
					IsBandit = true,
					Gold = 0,
					Color = cultureDef2.Color,
					Symbol = "☠",
					BanditTroops = new List<WeightedUnit>
					{
						new WeightedUnit
						{
							Unit = cultureDef2.BasicTroop,
							Weight = 1.0
						}
					}
				});
			}
			HashSet<string> present = new HashSet<string>(realms.Select((RealmSpec r) => r.Culture), StringComparer.Ordinal);
			List<string[]> list2 = new string[3][]
			{
				new string[2] { "viking", "medieval" },
				new string[2] { "fantasygood", "fantasyevil" },
				new string[2] { "pirate", "renaissance" }
			}.Where((string[] pair) => present.Contains(pair[0]) && present.Contains(pair[1])).ToList();
			if (list2.Count == 0)
			{
				for (int num3 = 0; num3 < Math.Max(1, count / 4); num3++)
				{
					list2.Add(new string[2]
					{
						realms[num3].Culture,
						realms[count - 1 - num3].Culture
					});
				}
			}
			foreach (string[] item3 in list2)
			{
				scenarioDef.Stances.Add(new StanceDef
				{
					A = "kingdom.continent." + item3[0],
					B = "kingdom.continent." + item3[1],
					War = true
				});
			}
			scenarioDef.Companions.Add(new CompanionDef
			{
				Id = "hero.companion.continent.trailguide",
				Name = "Pip Pebblefoot",
				Culture = "tribal",
				Unit = "tribal.spearthrower",
				Settlement = "town.medieval",
				Description = "A Bonewood guide who has walked the continent's mountain roads.",
				HireCost = 180L,
				DailyWage = 3,
				Skills = new SortedDictionary<string, int>
				{
					{ "scouting", 35 },
					{ "tactics", 10 }
				}
			});
			scenarioDef.Companions.Add(new CompanionDef
			{
				Id = "hero.companion.continent.healer",
				Name = "Elsie Stitch",
				Culture = "medieval",
				Unit = "medieval.healer",
				Settlement = "town.farmer",
				Description = "A travelling healer tending the harvest communities.",
				HireCost = 300L,
				DailyWage = 4,
				Skills = new SortedDictionary<string, int>
				{
					{ "medicine", 40 },
					{ "stewardship", 10 }
				}
			});
			scenarioDef.Companions.Add(new CompanionDef
			{
				Id = "hero.companion.continent.provisioner",
				Name = "Bryn Barrelback",
				Culture = "viking",
				Unit = "viking.brawler",
				Settlement = "town.pirate",
				Description = "A retired ship provisioner who knows the ports and their prices.",
				HireCost = 260L,
				DailyWage = 4,
				Skills = new SortedDictionary<string, int>
				{
					{ "stewardship", 40 },
					{ "trade", 15 }
				}
			});
			return scenarioDef;
		}

		private static ClanStartDef Clan(string id, string name, string kingdom, CultureDef culture, string hero, string[] settlements, List<string> troops, int renown, int basic, int veterans)
		{
			double num = 0.65 + 0.8 * (double)Jitter(id + "|party", 1000) / 1000.0;
			double num2 = 0.35 + 0.55 * (double)Jitter(id + "|second", 1000) / 1000.0;
			int gold = 4500 + 3000 * Jitter(id + "|gold", 1000) / 1000;
			ClanStartDef clanStartDef = new ClanStartDef
			{
				Id = id,
				Name = name,
				Kingdom = kingdom,
				Culture = culture.Id,
				Gold = gold,
				Renown = renown,
				Color = culture.Color,
				Symbol = culture.Symbol,
				Settlements = settlements.ToList(),
				Lords = new List<LordStartDef>()
			};
			double[] array = new double[2] { 1.0, num2 };
			foreach (double num3 in array)
			{
				clanStartDef.Lords.Add(new LordStartDef
				{
					Unit = hero,
					Troops = new List<RosterEntryDef>
					{
						new RosterEntryDef
						{
							Unit = culture.BasicTroop,
							Count = (int)Math.Round((double)basic * num * num3)
						},
						new RosterEntryDef
						{
							Unit = troops.Last(),
							Count = (int)Math.Round((double)veterans * num * num3)
						}
					}
				});
			}
			return clanStartDef;
		}

		private static int Jitter(string id, int range)
		{
			return (int)(StableHash.Fnv1a64(id) % (ulong)(range + 1));
		}

		private static PlayerStartDef Start(string id, CultureDef culture, string name)
		{
			return new PlayerStartDef
			{
				Id = id,
				Name = name,
				Description = "Begin beside the local fields with a small company and an open road.",
				Culture = culture.Id,
				StartLocation = "village." + culture.Id + ".fields",
				HeroUnit = culture.BasicTroop,
				Gold = 600,
				Food = 10.0,
				Troops = new List<RosterEntryDef>
				{
					new RosterEntryDef
					{
						Unit = culture.BasicTroop,
						Count = 8
					}
				}
			};
		}

		private static WorldDef GenerateVersion2(ulong seed)
		{
			return GenerateIsland(seed, 2, DefaultRealms());
		}

		private static WorldDef GenerateVersion3(ulong seed)
		{
			return GenerateIsland(seed, 3, DefaultRealms());
		}

		private static WorldDef GenerateVersion4(ulong seed)
		{
			return GenerateIsland(seed, 4, DefaultRealms());
		}

		internal static int RealmColumns(int count)
		{
			if (count != Cultures.Length)
			{
				return Math.Max(1, (int)Math.Ceiling(Math.Sqrt((double)(count * 4) / 3.0)));
			}
			return 4;
		}

		private static Vec2 RealmAnchor(int i, int count)
		{
			if (count == Cultures.Length)
			{
				return IslandRealms[i];
			}
			int num = RealmColumns(count);
			int num2 = (count + num - 1) / num;
			int num3 = i / num;
			int num4 = i % num;
			int num5 = ((num3 == num2 - 1) ? (count - num3 * num) : num);
			double num6 = 42.0;
			double num7 = 198.0;
			double num8 = 38.0;
			double num9 = 117.0;
			double x = num6 + (num7 - num6) * ((double)num4 + 0.5 + (double)(num - num5) * 0.5) / (double)num;
			double y = ((num2 == 1) ? ((num8 + num9) / 2.0) : (num8 + (num9 - num8) * (double)num3 / ((double)num2 - 1.0)));
			return new Vec2(x, y);
		}

		private static Vec2[] SpreadRealms(char[,] cells, double[,] coastDistance, int count, double scale)
		{
			int length = cells.GetLength(0);
			int length2 = cells.GetLength(1);
			List<Vec2> list = new List<Vec2>();
			for (int i = 6; i < length2 - 6; i += 2)
			{
				for (int j = 6; j < length - 6; j += 2)
				{
					if (cells[j, i] != '~' && coastDistance[j, i] >= 5.0)
					{
						list.Add(new Vec2((double)j + 0.5, (double)i + 0.5));
					}
				}
			}
			Vec2[] array = (from i2 in Enumerable.Range(0, count)
				select RealmAnchor(i2, count) * scale).ToArray();
			if (list.Count < count)
			{
				return array;
			}
			double[] array2 = new double[count];
			double[] array3 = new double[count];
			int[] array4 = new int[count];
			for (int num = 0; num < 16; num++)
			{
				Array.Clear(array2, 0, count);
				Array.Clear(array3, 0, count);
				Array.Clear(array4, 0, count);
				foreach (Vec2 item in list)
				{
					int num2 = 0;
					double num3 = double.MaxValue;
					for (int num4 = 0; num4 < count; num4++)
					{
						double lengthSquared = (item - array[num4]).LengthSquared;
						if (lengthSquared < num3)
						{
							num3 = lengthSquared;
							num2 = num4;
						}
					}
					array2[num2] += item.X;
					array3[num2] += item.Y;
					array4[num2]++;
				}
				for (int num5 = 0; num5 < count; num5++)
				{
					if (array4[num5] > 0)
					{
						array[num5] = new Vec2(array2[num5] / (double)array4[num5], array3[num5] / (double)array4[num5]);
					}
				}
			}
			Vec2 vec = new Vec2(list.Average((Vec2 p) => p.X), list.Average((Vec2 p) => p.Y));
			for (int num6 = 0; num6 < count; num6++)
			{
				array[num6] = vec + (array[num6] - vec) * 1.12;
			}
			return array;
		}

		internal static List<int> HideoutRealms(int count)
		{
			if (count == Cultures.Length)
			{
				return new List<int> { 0, 4, 7, 8, 11 };
			}
			return (from i in Enumerable.Range(0, count)
				where (i + 1) * 5 / 12 > i * 5 / 12 || (count < 3 && i == 0)
				select i).ToList();
		}

		private static WorldDef GenerateIsland(ulong seed, int version, List<RealmSpec> realms)
		{
			WorldDef worldDef = new WorldDef
			{
				Id = "world.continent",
				Name = "The Wobbling Continent",
				CellSize = 1.0,
				GenerationVersion = version,
				GenerationSeed = seed,
				Terrain = Legend()
			};
			double num = ((version >= 5) ? (1.25 * Math.Sqrt((double)Math.Max(realms.Count, 4) / 12.0)) : ((version >= 4) ? 1.25 : 1.0));
			int num2 = ((version >= 5) ? ((int)Math.Round(240.0 * num)) : ((version >= 4) ? 300 : 240));
			int num3 = ((version >= 5) ? ((int)Math.Round(160.0 * num)) : ((version >= 4) ? 200 : 160));
			List<Vec2> points = ShapeIslandOutline(seed, num);
			double[,] array = new double[num2, num3];
			char[,] array2 = new char[num2, num3];
			for (int i = 0; i < num3; i++)
			{
				for (int j = 0; j < num2; j++)
				{
					double x = (double)j + 0.5 + (SmoothNoise(seed + 101, (double)j * 0.045, (double)i * 0.045) - 0.5) * 5.0;
					double y = (double)i + 0.5 + (SmoothNoise(seed + 103, (double)j * 0.045, (double)i * 0.045) - 0.5) * 5.0;
					double num4 = (array[j, i] = DistanceInsideOutline(points, x, y) + (SmoothNoise(seed + 107, (double)j * 0.18, (double)i * 0.18) - 0.5) * 1.4);
					array2[j, i] = ((j < 6 || i < 6 || j >= num2 - 6 || i >= num3 - 6 || num4 <= 0.0) ? '~' : '.');
				}
			}
			KeepMainIsland(array2);
			Vec2[] array3 = ((version >= 6) ? SpreadRealms(array2, array, realms.Count, num) : null);
			for (int k = 0; k < realms.Count; k++)
			{
				Vec2 desired = ((array3 != null) ? array3[k] : (RealmAnchor(k, realms.Count) * num)) + new Vec2((int)(Noise(seed, k, 7) * 7.0) - 3, (int)(Noise(seed, k, 13) * 7.0) - 3);
				Vec2 vec = InlandSite(worldDef, array2, array, desired, 8.0);
				RealmSpec realmSpec = realms[k];
				worldDef.Regions.Add(new WorldRegionDef
				{
					Id = "region." + realmSpec.Culture,
					Culture = realmSpec.Culture,
					Name = realmSpec.Name,
					Biome = realmSpec.Biome,
					X = vec.X,
					Y = vec.Y
				});
			}
			for (int l = 0; l < num3; l++)
			{
				for (int m = 0; m < num2; m++)
				{
					if (array2[m, l] != '~')
					{
						WorldRegionDef worldRegionDef = RegionAt(worldDef, m, l);
						double num5 = SmoothNoise(seed + 17, (double)m * 0.12, (double)l * 0.12);
						char c = ((worldRegionDef.Biome == "snow") ? 'S' : ((worldRegionDef.Biome == "desert") ? 'D' : ((worldRegionDef.Biome == "swamp") ? 'W' : '.')));
						if (num5 > 0.6)
						{
							c = 'F';
						}
						if (num5 < 0.23)
						{
							c = '^';
						}
						double num6 = 61.0 * num + 7.0 * num * Math.Sin((double)l * 0.055 / num + Noise(seed, 31, 9) * 5.0);
						double num7 = 175.0 * num + 9.0 * num * Math.Sin((double)l * 0.043 / num + Noise(seed, 51, 9) * 5.0);
						if ((Math.Abs((double)m - num6) < 2.7 || Math.Abs((double)m - num7) < 3.0) && num5 > 0.3)
						{
							c = 'M';
						}
						double num8 = SmoothNoise(seed + 109, (double)m * 0.055, (double)l * 0.055);
						if (array[m, l] < 1.2 + num8 * 1.8 && num8 > 0.35)
						{
							c = ':';
						}
						double num9 = 116.0 * num + 10.0 * num * Math.Sin((double)l * 0.061 / num + Noise(seed, 83, 19) * 5.0);
						if (Math.Abs((double)m - num9) < 1.4)
						{
							c = '=';
						}
						array2[m, l] = c;
					}
				}
			}
			if (version >= 4)
			{
				PlaceRealmsVersion4(worldDef, array2, array, realms);
			}
			for (int n = 0; n < worldDef.Regions.Count; n++)
			{
				if (version >= 4)
				{
					break;
				}
				WorldRegionDef worldRegionDef2 = worldDef.Regions[n];
				string culture = worldRegionDef2.Culture;
				AddIslandLocation(worldDef, array2, array, "town." + culture, Capitals[n], SettlementType.Town, worldRegionDef2.X, worldRegionDef2.Y, culture, "", 8, 900.0);
				AddIslandLocation(worldDef, array2, array, "castle." + culture, Names[n] + " Keep", SettlementType.Castle, worldRegionDef2.X + 14.0, worldRegionDef2.Y - 9.0, culture, "", 5, 550.0);
				AddIslandLocation(worldDef, array2, array, "village." + culture + ".fields", Names[n] + " Fields", SettlementType.Village, worldRegionDef2.X - 11.0, worldRegionDef2.Y + 8.0, culture, "town." + culture, 6, 500.0);
				AddIslandLocation(worldDef, array2, array, "village." + culture + ".hollow", Names[n] + " Hollow", SettlementType.Village, worldRegionDef2.X + 13.0, worldRegionDef2.Y + 10.0, culture, "castle." + culture, 5, 400.0);
				if (version >= 3)
				{
					AddIslandLocation(worldDef, array2, array, "village." + culture + ".brook", Names[n] + " Brook", SettlementType.Village, worldRegionDef2.X - 5.0, worldRegionDef2.Y - 12.0, culture, "town." + culture, 5, 450.0);
					AddIslandLocation(worldDef, array2, array, "village." + culture + ".crossing", Names[n] + " Crossing", SettlementType.Village, worldRegionDef2.X + 3.0, worldRegionDef2.Y + 13.0, culture, "town." + culture, 4, 380.0);
					AddIslandLocation(worldDef, array2, array, "village." + culture + ".heath", Names[n] + " Heath", SettlementType.Village, worldRegionDef2.X + 26.0, worldRegionDef2.Y - 3.0, culture, "castle." + culture, 4, 420.0);
				}
				foreach (LocationDef item in worldDef.Locations.Where((LocationDef locationDef2) => locationDef2.Culture == culture && locationDef2.Type != SettlementType.Town))
				{
					IslandRoad(array2, worldRegionDef2.X, worldRegionDef2.Y, item.X, item.Y);
				}
				if (n % 4 > 0)
				{
					IslandRoad(array2, worldDef.Regions[n - 1].X, worldDef.Regions[n - 1].Y, worldRegionDef2.X, worldRegionDef2.Y);
				}
				if (n >= 4)
				{
					IslandRoad(array2, worldDef.Regions[n - 4].X, worldDef.Regions[n - 4].Y, worldRegionDef2.X, worldRegionDef2.Y);
				}
			}
			int[] array4 = new int[5] { 0, 4, 7, 8, 11 };
			foreach (int num11 in array4)
			{
				if (version >= 4)
				{
					break;
				}
				WorldRegionDef worldRegionDef3 = worldDef.Regions[num11];
				AddIslandLocation(worldDef, array2, array, "hideout.continent." + worldRegionDef3.Culture, Names[num11] + " Outlaw Camp", SettlementType.Hideout, worldRegionDef3.X - 17.0, worldRegionDef3.Y - 12.0, worldRegionDef3.Culture, "", 0, 100.0);
				LocationDef locationDef = worldDef.Locations.Last();
				locationDef.BanditClan = "clan.continent.outlaws." + worldRegionDef3.Culture;
				IslandRoad(array2, locationDef.X, locationDef.Y, worldRegionDef3.X, worldRegionDef3.Y);
			}
			if (version >= 3)
			{
				SpecialiseRealms(worldDef, array2, array, seed, realms);
			}
			for (int num12 = 0; num12 < num3; num12++)
			{
				char[] array5 = new char[num2];
				for (int num13 = 0; num13 < num2; num13++)
				{
					array5[num13] = array2[num13, num12];
				}
				worldDef.Rows.Add(new string(array5));
			}
			return worldDef;
		}

		private static List<Vec2> ShapeIslandOutline(ulong seed, double scale)
		{
			Vec2[] array = new Vec2[IslandOutline.Length];
			for (int i = 0; i < array.Length; i++)
			{
				array[i] = IslandOutline[i] * scale + new Vec2((Noise(seed + 113, i, 0) - 0.5) * 7.0 * scale, (Noise(seed + 127, i, 0) - 0.5) * 7.0 * scale);
			}
			List<Vec2> list = new List<Vec2>();
			for (int j = 0; j < array.Length; j++)
			{
				Vec2 vec = array[(j + array.Length - 1) % array.Length];
				Vec2 vec2 = array[j];
				Vec2 vec3 = array[(j + 1) % array.Length];
				Vec2 vec4 = array[(j + 2) % array.Length];
				for (int k = 0; k < 5; k++)
				{
					double num = (double)k / 5.0;
					double num2 = num * num;
					double num3 = num2 * num;
					list.Add(vec2 + (vec3 - vec) * (0.5 * num) + (vec * 2.0 - vec2 * 5.0 + vec3 * 4.0 - vec4) * (0.5 * num2) + (vec2 * 3.0 - vec3 * 3.0 + vec4 - vec) * (0.5 * num3));
				}
			}
			return list;
		}

		private static double DistanceInsideOutline(List<Vec2> points, double x, double y)
		{
			bool flag = false;
			double num = double.MaxValue;
			int num2 = 0;
			int index = points.Count - 1;
			while (num2 < points.Count)
			{
				Vec2 vec = points[index];
				Vec2 vec2 = points[num2];
				if (vec.Y > y != vec2.Y > y && x < (vec2.X - vec.X) * (y - vec.Y) / (vec2.Y - vec.Y) + vec.X)
				{
					flag = !flag;
				}
				double num3 = vec2.X - vec.X;
				double num4 = vec2.Y - vec.Y;
				double num5 = Math.Max(0.0, Math.Min(1.0, ((x - vec.X) * num3 + (y - vec.Y) * num4) / (num3 * num3 + num4 * num4)));
				double num6 = x - vec.X - num3 * num5;
				double num7 = y - vec.Y - num4 * num5;
				num = Math.Min(num, num6 * num6 + num7 * num7);
				index = num2++;
			}
			return Math.Sqrt(num) * (double)(flag ? 1 : (-1));
		}

		private static void KeepMainIsland(char[,] cells)
		{
			int length = cells.GetLength(0);
			int length2 = cells.GetLength(1);
			bool[,] array = new bool[length, length2];
			Queue<int> queue = new Queue<int>();
			queue.Enqueue(length2 / 2 * length + length / 2);
			array[length / 2, length2 / 2] = true;
			int[] array2 = new int[4] { 1, -1, 0, 0 };
			int[] array3 = new int[4] { 0, 0, 1, -1 };
			while (queue.Count > 0)
			{
				int num = queue.Dequeue();
				int num2 = num % length;
				int num3 = num / length;
				for (int i = 0; i < 4; i++)
				{
					int num4 = num2 + array2[i];
					int num5 = num3 + array3[i];
					if (num4 >= 0 && num5 >= 0 && num4 < length && num5 < length2 && !array[num4, num5] && cells[num4, num5] != '~')
					{
						array[num4, num5] = true;
						queue.Enqueue(num5 * length + num4);
					}
				}
			}
			for (int j = 0; j < length2; j++)
			{
				for (int k = 0; k < length; k++)
				{
					if (!array[k, j])
					{
						cells[k, j] = '~';
					}
				}
			}
		}

		private static Vec2 InlandSite(WorldDef world, char[,] cells, double[,] coastDistance, Vec2 desired, double margin)
		{
			int length = cells.GetLength(0);
			int length2 = cells.GetLength(1);
			Vec2 result = desired;
			double num = double.MaxValue;
			for (int i = 6; i < length2 - 6; i++)
			{
				for (int j = 6; j < length - 6; j++)
				{
					if (cells[j, i] == '~' || coastDistance[j, i] < margin)
					{
						continue;
					}
					Vec2 vec = new Vec2((double)j + 0.5, (double)i + 0.5);
					double lengthSquared = (vec - desired).LengthSquared;
					if (lengthSquared >= num || TooClose(world, vec))
					{
						continue;
					}
					bool flag = true;
					for (int k = -4; k <= 4 && flag; k++)
					{
						for (int l = -4; l <= 4; l++)
						{
							char c = cells[j + l, i + k];
							if (c == '~' || (Math.Abs(l) <= 2 && Math.Abs(k) <= 2 && (c == '=' || c == '+')))
							{
								flag = false;
								break;
							}
						}
					}
					if (flag)
					{
						result = vec;
						num = lengthSquared;
					}
				}
			}
			if (num == double.MaxValue)
			{
				throw new InvalidOperationException("The island has no inland location site.");
			}
			return result;
		}

		private static bool TooClose(WorldDef world, Vec2 point)
		{
			List<LocationDef> locations = world.Locations;
			for (int i = 0; i < locations.Count; i++)
			{
				if ((point - new Vec2(locations[i].X, locations[i].Y)).LengthSquared < 49.0)
				{
					return true;
				}
			}
			return false;
		}

		private static void AddIslandLocation(WorldDef world, char[,] cells, double[,] coastDistance, string id, string name, SettlementType type, double x, double y, string culture, string bound, int recruits, double prosperity)
		{
			Vec2 vec = InlandSite(world, cells, coastDistance, new Vec2(x, y), 4.5);
			AddLocation(world, cells, id, name, type, vec.X, vec.Y, culture, bound, recruits, prosperity);
		}

		private static void IslandRoad(char[,] cells, double ax, double ay, double bx, double by)
		{
			int length = cells.GetLength(0);
			int length2 = cells.GetLength(1);
			int num = (int)ay * length + (int)ax;
			int num2 = (int)by * length + (int)bx;
			int[] array = new int[length * length2];
			double[] array2 = new double[length * length2];
			bool[] array3 = new bool[length * length2];
			for (int i = 0; i < array2.Length; i++)
			{
				array2[i] = double.MaxValue;
				array[i] = -1;
			}
			IslandRoadHeap islandRoadHeap = new IslandRoadHeap();
			array2[num] = 0.0;
			islandRoadHeap.Push(num, 0.0);
			int[] array4 = new int[8] { 1, -1, 0, 0, 1, 1, -1, -1 };
			int[] array5 = new int[8] { 0, 0, 1, -1, 1, -1, 1, -1 };
			while (islandRoadHeap.Count > 0)
			{
				int num3 = islandRoadHeap.Pop();
				if (array3[num3])
				{
					continue;
				}
				array3[num3] = true;
				if (num3 == num2)
				{
					break;
				}
				int num4 = num3 % length;
				int num5 = num3 / length;
				for (int j = 0; j < array4.Length; j++)
				{
					int num6 = num4 + array4[j];
					int num7 = num5 + array5[j];
					if (!IslandRoadCellAllowed(cells, num6, num7) || (j >= 4 && (!IslandRoadCellAllowed(cells, num6, num5) || !IslandRoadCellAllowed(cells, num4, num7))))
					{
						continue;
					}
					int num8 = num7 * length + num6;
					if (!array3[num8])
					{
						double num9;
						switch (cells[num6, num7])
						{
						default:
							num9 = 1.0;
							break;
						case '^':
							num9 = 1.4;
							break;
						case '#':
						case '+':
							num9 = 0.8;
							break;
						case '=':
							num9 = 2.8;
							break;
						case 'M':
							num9 = 3.5;
							break;
						}
						double num10 = num9;
						double num11 = array2[num3] + num10 * ((j >= 4) ? 1.41421356237 : 1.0);
						if (!(num11 >= array2[num8]))
						{
							array2[num8] = num11;
							array[num8] = num3;
							double num12 = num6 - num2 % length;
							double num13 = num7 - num2 / length;
							islandRoadHeap.Push(num8, num11 + Math.Sqrt(num12 * num12 + num13 * num13) * 0.8);
						}
					}
				}
			}
			if (!array3[num2])
			{
				throw new InvalidOperationException("An island road could not reach its destination by land.");
			}
			for (int num14 = num2; num14 != num; num14 = array[num14])
			{
				int num15 = array[num14];
				int num16 = num14 % length;
				int num17 = num14 / length;
				int num18 = num15 % length;
				int num19 = num15 / length;
				if (num16 != num18 && num17 != num19)
				{
					RoadCell(cells, num16, num19);
				}
				RoadCell(cells, num16, num17);
			}
			RoadCell(cells, num % length, num / length);
		}

		private static bool IslandRoadCellAllowed(char[,] cells, int x, int y)
		{
			if (x < 2 || y < 2 || x >= cells.GetLength(0) - 2 || y >= cells.GetLength(1) - 2 || cells[x, y] == '~')
			{
				return false;
			}
			if (cells[x, y] == '=' || cells[x, y] == '+')
			{
				for (int i = -1; i <= 1; i++)
				{
					for (int j = -1; j <= 1; j++)
					{
						if (cells[x + j, y + i] == '~')
						{
							return false;
						}
					}
				}
			}
			return true;
		}

		public static string MarketTownId(string culture)
		{
			return "town." + culture + ".market";
		}

		private static void PlaceRealmsVersion4(WorldDef world, char[,] cells, double[,] coastDistance, List<RealmSpec> realms)
		{
			for (int i = 0; i < world.Regions.Count; i++)
			{
				WorldRegionDef worldRegionDef = world.Regions[i];
				string culture = worldRegionDef.Culture;
				string name = realms[i].Name;
				AddIslandLocation(world, cells, coastDistance, "town." + culture, realms[i].Capital, SettlementType.Town, worldRegionDef.X, worldRegionDef.Y, culture, "", 8, 900.0);
				AddIslandLocation(world, cells, coastDistance, "castle." + culture, name + " Keep", SettlementType.Castle, worldRegionDef.X + 14.0, worldRegionDef.Y - 9.0, culture, "", 5, 550.0);
				AddIslandLocation(world, cells, coastDistance, "village." + culture + ".fields", name + " Fields", SettlementType.Village, worldRegionDef.X - 11.0, worldRegionDef.Y + 8.0, culture, "town." + culture, 6, 500.0);
				AddIslandLocation(world, cells, coastDistance, "village." + culture + ".hollow", name + " Hollow", SettlementType.Village, worldRegionDef.X + 13.0, worldRegionDef.Y + 10.0, culture, "castle." + culture, 5, 400.0);
				AddIslandLocation(world, cells, coastDistance, "village." + culture + ".brook", name + " Brook", SettlementType.Village, worldRegionDef.X - 5.0, worldRegionDef.Y - 12.0, culture, "town." + culture, 5, 450.0);
				AddIslandLocation(world, cells, coastDistance, "village." + culture + ".crossing", name + " Crossing", SettlementType.Village, worldRegionDef.X + 3.0, worldRegionDef.Y + 13.0, culture, "town." + culture, 4, 380.0);
				AddIslandLocation(world, cells, coastDistance, "village." + culture + ".heath", name + " Heath", SettlementType.Village, worldRegionDef.X + 26.0, worldRegionDef.Y - 3.0, culture, "castle." + culture, 4, 420.0);
			}
			for (int j = 0; j < world.Regions.Count; j++)
			{
				WorldRegionDef worldRegionDef2 = world.Regions[j];
				string culture2 = worldRegionDef2.Culture;
				string text = MarketTownId(culture2);
				Vec2 vec = new Vec2(worldRegionDef2.X, worldRegionDef2.Y);
				Vec2 vec2 = SecondTownSite(world, cells, coastDistance, worldRegionDef2);
				string marketTown = realms[j].MarketTown;
				AddLocation(world, cells, text, marketTown, SettlementType.Town, vec2.X, vec2.Y, culture2, "", 7, 700.0);
				Vec2 normalized = (vec2 - vec).Normalized;
				AddIslandLocation(world, cells, coastDistance, "village." + culture2 + ".meadow", marketTown + " Meadow", SettlementType.Village, (vec2 + Turn(normalized, 75.0) * 10.0).X, (vec2 + Turn(normalized, 75.0) * 10.0).Y, culture2, text, 5, 450.0);
				AddIslandLocation(world, cells, coastDistance, "village." + culture2 + ".orchard", marketTown + " Orchard", SettlementType.Village, (vec2 + Turn(normalized, -75.0) * 10.0).X, (vec2 + Turn(normalized, -75.0) * 10.0).Y, culture2, text, 5, 420.0);
				AddIslandLocation(world, cells, coastDistance, "village." + culture2 + ".landing", marketTown + " Landing", SettlementType.Village, (vec2 + normalized * 11.0).X, (vec2 + normalized * 11.0).Y, culture2, text, 4, 380.0);
			}
			foreach (int item in HideoutRealms(world.Regions.Count))
			{
				WorldRegionDef worldRegionDef3 = world.Regions[item];
				AddIslandLocation(world, cells, coastDistance, "hideout.continent." + worldRegionDef3.Culture, realms[item].Name + " Outlaw Camp", SettlementType.Hideout, worldRegionDef3.X - 17.0, worldRegionDef3.Y - 12.0, worldRegionDef3.Culture, "", 0, 100.0);
				world.Locations.Last().BanditClan = "clan.continent.outlaws." + worldRegionDef3.Culture;
			}
			for (int k = 0; k < world.Regions.Count; k++)
			{
				WorldRegionDef worldRegionDef4 = world.Regions[k];
				string culture3 = worldRegionDef4.Culture;
				string market = MarketTownId(culture3);
				LocationDef locationDef = world.Locations.First((LocationDef l) => l.Id == market);
				foreach (LocationDef item2 in world.Locations.Where((LocationDef l) => l.Culture == culture3 && l.Id != "town." + culture3 && l.Type != SettlementType.Hideout))
				{
					if (item2.BoundTo == market)
					{
						IslandRoad(cells, locationDef.X, locationDef.Y, item2.X, item2.Y);
					}
					else
					{
						IslandRoad(cells, worldRegionDef4.X, worldRegionDef4.Y, item2.X, item2.Y);
					}
				}
				int num = RealmColumns(world.Regions.Count);
				if (k % num > 0)
				{
					IslandRoad(cells, world.Regions[k - 1].X, world.Regions[k - 1].Y, worldRegionDef4.X, worldRegionDef4.Y);
				}
				if (k >= num)
				{
					IslandRoad(cells, world.Regions[k - num].X, world.Regions[k - num].Y, worldRegionDef4.X, worldRegionDef4.Y);
				}
			}
			foreach (LocationDef camp in world.Locations.Where((LocationDef l) => l.Type == SettlementType.Hideout).ToList())
			{
				WorldRegionDef worldRegionDef5 = world.Regions.First((WorldRegionDef r) => r.Culture == camp.Culture);
				IslandRoad(cells, camp.X, camp.Y, worldRegionDef5.X, worldRegionDef5.Y);
			}
		}

		private static Vec2 Turn(Vec2 v, double degrees)
		{
			double num = degrees * Math.PI / 180.0;
			double num2 = Math.Cos(num);
			double num3 = Math.Sin(num);
			return new Vec2(v.X * num2 - v.Y * num3, v.X * num3 + v.Y * num2);
		}

		private static Vec2 SecondTownSite(WorldDef world, char[,] cells, double[,] coastDistance, WorldRegionDef region)
		{
			int length = cells.GetLength(0);
			int length2 = cells.GetLength(1);
			Vec2 vec = new Vec2(region.X, region.Y);
			double[][] array = new double[2][]
			{
				new double[4] { 20.0, 30.0, 12.0, 7.0 },
				new double[4] { 14.0, 36.0, 9.0, 5.0 }
			};
			foreach (double[] obj in array)
			{
				double num = obj[0];
				double num2 = obj[1];
				double num3 = obj[2];
				double num4 = obj[3];
				Vec2 result = vec;
				double num5 = double.MinValue;
				for (int j = Math.Max(6, (int)(region.Y - num2)); j <= Math.Min(length2 - 7, (int)(region.Y + num2)); j++)
				{
					for (int k = Math.Max(6, (int)(region.X - num2)); k <= Math.Min(length - 7, (int)(region.X + num2)); k++)
					{
						if (cells[k, j] == '~' || coastDistance[k, j] < num4)
						{
							continue;
						}
						Vec2 vec2 = new Vec2((double)k + 0.5, (double)j + 0.5);
						double length3 = (vec2 - vec).Length;
						if (length3 < num || length3 > num2 || RegionAt(world, vec2.X, vec2.Y) != region)
						{
							continue;
						}
						double num6 = double.MaxValue;
						foreach (LocationDef location in world.Locations)
						{
							num6 = Math.Min(num6, (vec2 - new Vec2(location.X, location.Y)).Length);
						}
						if (!(num6 < num3))
						{
							double num7 = Math.Min(num6, 20.0) - Math.Abs(length3 - 24.0) * 0.5;
							if (!(num7 <= num5) && DrySite(cells, k, j))
							{
								result = vec2;
								num5 = num7;
							}
						}
					}
				}
				if (num5 > double.MinValue)
				{
					return result;
				}
			}
			return InlandSite(world, cells, coastDistance, vec + new Vec2(-22.0, 4.0), 4.5);
		}

		private static bool DrySite(char[,] cells, int x, int y)
		{
			for (int i = -4; i <= 4; i++)
			{
				for (int j = -4; j <= 4; j++)
				{
					char c = cells[x + j, y + i];
					if (c == '~' || (Math.Abs(j) <= 2 && Math.Abs(i) <= 2 && (c == '=' || c == '+')))
					{
						return false;
					}
				}
			}
			return true;
		}

		private static void SpecialiseRealms(WorldDef world, char[,] cells, double[,] coastDistance, ulong seed, List<RealmSpec> realms)
		{
			for (int i = 0; i < realms.Count; i++)
			{
				string culture = realms[i].Culture;
				HashSet<string> used = new HashSet<string>(StringComparer.Ordinal);
				string[] villageSlots = VillageSlots;
				foreach (string slot in villageSlots)
				{
					LocationDef village = world.Locations.FirstOrDefault((LocationDef l) => l.Id == "village." + culture + "." + slot);
					if (village != null)
					{
						bool food = FoodSlots.Contains(slot);
						VillageProductDef kind = (from k in VillageProducts.All
							where !used.Contains(k.GoodId) && (!food || k.FoodVillage)
							select new
							{
								Kind = k,
								Score = ProductScore(k.GoodId, culture, village, cells, coastDistance, seed)
							} into x
							orderby x.Score descending
							select x).ThenBy(x => x.Kind.GoodId, StringComparer.Ordinal).First().Kind;
						used.Add(kind.GoodId);
						village.Product = kind.GoodId;
						village.Name = ((village.BoundTo == MarketTownId(culture)) ? realms[i].MarketTown : realms[i].Name) + " " + kind.NameSuffix;
					}
				}
				foreach (LocationDef item in world.Locations.Where((LocationDef l) => l.Culture == culture))
				{
					if (item.Type == SettlementType.Town)
					{
						item.WallLevel = 2;
					}
					else if (item.Type == SettlementType.Castle)
					{
						item.WallLevel = 1;
					}
				}
			}
		}

		private static double ProductScore(string good, string culture, LocationDef village, char[,] cells, double[,] coastDistance, ulong seed)
		{
			double num = 0.0;
			if (CultureProducts.TryGetValue(culture, out var value))
			{
				int num2 = Array.IndexOf(value, good);
				if (num2 >= 0)
				{
					num += 1.5 - 0.15 * (double)num2;
				}
			}
			int num3 = (int)village.X;
			int num4 = (int)village.Y;
			Dictionary<char, int> count = new Dictionary<char, int>();
			for (int i = num4 - 5; i <= num4 + 5; i++)
			{
				for (int j = num3 - 5; j <= num3 + 5; j++)
				{
					if (j >= 0 && i >= 0 && j < cells.GetLength(0) && i < cells.GetLength(1))
					{
						char key = cells[j, i];
						count[key] = (count.TryGetValue(key, out var value2) ? value2 : 0) + 1;
					}
				}
			}
			int num5 = Of('F');
			int num6 = Of('^');
			int num7 = Of('M');
			int num8 = Of('D');
			int num9 = Of('S');
			int num10 = Of('W');
			int num11 = Of('=') + Of('+');
			int num12 = Of(':');
			int num13 = Of('.') + Of('#');
			bool flag = coastDistance[Math.Max(0, Math.Min(cells.GetLength(0) - 1, num3)), Math.Max(0, Math.Min(cells.GetLength(1) - 1, num4))] < 9.0 || num12 >= 3;
			double val;
			switch (good)
			{
			case "good.fish":
				val = ((flag || num11 >= 3) ? 1 : 0);
				break;
			case "good.salt":
				val = Math.Max((num12 >= 3) ? 0.7 : 0.0, Cap((double)num8 / 30.0) * 0.7);
				break;
			case "good.timber":
				val = Cap((double)num5 / 30.0);
				break;
			case "good.fur":
				val = Cap((double)(num5 + num9) / 40.0) * ((num9 > 0) ? 1.0 : 0.5);
				break;
			case "good.ore":
				val = Cap((double)(num6 + num7) / 25.0);
				break;
			case "good.silver":
				val = Cap((double)(num6 + num7) / 25.0) * 0.8;
				break;
			case "good.clay":
				val = ((num11 >= 2) ? 0.5 : 0.0) + Cap((double)num10 / 60.0);
				break;
			case "good.dates":
				val = Cap((double)num8 / 40.0);
				break;
			case "good.grain":
				val = Cap((double)num13 / 70.0);
				break;
			case "good.horses":
				val = Cap((double)num13 / 70.0) * 0.8;
				break;
			case "good.flax":
				val = Cap((double)num13 / 70.0) * 0.6 + Cap((double)num10 / 60.0) * 0.4;
				break;
			case "good.cotton":
				val = Cap((double)num13 / 70.0) * 0.5;
				break;
			case "good.wool":
				val = Cap((double)num6 / 30.0) + Cap((double)num13 / 120.0);
				break;
			case "good.olives":
			case "good.grapes":
				val = Cap((double)num6 / 40.0) + Cap((double)num13 / 120.0);
				break;
			case "good.meat":
			case "good.hides":
				val = Cap((double)num13 / 100.0) + Cap((double)num5 / 100.0);
				break;
			default:
				val = 0.0;
				break;
			}
			num += 0.8 * Math.Min(1.2, val);
			return num + 0.5 * Noise(seed + 211, (int)(StableHash.Fnv1a64(village.Id + "|" + good) % 1000003), 17);
			static double Cap(double v)
			{
				return Math.Max(0.0, Math.Min(1.0, v));
			}
			int Of(char c)
			{
				if (!count.TryGetValue(c, out var value3))
				{
					return 0;
				}
				return value3;
			}
		}
	}
}
namespace TabsBannerlord.Core.State
{
	public sealed class KingdomArmiesState
	{
		public bool Initialized;

		public long LastDay = -1L;

		public List<AiArmy> Armies = new List<AiArmy>();

		public SortedDictionary<string, string> AttachedTo = new SortedDictionary<string, string>(StringComparer.Ordinal);
	}
	public sealed class AiArmy
	{
		public string Id = "";

		public string KingdomId = "";

		public string LeaderPartyId = "";

		public string LeaderHeroId = "";

		public List<string> MemberPartyIds = new List<string>();

		public long FormedDay;

		public double Cohesion = 100.0;

		public string TargetSettlementId = "";

		public bool PlayerJoined;

		public long PlayerJoinedDay = -1L;
	}
	public enum TimeSpeed
	{
		Paused,
		Normal,
		Fast,
		Rapid
	}
	public sealed class ClockState
	{
		public long Minute;

		public double PendingMinutes;

		public TimeSpeed Speed;

		public TimeSpeed LastRunningSpeed = TimeSpeed.Normal;

		public long NextHourMinute = 60L;

		public long NextDayMinute = 1440L;

		public string PauseReason = "";

		public long Day => Minute / 1440;

		public int HourOfDay => (int)(Minute % 1440 / 60);
	}
	public sealed class CampaignSettings
	{
		public string Difficulty = "Hard";

		public double PlayerDamageTaken = 1.0;

		public double TroopDamageTaken = 1.0;

		public double PersuasionEase;

		public double PlayerMapSpeedBonus;

		public double DisguiseDetection = 1.0;

		public double AiLordStrength = 1.0;

		public int ActiveUnitBudget = 120;

		public bool FullInformation;

		public bool HeroesCanDie;

		public bool Ironman;

		public bool PeopleAge = true;

		public bool OldAgeDeaths = true;

		public bool Births = true;

		public bool LordsMarry = true;

		public bool Rebellions = true;

		public bool RandomEvents = true;

		public bool AiGarrisonsImprove;

		public int AutosaveSlots = 3;

		public double AutosaveIntervalDays = 1.0;
	}
	public enum PlayerActivity
	{
		Idle,
		Travelling,
		InSettlement,
		Waiting,
		InEncounter,
		InBattle,
		Captive,
		Raiding,
		Besieging
	}
	public sealed class PlayerState
	{
		public PlayerActivity Activity;

		public string MenuSettlementId = "";

		public string KeepAccessSettlementId = "";

		public string PendingEncounterId = "";

		public long WaitUntilMinute;

		public string CaptorPartyId = "";

		public string CaptorSettlementId = "";

		public long CaptiveSinceMinute;

		public int CaptivityMaxDays;

		public long LastAutosaveDay;

		public BattleReport LastReport;

		public bool LastReportSeen = true;
	}
	public sealed class PendingConsequence
	{
		public string Id = "";

		public string Kind = "";

		public string SubjectId = "";

		public string Data = "";
	}
	public sealed class CampaignState
	{
		public const int CurrentSchemaVersion = 1;

		public int SchemaVersion = 1;

		public string CampaignId = "";

		public string ScenarioId = "";

		public string WorldId = "";

		public WorldGenerationState WorldGeneration;

		public CustomContentState CustomContent;

		public string PlayerStartId = "";

		public ContentManifest Content = new ContentManifest();

		public CampaignSettings Settings = new CampaignSettings();

		public RandomStreams Random = new RandomStreams();

		public IdAllocator Ids = new IdAllocator();

		public ClockState Clock = new ClockState();

		public long Revision;

		public string PlayerHeroId = "";

		public string PlayerClanId = "";

		public string PlayerPartyId = "";

		public PlayerState Player = new PlayerState();

		public EntityTable<Hero> Heroes = new EntityTable<Hero>();

		public EntityTable<Clan> Clans = new EntityTable<Clan>();

		public EntityTable<Kingdom> Kingdoms = new EntityTable<Kingdom>();

		public EntityTable<Settlement> Settlements = new EntityTable<Settlement>();

		public EntityTable<Party> Parties = new EntityTable<Party>();

		public EntityTable<Encounter> Encounters = new EntityTable<Encounter>();

		public List<StanceRecord> Stances = new List<StanceRecord>();

		public SortedDictionary<string, Sighting> PlayerSightings = new SortedDictionary<string, Sighting>(StringComparer.Ordinal);

		public SortedSet<string> CommittedEncounters = new SortedSet<string>(StringComparer.Ordinal);

		public SortedSet<string> AppliedEventIds = new SortedSet<string>(StringComparer.Ordinal);

		public List<PendingConsequence> PendingConsequences = new List<PendingConsequence>();

		public List<LedgerEntry> Ledger = new List<LedgerEntry>();

		public List<LogEntry> Log = new List<LogEntry>();

		public List<WorkshopState> Workshops = new List<WorkshopState>();

		public List<ContractState> Contracts = new List<ContractState>();

		public List<CaravanState> Caravans = new List<CaravanState>();

		public List<MilitaryPartyState> MilitaryParties = new List<MilitaryPartyState>();

		public List<GarrisonPartyState> GarrisonParties = new List<GarrisonPartyState>();

		public ClanArmyState ClanArmy = new ClanArmyState();

		public List<NotableState> Notables = new List<NotableState>();

		public ClanServiceState ClanService = new ClanServiceState();

		public DiplomacyState Diplomacy = new DiplomacyState();

		public KingdomArmiesState KingdomArmies = new KingdomArmiesState();

		public List<SiegeState> Sieges = new List<SiegeState>();

		public PoliticsState Politics = new PoliticsState();

		public ClanAllegianceState ClanAllegiance = new ClanAllegianceState();

		public WardrobeState Wardrobe = new WardrobeState();

		public SmithingState Smithing = new SmithingState();

		public HorsesState Horses = new HorsesState();

		public bool OutlawPartiesDoubled;

		public bool LordUnitsVaried;

		public bool MerchantCaravansStarted;

		public bool EquipmentPiecesStocked;

		public SortedSet<string> EquipmentIntroduced = new SortedSet<string>(StringComparer.Ordinal);

		public bool DeveloperTools;

		public bool DeveloperToolsDecided;

		public HeroBondsState HeroBonds = new HeroBondsState();

		public SortedDictionary<string, long> MerchantCaravanDue = new SortedDictionary<string, long>(StringComparer.Ordinal);

		public SortedDictionary<string, long> ClanCaravanDue = new SortedDictionary<string, long>(StringComparer.Ordinal);

		public SortedDictionary<string, long> PlayerCargoCosts = new SortedDictionary<string, long>(StringComparer.Ordinal);

		public TournamentsState Tournaments = new TournamentsState();

		public AbilitiesState Abilities = new AbilitiesState();

		public List<string> EncyclopediaBookmarks = new List<string>();

		public SortedSet<string> MetHeroes = new SortedSet<string>(StringComparer.Ordinal);

		public ConversationsState Conversations = new ConversationsState();

		public WanderersState Wanderers = new WanderersState();

		public FamiliesState Families = new FamiliesState();

		public CrimeState Crime = new CrimeState();

		public RebellionState Rebellions = new RebellionState();

		public QuestsState Quests = new QuestsState();

		public TutorialState Tutorial = new TutorialState();

		public StoryEventsState StoryEvents = new StoryEventsState();

		public HideoutsState Hideouts = new HideoutsState();

		public LegendsState Legends = new LegendsState();

		public ScoutingState Scouting = new ScoutingState();

		public const int MaxLogEntries = 400;

		public const int MaxLedgerEntries = 600;

		public const double HuntedCrime = 90.0;

		public Party PlayerParty => Parties.Get(PlayerPartyId);

		public Hero PlayerHero => Heroes.Get(PlayerHeroId);

		public Clan PlayerClan => Clans.Get(PlayerClanId);

		public IEnumerable<Party> ActiveParties => Parties.Where((Party p) => p.State == PartyState.Active || p.State == PartyState.InSettlement || p.State == PartyState.Engaged);

		public string FactionOf(Clan clan)
		{
			if (clan != null)
			{
				if (!string.IsNullOrEmpty(clan.KingdomId))
				{
					return clan.KingdomId;
				}
				return clan.Id;
			}
			return "";
		}

		public string FactionOfParty(Party p)
		{
			return FactionOf(Clans.Get(p.ClanId));
		}

		public StanceRecord FindStance(string a, string b)
		{
			foreach (StanceRecord stance in Stances)
			{
				if ((stance.A == a && stance.B == b) || (stance.A == b && stance.B == a))
				{
					return stance;
				}
			}
			return null;
		}

		public bool AtWar(string factionA, string factionB)
		{
			if (factionA == factionB)
			{
				return false;
			}
			StanceRecord stanceRecord = FindStance(factionA, factionB);
			if (stanceRecord != null)
			{
				return stanceRecord.Stance == Stance.War;
			}
			return false;
		}

		public bool AreHostile(Clan a, Clan b)
		{
			if (a == null || b == null || a.Id == b.Id)
			{
				return false;
			}
			if (a.IsBandit || b.IsBandit)
			{
				return a.IsBandit != b.IsBandit;
			}
			return AtWar(FactionOf(a), FactionOf(b));
		}

		public bool AreHostile(Party a, Party b)
		{
			if (!AreHostile(Clans.Get(a.ClanId), Clans.Get(b.ClanId)) && !HuntsPlayer(a, b))
			{
				return HuntsPlayer(b, a);
			}
			return true;
		}

		public bool HuntsPlayer(Party hunter, Party player)
		{
			if (hunter == null || player == null || player.Id != PlayerPartyId || hunter.Id == player.Id || Crime?.Rating == null)
			{
				return false;
			}
			if (hunter.Kind == PartyKind.Villager || hunter.Kind == PartyKind.Caravan)
			{
				return false;
			}
			Clan clan = Clans.Get(hunter.ClanId);
			if (clan == null || clan.IsBandit || clan.Id == PlayerClanId)
			{
				return false;
			}
			string text = FactionOf(clan);
			if (text == FactionOf(PlayerClan))
			{
				return false;
			}
			if (Crime.Rating.TryGetValue(text, out var value))
			{
				return value >= 90.0;
			}
			return false;
		}

		public bool AreAllied(Party a, Party b)
		{
			Clan clan = Clans.Get(a.ClanId);
			Clan clan2 = Clans.Get(b.ClanId);
			if (clan == null || clan2 == null)
			{
				return false;
			}
			if (clan.IsBandit || clan2.IsBandit)
			{
				if (clan.IsBandit && clan2.IsBandit)
				{
					return clan.Id == clan2.Id;
				}
				return false;
			}
			string text = FactionOf(clan);
			string text2 = FactionOf(clan2);
			if (!(text == text2))
			{
				return FactionsAllied(text, text2);
			}
			return true;
		}

		public bool FactionsAllied(string a, string b)
		{
			if (a != b && Diplomacy?.Agreements != null)
			{
				return Diplomacy.Agreements.Any((DiplomaticAgreement x) => x != null && x.Kind == AgreementKind.Alliance && x.EndsDay > Clock.Day && ((x.A == a && x.B == b) || (x.A == b && x.B == a)));
			}
			return false;
		}

		public void SetStance(string a, string b, Stance stance)
		{
			StanceRecord stanceRecord = FindStance(a, b);
			if (stanceRecord == null)
			{
				stanceRecord = new StanceRecord
				{
					A = ((string.CompareOrdinal(a, b) < 0) ? a : b),
					B = ((string.CompareOrdinal(a, b) < 0) ? b : a)
				};
				Stances.Add(stanceRecord);
			}
			if (stanceRecord.Stance != stance)
			{
				stanceRecord.Stance = stance;
				stanceRecord.SinceMinute = Clock.Minute;
			}
		}

		public LogEntry AddLog(LogCategory category, string text, params string[] links)
		{
			LogEntry logEntry = new LogEntry
			{
				Id = Ids.AllocateNumber("log"),
				Minute = Clock.Minute,
				Category = category,
				Text = text
			};
			logEntry.Links.AddRange(links.Where((string l) => !string.IsNullOrEmpty(l)));
			Log.Add(logEntry);
			if (Log.Count > 400)
			{
				Log.RemoveRange(0, Log.Count - 400);
			}
			return logEntry;
		}

		public long Transact(Clan clan, long amount, LedgerAccount account, string reason, bool allowPartial = false)
		{
			if (clan == null || amount == 0L)
			{
				return 0L;
			}
			if (amount < 0 && clan.Gold + amount < 0)
			{
				if (!allowPartial)
				{
					throw new InvalidOperationException($"{clan.Name} cannot afford {-amount} gold for {reason}");
				}
				amount = -clan.Gold;
				if (amount == 0L)
				{
					return 0L;
				}
			}
			clan.Gold += amount;
			Ledger.Add(new LedgerEntry
			{
				Minute = Clock.Minute,
				ClanId = clan.Id,
				Account = account,
				Amount = amount,
				Reason = reason
			});
			if (Ledger.Count > 600)
			{
				Ledger.RemoveRange(0, Ledger.Count - 600);
			}
			return amount;
		}

		public void Touch()
		{
			Revision++;
		}
	}
	public sealed class CaravanState
	{
		public string Id = "";

		public string PartyId = "";

		public string OwnerClanId = "";

		public string HomeSettlementId = "";

		public string Status = "Trading";

		public bool RecallRequested;

		public long LastArrivalMinute = -1L;

		public string LastArrivalSettlementId = "";

		public long LastTradeProfit;

		public long TotalTradeProfit;

		public long StartedMinute;

		public bool Merchant;

		public bool ClanCaravan;

		public SortedDictionary<string, long> CargoCosts = new SortedDictionary<string, long>(StringComparer.Ordinal);

		public bool IsActive
		{
			get
			{
				if (Status != "Lost")
				{
					return Status != "Disbanded";
				}
				return false;
			}
		}
	}
	public sealed class CharacterCreationState
	{
		public string BackgroundId = "";

		public string TrainingId = "";

		public string TraitId = "";

		public string CultureId = "";

		public string ArchetypeId = "";

		public string BannerColor = "";

		public string BannerSymbol = "";

		public string BannerEmblemColor = "";

		public SortedDictionary<string, string> LifeStages = new SortedDictionary<string, string>(StringComparer.Ordinal);

		public int Age;

		public SortedDictionary<string, int> Focus = new SortedDictionary<string, int>(StringComparer.Ordinal);

		public string StartSettlementId = "";

		public bool HasLifeStages
		{
			get
			{
				if (LifeStages != null)
				{
					return LifeStages.Count > 0;
				}
				return false;
			}
		}
	}
	public enum ClanAllegianceChange
	{
		Recruited,
		Expelled,
		Seceded,
		Swore
	}
	public sealed class ClanAllegianceState
	{
		public bool Initialized;

		public long LastAiDay = -1L;

		public List<ClanAllegianceRecord> Records = new List<ClanAllegianceRecord>();
	}
	public sealed class ClanAllegianceRecord
	{
		public string ClanId = "";

		public string PreviousKingdomId = "";

		public string KingdomId = "";

		public string SponsorClanId = "";

		public ClanAllegianceChange Kind;

		public long ChangedDay;

		public long CooldownUntilDay;

		public long SigningPayment;
	}
	public sealed class ClanArmyMember
	{
		public string PartyId = "";

		public MilitaryPartyDirective PreviousDirective;

		public bool KingdomCall;

		public string OwnerClanId = "";

		public string LeaderHeroId = "";

		public string KingdomId = "";

		public long CalledDay = -1L;

		public long EndsDay = -1L;

		public long LastSupportDay = -1L;
	}
	public sealed class ClanArmyState
	{
		public bool Active;

		public string Id = "";

		public string OwnerClanId = "";

		public string LeaderPartyId = "";

		public long StartedMinute;

		public long LastDailyDay = -1L;

		public long LastRallyDay = -1L;

		public double Cohesion = 100.0;

		public List<ClanArmyMember> Members = new List<ClanArmyMember>();
	}
	public enum ClanServiceKind
	{
		Independent,
		Mercenary,
		Vassal,
		Ruler
	}
	public sealed class ClanServiceState
	{
		public bool Initialized;

		public ClanServiceKind Kind;

		public string KingdomId = "";

		public long StartedDay;

		public long EndsDay = -1L;

		public long LastPaidDay = -1L;

		public long LastPayment;
	}
	public enum ContractKind
	{
		Delivery,
		BanditBounty,
		SettlementSupply
	}
	public enum ContractStatus
	{
		Offered,
		Active,
		Completed,
		Failed,
		Cancelled
	}
	public sealed class ContractState
	{
		public string Id = "";

		public ContractKind Kind;

		public ContractStatus Status;

		public bool WasAccepted;

		public string IssuerSettlementId = "";

		public string IssuerClanId = "";

		public string IssuerNotableId = "";

		public string TargetSettlementId = "";

		public string TargetPartyId = "";

		public string GoodId = "";

		public int Quantity;

		public long Reward;

		public long Escrow;

		public long OfferedDay;

		public long DeadlineMinute;

		public int Progress;

		public string Description = "";
	}
	public sealed class ConversationsState
	{
		public SortedDictionary<string, long> SmallTalkDay = new SortedDictionary<string, long>(StringComparer.Ordinal);

		public SortedDictionary<string, long> PersuasionBarredUntilDay = new SortedDictionary<string, long>(StringComparer.Ordinal);

		public SortedDictionary<string, long> RecruitPersuadedUntilDay = new SortedDictionary<string, long>(StringComparer.Ordinal);
	}
	public sealed class CrimeState
	{
		public SortedDictionary<string, double> Rating = new SortedDictionary<string, double>(StringComparer.Ordinal);

		public string GateSettlementId = "";

		public string DisguisedSettlementId = "";

		public SortedDictionary<string, long> SneakBarredUntil = new SortedDictionary<string, long>(StringComparer.Ordinal);

		public List<AlleyState> Alleys = new List<AlleyState>();

		public bool AlleysStarted;

		public List<string> Executed = new List<string>();
	}
	public sealed class AlleyState
	{
		public string Id = "";

		public string SettlementId = "";

		public int Index;

		public string Name = "";

		public string OwnerClanId = "";

		public string GangName = "";

		public string GangLeaderName = "";

		public int GangSize = 4;

		public string OverseerHeroId = "";

		public long TakenDay = -1L;

		public long IncomeTotal;

		public long ChallengeDay;
	}
	public sealed class DiplomacyState
	{
		public bool Initialized;

		public long LastAiDay = -1L;

		public List<DiplomaticTruce> Truces = new List<DiplomaticTruce>();

		public List<DiplomaticAgreement> Agreements = new List<DiplomaticAgreement>();

		public List<CallToWarRequest> Calls = new List<CallToWarRequest>();

		public List<WarCapture> Captures = new List<WarCapture>();

		public List<string> PendingElections = new List<string>();

		public long LastPactAiDay = -1L;
	}
	public enum AgreementKind
	{
		Alliance,
		TradeAgreement,
		Tribute
	}
	public sealed class DiplomaticAgreement
	{
		public string Id = "";

		public AgreementKind Kind;

		public string A = "";

		public string B = "";

		public long SignedDay;

		public long EndsDay;

		public string PayerFactionId = "";

		public string RecipientFactionId = "";

		public long DailyAmount;

		public long PaidTotal;

		public int MissedDays;
	}
	public enum CallToWarStatus
	{
		Pending,
		Accepted,
		Declined,
		Expired
	}
	public sealed class CallToWarRequest
	{
		public string Id = "";

		public string CallerFactionId = "";

		public string AllyFactionId = "";

		public string EnemyFactionId = "";

		public long Payment;

		public long CreatedDay;

		public long ExpiresDay;

		public CallToWarStatus Status;

		public long AnsweredDay = -1L;
	}
	public sealed class WarCapture
	{
		public string CaptorFactionId = "";

		public string LoserFactionId = "";

		public string SettlementId = "";

		public long Day;
	}
	public sealed class DiplomaticTruce
	{
		public string A = "";

		public string B = "";

		public long SignedDay;

		public long EndsDay;

		public string PayerClanId = "";

		public string RecipientClanId = "";

		public long Payment;
	}
	public sealed class WorkshopState
	{
		public string Id = "";

		public string OwnerClanId = "";

		public string SettlementId = "";

		public string SettlementOwnerClanId = "";

		public string TypeId = "";

		public long PurchasePrice;

		public long Capital;

		public long LastProfit;

		public long TotalProfit;

		public bool Enabled = true;

		public string Status = "Ready";

		public long LastDay = -1L;
	}
	public enum HeroState
	{
		Active,
		Captive,
		Dead,
		Inactive
	}
	public enum HeroOccupation
	{
		Player,
		Lord,
		Companion,
		Notable,
		BanditBoss
	}
	public sealed class Hero : IEntity
	{
		public string Name = "";

		public string Description = "";

		public CharacterCreationState Creation;

		public HeroEquipmentState Loadout;

		public long HireCost;

		public int DailyWage;

		public string ClanId = "";

		public string CultureId = "";

		public string UnitId = "";

		public HeroOccupation Occupation = HeroOccupation.Lord;

		public bool IsPlayer;

		public HeroState State;

		public double Health = 1.0;

		public string PartyId = "";

		public string SettlementId = "";

		public string CaptorPartyId = "";

		public string CaptorSettlementId = "";

		public long CaptiveSinceMinute;

		public int Level = 1;

		public long Xp;

		public SortedDictionary<string, int> Skills = new SortedDictionary<string, int>(StringComparer.Ordinal);

		public SortedDictionary<string, int> SkillXp = new SortedDictionary<string, int>(StringComparer.Ordinal);

		public SortedDictionary<string, int> SkillFocus = new SortedDictionary<string, int>(StringComparer.Ordinal);

		public SortedDictionary<string, int> Attributes = new SortedDictionary<string, int>(StringComparer.Ordinal);

		public int OriginAttributes;

		public int BonusFocus;

		public int OriginFocus;

		public List<string> Perks = new List<string>();

		public SortedDictionary<string, double> SkillXpRemainders = new SortedDictionary<string, double>(StringComparer.Ordinal);

		public long RoleTrainingDay = -1L;

		public SortedDictionary<string, double> RoleTrainingEarned = new SortedDictionary<string, double>(StringComparer.Ordinal);

		public long BornDay;

		public string MountId = "";

		public bool Dismounted;

		public List<string> Abilities = new List<string>();

		public HeroSex Sex;

		public string FatherId = "";

		public string MotherId = "";

		public string SpouseId = "";

		public List<string> FormerSpouseIds = new List<string>();

		public List<string> DivorcedIds = new List<string>();

		public string BirthClanId = "";

		public bool IsFamily;

		public long DeathDay = -1L;

		public string DeathCause = "";

		public long ChildDueDay = -1L;

		public string ChildFatherId = "";

		public SortedDictionary<string, string> Education = new SortedDictionary<string, string>(StringComparer.Ordinal);

		public const double FitToFight = 0.5;

		public string Id { get; set; } = "";

		public bool CanFight
		{
			get
			{
				if (State == HeroState.Active)
				{
					return Health >= 0.5;
				}
				return false;
			}
		}

		public int Skill(string id)
		{
			if (!Skills.TryGetValue(id, out var value))
			{
				return 0;
			}
			return value;
		}
	}
	public static class SkillIds
	{
		public const string OneHanded = "one_handed";

		public const string TwoHanded = "two_handed";

		public const string Polearm = "polearm";

		public const string Bow = "bow";

		public const string Crossbow = "crossbow";

		public const string Throwing = "throwing";

		public const string Riding = "riding";

		public const string Athletics = "athletics";

		public const string Smithing = "smithing";

		public const string Scouting = "scouting";

		public const string Tactics = "tactics";

		public const string Roguery = "roguery";

		public const string Charm = "charm";

		public const string Leadership = "leadership";

		public const string Trade = "trade";

		public const string Diplomacy = "diplomacy";

		public const string Stewardship = "stewardship";

		public const string Medicine = "medicine";

		public const string Engineering = "engineering";

		public static readonly string[] All = new string[19]
		{
			"one_handed", "two_handed", "polearm", "bow", "crossbow", "throwing", "riding", "athletics", "smithing", "scouting",
			"tactics", "roguery", "charm", "leadership", "trade", "diplomacy", "stewardship", "medicine", "engineering"
		};

		public static readonly string[] Original = new string[10] { "leadership", "tactics", "scouting", "stewardship", "medicine", "trade", "engineering", "diplomacy", "smithing", "riding" };

		public static readonly string[] Weapons = new string[6] { "one_handed", "two_handed", "polearm", "bow", "crossbow", "throwing" };
	}
	public static class AttributeIds
	{
		public const string Vigor = "vigor";

		public const string Control = "control";

		public const string Endurance = "endurance";

		public const string Cunning = "cunning";

		public const string Social = "social";

		public const string Intelligence = "intelligence";

		public static readonly string[] All = new string[6] { "vigor", "control", "endurance", "cunning", "social", "intelligence" };
	}
	public sealed class Clan : IEntity
	{
		public string Name = "";

		public string KingdomId = "";

		public string LeaderHeroId = "";

		public string CultureId = "";

		public long Gold;

		public double Renown;

		public double Influence;

		public string Color = "#888888";

		public string Symbol = "?";

		public string EmblemColor = "";

		public bool IsBandit;

		public bool IsPlayerClan;

		public bool IsEliminated;

		public List<WeightedUnit> BanditTroops = new List<WeightedUnit>();

		public int UnpaidDays;

		public string Id { get; set; } = "";
	}
	public sealed class Kingdom : IEntity
	{
		public string Name = "";

		public string CultureId = "";

		public string RulerClanId = "";

		public string Color = "#888888";

		public string Symbol = "?";

		public bool IsEliminated;

		public string Id { get; set; } = "";
	}
	public sealed class RecruitSlot
	{
		public string UnitId = "";

		public bool Available;

		public string ProviderId = "";

		public int RequiredRelation;
	}
	public sealed class Settlement : IEntity
	{
		public string Name = "";

		public SettlementType Type;

		public Vec2 Position;

		public string CultureId = "";

		public string OwnerClanId = "";

		public string BoundTo = "";

		public string Product = "";

		public int WallLevel;

		public double Prosperity = 500.0;

		public double Security = 60.0;

		public double Loyalty = 60.0;

		public double FoodStock;

		public bool FoodInMarket;

		public double FoodPart;

		public MarketState Market = new MarketState();

		public SettlementManagementState Management = new SettlementManagementState();

		public VillageRecoveryState VillageRecovery = new VillageRecoveryState();

		public int Militia;

		public int WoundedMilitia;

		public TroopRoster Garrison = new TroopRoster();

		public TroopRoster Prisoners = new TroopRoster();

		public List<RecruitSlot> Recruits = new List<RecruitSlot>();

		public bool HideoutActive = true;

		public string BanditClanId = "";

		public long LastSpawnDay = -1L;

		public long StatsDay = -1L;

		public int StarvingDays;

		public double Unrest;

		public string Id { get; set; } = "";
	}
	public enum PartyKind
	{
		Player,
		Lord,
		Bandit,
		Caravan,
		Villager,
		SiegeDefender,
		Garrison
	}
	public enum PartyState
	{
		Active,
		InSettlement,
		Engaged,
		Destroyed,
		Disbanded
	}
	public enum PartyRole
	{
		Scout,
		Surgeon,
		Quartermaster,
		Engineer
	}
	public enum OrderKind
	{
		None,
		MoveToPoint,
		GoToSettlement,
		Engage,
		Follow,
		Flee,
		Patrol,
		Hold,
		Raid,
		Besiege
	}
	public sealed class MovementOrder
	{
		public OrderKind Kind;

		public Vec2 Point;

		public string TargetId = "";

		public Vec2 LastKnownTargetPosition;

		public bool TargetLost;

		public List<Vec2> Path = new List<Vec2>();

		public int PathIndex;

		public Vec2 PathGoal;

		public long IssuedAtMinute;

		public string Reason = "";

		public string ThreatId = "";

		public bool HasPath
		{
			get
			{
				if (Path != null)
				{
					return PathIndex < Path.Count;
				}
				return false;
			}
		}
	}
	public sealed class AiAlternative
	{
		public string Task = "";

		public double Score;

		public string Detail = "";
	}
	public sealed class AiDecision
	{
		public string Task = "Idle";

		public string Reason = "";

		public long AtMinute;

		public List<AiAlternative> Alternatives = new List<AiAlternative>();

		public int OscillationCount;

		public string PreviousTask = "";
	}
	public sealed class Party : IEntity
	{
		public string Name = "";

		public PartyKind Kind = PartyKind.Lord;

		public PartyState State;

		public string ClanId = "";