Decompiled source of MigratingCreatures v0.2.0

MigratingCreatures.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("0.0.0.0")]
namespace MigratingCreatures;

internal sealed class BandMember
{
	internal string Prefab;

	internal int Min = 1;

	internal int Max = 1;

	internal int[] Choices;

	internal float Chance = 1f;

	internal int Needs;

	internal int RollCount(DetRandom rng)
	{
		if (Choices != null && Choices.Length != 0)
		{
			return Choices[rng.RangeInt(0, Choices.Length - 1)];
		}
		return rng.RangeInt(Min, Max);
	}

	internal string Describe()
	{
		string text = ((Choices != null) ? string.Join(",", Array.ConvertAll(Choices, (int c) => c.ToString(CultureInfo.InvariantCulture))) : ((Min == Max) ? Min.ToString(CultureInfo.InvariantCulture) : (Min + "-" + Max)));
		return Prefab + " " + text + ((Chance < 1f) ? (" " + Math.Round(Chance * 100f).ToString(CultureInfo.InvariantCulture) + "%") : "") + ((Needs > 0) ? (" needs " + Needs) : "");
	}
}
internal sealed class BandDefinition
{
	internal string Name;

	internal readonly List<BandMember> Members = new List<BandMember>();

	internal Biome Home;

	internal Biome Enters;

	internal Biome Heads;

	internal Biome Avoid;

	internal ActiveTime Time;

	internal float Weight = 1f;

	internal int Tier = -1;

	internal readonly List<string> RequiresKeys = new List<string>();

	internal float Speed = 1.3f;

	internal float MinRoute;

	internal float MaxRoute;

	internal float MinLifetime;

	internal float MaxLifetime;

	internal bool StayTogether = true;

	internal bool Assist = true;

	internal float AssistRange = 30f;

	internal int Line;

	internal MigrationEntry Entry;

	internal int EffectiveTier
	{
		get
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			if (Tier < 0)
			{
				return Progression.BiomeTier(Home);
			}
			return Tier;
		}
	}

	internal void Roll(DetRandom rng, int cap, List<string> into)
	{
		into.Clear();
		for (int i = 0; i < Members.Count; i++)
		{
			BandMember bandMember = Members[i];
			bool num = bandMember.Chance >= 1f || rng.Next() < bandMember.Chance;
			int num2 = bandMember.RollCount(rng);
			if (!num || into.Count < bandMember.Needs)
			{
				continue;
			}
			for (int j = 0; j < num2; j++)
			{
				if (into.Count >= cap)
				{
					break;
				}
				into.Add(bandMember.Prefab);
			}
		}
		if (into.Count == 0 && Members.Count > 0)
		{
			into.Add(Members[0].Prefab);
		}
	}

	internal string Describe()
	{
		//IL_0099: Unknown result type (might be due to invalid IL or missing references)
		List<string> list = new List<string>();
		for (int i = 0; i < Members.Count; i++)
		{
			list.Add(Members[i].Describe());
		}
		return Name + ": " + string.Join("; ", list.ToArray()) + ", " + ((object)Unsafe.As<Biome, Biome>(ref Home)/*cast due to .constrained prefix*/).ToString() + " -> " + ((object)Unsafe.As<Biome, Biome>(ref Heads)/*cast due to .constrained prefix*/).ToString() + (((int)Enters != 0) ? (" via " + ((object)Unsafe.As<Biome, Biome>(ref Enters)/*cast due to .constrained prefix*/).ToString()) : "") + ", " + Time.ToString() + ", weight " + Weight.ToString("0.##", CultureInfo.InvariantCulture) + ", tier " + EffectiveTier + ((RequiresKeys.Count > 0) ? (", needs " + string.Join("+", RequiresKeys.ToArray())) : "");
	}
}
internal static class BandTable
{
	internal const string FileName = "marc.valheim.migratingcreatures.bands.txt";

	private const Biome AllLand = (Biome)639;

	private const string DefaultText = "# Migrating Creatures - travelling bands. A band is a mixed group, usually enemies, that travels the\n# world like the migration table's creatures: war parties, packs, night raids.\n#\n# One [Name] line per band, then Key = Value lines. Lines starting with # are comments. mc_reload in the\n# console re-reads this file; mc_bands shows every band and how likely it is in this world right now.\n#\n# Members      who travels, separated by ; (the first line leads the band):\n#                Prefab Count [Chance%] [needs N]\n#              Count    a number (2), a range (2-5), or a list to pick one from (1,1,2,3 = alone half the time)\n#              Chance%  the chance this line comes along at all (default 100%)\n#              needs N  this line only comes along when the band already has N or more members\n#              e.g.  Goblin 2-5; GoblinBrute 1 30% needs 3\n# Home         the one biome the band starts in.\n# Enters       biomes the band may walk through (comma separated). Its route never touches a biome that\n#              is not Home, Enters or Heads. Empty = no limit (only Avoid applies).\n# Heads        biomes its destination may be in (default: Enters, or Home when Enters is empty).\n# Avoid        biomes the route must never enter, on top of Enters.\n# Time         Any, Day or Night: when the band sets out and when it travels. Outside that time it holds\n#              where it is, so give a night band a short Lifetime: it is gone by the next night.\n# Weight       how often it is picked, against the other bands and the migration table's Frequency.\n#              0 switches the band off.\n# Tier         0 Meadows, 1 Black Forest, 2 Swamp, 3 Mountain, 4 Plains, 5 Mistlands, 6 Ashlands, 7 Deep North.\n#              Default: the Home biome's. For every boss the world is short of the tier (ProgressionKeys\n#              in the .cfg: Eikthyr opens tier 1, The Elder 2, Bonemass 3, Moder 4, Yagluth 5, The Queen 6,\n#              Fader 7), the weight is multiplied by EarlyTierFactor (0.15 by default): a tier 4 band in a\n#              fresh world is about 2000 times rarer than once Moder is dead.\n# RequiresKeys global keys that must ALL be set before the band can set out at all (e.g. defeated_bonemass).\n# Speed        metres per second while travelling with purpose.\n# RouteLength  straight distance from start to destination in metres, min-max (default: the .cfg's).\n# Lifetime     game days, min-max (default: the .cfg's). 1 game day = 30 minutes.\n# StayTogether yes: a member that fights stays in the band and travels on afterwards. no: it leaves the\n#              band for good and stays where it fought, like the migration table's creatures.\n# Assist       yes: when one member fights, pack-mates within AssistRange metres join in against that foe.\n# AssistRange  metres (default 30).\n#\n# Biomes: Meadows, BlackForest, Swamp, Mountain, Plains, Mistlands, AshLands, DeepNorth, Ocean\n# Members fight players and other creatures as they always do. They never target buildings.\n\n# A fuling war party passing through, now and then with a berserker or a shaman.\n[FulingWarband]\nMembers = Goblin 2-5; GoblinBrute 1 30% needs 3; GoblinShaman 1 20% needs 2\nHome = Plains\nEnters = Plains, BlackForest, Mountain, Meadows\nHeads = BlackForest, Mountain, Meadows\nTime = Any\nWeight = 1\nSpeed = 1.5\n\n# Trolls wandering alone or in twos and threes.\n[TrollWanderers]\nMembers = Troll 1,1,2,3\nHome = BlackForest\nEnters = BlackForest, Meadows, Swamp\nHeads = BlackForest, Meadows\nTime = Any\nWeight = 0.8\nTier = 2\nSpeed = 1.4\n\n# Greydwarfs and their brutes roaming out of the forest after dark.\n[GreydwarfNightRaid]\nMembers = Greydwarf 2-4; Greydwarf_Elite 1-2 70%; Greydwarf_Shaman 1 30%\nHome = BlackForest\nEnters = BlackForest, Meadows\nHeads = Meadows\nTime = Night\nWeight = 1.2\nTier = 1\nSpeed = 1.4\nRouteLength = 300-1200\nLifetime = 0.3-0.5\n\n# A few greydwarfs scouting the meadows: the common early traveller.\n[GreydwarfScouts]\nMembers = Greydwarf 1-3\nHome = BlackForest\nEnters = BlackForest, Meadows\nHeads = Meadows\nTime = Any\nWeight = 1.5\nTier = 0\nRouteLength = 400-1500\n\n# Draugr walking out of the swamp into the forest at night.\n[DraugrNightPatrol]\nMembers = Draugr 2-3; Draugr_Elite 1 25% needs 2\nHome = Swamp\nEnters = Swamp, BlackForest\nHeads = BlackForest\nTime = Night\nWeight = 0.6\nTier = 2\nSpeed = 1.2\nRouteLength = 400-1500\nLifetime = 0.4-0.8\n\n# A fenring coming down off the mountain on a night hunt.\n[FenringNightHunt]\nMembers = Fenring 1\nHome = Mountain\nEnters = Mountain, BlackForest\nHeads = BlackForest\nTime = Night\nWeight = 0.4\nSpeed = 1.8\nRouteLength = 400-1500\nLifetime = 0.3-0.5\n";

	private static readonly List<BandDefinition> bands = new List<BandDefinition>();

	private static readonly List<MigrationEntry> entries = new List<MigrationEntry>();

	internal static IReadOnlyList<BandDefinition> Bands => bands;

	internal static IReadOnlyList<MigrationEntry> Entries => entries;

	internal static string LastLoadSummary { get; private set; }

	internal static string PathOnDisk => Path.Combine(Paths.ConfigPath, "marc.valheim.migratingcreatures.bands.txt");

	internal static BandDefinition Find(string name)
	{
		for (int i = 0; i < bands.Count; i++)
		{
			if (string.Equals(bands[i].Name, name, StringComparison.OrdinalIgnoreCase))
			{
				return bands[i];
			}
		}
		return null;
	}

	internal static void Load()
	{
		bands.Clear();
		entries.Clear();
		string pathOnDisk = PathOnDisk;
		string text;
		try
		{
			if (!File.Exists(pathOnDisk))
			{
				File.WriteAllText(pathOnDisk, "# Migrating Creatures - travelling bands. A band is a mixed group, usually enemies, that travels the\n# world like the migration table's creatures: war parties, packs, night raids.\n#\n# One [Name] line per band, then Key = Value lines. Lines starting with # are comments. mc_reload in the\n# console re-reads this file; mc_bands shows every band and how likely it is in this world right now.\n#\n# Members      who travels, separated by ; (the first line leads the band):\n#                Prefab Count [Chance%] [needs N]\n#              Count    a number (2), a range (2-5), or a list to pick one from (1,1,2,3 = alone half the time)\n#              Chance%  the chance this line comes along at all (default 100%)\n#              needs N  this line only comes along when the band already has N or more members\n#              e.g.  Goblin 2-5; GoblinBrute 1 30% needs 3\n# Home         the one biome the band starts in.\n# Enters       biomes the band may walk through (comma separated). Its route never touches a biome that\n#              is not Home, Enters or Heads. Empty = no limit (only Avoid applies).\n# Heads        biomes its destination may be in (default: Enters, or Home when Enters is empty).\n# Avoid        biomes the route must never enter, on top of Enters.\n# Time         Any, Day or Night: when the band sets out and when it travels. Outside that time it holds\n#              where it is, so give a night band a short Lifetime: it is gone by the next night.\n# Weight       how often it is picked, against the other bands and the migration table's Frequency.\n#              0 switches the band off.\n# Tier         0 Meadows, 1 Black Forest, 2 Swamp, 3 Mountain, 4 Plains, 5 Mistlands, 6 Ashlands, 7 Deep North.\n#              Default: the Home biome's. For every boss the world is short of the tier (ProgressionKeys\n#              in the .cfg: Eikthyr opens tier 1, The Elder 2, Bonemass 3, Moder 4, Yagluth 5, The Queen 6,\n#              Fader 7), the weight is multiplied by EarlyTierFactor (0.15 by default): a tier 4 band in a\n#              fresh world is about 2000 times rarer than once Moder is dead.\n# RequiresKeys global keys that must ALL be set before the band can set out at all (e.g. defeated_bonemass).\n# Speed        metres per second while travelling with purpose.\n# RouteLength  straight distance from start to destination in metres, min-max (default: the .cfg's).\n# Lifetime     game days, min-max (default: the .cfg's). 1 game day = 30 minutes.\n# StayTogether yes: a member that fights stays in the band and travels on afterwards. no: it leaves the\n#              band for good and stays where it fought, like the migration table's creatures.\n# Assist       yes: when one member fights, pack-mates within AssistRange metres join in against that foe.\n# AssistRange  metres (default 30).\n#\n# Biomes: Meadows, BlackForest, Swamp, Mountain, Plains, Mistlands, AshLands, DeepNorth, Ocean\n# Members fight players and other creatures as they always do. They never target buildings.\n\n# A fuling war party passing through, now and then with a berserker or a shaman.\n[FulingWarband]\nMembers = Goblin 2-5; GoblinBrute 1 30% needs 3; GoblinShaman 1 20% needs 2\nHome = Plains\nEnters = Plains, BlackForest, Mountain, Meadows\nHeads = BlackForest, Mountain, Meadows\nTime = Any\nWeight = 1\nSpeed = 1.5\n\n# Trolls wandering alone or in twos and threes.\n[TrollWanderers]\nMembers = Troll 1,1,2,3\nHome = BlackForest\nEnters = BlackForest, Meadows, Swamp\nHeads = BlackForest, Meadows\nTime = Any\nWeight = 0.8\nTier = 2\nSpeed = 1.4\n\n# Greydwarfs and their brutes roaming out of the forest after dark.\n[GreydwarfNightRaid]\nMembers = Greydwarf 2-4; Greydwarf_Elite 1-2 70%; Greydwarf_Shaman 1 30%\nHome = BlackForest\nEnters = BlackForest, Meadows\nHeads = Meadows\nTime = Night\nWeight = 1.2\nTier = 1\nSpeed = 1.4\nRouteLength = 300-1200\nLifetime = 0.3-0.5\n\n# A few greydwarfs scouting the meadows: the common early traveller.\n[GreydwarfScouts]\nMembers = Greydwarf 1-3\nHome = BlackForest\nEnters = BlackForest, Meadows\nHeads = Meadows\nTime = Any\nWeight = 1.5\nTier = 0\nRouteLength = 400-1500\n\n# Draugr walking out of the swamp into the forest at night.\n[DraugrNightPatrol]\nMembers = Draugr 2-3; Draugr_Elite 1 25% needs 2\nHome = Swamp\nEnters = Swamp, BlackForest\nHeads = BlackForest\nTime = Night\nWeight = 0.6\nTier = 2\nSpeed = 1.2\nRouteLength = 400-1500\nLifetime = 0.4-0.8\n\n# A fenring coming down off the mountain on a night hunt.\n[FenringNightHunt]\nMembers = Fenring 1\nHome = Mountain\nEnters = Mountain, BlackForest\nHeads = BlackForest\nTime = Night\nWeight = 0.4\nSpeed = 1.8\nRouteLength = 400-1500\nLifetime = 0.3-0.5\n", new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
			}
			text = File.ReadAllText(pathOnDisk);
		}
		catch (Exception ex)
		{
			Plugin.LogOnce("bands-io", "Migrating Creatures: could not read " + pathOnDisk + " (" + ex.Message + "); using the built-in bands.");
			text = "# Migrating Creatures - travelling bands. A band is a mixed group, usually enemies, that travels the\n# world like the migration table's creatures: war parties, packs, night raids.\n#\n# One [Name] line per band, then Key = Value lines. Lines starting with # are comments. mc_reload in the\n# console re-reads this file; mc_bands shows every band and how likely it is in this world right now.\n#\n# Members      who travels, separated by ; (the first line leads the band):\n#                Prefab Count [Chance%] [needs N]\n#              Count    a number (2), a range (2-5), or a list to pick one from (1,1,2,3 = alone half the time)\n#              Chance%  the chance this line comes along at all (default 100%)\n#              needs N  this line only comes along when the band already has N or more members\n#              e.g.  Goblin 2-5; GoblinBrute 1 30% needs 3\n# Home         the one biome the band starts in.\n# Enters       biomes the band may walk through (comma separated). Its route never touches a biome that\n#              is not Home, Enters or Heads. Empty = no limit (only Avoid applies).\n# Heads        biomes its destination may be in (default: Enters, or Home when Enters is empty).\n# Avoid        biomes the route must never enter, on top of Enters.\n# Time         Any, Day or Night: when the band sets out and when it travels. Outside that time it holds\n#              where it is, so give a night band a short Lifetime: it is gone by the next night.\n# Weight       how often it is picked, against the other bands and the migration table's Frequency.\n#              0 switches the band off.\n# Tier         0 Meadows, 1 Black Forest, 2 Swamp, 3 Mountain, 4 Plains, 5 Mistlands, 6 Ashlands, 7 Deep North.\n#              Default: the Home biome's. For every boss the world is short of the tier (ProgressionKeys\n#              in the .cfg: Eikthyr opens tier 1, The Elder 2, Bonemass 3, Moder 4, Yagluth 5, The Queen 6,\n#              Fader 7), the weight is multiplied by EarlyTierFactor (0.15 by default): a tier 4 band in a\n#              fresh world is about 2000 times rarer than once Moder is dead.\n# RequiresKeys global keys that must ALL be set before the band can set out at all (e.g. defeated_bonemass).\n# Speed        metres per second while travelling with purpose.\n# RouteLength  straight distance from start to destination in metres, min-max (default: the .cfg's).\n# Lifetime     game days, min-max (default: the .cfg's). 1 game day = 30 minutes.\n# StayTogether yes: a member that fights stays in the band and travels on afterwards. no: it leaves the\n#              band for good and stays where it fought, like the migration table's creatures.\n# Assist       yes: when one member fights, pack-mates within AssistRange metres join in against that foe.\n# AssistRange  metres (default 30).\n#\n# Biomes: Meadows, BlackForest, Swamp, Mountain, Plains, Mistlands, AshLands, DeepNorth, Ocean\n# Members fight players and other creatures as they always do. They never target buildings.\n\n# A fuling war party passing through, now and then with a berserker or a shaman.\n[FulingWarband]\nMembers = Goblin 2-5; GoblinBrute 1 30% needs 3; GoblinShaman 1 20% needs 2\nHome = Plains\nEnters = Plains, BlackForest, Mountain, Meadows\nHeads = BlackForest, Mountain, Meadows\nTime = Any\nWeight = 1\nSpeed = 1.5\n\n# Trolls wandering alone or in twos and threes.\n[TrollWanderers]\nMembers = Troll 1,1,2,3\nHome = BlackForest\nEnters = BlackForest, Meadows, Swamp\nHeads = BlackForest, Meadows\nTime = Any\nWeight = 0.8\nTier = 2\nSpeed = 1.4\n\n# Greydwarfs and their brutes roaming out of the forest after dark.\n[GreydwarfNightRaid]\nMembers = Greydwarf 2-4; Greydwarf_Elite 1-2 70%; Greydwarf_Shaman 1 30%\nHome = BlackForest\nEnters = BlackForest, Meadows\nHeads = Meadows\nTime = Night\nWeight = 1.2\nTier = 1\nSpeed = 1.4\nRouteLength = 300-1200\nLifetime = 0.3-0.5\n\n# A few greydwarfs scouting the meadows: the common early traveller.\n[GreydwarfScouts]\nMembers = Greydwarf 1-3\nHome = BlackForest\nEnters = BlackForest, Meadows\nHeads = Meadows\nTime = Any\nWeight = 1.5\nTier = 0\nRouteLength = 400-1500\n\n# Draugr walking out of the swamp into the forest at night.\n[DraugrNightPatrol]\nMembers = Draugr 2-3; Draugr_Elite 1 25% needs 2\nHome = Swamp\nEnters = Swamp, BlackForest\nHeads = BlackForest\nTime = Night\nWeight = 0.6\nTier = 2\nSpeed = 1.2\nRouteLength = 400-1500\nLifetime = 0.4-0.8\n\n# A fenring coming down off the mountain on a night hunt.\n[FenringNightHunt]\nMembers = Fenring 1\nHome = Mountain\nEnters = Mountain, BlackForest\nHeads = BlackForest\nTime = Night\nWeight = 0.4\nSpeed = 1.8\nRouteLength = 400-1500\nLifetime = 0.3-0.5\n";
		}
		int num = 0;
		BandDefinition bandDefinition = null;
		string[] array = text.Replace("\r", "").Split(new char[1] { '\n' });
		for (int i = 0; i < array.Length; i++)
		{
			string text2 = array[i].Trim();
			if (text2.Length == 0 || text2.StartsWith("#", StringComparison.Ordinal))
			{
				continue;
			}
			if (text2.StartsWith("[", StringComparison.Ordinal))
			{
				if (bandDefinition != null && !Finish(bandDefinition))
				{
					num++;
				}
				string text3 = text2.Trim('[', ']', ' ');
				bandDefinition = new BandDefinition
				{
					Name = text3,
					Line = i + 1
				};
				if (text3.Length == 0 || text3.IndexOfAny(new char[4] { '|', ';', ':', ' ' }) >= 0)
				{
					Plugin.Log.LogWarning((object)("Migrating Creatures: bands line " + (i + 1) + ": a band name must be one word without | ; or : ('" + text3 + "'); band skipped."));
					bandDefinition = null;
					num++;
				}
				else if (Find(text3) != null)
				{
					Plugin.Log.LogWarning((object)("Migrating Creatures: bands line " + (i + 1) + ": a second band called " + text3 + "; skipped."));
					bandDefinition = null;
					num++;
				}
			}
			else
			{
				if (bandDefinition == null)
				{
					continue;
				}
				int num2 = text2.IndexOf('=');
				if (num2 <= 0)
				{
					Plugin.Log.LogWarning((object)("Migrating Creatures: bands line " + (i + 1) + " has no '=' and was ignored: " + text2));
					continue;
				}
				string text4 = Apply(bandDefinition, text2.Substring(0, num2).Trim(), text2.Substring(num2 + 1).Trim());
				if (text4 != null)
				{
					Plugin.Log.LogWarning((object)("Migrating Creatures: bands line " + (i + 1) + " (" + bandDefinition.Name + ") ignored: " + text4 + "  [" + text2 + "]"));
				}
			}
		}
		if (bandDefinition != null && !Finish(bandDefinition))
		{
			num++;
		}
		LastLoadSummary = bands.Count + " band(s)" + ((num > 0) ? (", " + num + " skipped") : "");
	}

	private static bool Finish(BandDefinition band)
	{
		//IL_0018: Unknown result type (might be due to invalid IL or missing references)
		//IL_007b: Unknown result type (might be due to invalid IL or missing references)
		//IL_009f: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
		//IL_0084: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
		//IL_0094: Unknown result type (might be due to invalid IL or missing references)
		//IL_008c: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00db: Unknown result type (might be due to invalid IL or missing references)
		//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
		//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
		//IL_0099: Unknown result type (might be due to invalid IL or missing references)
		//IL_0179: Unknown result type (might be due to invalid IL or missing references)
		//IL_017e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0185: Unknown result type (might be due to invalid IL or missing references)
		//IL_018a: Unknown result type (might be due to invalid IL or missing references)
		//IL_018b: Unknown result type (might be due to invalid IL or missing references)
		//IL_018c: Unknown result type (might be due to invalid IL or missing references)
		//IL_018d: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c5: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c6: Unknown result type (might be due to invalid IL or missing references)
		string text = null;
		if (band.Members.Count == 0)
		{
			text = "no Members";
		}
		else if ((int)band.Home == 0)
		{
			text = "no Home biome";
		}
		if (text != null)
		{
			Plugin.Log.LogWarning((object)("Migrating Creatures: band " + band.Name + " (line " + band.Line + ") skipped: " + text + "."));
			return false;
		}
		if ((int)band.Heads == 0)
		{
			band.Heads = (((int)band.Enters != 0) ? band.Enters : band.Home);
		}
		Biome val = band.Avoid;
		if ((int)band.Enters != 0)
		{
			val = (Biome)(val | (0x27F & ~(band.Home | band.Enters | band.Heads)));
		}
		if ((val & band.Home) != 0 || (band.Heads & ~val) == 0)
		{
			Plugin.Log.LogWarning((object)("Migrating Creatures: band " + band.Name + " (line " + band.Line + ") skipped: Avoid shuts out its Home or every Heads biome."));
			return false;
		}
		if (band.MaxLifetime < band.MinLifetime)
		{
			band.MaxLifetime = band.MinLifetime;
		}
		if (band.MaxRoute < band.MinRoute)
		{
			band.MaxRoute = band.MinRoute;
		}
		band.Entry = new MigrationEntry
		{
			Prefab = band.Members[0].Prefab,
			Source = band.Home,
			Destinations = (Biome)(band.Heads & ~val),
			MinCount = 1,
			MaxCount = 12,
			Speed = band.Speed,
			Frequency = band.Weight,
			SeasonWindow = "Always",
			Avoid = val,
			Tier = band.EffectiveTier,
			MinRoute = band.MinRoute,
			MaxRoute = band.MaxRoute,
			Band = band,
			Line = band.Line
		};
		bands.Add(band);
		entries.Add(band.Entry);
		return true;
	}

	private static string Apply(BandDefinition band, string key, string value)
	{
		//IL_025f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0262: Unknown result type (might be due to invalid IL or missing references)
		//IL_0263: Unknown result type (might be due to invalid IL or missing references)
		//IL_0265: Unknown result type (might be due to invalid IL or missing references)
		//IL_0266: Unknown result type (might be due to invalid IL or missing references)
		//IL_0270: Unknown result type (might be due to invalid IL or missing references)
		//IL_0271: Unknown result type (might be due to invalid IL or missing references)
		float value2;
		bool result;
		switch (key.ToLowerInvariant())
		{
		case "members":
			return ParseMembers(band, value);
		case "home":
		{
			if (!MigrationTable.TryBiome(value, out var biome) || (int)biome == 0 || (biome & (biome - 1)) != 0)
			{
				return "Home must be one biome";
			}
			band.Home = biome;
			return null;
		}
		case "enters":
			return Biomes(value, out band.Enters);
		case "heads":
			return Biomes(value, out band.Heads);
		case "avoid":
			return Biomes(value, out band.Avoid);
		case "time":
			if (string.Equals(value, "any", StringComparison.OrdinalIgnoreCase) || value.Length == 0)
			{
				band.Time = ActiveTime.Any;
			}
			else if (string.Equals(value, "day", StringComparison.OrdinalIgnoreCase))
			{
				band.Time = ActiveTime.Day;
			}
			else
			{
				if (!string.Equals(value, "night", StringComparison.OrdinalIgnoreCase))
				{
					return "Time must be Any, Day or Night";
				}
				band.Time = ActiveTime.Night;
			}
			return null;
		case "weight":
			if (!McConfig.TryFloat(value, out value2) || value2 < 0f)
			{
				return "Weight must be 0 or more";
			}
			band.Weight = value2;
			return null;
		case "tier":
			if (value.Length == 0 || string.Equals(value, "auto", StringComparison.OrdinalIgnoreCase))
			{
				band.Tier = -1;
				return null;
			}
			if (!int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out band.Tier) || band.Tier < 0 || band.Tier > 7)
			{
				band.Tier = -1;
				return "Tier must be 0-" + 7 + " or auto";
			}
			return null;
		case "requireskeys":
		case "requireskey":
		{
			band.RequiresKeys.Clear();
			string[] array = value.Split(new char[1] { ',' });
			foreach (string text in array)
			{
				if (text.Trim().Length > 0)
				{
					band.RequiresKeys.Add(text.Trim().ToLowerInvariant());
				}
			}
			return null;
		}
		case "speed":
			if (!McConfig.TryFloat(value, out value2) || value2 <= 0f)
			{
				return "Speed must be a positive number";
			}
			band.Speed = value2;
			return null;
		case "routelength":
			return Range(value, 50f, 10000f, out band.MinRoute, out band.MaxRoute);
		case "lifetime":
			return Range(value, 0.05f, 100f, out band.MinLifetime, out band.MaxLifetime);
		case "staytogether":
			if (!Bool(value, out result))
			{
				return "StayTogether must be yes or no";
			}
			band.StayTogether = result;
			return null;
		case "assist":
			if (!Bool(value, out result))
			{
				return "Assist must be yes or no";
			}
			band.Assist = result;
			return null;
		case "assistrange":
			if (!McConfig.TryFloat(value, out value2) || value2 < 0f)
			{
				return "AssistRange must be 0 or more";
			}
			band.AssistRange = value2;
			return null;
		default:
			return "unknown key '" + key + "'";
		}
	}

	private static string ParseMembers(BandDefinition band, string value)
	{
		band.Members.Clear();
		string[] array = value.Split(new char[1] { ';' });
		for (int i = 0; i < array.Length; i++)
		{
			string[] array2 = array[i].Trim().Split(new char[2] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
			if (array2.Length == 0)
			{
				continue;
			}
			BandMember bandMember = new BandMember
			{
				Prefab = array2[0]
			};
			for (int j = 1; j < array2.Length; j++)
			{
				string text = array2[j];
				if (text.EndsWith("%", StringComparison.Ordinal))
				{
					if (!McConfig.TryFloat(text.TrimEnd(new char[1] { '%' }), out var value2) || value2 < 0f)
					{
						return "bad chance '" + text + "' for " + bandMember.Prefab;
					}
					bandMember.Chance = Math.Min(1f, value2 / 100f);
				}
				else if (string.Equals(text, "needs", StringComparison.OrdinalIgnoreCase))
				{
					if (j + 1 >= array2.Length || !int.TryParse(array2[j + 1], NumberStyles.Integer, CultureInfo.InvariantCulture, out bandMember.Needs) || bandMember.Needs < 0)
					{
						return "'needs' must be followed by a number for " + bandMember.Prefab;
					}
					j++;
				}
				else if (!ParseCount(text, bandMember))
				{
					return "bad count '" + text + "' for " + bandMember.Prefab + " (a number, a range like 2-5 or a list like 1,1,2)";
				}
			}
			band.Members.Add(bandMember);
		}
		if (band.Members.Count != 0)
		{
			return null;
		}
		return "no members";
	}

	private static bool ParseCount(string word, BandMember member)
	{
		if (word.IndexOf(',') >= 0)
		{
			string[] array = word.Split(new char[1] { ',' });
			List<int> list = new List<int>();
			for (int i = 0; i < array.Length; i++)
			{
				if (array[i].Length != 0)
				{
					if (!int.TryParse(array[i], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || result < 0)
					{
						return false;
					}
					list.Add(result);
				}
			}
			if (list.Count == 0)
			{
				return false;
			}
			member.Choices = list.ToArray();
			return true;
		}
		int num = word.IndexOf('-');
		int result2;
		int result3;
		if (num > 0)
		{
			if (!int.TryParse(word.Substring(0, num), NumberStyles.Integer, CultureInfo.InvariantCulture, out result2) || !int.TryParse(word.Substring(num + 1), NumberStyles.Integer, CultureInfo.InvariantCulture, out result3) || result2 < 0 || result3 < result2)
			{
				return false;
			}
		}
		else
		{
			if (!int.TryParse(word, NumberStyles.Integer, CultureInfo.InvariantCulture, out result2) || result2 < 0)
			{
				return false;
			}
			result3 = result2;
		}
		member.Min = result2;
		member.Max = result3;
		member.Choices = null;
		return true;
	}

	private static string Biomes(string value, out Biome biomes)
	{
		if (value.Length == 0)
		{
			biomes = (Biome)0;
			return null;
		}
		if (!MigrationTable.TryBiomes(value, out biomes))
		{
			return "unknown biome in '" + value + "'";
		}
		return null;
	}

	private static string Range(string value, float floor, float ceiling, out float min, out float max)
	{
		min = (max = 0f);
		int num = value.IndexOf('-');
		string text = ((num > 0) ? value.Substring(0, num) : value);
		string text2 = ((num > 0) ? value.Substring(num + 1) : value);
		if (!McConfig.TryFloat(text, out min) || !McConfig.TryFloat(text2, out max) || min < floor || max > ceiling || max < min)
		{
			min = (max = 0f);
			return "must be min-max between " + floor.ToString(CultureInfo.InvariantCulture) + " and " + ceiling.ToString(CultureInfo.InvariantCulture);
		}
		return null;
	}

	private static bool Bool(string value, out bool result)
	{
		switch (value.Trim().ToLowerInvariant())
		{
		case "yes":
		case "on":
		case "1":
		case "true":
			result = true;
			return true;
		case "off":
		case "no":
		case "0":
		case "false":
			result = false;
			return true;
		default:
			result = false;
			return false;
		}
	}
}
internal sealed class DetRandom
{
	private ulong state;

	internal DetRandom(ulong seed)
	{
		state = seed;
	}

	internal static ulong Seed(long worldSeed, int groupId, string salt, int index)
	{
		return Mix(Mix(Mix(Mix((ulong)(worldSeed ^ -7046029254386353131L)) ^ (uint)groupId) ^ (uint)StableHash(salt)) ^ (uint)index);
	}

	internal static float Roll(long worldSeed, int groupId, string salt, int index)
	{
		return ToUnit(Mix(Seed(worldSeed, groupId, salt, index)));
	}

	internal ulong NextULong()
	{
		state += 11400714819323198485uL;
		return Mix(state);
	}

	internal float Next()
	{
		return ToUnit(NextULong());
	}

	internal float Range(float min, float max)
	{
		return min + (max - min) * Next();
	}

	internal int RangeInt(int min, int max)
	{
		if (max <= min)
		{
			return min;
		}
		return min + (int)(NextULong() % (ulong)(max - min + 1));
	}

	internal Vector2 InsideUnitCircle()
	{
		//IL_002f: Unknown result type (might be due to invalid IL or missing references)
		float num = Next() * (float)Math.PI * 2f;
		float num2 = Mathf.Sqrt(Next());
		return new Vector2(Mathf.Cos(num) * num2, Mathf.Sin(num) * num2);
	}

	private static float ToUnit(ulong value)
	{
		return (float)(value >> 40) / 16777216f;
	}

	private static ulong Mix(ulong z)
	{
		z = (z ^ (z >> 30)) * 13787848793156543929uL;
		z = (z ^ (z >> 27)) * 10723151780598845931uL;
		return z ^ (z >> 31);
	}

	private static int StableHash(string text)
	{
		if (string.IsNullOrEmpty(text))
		{
			return 0;
		}
		uint num = 2166136261u;
		for (int i = 0; i < text.Length; i++)
		{
			num ^= text[i];
			num *= 16777619;
		}
		return (int)num;
	}
}
internal static class GameTime
{
	private const double FallbackDayLength = 1800.0;

	internal static double NowSeconds
	{
		get
		{
			if (!((Object)(object)ZNet.instance != (Object)null))
			{
				return 0.0;
			}
			return ZNet.instance.GetTimeSeconds();
		}
	}

	internal static double DayLength
	{
		get
		{
			if ((Object)(object)EnvMan.instance != (Object)null && EnvMan.instance.m_dayLengthSec > 0)
			{
				return EnvMan.instance.m_dayLengthSec;
			}
			return 1800.0;
		}
	}

	internal static double NowDay => NowSeconds / DayLength;

	internal static double DaysToSeconds(double days)
	{
		return days * DayLength;
	}
}
internal sealed class ConfigurationManagerAttributes
{
	public int? Order;

	public bool? IsAdvanced;

	public bool? Browsable;

	public string DispName;

	public bool? ShowRangeAsPercent;

	public bool? ReadOnly;
}
internal static class McConfig
{
	private const string General = "1 - General";

	private const string Distances = "2 - Distances";

	private const string Composition = "3 - Group size";

	private const string Travel = "4 - Travel";

	private const string Routes = "5 - Routes";

	private const string Safety = "6 - Safety";

	private const string Bands = "7 - Bands and progression";

	private const string Debug = "9 - Debug";

	private static int order;

	public static ConfigEntry<bool> Enabled;

	public static ConfigEntry<int> GroupBudget;

	public static ConfigEntry<float> MeanMinutesBetweenGroups;

	public static ConfigEntry<float> MinLifetimeDays;

	public static ConfigEntry<float> MaxLifetimeDays;

	public static ConfigEntry<float> DirectorTickSeconds;

	public static ConfigEntry<string> OnlyCreatures;

	public static ConfigEntry<float> SpeedOverride;

	public static ConfigEntry<float> MaterialiseRadius;

	public static ConfigEntry<float> DematerialiseRadius;

	public static ConfigEntry<float> MinStartDistance;

	public static ConfigEntry<float> MaxStartDistance;

	public static ConfigEntry<float> MinRouteLength;

	public static ConfigEntry<float> MaxRouteLength;

	public static ConfigEntry<float> LoneWeight;

	public static ConfigEntry<float> PackWeight;

	public static ConfigEntry<float> HerdWeight;

	public static ConfigEntry<int> MaxMembersPerGroup;

	public static ConfigEntry<float> PurposefulChance;

	public static ConfigEntry<float> MidLegSwitchChance;

	public static ConfigEntry<float> RangingSpeedFactor;

	public static ConfigEntry<float> RangingRadius;

	public static ConfigEntry<float> RestChance;

	public static ConfigEntry<float> MinRestMinutes;

	public static ConfigEntry<float> MaxRestMinutes;

	public static ConfigEntry<float> StopChance;

	public static ConfigEntry<float> MinStopDays;

	public static ConfigEntry<float> MaxStopDays;

	public static ConfigEntry<int> MinWaypoints;

	public static ConfigEntry<int> MaxWaypoints;

	public static ConfigEntry<int> RouteAttempts;

	public static ConfigEntry<float> MaxSlopeDegrees;

	public static ConfigEntry<float> WadeDepth;

	public static ConfigEntry<float> MaxSwimMeters;

	public static ConfigEntry<float> WardExclusionRadius;

	public static ConfigEntry<bool> OnlyActiveWards;

	public static ConfigEntry<string> WardPrefabs;

	public static ConfigEntry<int> MaxStars;

	public static ConfigEntry<float> StarChance;

	public static ConfigEntry<bool> NoStructureDamage;

	public static ConfigEntry<bool> BandsEnabled;

	public static ConfigEntry<int> MaxBands;

	public static ConfigEntry<float> BandWeightScale;

	public static ConfigEntry<float> EarlyTierFactor;

	public static ConfigEntry<string> ProgressionKeys;

	public static ConfigEntry<int> ProgressionOverride;

	public static ConfigEntry<bool> LogEvents;

	public static ConfigEntry<bool> VerboseLogging;

	private static readonly List<string> onlyCreatures = new List<string>();

	private static readonly List<string> wardPrefabs = new List<string>();

	private static readonly List<string> progressionKeys = new List<string>();

	internal static IReadOnlyList<string> OnlyCreatureList => onlyCreatures;

	internal static IReadOnlyList<string> ProgressionKeyList => progressionKeys;

	internal static IReadOnlyList<string> WardPrefabList => wardPrefabs;

	internal static float DematerialiseDistance => Math.Max(DematerialiseRadius.Value, MaterialiseRadius.Value + 30f);

	internal static void Bind(ConfigFile config)
	{
		order = 1000;
		Enabled = B(config, "1 - General", "Enabled", value: true, "Master switch for new migrations. Off: no new groups start; groups already travelling carry on until they expire.", null);
		GroupBudget = B(config, "1 - General", "GroupBudget", 12, "Most groups travelling at once, world-wide. Keep it low: most of what a player meets should be a single animal that should not be there.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 64));
		MeanMinutesBetweenGroups = B(config, "1 - General", "MeanMinutesBetweenGroups", 15f, "Average minutes of play between new groups while under the budget (world time: sleeping skips it ahead; 1 game day = 30 minutes). The real gap is random, so nobody can tell when the next one is due.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 1440f));
		MinLifetimeDays = B(config, "1 - General", "MinLifetimeDays", 3f, "Shortest life of a group in game days. A group never vanishes while a player is near it: it expires when it is next out of sight.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 100f));
		MaxLifetimeDays = B(config, "1 - General", "MaxLifetimeDays", 6f, "Longest life of a group in game days.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 100f));
		DirectorTickSeconds = B(config, "1 - General", "DirectorTickSeconds", 10f, "Seconds between director ticks (server only). Groups only move, appear and disappear on a tick.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 120f));
		OnlyCreatures = B(config, "1 - General", "OnlyCreatures", "", "Testing: comma separated creature prefabs and band names. When set, only these table rows and bands start new groups (e.g. Wolf, FulingWarband).", null);
		SpeedOverride = B(config, "1 - General", "SpeedOverride", 0f, "Testing: when above 0, every group travels at this many m/s while abstract (e.g. 10 to make tests fast). 0 = the table's speeds.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 50f));
		MaterialiseRadius = B(config, "2 - Distances", "MaterialiseRadius", 120f, "A group becomes real creatures when a player comes this close to it.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(40f, 400f));
		DematerialiseRadius = B(config, "2 - Distances", "DematerialiseRadius", 180f, "Real creatures go back to being an abstract group when no player is this close to any of them. Must be larger than MaterialiseRadius (it is kept at least 30 m larger) or groups flicker.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(70f, 600f));
		MinStartDistance = B(config, "2 - Distances", "MinStartDistance", 400f, "A new group starts at least this far from every player.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(150f, 5000f));
		MaxStartDistance = B(config, "2 - Distances", "MaxStartDistance", 2500f, "A new group starts at most this far from the player it is placed for, so routes cross the land players use.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(300f, 10000f));
		MinRouteLength = B(config, "2 - Distances", "MinRouteLength", 600f, "Shortest straight distance from a route's start to its destination.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(100f, 5000f));
		MaxRouteLength = B(config, "2 - Distances", "MaxRouteLength", 3000f, "Longest straight distance from a route's start to its destination.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(200f, 10000f));
		LoneWeight = B(config, "3 - Group size", "LoneWeight", 70f, "Weight of a lone traveller (1 member).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1000f));
		PackWeight = B(config, "3 - Group size", "PackWeight", 25f, "Weight of a pack (2-5 members).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1000f));
		HerdWeight = B(config, "3 - Group size", "HerdWeight", 5f, "Weight of a herd (6-12 members, capped by MaxMembersPerGroup). Rare by design.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1000f));
		MaxMembersPerGroup = B(config, "3 - Group size", "MaxMembersPerGroup", 8, "Hard cap on members in one group.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 12));
		PurposefulChance = B(config, "4 - Travel", "PurposefulChance", 0.3f, "Chance that a leg of the route is walked with purpose (straight, steady). The rest are ranging: wandering around the route line, pausing, slow net progress.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f));
		MidLegSwitchChance = B(config, "4 - Travel", "MidLegSwitchChance", 0.15f, "Chance that a group re-rolls its travel mode halfway along a leg.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f));
		RangingSpeedFactor = B(config, "4 - Travel", "RangingSpeedFactor", 0.35f, "Net progress along the route while ranging, as a share of the purposeful speed.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 1f));
		RangingRadius = B(config, "4 - Travel", "RangingRadius", 25f, "How far from the route line a ranging group wanders (metres).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f));
		RestChance = B(config, "4 - Travel", "RestChance", 0.35f, "Chance that a group rests at a waypoint (grazing, sniffing).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f));
		MinRestMinutes = B(config, "4 - Travel", "MinRestMinutes", 2f, "Shortest rest in minutes of play.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 600f));
		MaxRestMinutes = B(config, "4 - Travel", "MaxRestMinutes", 8f, "Longest rest in minutes of play.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 600f));
		StopChance = B(config, "4 - Travel", "StopChance", 0.04f, "Chance at each waypoint that a group settles where it is for a day or more (a wolf pack that moved into your forest and stayed).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f));
		MinStopDays = B(config, "4 - Travel", "MinStopDays", 1f, "Shortest settled stay in game days.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 30f));
		MaxStopDays = B(config, "4 - Travel", "MaxStopDays", 2.5f, "Longest settled stay in game days.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 30f));
		MinWaypoints = B(config, "5 - Routes", "MinWaypoints", 6, "Fewest intermediate waypoints on a route.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 30));
		MaxWaypoints = B(config, "5 - Routes", "MaxWaypoints", 12, "Most intermediate waypoints on a route.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 30));
		RouteAttempts = B(config, "5 - Routes", "RouteAttempts", 12, "How many start/destination pairs are tried for a new group before it is silently abandoned.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100));
		MaxSlopeDegrees = B(config, "5 - Routes", "MaxSlopeDegrees", 45f, "Steepest ground a route may cross, in degrees, measured over 24 m of the world's base terrain (the walk goes round anything steeper).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 80f));
		WadeDepth = B(config, "5 - Routes", "WadeDepth", 1f, "Deepest water (metres) a route may cross on foot.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f));
		MaxSwimMeters = B(config, "5 - Routes", "MaxSwimMeters", 40f, "Creatures that can swim may cross deeper water for at most this many metres at a time. 0 = never.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1000f));
		WardExclusionRadius = B(config, "6 - Safety", "WardExclusionRadius", 60f, "Routes never come within this many metres of a player ward (never less than the ward's own radius + 10 m). A migration must not flatten a base while its owner is away.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 500f));
		OnlyActiveWards = B(config, "6 - Safety", "OnlyActiveWards", value: false, "Off (default): every placed ward counts, switched on or not. On: only wards that are switched on.", null);
		WardPrefabs = B(config, "6 - Safety", "WardPrefabs", "guard_stone", "Prefab names of the wards routes keep away from (comma separated).", null);
		MaxStars = B(config, "6 - Safety", "MaxStars", 1, "Most stars a migrant can have. A two-star plains creature in the meadows at hour three kills a new character with no counterplay.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 2));
		StarChance = B(config, "6 - Safety", "StarChance", 0.1f, "Chance of each extra star (rolled once per star, like vanilla).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f));
		NoStructureDamage = B(config, "6 - Safety", "NoStructureDamage", value: true, "Migrants never target player buildings, and their hits do no damage to them.", null);
		BandsEnabled = B(config, "7 - Bands and progression", "BandsEnabled", value: true, "Bands (marc.valheim.migratingcreatures.bands.txt: fuling war parties, trolls, greydwarf night raids...) start alongside the migration table's creatures. Off: only table rows start; bands already travelling carry on.", null);
		MaxBands = B(config, "7 - Bands and progression", "MaxBands", 3, "Most bands travelling at once, world-wide. They count toward GroupBudget too.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 64));
		BandWeightScale = B(config, "7 - Bands and progression", "BandWeightScale", 1f, "Multiplies every band's Weight against the migration table's Frequency. 2 = bands twice as common.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f));
		EarlyTierFactor = B(config, "7 - Bands and progression", "EarlyTierFactor", 0.15f, "For every boss the world is short of a band's (or a hostile table row's) tier, its weight is multiplied by this. 0.15: one boss short = 15%, two = about 2%, four = 0.05%. 1 = progression does not matter.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f));
		ProgressionKeys = B(config, "7 - Bands and progression", "ProgressionKeys", "defeated_eikthyr, defeated_gdking, defeated_bonemass, defeated_dragon, defeated_goblinking, defeated_queen, defeated_fader", "Global keys that count as progress, one per tier (1 Black Forest ... 7 Deep North). The world's level is how many are set, in any order.", null);
		ProgressionOverride = B(config, "7 - Bands and progression", "ProgressionOverride", -1, "Testing: when 0 or more, the world's level is this instead of its boss keys (e.g. 0 = fresh world, 4 = Moder dead).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(-1, 7));
		LogEvents = B(config, "9 - Debug", "LogEvents", value: true, "One [mc] line in LogOutput.log per group created, materialised, dematerialised, rerouted, left or expired.", null);
		VerboseLogging = B(config, "9 - Debug", "VerboseLogging", value: false, "Extra diagnostics in LogOutput.log.", null);
		OnlyCreatures.SettingChanged += delegate
		{
			Reparse();
		};
		WardPrefabs.SettingChanged += delegate
		{
			Reparse();
		};
		ProgressionKeys.SettingChanged += delegate
		{
			Reparse();
		};
		Reparse();
	}

	internal static void Reparse()
	{
		Split(OnlyCreatures.Value, onlyCreatures);
		Split(WardPrefabs.Value, wardPrefabs);
		Split(ProgressionKeys.Value, progressionKeys);
		for (int i = 0; i < progressionKeys.Count; i++)
		{
			progressionKeys[i] = progressionKeys[i].ToLowerInvariant();
		}
	}

	internal static bool CreatureAllowed(string name)
	{
		if (onlyCreatures.Count == 0)
		{
			return true;
		}
		for (int i = 0; i < onlyCreatures.Count; i++)
		{
			if (string.Equals(onlyCreatures[i], name, StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
		}
		return false;
	}

	internal static bool TryFloat(string text, out float value)
	{
		return float.TryParse((text ?? "").Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out value);
	}

	private static void Split(string text, List<string> into)
	{
		into.Clear();
		if (string.IsNullOrEmpty(text))
		{
			return;
		}
		string[] array = text.Split(new char[1] { ',' });
		for (int i = 0; i < array.Length; i++)
		{
			string text2 = array[i].Trim();
			if (text2.Length > 0)
			{
				into.Add(text2);
			}
		}
	}

	private static ConfigEntry<T> B<T>(ConfigFile config, string section, string key, T value, string description, AcceptableValueBase range)
	{
		//IL_0030: Unknown result type (might be due to invalid IL or missing references)
		//IL_003a: Expected O, but got Unknown
		ConfigurationManagerAttributes configurationManagerAttributes = new ConfigurationManagerAttributes
		{
			Order = order--
		};
		return config.Bind<T>(section, key, value, new ConfigDescription(description, range, new object[1] { configurationManagerAttributes }));
	}
}
internal sealed class MigrationEntry
{
	internal string Prefab;

	internal Biome Source;

	internal Biome Destinations;

	internal int MinCount;

	internal int MaxCount;

	internal float Speed;

	internal float Frequency;

	internal string SeasonWindow;

	internal Biome Avoid;

	internal int Tier = -1;

	internal float MinRoute;

	internal float MaxRoute;

	internal BandDefinition Band;

	internal int Line;

	internal string Name
	{
		get
		{
			if (Band == null)
			{
				return Prefab;
			}
			return Band.Name;
		}
	}

	internal string Describe()
	{
		//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
		if (Band != null)
		{
			return Band.Describe();
		}
		return Prefab + " " + ((object)Unsafe.As<Biome, Biome>(ref Source)/*cast due to .constrained prefix*/).ToString() + " -> " + ((object)Unsafe.As<Biome, Biome>(ref Destinations)/*cast due to .constrained prefix*/).ToString() + " x" + MinCount + "-" + MaxCount + " " + Speed.ToString("0.##", CultureInfo.InvariantCulture) + " m/s w" + Frequency.ToString("0.##", CultureInfo.InvariantCulture) + (((int)Avoid != 0) ? (" avoid " + ((object)Unsafe.As<Biome, Biome>(ref Avoid)/*cast due to .constrained prefix*/).ToString()) : "") + ((Tier >= 0) ? (" tier " + Tier) : "");
	}
}
internal static class MigrationTable
{
	internal const string FileName = "marc.valheim.migratingcreatures.table.txt";

	private const string DefaultText = "# Migrating Creatures - migration table. One migration per line, columns separated by |\n#\n# CreaturePrefab | SourceBiome | DestBiomes | MinCount | MaxCount | Speed | Frequency | SeasonWindow | AvoidBiomes | Tier\n#\n# CreaturePrefab  the creature's prefab name (Wolf, Boar, Greydwarf, Lox, Deer, Neck...).\n# SourceBiome     where the group starts. DestBiomes: where it is heading (comma separated).\n# MinCount/MaxCount  the group size this creature allows. The size type (lone 1, pack 2-5, herd 6-12)\n#                 is rolled first and then clamped into this range and the MaxMembersPerGroup cap.\n# Speed           metres per second while travelling with purpose (ranging is slower).\n# Frequency       relative weight when a new group is created. 0 switches a row off.\n# SeasonWindow    reserved for seasons (a later version); write Always.\n# AvoidBiomes     optional: biomes the route must never enter (comma separated).\n# Tier            optional progression tier, 0 Meadows ... 4 Plains ... 7 Deep North. Empty: a hostile\n#                 creature gets its SourceBiome's tier, an animal 0. For every boss the world is short\n#                 of it the row's Frequency is multiplied by EarlyTierFactor (see the .cfg).\n#\n# Mixed groups (war parties, packs, night raids) are in marc.valheim.migratingcreatures.bands.txt.\n#\n# Biomes: Meadows, BlackForest, Swamp, Mountain, Plains, Mistlands, AshLands, DeepNorth, Ocean\n\nWolf      | Mountain    | BlackForest | 1 | 5 | 1.6 | 3 | Always | Meadows\nBoar      | Meadows     | BlackForest | 1 | 8 | 1.2 | 3 | Always |\nGreydwarf | BlackForest | Meadows     | 1 | 5 | 1.3 | 2 | Always | Mountain\nLox       | Plains      | Plains      | 1 | 6 | 1.0 | 1 | Always | Meadows\n";

	private static readonly List<MigrationEntry> entries = new List<MigrationEntry>();

	internal static IReadOnlyList<MigrationEntry> Entries => entries;

	internal static string LastLoadSummary { get; private set; }

	internal static string PathOnDisk => Path.Combine(Paths.ConfigPath, "marc.valheim.migratingcreatures.table.txt");

	internal static void Load()
	{
		entries.Clear();
		string pathOnDisk = PathOnDisk;
		string text;
		try
		{
			if (!File.Exists(pathOnDisk))
			{
				File.WriteAllText(pathOnDisk, "# Migrating Creatures - migration table. One migration per line, columns separated by |\n#\n# CreaturePrefab | SourceBiome | DestBiomes | MinCount | MaxCount | Speed | Frequency | SeasonWindow | AvoidBiomes | Tier\n#\n# CreaturePrefab  the creature's prefab name (Wolf, Boar, Greydwarf, Lox, Deer, Neck...).\n# SourceBiome     where the group starts. DestBiomes: where it is heading (comma separated).\n# MinCount/MaxCount  the group size this creature allows. The size type (lone 1, pack 2-5, herd 6-12)\n#                 is rolled first and then clamped into this range and the MaxMembersPerGroup cap.\n# Speed           metres per second while travelling with purpose (ranging is slower).\n# Frequency       relative weight when a new group is created. 0 switches a row off.\n# SeasonWindow    reserved for seasons (a later version); write Always.\n# AvoidBiomes     optional: biomes the route must never enter (comma separated).\n# Tier            optional progression tier, 0 Meadows ... 4 Plains ... 7 Deep North. Empty: a hostile\n#                 creature gets its SourceBiome's tier, an animal 0. For every boss the world is short\n#                 of it the row's Frequency is multiplied by EarlyTierFactor (see the .cfg).\n#\n# Mixed groups (war parties, packs, night raids) are in marc.valheim.migratingcreatures.bands.txt.\n#\n# Biomes: Meadows, BlackForest, Swamp, Mountain, Plains, Mistlands, AshLands, DeepNorth, Ocean\n\nWolf      | Mountain    | BlackForest | 1 | 5 | 1.6 | 3 | Always | Meadows\nBoar      | Meadows     | BlackForest | 1 | 8 | 1.2 | 3 | Always |\nGreydwarf | BlackForest | Meadows     | 1 | 5 | 1.3 | 2 | Always | Mountain\nLox       | Plains      | Plains      | 1 | 6 | 1.0 | 1 | Always | Meadows\n", new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
			}
			text = File.ReadAllText(pathOnDisk);
		}
		catch (Exception ex)
		{
			Plugin.LogOnce("table-io", "Migrating Creatures: could not read " + pathOnDisk + " (" + ex.Message + "); using the built-in table.");
			text = "# Migrating Creatures - migration table. One migration per line, columns separated by |\n#\n# CreaturePrefab | SourceBiome | DestBiomes | MinCount | MaxCount | Speed | Frequency | SeasonWindow | AvoidBiomes | Tier\n#\n# CreaturePrefab  the creature's prefab name (Wolf, Boar, Greydwarf, Lox, Deer, Neck...).\n# SourceBiome     where the group starts. DestBiomes: where it is heading (comma separated).\n# MinCount/MaxCount  the group size this creature allows. The size type (lone 1, pack 2-5, herd 6-12)\n#                 is rolled first and then clamped into this range and the MaxMembersPerGroup cap.\n# Speed           metres per second while travelling with purpose (ranging is slower).\n# Frequency       relative weight when a new group is created. 0 switches a row off.\n# SeasonWindow    reserved for seasons (a later version); write Always.\n# AvoidBiomes     optional: biomes the route must never enter (comma separated).\n# Tier            optional progression tier, 0 Meadows ... 4 Plains ... 7 Deep North. Empty: a hostile\n#                 creature gets its SourceBiome's tier, an animal 0. For every boss the world is short\n#                 of it the row's Frequency is multiplied by EarlyTierFactor (see the .cfg).\n#\n# Mixed groups (war parties, packs, night raids) are in marc.valheim.migratingcreatures.bands.txt.\n#\n# Biomes: Meadows, BlackForest, Swamp, Mountain, Plains, Mistlands, AshLands, DeepNorth, Ocean\n\nWolf      | Mountain    | BlackForest | 1 | 5 | 1.6 | 3 | Always | Meadows\nBoar      | Meadows     | BlackForest | 1 | 8 | 1.2 | 3 | Always |\nGreydwarf | BlackForest | Meadows     | 1 | 5 | 1.3 | 2 | Always | Mountain\nLox       | Plains      | Plains      | 1 | 6 | 1.0 | 1 | Always | Meadows\n";
		}
		int num = 0;
		string[] array = text.Replace("\r", "").Split(new char[1] { '\n' });
		for (int i = 0; i < array.Length; i++)
		{
			string text2 = array[i].Trim();
			if (text2.Length != 0 && !text2.StartsWith("#", StringComparison.Ordinal))
			{
				string error;
				MigrationEntry migrationEntry = Parse(text2, i + 1, out error);
				if (migrationEntry == null)
				{
					num++;
					Plugin.Log.LogWarning((object)("Migrating Creatures: table line " + (i + 1) + " skipped: " + error + "  [" + text2 + "]"));
				}
				else
				{
					entries.Add(migrationEntry);
				}
			}
		}
		LastLoadSummary = entries.Count + " row(s)" + ((num > 0) ? (", " + num + " skipped") : "");
	}

	internal static MigrationEntry Find(string prefab)
	{
		for (int i = 0; i < entries.Count; i++)
		{
			if (string.Equals(entries[i].Prefab, prefab, StringComparison.OrdinalIgnoreCase))
			{
				return entries[i];
			}
		}
		return null;
	}

	private static MigrationEntry Parse(string line, int lineNumber, out string error)
	{
		//IL_0080: Unknown result type (might be due to invalid IL or missing references)
		//IL_0088: Unknown result type (might be due to invalid IL or missing references)
		//IL_008e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0094: Unknown result type (might be due to invalid IL or missing references)
		//IL_0095: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
		//IL_0188: Unknown result type (might be due to invalid IL or missing references)
		string[] array = line.Split(new char[1] { '|' });
		if (array.Length < 8)
		{
			error = "needs at least 8 columns";
			return null;
		}
		for (int i = 0; i < array.Length; i++)
		{
			array[i] = array[i].Trim();
		}
		MigrationEntry migrationEntry = new MigrationEntry
		{
			Prefab = array[0],
			Line = lineNumber,
			SeasonWindow = array[7]
		};
		if (migrationEntry.Prefab.Length == 0)
		{
			error = "no creature prefab";
			return null;
		}
		if (!TryBiome(array[1], out migrationEntry.Source) || (int)migrationEntry.Source == 0 || (migrationEntry.Source & (migrationEntry.Source - 1)) != 0)
		{
			error = "the source must be one biome ('" + array[1] + "')";
			return null;
		}
		if (!TryBiomes(array[2], out migrationEntry.Destinations) || (int)migrationEntry.Destinations == 0)
		{
			error = "unknown destination biome in '" + array[2] + "'";
			return null;
		}
		if (!int.TryParse(array[3], NumberStyles.Integer, CultureInfo.InvariantCulture, out migrationEntry.MinCount) || !int.TryParse(array[4], NumberStyles.Integer, CultureInfo.InvariantCulture, out migrationEntry.MaxCount))
		{
			error = "MinCount/MaxCount must be whole numbers";
			return null;
		}
		migrationEntry.MinCount = Math.Max(1, migrationEntry.MinCount);
		migrationEntry.MaxCount = Math.Max(migrationEntry.MinCount, migrationEntry.MaxCount);
		if (!McConfig.TryFloat(array[5], out migrationEntry.Speed) || migrationEntry.Speed <= 0f)
		{
			error = "Speed must be a positive number";
			return null;
		}
		if (!McConfig.TryFloat(array[6], out migrationEntry.Frequency) || migrationEntry.Frequency < 0f)
		{
			error = "Frequency must be 0 or more";
			return null;
		}
		migrationEntry.Avoid = (Biome)0;
		if (array.Length > 8 && array[8].Length > 0 && !TryBiomes(array[8], out migrationEntry.Avoid))
		{
			error = "unknown avoid biome in '" + array[8] + "'";
			return null;
		}
		migrationEntry.Tier = -1;
		if (array.Length > 9 && array[9].Length > 0 && !string.Equals(array[9], "auto", StringComparison.OrdinalIgnoreCase) && (!int.TryParse(array[9], NumberStyles.Integer, CultureInfo.InvariantCulture, out migrationEntry.Tier) || migrationEntry.Tier < 0 || migrationEntry.Tier > 7))
		{
			error = "Tier must be 0-" + 7 + ", auto or empty";
			return null;
		}
		error = null;
		return migrationEntry;
	}

	internal static bool TryBiome(string text, out Biome biome)
	{
		//IL_004a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0050: Expected I4, but got Unknown
		biome = (Biome)0;
		if (string.IsNullOrEmpty(text))
		{
			return false;
		}
		string text2 = text.Trim().Replace(" ", "");
		if (string.Equals(text2, "Ashlands", StringComparison.OrdinalIgnoreCase))
		{
			text2 = "AshLands";
		}
		try
		{
			biome = (Biome)(int)(Biome)Enum.Parse(typeof(Biome), text2, ignoreCase: true);
			return Enum.IsDefined(typeof(Biome), biome);
		}
		catch
		{
			return false;
		}
	}

	internal static bool TryBiomes(string text, out Biome biomes)
	{
		//IL_0043: Unknown result type (might be due to invalid IL or missing references)
		//IL_0044: Unknown result type (might be due to invalid IL or missing references)
		//IL_0046: Expected I4, but got Unknown
		biomes = (Biome)0;
		if (string.IsNullOrEmpty(text))
		{
			return false;
		}
		string[] array = text.Split(new char[1] { ',' });
		for (int i = 0; i < array.Length; i++)
		{
			if (array[i].Trim().Length != 0)
			{
				if (!TryBiome(array[i], out var biome))
				{
					return false;
				}
				biomes |= biome;
			}
		}
		return true;
	}
}
internal static class Progression
{
	internal const int MaxTier = 7;

	internal static int WorldLevel
	{
		get
		{
			if (McConfig.ProgressionOverride.Value >= 0)
			{
				return McConfig.ProgressionOverride.Value;
			}
			if ((Object)(object)ZoneSystem.instance == (Object)null)
			{
				return 0;
			}
			int num = 0;
			IReadOnlyList<string> progressionKeyList = McConfig.ProgressionKeyList;
			for (int i = 0; i < progressionKeyList.Count; i++)
			{
				if (ZoneSystem.instance.GetGlobalKey(progressionKeyList[i]))
				{
					num++;
				}
			}
			return num;
		}
	}

	internal static int BiomeTier(Biome biome)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0002: Invalid comparison between Unknown and I4
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_0015: Invalid comparison between Unknown and I4
		//IL_0004: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Invalid comparison between Unknown and I4
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		//IL_0026: Invalid comparison between Unknown and I4
		//IL_0017: Unknown result type (might be due to invalid IL or missing references)
		//IL_001a: Invalid comparison between Unknown and I4
		//IL_0008: Unknown result type (might be due to invalid IL or missing references)
		//IL_000a: Invalid comparison between Unknown and I4
		//IL_0028: Unknown result type (might be due to invalid IL or missing references)
		//IL_002e: Invalid comparison between Unknown and I4
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_001f: Invalid comparison between Unknown and I4
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		//IL_000e: Invalid comparison between Unknown and I4
		if ((int)biome <= 8)
		{
			if ((int)biome == 2)
			{
				return 2;
			}
			if ((int)biome == 4)
			{
				return 3;
			}
			if ((int)biome == 8)
			{
				return 1;
			}
		}
		else if ((int)biome <= 32)
		{
			if ((int)biome == 16)
			{
				return 4;
			}
			if ((int)biome == 32)
			{
				return 6;
			}
		}
		else
		{
			if ((int)biome == 64)
			{
				return 7;
			}
			if ((int)biome == 512)
			{
				return 5;
			}
		}
		return 0;
	}

	internal static float Scale(int tier, int level)
	{
		if (tier <= level)
		{
			return 1f;
		}
		return Mathf.Pow(Mathf.Clamp01(McConfig.EarlyTierFactor.Value), (float)(tier - level));
	}

	internal static bool KeysMet(List<string> keys, out string missing)
	{
		missing = null;
		if (keys == null || keys.Count == 0)
		{
			return true;
		}
		if ((Object)(object)ZoneSystem.instance == (Object)null)
		{
			missing = keys[0];
			return false;
		}
		for (int i = 0; i < keys.Count; i++)
		{
			if (!ZoneSystem.instance.GetGlobalKey(keys[i]))
			{
				missing = keys[i];
				return false;
			}
		}
		return true;
	}

	internal static bool IsHostile(Character character)
	{
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_0014: Unknown result type (might be due to invalid IL or missing references)
		//IL_004a: Expected I4, but got Unknown
		if ((Object)(object)character == (Object)null)
		{
			return false;
		}
		Faction faction = character.m_faction;
		switch (faction - 2)
		{
		case 0:
		case 1:
		case 2:
		case 3:
		case 4:
		case 5:
		case 7:
		case 11:
			return true;
		default:
			return false;
		}
	}
}
internal enum ActiveTime
{
	Any,
	Day,
	Night
}
internal static class TimeWindow
{
	internal static bool Open(ActiveTime window)
	{
		return window switch
		{
			ActiveTime.Day => EnvMan.IsDay(), 
			ActiveTime.Night => EnvMan.IsNight(), 
			_ => true, 
		};
	}
}
internal static class Reflect
{
	internal delegate bool MoveToFn(BaseAI ai, float dt, Vector3 point, float dist, bool run);

	internal delegate bool FoundPathFn(BaseAI ai);

	internal delegate void RandomMovementFn(BaseAI ai, float dt, Vector3 centerPoint, bool snapToGround);

	internal delegate void SetTargetFn(MonsterAI ai, Character target);

	internal delegate void SetAlertedFn(BaseAI ai, bool alert);

	internal static readonly MoveToFn MoveTo = Method<MoveToFn>("MoveTo", new Type[4]
	{
		typeof(float),
		typeof(Vector3),
		typeof(float),
		typeof(bool)
	});

	internal static readonly FoundPathFn FoundPath = Method<FoundPathFn>("FoundPath", Type.EmptyTypes);

	internal static readonly RandomMovementFn RandomMovement = Method<RandomMovementFn>("RandomMovement", new Type[3]
	{
		typeof(float),
		typeof(Vector3),
		typeof(bool)
	});

	internal static readonly FieldRef<BaseAI, Vector3> SpawnPoint = Field<BaseAI, Vector3>("m_spawnPoint");

	internal static readonly FieldRef<Character, ZNetView> CharacterNView = Field<Character, ZNetView>("m_nview");

	internal static readonly SetTargetFn SetTarget = Method<SetTargetFn>(typeof(MonsterAI), "SetTarget", new Type[1] { typeof(Character) }, "band members do not join each other's fights");

	internal static readonly SetAlertedFn SetAlerted = Method<SetAlertedFn>(typeof(BaseAI), "SetAlerted", new Type[1] { typeof(bool) }, "band members do not join each other's fights");

	internal static bool HerdControlAvailable
	{
		get
		{
			if (MoveTo != null && FoundPath != null)
			{
				return RandomMovement != null;
			}
			return false;
		}
	}

	internal static bool AssistAvailable
	{
		get
		{
			if (SetTarget != null)
			{
				return SetAlerted != null;
			}
			return false;
		}
	}

	private static T Method<T>(string name, Type[] parameters) where T : Delegate
	{
		return Method<T>(typeof(BaseAI), name, parameters, "herds fall back to vanilla idle movement");
	}

	private static T Method<T>(Type type, string name, Type[] parameters, string fallback) where T : Delegate
	{
		try
		{
			MethodInfo methodInfo = AccessTools.Method(type, name, parameters, (Type[])null);
			if (methodInfo == null)
			{
				throw new MissingMethodException(type.Name + "." + name);
			}
			return AccessTools.MethodDelegate<T>(methodInfo, (object)null, true);
		}
		catch (Exception ex)
		{
			Plugin.LogOnce("method-" + type.Name + "." + name, "Migrating Creatures: " + type.Name + "." + name + " not found (" + ex.Message + "); " + fallback + ".");
			return null;
		}
	}

	private static FieldRef<TObject, TField> Field<TObject, TField>(string field) where TObject : class
	{
		try
		{
			return AccessTools.FieldRefAccess<TObject, TField>(field);
		}
		catch (Exception ex)
		{
			Plugin.LogOnce("field-" + typeof(TObject).Name + "." + field, "Migrating Creatures: field " + typeof(TObject).Name + "." + field + " not found (" + ex.Message + "); using a fallback.");
			return null;
		}
	}

	internal static ZNetView NView(Character character)
	{
		if ((Object)(object)character == (Object)null)
		{
			return null;
		}
		if (CharacterNView != null)
		{
			try
			{
				return CharacterNView.Invoke(character);
			}
			catch
			{
			}
		}
		return ((Component)character).GetComponent<ZNetView>();
	}

	internal static void SetSpawnPoint(BaseAI ai, Vector3 point)
	{
		//IL_001d: Unknown result type (might be due to invalid IL or missing references)
		//IL_001e: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)ai == (Object)null || SpawnPoint == null)
		{
			return;
		}
		try
		{
			SpawnPoint.Invoke(ai) = point;
		}
		catch
		{
		}
	}
}
internal static class ZdoKeys
{
	internal static readonly int DirectorMarker = StringExtensionMethods.GetStableHashCode("mc_dir");

	internal static readonly int DirectorState = StringExtensionMethods.GetStableHashCode("mc_dstate");

	internal static readonly int DirectorSequence = StringExtensionMethods.GetStableHashCode("mc_dseq");

	internal static readonly int Migrant = StringExtensionMethods.GetStableHashCode("mc_migrant");

	internal static readonly int Group = StringExtensionMethods.GetStableHashCode("mc_group");

	internal static readonly int MemberIndex = StringExtensionMethods.GetStableHashCode("mc_idx");

	internal static readonly int Route = StringExtensionMethods.GetStableHashCode("mc_route");

	internal static readonly int Generation = StringExtensionMethods.GetStableHashCode("mc_gen");

	internal static readonly int Travel = StringExtensionMethods.GetStableHashCode("mc_tp");

	internal static readonly int RouteIndex = StringExtensionMethods.GetStableHashCode("mc_ri");

	internal static readonly int LegSwitched = StringExtensionMethods.GetStableHashCode("mc_ls");

	internal static readonly int State = StringExtensionMethods.GetStableHashCode("mc_st");

	internal static readonly int StateUntil = StringExtensionMethods.GetStableHashCode("mc_until");

	internal static readonly int Center = StringExtensionMethods.GetStableHashCode("mc_ctr");

	internal static readonly int Mode = StringExtensionMethods.GetStableHashCode("mc_mode");

	internal static readonly int Snap = StringExtensionMethods.GetStableHashCode("mc_snap");

	internal static readonly int LeftReason = StringExtensionMethods.GetStableHashCode("mc_left");
}
internal static class Director
{
	private sealed class PendingGroup
	{
		internal int GroupId;

		internal MigrationEntry Entry;

		internal List<string> Prefabs;

		internal DetRandom Rng;

		internal RoutePlanner.PlanJob Job;
	}

	private const float WorldStartDelay = 4f;

	private static DirectorState state;

	private static bool loaded;

	private static float readySince = -1f;

	private static float nextTick;

	private static string lastSaved;

	private static int lastWardVersion = -1;

	private static long cachedSeed;

	private static bool seedCached;

	private static readonly List<Vector2> players = new List<Vector2>();

	internal static int CreatedThisSession;

	internal static int AbandonedThisSession;

	internal static int MaterialisedThisSession;

	internal static int DematerialisedThisSession;

	internal static double LastTickMs;

	private const float DungeonHeight = 3000f;

	private static PendingGroup pending;

	private static readonly Dictionary<int, KeyValuePair<RoutePlanner.PlanJob, string>> pendingReroutes = new Dictionary<int, KeyValuePair<RoutePlanner.PlanJob, string>>();

	internal static DirectorState State => state;

	internal static bool Running
	{
		get
		{
			if (loaded)
			{
				return state != null;
			}
			return false;
		}
	}

	internal static bool IsServer
	{
		get
		{
			if ((Object)(object)ZNet.instance != (Object)null)
			{
				return ZNet.instance.IsServer();
			}
			return false;
		}
	}

	internal static bool IsHeadless
	{
		get
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Invalid comparison between Unknown and I4
			if (!((Object)(object)ZNet.instance != (Object)null) || !ZNet.instance.IsDedicated())
			{
				return (int)SystemInfo.graphicsDeviceType == 4;
			}
			return true;
		}
	}

	internal static long WorldSeed
	{
		get
		{
			if (!seedCached && WorldGenerator.instance != null)
			{
				cachedSeed = WorldGenerator.instance.GetSeed();
				seedCached = true;
			}
			return cachedSeed;
		}
	}

	internal static IReadOnlyList<Vector2> Players => players;

	internal static bool HasMaterialised
	{
		get
		{
			if (state == null)
			{
				return false;
			}
			for (int i = 0; i < state.Groups.Count; i++)
			{
				if (state.Groups[i].State == GroupState.Materialised)
				{
					return true;
				}
			}
			return false;
		}
	}

	internal static void Reset()
	{
		state = null;
		pending = null;
		pendingReroutes.Clear();
		loaded = false;
		readySince = -1f;
		nextTick = 0f;
		lastSaved = null;
		lastWardVersion = -1;
		seedCached = false;
		players.Clear();
		DirectorStore.Reset();
		MemberResolver.Reset();
	}

	internal static void Tick()
	{
		if (!IsServer || !WorldReady())
		{
			return;
		}
		if (!loaded)
		{
			DirectorStore.TickSearch();
			if (!DirectorStore.Ready)
			{
				return;
			}
			Load();
		}
		WardIndex.Tick();
		MemberResolver.Tick();
		StepPending();
		if (!(Time.time < nextTick))
		{
			nextTick = Time.time + Mathf.Max(1f, McConfig.DirectorTickSeconds.Value);
			RunTick();
		}
	}

	internal static void RunTick()
	{
		//IL_014b: Unknown result type (might be due to invalid IL or missing references)
		if (!Running)
		{
			return;
		}
		Stopwatch stopwatch = Stopwatch.StartNew();
		double nowSeconds = GameTime.NowSeconds;
		double dt = ((state.LastTickSeconds < 0.0) ? 0.0 : Math.Max(0.0, nowSeconds - state.LastTickSeconds));
		state.LastTickSeconds = nowSeconds;
		CollectPlayers();
		double nowDay = GameTime.NowDay;
		for (int num = state.Groups.Count - 1; num >= 0; num--)
		{
			MigrationGroup migrationGroup = state.Groups[num];
			if (migrationGroup.State == GroupState.Materialised)
			{
				if (!TrackMaterialised(migrationGroup, nowDay, nowSeconds, dt))
				{
					state.Groups.RemoveAt(num);
				}
			}
			else if (nowDay >= migrationGroup.ExpiryDay)
			{
				if (Plugin.LogEvents)
				{
					Plugin.Event("group " + migrationGroup.GroupId + " expired on day " + nowDay.ToString("0.00", CultureInfo.InvariantCulture) + " (" + Describe(migrationGroup) + ")");
				}
				state.Groups.RemoveAt(num);
			}
			else
			{
				Advance(migrationGroup, nowSeconds, dt);
				if (NearestPlayer(migrationGroup.WorldPos) <= McConfig.MaterialiseRadius.Value)
				{
					Materialiser.Materialise(migrationGroup, players);
				}
			}
		}
		if (WardIndex.Ready && WardIndex.Version != lastWardVersion)
		{
			lastWardVersion = WardIndex.Version;
			for (int i = 0; i < state.Groups.Count; i++)
			{
				MigrationGroup migrationGroup2 = state.Groups[i];
				if (migrationGroup2.State != GroupState.Materialised && RoutePlanner.RemainingTouchesWard(migrationGroup2))
				{
					RequestReroute(migrationGroup2, "a ward on its way");
				}
			}
		}
		MaybeSpawn(nowSeconds);
		Save();
		LastTickMs = stopwatch.Elapsed.TotalMilliseconds;
	}

	private static bool WorldReady()
	{
		if (ZDOMan.instance == null || !((Object)(object)ZNetScene.instance != (Object)null) || !((Object)(object)ZoneSystem.instance != (Object)null) || WorldGenerator.instance == null || !((Object)(object)EnvMan.instance != (Object)null) || (!IsHeadless && !((Object)(object)Player.m_localPlayer != (Object)null)))
		{
			readySince = -1f;
			return false;
		}
		if (readySince < 0f)
		{
			readySince = Time.time;
		}
		return Time.time - readySince >= 4f;
	}

	private static void Load()
	{
		loaded = true;
		string text = DirectorStore.Read();
		string error = null;
		if (text.Length > 0 && DirectorState.TryDeserialize(text, out var directorState, out error))
		{
			state = directorState;
			for (int i = 0; i < state.Groups.Count; i++)
			{
				if (state.Groups[i].State == GroupState.Materialised)
				{
					state.Groups[i].NeedsResolve = true;
				}
			}
			MemberResolver.Request(state.Groups);
			if (Plugin.LogEvents)
			{
				Plugin.Event("director loaded " + state.Groups.Count + " group(s) from the world");
			}
		}
		else
		{
			if (text.Length > 0)
			{
				Plugin.Log.LogWarning((object)("Migrating Creatures: the saved director state could not be read (" + error + "); starting fresh."));
			}
			state = new DirectorState();
			MemberResolver.Request(state.Groups);
			if (Plugin.LogEvents)
			{
				Plugin.Event("director started fresh" + (DirectorStore.PrefabRegistered ? "" : " (holder prefab missing: state not saved)"));
			}
		}
		lastSaved = text;
		nextTick = Time.time + 1f;
	}

	internal static void Save()
	{
		if (Running)
		{
			string text = state.Serialize();
			if (!(text == lastSaved))
			{
				DirectorStore.Write(text);
				lastSaved = text;
			}
		}
	}

	private static void CollectPlayers()
	{
		//IL_008e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0093: Unknown result type (might be due to invalid IL or missing references)
		//IL_0094: Unknown result type (might be due to invalid IL or missing references)
		//IL_0059: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
		players.Clear();
		List<ZNetPeer> peers = ZNet.instance.GetPeers();
		for (int i = 0; i < peers.Count; i++)
		{
			ZNetPeer val = peers[i];
			if (val != null && val.IsReady() && !(val.m_refPos.y > 3000f))
			{
				players.Add(new Vector2(val.m_refPos.x, val.m_refPos.z));
			}
		}
		if (!IsHeadless && (Object)(object)Player.m_localPlayer != (Object)null)
		{
			Vector3 position = ((Component)Player.m_localPlayer).transform.position;
			if (position.y <= 3000f)
			{
				players.Add(new Vector2(position.x, position.z));
			}
		}
	}

	internal static float NearestPlayer(Vector2 p)
	{
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		//IL_0016: Unknown result type (might be due to invalid IL or missing references)
		float num = float.MaxValue;
		for (int i = 0; i < players.Count; i++)
		{
			num = Mathf.Min(num, Vector2.Distance(players[i], p));
		}
		return num;
	}

	internal static void AdvanceGroup(MigrationGroup group, double now, double dt)
	{
		Advance(group, now, dt);
	}

	private static void Advance(MigrationGroup group, double now, double dt)
	{
		//IL_0234: Unknown result type (might be due to invalid IL or missing references)
		//IL_0239: Unknown result type (might be due to invalid IL or missing references)
		//IL_0261: Unknown result type (might be due to invalid IL or missing references)
		//IL_0266: Unknown result type (might be due to invalid IL or missing references)
		//IL_0269: Unknown result type (might be due to invalid IL or missing references)
		//IL_026e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0270: Unknown result type (might be due to invalid IL or missing references)
		//IL_0275: Unknown result type (might be due to invalid IL or missing references)
		//IL_0277: Unknown result type (might be due to invalid IL or missing references)
		//IL_028a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0294: Unknown result type (might be due to invalid IL or missing references)
		//IL_02ac: Unknown result type (might be due to invalid IL or missing references)
		//IL_02ae: Unknown result type (might be due to invalid IL or missing references)
		//IL_029b: Unknown result type (might be due to invalid IL or missing references)
		//IL_029d: Unknown result type (might be due to invalid IL or missing references)
		//IL_02a3: Unknown result type (might be due to invalid IL or missing references)
		//IL_02a8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c8: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ca: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
		//IL_0146: Unknown result type (might be due to invalid IL or missing references)
		//IL_0129: Unknown result type (might be due to invalid IL or missing references)
		if (!TimeWindow.Open(group.Window))
		{
			return;
		}
		TravelParams travelParams = TravelParams.FromConfig();
		double num = dt;
		if (group.State == GroupState.Resting || group.State == GroupState.Stopped)
		{
			if (now < group.StateUntil)
			{
				return;
			}
			num = Math.Min(dt, Math.Max(0.0, now - group.StateUntil));
			group.State = GroupState.Travelling;
		}
		if (group.Arrived)
		{
			Settle(group, now);
			return;
		}
		float num2 = ((McConfig.SpeedOverride.Value > 0f) ? McConfig.SpeedOverride.Value : group.Speed);
		int num3 = 0;
		while (num > 0.0 && !group.Arrived && group.State == GroupState.Travelling && num3++ < 64)
		{
			float num4 = ((group.Mode == TravelMode.Purposeful) ? num2 : (num2 * McConfig.RangingSpeedFactor.Value));
			if (num4 <= 0.001f)
			{
				break;
			}
			Vector2 val = group.Route[group.RouteIndex];
			Vector2 val2 = group.Route[group.RouteIndex - 1];
			float num5 = Vector2.Distance(group.LinePos, val);
			float num6 = Vector2.Distance(val2, val);
			if (!group.LegSwitched && num5 > num6 * 0.5f)
			{
				float num7 = num5 - num6 * 0.5f;
				double num8 = num7 / num4;
				if (num8 > num)
				{
					Step(group, val, (float)((double)num4 * num));
					num = 0.0;
					break;
				}
				Step(group, val, num7);
				num -= num8;
				group.LegSwitched = true;
				if (TravelRules.SwitchesMidLeg(WorldSeed, group.GroupId, group.Reroutes, group.RouteIndex, travelParams))
				{
					group.Mode = ((group.Mode == TravelMode.Purposeful) ? TravelMode.Ranging : TravelMode.Purposeful);
				}
				continue;
			}
			if (!group.LegSwitched)
			{
				group.LegSwitched = true;
			}
			double num9 = num5 / num4;
			if (num9 > num)
			{
				Step(group, val, (float)((double)num4 * num));
				num = 0.0;
				break;
			}
			group.LinePos = val;
			num -= num9;
			double num10 = now - num;
			ArriveAtWaypoint(group, num10, travelParams);
			if (group.State == GroupState.Resting && group.StateUntil - num10 < num)
			{
				num -= group.StateUntil - num10;
				group.State = GroupState.Travelling;
			}
		}
		group.WorldPos = group.LinePos;
		if (group.State == GroupState.Travelling && group.Mode == TravelMode.Ranging)
		{
			Vector2 val3 = TravelRules.RangingOffset(WorldSeed, group.GroupId, now, travelParams.RangingRadius);
			Vector2 val4 = group.LinePos + val3;
			if (WorldSampler.Depth(val4) < McConfig.WadeDepth.Value && !WardIndex.Excluded(val4) && ((int)group.AvoidBiomes == 0 || (WorldSampler.Biome(val4) & group.AvoidBiomes) == 0))
			{
				group.WorldPos = val4;
			}
		}
		if (group.Arrived)
		{
			Settle(group, now);
		}
	}

	private static void Step(MigrationGroup group, Vector2 waypoint, float metres)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0022: Unknown result type (might be due to invalid IL or missing references)
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		//IL_002b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0030: Unknown result type (might be due to invalid IL or missing references)
		//IL_0032: Unknown result type (might be due to invalid IL or missing references)
		//IL_0038: Unknown result type (might be due to invalid IL or missing references)
		//IL_003d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0042: Unknown result type (might be due to invalid IL or missing references)
		Vector2 val = waypoint - group.LinePos;
		float magnitude = ((Vector2)(ref val)).magnitude;
		if (magnitude <= metres || magnitude < 0.001f)
		{
			group.LinePos = waypoint;
		}
		else
		{
			group.LinePos += val / magnitude * metres;
		}
	}

	private static void ArriveAtWaypoint(MigrationGroup group, double when, TravelParams travel)
	{
		//IL_0074: Unknown result type (might be due to invalid IL or missing references)
		//IL_0079: Unknown result type (might be due to invalid IL or missing references)
		int routeIndex = group.RouteIndex;
		double until;
		GroupState groupState = TravelRules.OnArrive(WorldSeed, group.GroupId, group.Reroutes, routeIndex, travel, when, out until);
		group.RouteIndex = routeIndex + 1;
		group.LegSwitched = false;
		if (!group.Arrived)
		{
			group.Mode = TravelRules.ModeFor(WorldSeed, group.GroupId, group.Reroutes, group.RouteIndex, travel);
		}
		if (groupState != GroupState.Travelling)
		{
			group.State = groupState;
			group.StateUntil = until;
			group.StopCenter = group.LinePos;
			if (Plugin.LogEvents && groupState == GroupState.Stopped)
			{
				Plugin.Event("group " + group.GroupId + " settles at waypoint " + routeIndex + " for " + ((until - when) / GameTime.DayLength).ToString("0.0", CultureInfo.InvariantCulture) + " day(s)");
			}
		}
	}

	private static void Settle(MigrationGroup group, double now)
	{
		//IL_0037: Unknown result type (might be due to invalid IL or missing references)
		//IL_003c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0043: Unknown result type (might be due to invalid IL or missing references)
		//IL_0048: Unknown result type (might be due to invalid IL or missing references)
		//IL_007b: Unknown result type (might be due to invalid IL or missing references)
		if (group.State != GroupState.Stopped || !(group.StateUntil > now))
		{
			group.State = GroupState.Stopped;
			group.StateUntil = GameTime.DaysToSeconds(group.ExpiryDay) + 60.0;
			group.StopCenter = group.LinePos;
			group.WorldPos = group.LinePos;
			if (Plugin.LogEvents)
			{
				Plugin.Event("group " + group.GroupId + " reached its destination " + Fmt(group.LinePos) + " and stays until it expires");
			}
		}
	}

	private static bool TrackMaterialised(MigrationGroup group, double today, double now, double dt)
	{
		//IL_002b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0030: Unknown result type (might be due to invalid IL or missing references)
		//IL_0044: Unknown result type (might be due to invalid IL or missing references)
		//IL_0049: Unknown result type (might be due to invalid IL or missing references)
		//IL_0051: Unknown result type (might be due to invalid IL or missing references)
		//IL_005f: Unknown result type (might be due to invalid IL or missing references)
		//IL_007b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0225: Unknown result type (might be due to invalid IL or missing references)
		//IL_0228: Unknown result type (might be due to invalid IL or missing references)
		//IL_022d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0234: Unknown result type (might be due to invalid IL or missing references)
		//IL_0239: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a7: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ac: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b0: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b7: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c3: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c4: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c6: Unknown result type (might be due to invalid IL or missing references)
		//IL_01cb: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d1: Unknown result type (might be due to invalid IL or missing references)
		//IL_0112: Unknown result type (might be due to invalid IL or missing references)
		if (group.NeedsResolve)
		{
			return true;
		}
		if (today >= group.ExpiryDay)
		{
			group.ExpirePending = true;
		}
		List<ZDOID> list = new List<ZDOID>(group.Live.Keys);
		Vector2 val = Vector2.zero;
		int num = 0;
		float num2 = float.MaxValue;
		Vector2 val3 = default(Vector2);
		for (int i = 0; i < list.Count; i++)
		{
			ZDOID val2 = list[i];
			int index = group.Live[val2];
			ZDO zDO = ZDOMan.instance.GetZDO(val2);
			if (zDO == null || !zDO.IsValid())
			{
				group.Live.Remove(val2);
				RemoveMember(group, index);
				if (Plugin.LogEvents)
				{
					Plugin.Event("group " + group.GroupId + " member " + index + " is gone (killed or removed); " + group.Count + " left");
				}
			}
			else if (zDO.GetInt(ZdoKeys.Group, 0) != group.GroupId)
			{
				group.Live.Remove(val2);
				RemoveMember(group, index);
				if (Plugin.LogEvents)
				{
					Plugin.Event("group " + group.GroupId + " member " + index + " left the herd (" + zDO.GetString(ZdoKeys.LeftReason, "?") + "); it stays as a local creature; " + group.Count + " left");
				}
			}
			else
			{
				Vector3 position = zDO.GetPosition();
				((Vector2)(ref val3))..ctor(position.x, position.z);
				val += val3;
				num++;
				num2 = Mathf.Min(num2, NearestPlayer(val3));
			}
		}
		if (num == 0 || group.Count == 0)
		{
			if (Plugin.LogEvents)
			{
				Plugin.Event("group " + group.GroupId + " is over: no members left in the herd");
			}
			return false;
		}
		group.MemberCentroid = val / (float)num;
		group.WorldPos = group.MemberCentroid;
		if (num2 > McConfig.DematerialiseDistance)
		{
			Materialiser.Dematerialise(group);
			if (group.ExpirePending)
			{
				if (Plugin.LogEvents)
				{
					Plugin.Event("group " + group.GroupId + " expired (out of sight now)");
				}
				return false;
			}
			if (RoutePlanner.RemainingTouchesWard(group))
			{
				RequestReroute(group, "a ward on its way");
			}
			return true;
		}
		if (Materialiser.AnyMemberSimulated(group))
		{
			group.DormantTicks = 0;
			group.Dormant = false;
		}
		else if (++group.DormantTicks >= 3)
		{
			Materialiser.Drift(group, now, dt);
		}
		return true;
	}

	private static void RemoveMember(MigrationGroup group, int index)
	{
		for (int num = group.Members.Count - 1; num >= 0; num--)
		{
			if (group.Members[num].Index == index)
			{
				group.Members.RemoveAt(num);
			}
		}
	}

	private static void MaybeSpawn(double now)
	{
		if (!McConfig.Enabled.Value)
		{
			return;
		}
		double num = Math.Max(30.0, (double)McConfig.MeanMinutesBetweenGroups.Value * 60.0);
		if (state.NextSpawnSeconds < 0.0)
		{
			state.NextSpawnSeconds = now + NextGap(num);
		}
		if (now < state.NextSpawnSeconds)
		{
			return;
		}
		if (state.Groups.Count >= McConfig.GroupBudget.Value || players.Count == 0)
		{
			state.NextSpawnSeconds = now + Math.Min(num, 120.0);
			return;
		}
		state.NextSpawnSeconds = now + NextGap(num);
		if (pending == null)
		{
			pending = Prepare(null, null, 0, out var _);
		}
	}

	private static double NextGap(double mean)
	{
		float num = DetRandom.Roll(WorldSeed, 0, "spawngap", state.SpawnCounter);
		double val = (0.0 - Math.Log(1.0 - Math.Min(0.999, num))) * mean;
		return Math.Max(mean * 0.2, Math.Min(mean * 4.0, val));
	}

	internal static MigrationGroup TryCreateGroup(MigrationEntry forced, Vector2? anchor, int forcedCount, out string reason)
	{
		PendingGroup pendingGroup = Prepare(forced, anchor, forcedCount, out reason);
		if (pendingGroup == null)
		{
			return null;
		}
		pendingGroup.Job.Step(double.MaxValue);
		return Finish(pendingGroup, out reason);
	}

	private static PendingGroup Prepare(MigrationEntry forced, Vector2? anchor, int forcedCount, out string reason)
	{
		//IL_0210: Unknown result type (might be due to invalid IL or missing references)
		//IL_0215: Unknown result type (might be due to invalid IL or missing references)
		//IL_023e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0243: Unknown result type (might be due to invalid IL or missing references)
		//IL_0274: Unknown result type (might be due to invalid IL or missing references)
		//IL_0276: Unknown result type (might be due to invalid IL or missing references)
		reason = null;
		if (!Running)
		{
			reason = "the director is not running (host/server only, after the world has loaded)";
			return null;
		}
		state.SpawnCounter++;
		int groupId = state.NextGroupId++;
		DetRandom detRandom = new DetRandom(DetRandom.Seed(WorldSeed, groupId, "group", 0));
		MigrationEntry migrationEntry = forced ?? PickEntry(detRandom);
		if (migrationEntry == null)
		{
			reason = "no usable table row or band (check OnlyCreatures, Frequency/Weight, prefab names, the time of day and progression: mc_bands)";
			Abandon(groupId, reason);
			return null;
		}
		int num = Mathf.Max(1, McConfig.MaxMembersPerGroup.Value);
		List<string> list = new List<string>();
		if (migrationEntry.Band != null)
		{
			List<string> list2 = new List<string>();
			migrationEntry.Band.Roll(detRandom, (forcedCount > 0) ? Mathf.Min(num, forcedCount) : num, list2);
			for (int i = 0; i < list2.Count; i++)
			{
				GameObject prefab = ZNetScene.instance.GetPrefab(list2[i]);
				if (!IsCreaturePrefab(prefab))
				{
					Plugin.LogOnce("band-prefab-" + list2[i], "Migrating Creatures: band " + migrationEntry.Band.Name + ": '" + list2[i] + "' is not a creature prefab; the band goes without it.");
				}
				else
				{
					list.Add(Utils.GetPrefabName(prefab));
				}
			}
			if (list.Count == 0)
			{
				reason = "band " + migrationEntry.Band.Name + ": none of its members is a creature prefab";
				Abandon(groupId, reason);
				return null;
			}
		}
		else
		{
			GameObject prefab2 = ZNetScene.instance.GetPrefab(migrationEntry.Prefab);
			if (!IsCreaturePrefab(prefab2))
			{
				reason = "'" + migrationEntry.Prefab + "' is not a creature prefab";
				Abandon(groupId, reason);
				return null;
			}
			int num2 = ((forcedCount > 0) ? forcedCount : RollCount(detRandom, migrationEntry));
			num2 = Mathf.Clamp(num2, 1, num);
			for (int j = 0; j < num2; j++)
			{
				list.Add(Utils.GetPrefabName(prefab2));
			}
		}
		Vector2 anchor2;
		if (anchor.HasValue)
		{
			anchor2 = anchor.Value;
		}
		else
		{
			if (players.Count <= 0)
			{
				reason = "no player to place a group for";
				Abandon(groupId, reason);
				return null;
			}
			anchor2 = players[detRandom.RangeInt(0, players.Count - 1)];
		}
		RoutePlanner.Request request = new RoutePlanner.Request
		{
			Entry = migrationEntry,
			GroupId = groupId,
			Generation = 0,
			Anchor = anchor2,
			Players = new List<Vector2>(players),
			CanSwim = AllCanSwim(list)
		};
		return new PendingGroup
		{
			GroupId = groupId,
			Entry = migrationEntry,
			Prefabs = list,
			Rng = detRandom,
			Job = new RoutePlanner.PlanJob(request)
		};
	}

	private static bool IsCreaturePrefab(GameObject prefab)
	{
		if ((Object)(object)prefab != (Object)null && (Object)(object)prefab.GetComponent<Character>() != (Object)null && (Object)(object)prefab.GetComponent<BaseAI>() != (Object)null)
		{
			return (Object)(object)prefab.GetComponent<ZNetView>() != (Object)null;
		}
		return false;
	}

	internal static bool AllCanSwim(List<string> prefabs)
	{
		if (prefabs == null || prefabs.Count == 0 || (Object)(object)ZNetScene.instance == (Object)null)
		{
			return false;
		}
		for (int i = 0; i < prefabs.Count; i++)
		{
			GameObject prefab = ZNetScene.instance.GetPrefab(prefabs[i]);
			Character val = (((Object)(object)prefab != (Object)null) ? prefab.GetComponent<Character>() : null);
			if ((Object)(object)val == (Object)null || !val.m_canSwim)
			{
				return false;
			}
		}
		return true;
	}

	private static MigrationGroup Finish(PendingGroup plan, out string reason)
	{
		//IL_0152: Unknown result type (might be due to invalid IL or missing references)
		//IL_0157: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f1: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f6: Unknown result type (might be due to invalid IL or missing references)
		//IL_0205: Unknown result type (might be due to invalid IL or missing references)
		//IL_020a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0308: Unknown result type (might be due to invalid IL or missing references)
		//IL_030d: Unknown result type (might be due to invalid IL or missing references)
		//IL_031a: Unknown result type (might be due to invalid IL or missing references)
		//IL_031f: Unknown result type (might be due to invalid IL or missing references)
		//IL_032c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0331: Unknown result type (might be due to invalid IL or missing references)
		//IL_03e9: Unknown result type (might be due to invalid IL or missing references)
		//IL_0406: Unknown result type (might be due to invalid IL or missing references)
		//IL_040b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0410: Unknown result type (might be due to invalid IL or missing references)
		//IL_042d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0446: Unknown result type (might be due to invalid IL or missing references)
		//IL_044b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0450: Unknown result type (might be due to invalid IL or missing references)
		RoutePlanner.PlanJob job = plan.Job;
		RoutePlanner.Result result = job.Result;
		string text = result.AttemptsUsed + " attempts, " + result.Samples + " samples, " + result.Expansions + " steps, " + job.Milliseconds.ToString("0.0", CultureInfo.InvariantCulture) + " ms";
		if (!job.Ok)
		{
			reason = plan.Entry.Prefab + ": " + job.Reason + " (" + text + "; " + result.Rejections() + ")";
			Abandon(plan.GroupId, reason);
			return null;
		}
		reason = null;
		DetRandom rng = plan.Rng;
		BandDefinition band = plan.Entry.Band;
		MigrationGroup migrationGroup = new MigrationGroup
		{
			GroupId = plan.GroupId,
			Prefab = plan.Prefabs[0],
			Speed = plan.Entry.Speed * rng.Range(0.9f, 1.1f),
			BornDay = GameTime.NowDay,
			Destination = result.Destination
		};
		float num = ((band != null && band.MinLifetime > 0f) ? band.MinLifetime : McConfig.MinLifetimeDays.Value);
		float num2 = ((band != null && band.MaxLifetime > 0f) ? band.MaxLifetime : McConfig.MaxLifetimeDays.Value);
		migrationGroup.ExpiryDay =