Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of MigratingCreatures v0.2.0
MigratingCreatures.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
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 =