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 VikingColonies v1.5.1
plugins/VikingColonies.dll
Decompiled 15 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using HarmonyLib; using Jotunn; using Jotunn.Configs; using Jotunn.Entities; using Jotunn.Extensions; using Jotunn.Managers; using Jotunn.Utils; using Microsoft.CodeAnalysis; using Splatform; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.UI; using VikingColonies.AI; using VikingColonies.Buildings; using VikingColonies.Citizens; using VikingColonies.Colony; using VikingColonies.Config; using VikingColonies.Construction; using VikingColonies.Core; using VikingColonies.DevTools; using VikingColonies.Gui; using VikingColonies.Jobs; using VikingColonies.Visuals; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")] [assembly: AssemblyCompany("VikingColonies")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+6b10fbcf80c22850e5dad5809378ef7d503bf22b")] [assembly: AssemblyProduct("VikingColonies")] [assembly: AssemblyTitle("VikingColonies")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] internal sealed class IsReadOnlyAttribute : Attribute { } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace VikingColonies { internal static class GameCheck { private struct Part { public Type Type; public string TypeName; public string Name; public bool IsMethod; public string Feature; public Type FieldType; } private static readonly Part[] s_parts = new Part[36] { Field(typeof(BaseAI), "m_path", "settlers looking ahead for doors and getting unstuck"), Field(typeof(BaseAI), "m_lastFindPathTime", "settlers finding a new way at once"), Field(typeof(Character), "m_maxAirAltitude", "no fall damage when a settler is moved out or recalled"), Field(typeof(Character), "m_lastHit", "the cause of a settler's death"), Field(typeof(Humanoid), "m_rightItem", "what a settler holds when they fight off a weak creature"), Field(typeof(PrivateArea), "m_allAreas", "doors in warded areas and colony permissions"), Method(typeof(PrivateArea), "IsEnabled", "doors in warded areas"), Method(typeof(PrivateArea), "IsInside", "doors in warded areas and colony permissions"), Method(typeof(PrivateArea), "IsPermitted", "doors in warded areas and colony permissions"), Field(typeof(PrivateArea), "m_piece", "doors in warded areas and colony permissions"), Field(typeof(Inventory), "m_height", "hut chests growing with their level"), Method(typeof(Container), "Load", "settlers' deliveries in multiplayer"), Field(typeof(Chat), "m_hideTimer", "settler deaths shown in chat"), Field(typeof(InventoryGui), "m_currentContainer", "settler cairns"), Field(typeof(Minimap), "m_pins", "map pins for settler cairns"), Field(typeof(Player), "m_placementGhost", "the placement outline and colony rings"), Field(typeof(Player), "m_placementStatus", "the placement colour of huts"), Method(typeof(Player), "SetPlacementGhostValid", "the placement colour of huts"), Field(typeof(Player), "m_buildPieces", "the placement outline"), Field(typeof(WearNTear), "s_rayMask", "the builder's support check"), Field(typeof(WearNTear), "s_terrainLayer", "the builder's support check"), Field(typeof(WearNTear), "m_support", "the builder's support check"), Method(typeof(WearNTear), "GetMaterialProperties", "the builder's support check"), Field(typeof(Pickable), "m_nview", "the forager"), Field(typeof(Game), "m_timeScale", "vc_speed (a test tool)"), Field(typeof(Character), "m_animator", "settlers sitting, lying and gesturing"), Field(typeof(Character), "m_zanim", "settlers sitting, lying and gesturing"), Method(typeof(Humanoid), "ShowHandItems", "settlers taking their tool out again after sitting"), Field(typeof(Character), "m_blocking", "guards holding their shield up"), Field(typeof(Humanoid), "m_attackDrawTime", "guards drawing their bow"), Typed(typeof(ZNetScene), "m_instances", typeof(Dictionary<ZDO, ZNetView>), "fast world scans (the slow way is used instead: settlers may make the game stutter)"), Field(typeof(Pathfinding), "m_tiles", "vc_perf's walking map (a test tool)"), Named("UnifiedPopup", "IsVisible", isMethod: true, "the test runner naming a popup (a test tool)"), Named("UnifiedPopup", "instance", isMethod: false, "the test runner naming a popup (a test tool)"), Named("UnifiedPopup", "headerText", isMethod: false, "the test runner naming a popup (a test tool)"), Named("UnifiedPopup", "bodyText", isMethod: false, "the test runner naming a popup (a test tool)") }; public static readonly List<string> Missing = new List<string>(); public static readonly List<string> StartupFailures = new List<string>(); public static int Count => s_parts.Length; public static void Run() { Missing.Clear(); Part[] array = s_parts; for (int i = 0; i < array.Length; i++) { Part part = array[i]; bool flag; try { int num; if (part.Type != null) { if (!part.IsMethod) { FieldInfo fieldInfo = AccessTools.Field(part.Type, part.Name); num = (((object)fieldInfo != null && (part.FieldType == null || fieldInfo.FieldType == part.FieldType)) ? 1 : 0); } else { num = ((AccessTools.Method(part.Type, part.Name, (Type[])null, (Type[])null) != null) ? 1 : 0); } } else { num = 0; } flag = (byte)num != 0; } catch (Exception) { flag = false; } if (!flag) { string text = ((part.Type != null) ? part.Type.Name : part.TypeName) + "." + part.Name + " (" + part.Feature + ")"; Missing.Add(text); Log.Warn("Game check: " + text + " is missing — a Valheim update? That part of the mod won't work until it is updated."); } } foreach (string startupFailure in StartupFailures) { Log.Error("Game check: a part of the start-up failed: " + startupFailure + " — a Valheim update? That part of the mod won't work until it is updated; the rest runs."); } if (Missing.Count == 0) { Log.Event($"Game check: all {s_parts.Length} game parts the mod reads were found" + ((StartupFailures.Count == 0) ? ", and every part of the start-up ran" : $"; {StartupFailures.Count} parts of the start-up failed")); } else { Log.Warn($"Game check: {Missing.Count} of {s_parts.Length} game parts missing; see the lines above"); } } private static Part Field(Type type, string name, string feature) { return new Part { Type = type, Name = name, IsMethod = false, Feature = feature }; } private static Part Method(Type type, string name, string feature) { return new Part { Type = type, Name = name, IsMethod = true, Feature = feature }; } private static Part Named(string typeName, string name, bool isMethod, string feature) { return new Part { Type = AccessTools.TypeByName(typeName), TypeName = typeName, Name = name, IsMethod = isMethod, Feature = feature }; } private static Part Typed(Type type, string name, Type fieldType, string feature) { return new Part { Type = type, Name = name, IsMethod = false, Feature = feature, FieldType = fieldType }; } } internal static class Log { private static ZNet s_worldChecked; private static bool s_testWorld; private const int RecentKept = 40; private static readonly Queue<string> s_recent = new Queue<string>(); internal static IReadOnlyList<string> Recent { get { lock (s_recent) { return s_recent.ToArray(); } } } public static bool Verbose { get { ConfigEntry<bool> verboseLog = ColonyConfig.VerboseLog; if (verboseLog == null || !verboseLog.Value) { ConfigEntry<bool> commandFile = ColonyConfig.CommandFile; if (commandFile == null || !commandFile.Value) { return InTestWorld; } } return true; } } private static bool InTestWorld { get { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null) { return false; } if (instance != s_worldChecked) { s_worldChecked = instance; s_testWorld = TestWorlds.IsTestWorld(instance.GetWorldName()); } return s_testWorld; } } public static void Info(string message) { if (Verbose) { Logger.LogInfo((object)message); } } public static void Event(string message) { Logger.LogInfo((object)message); } public static void Warn(string message) { Keep("warning", message); Logger.LogWarning((object)message); } public static void Error(string message) { Keep("error", message); Logger.LogError((object)message); } private static void Keep(string level, string message) { string text = message ?? ""; if (text.Length > 400) { text = text.Substring(0, 400) + "…"; } lock (s_recent) { s_recent.Enqueue(BugReport.Clock(DateTime.Now) + " " + level + ": " + text); while (s_recent.Count > 40) { s_recent.Dequeue(); } } } } internal static class Perf { public static bool On; private static readonly Dictionary<string, long> s_total = new Dictionary<string, long>(); private static readonly Dictionary<string, long> s_worst = new Dictionary<string, long>(); private static readonly Dictionary<string, int> s_count = new Dictionary<string, int>(); public static long Start() { if (!On) { return 0L; } return Stopwatch.GetTimestamp(); } public static void Stop(string part, long start) { if (start != 0L && On) { long num = Stopwatch.GetTimestamp() - start; s_total.TryGetValue(part, out var value); s_total[part] = value + num; s_count.TryGetValue(part, out var value2); s_count[part] = value2 + 1; s_worst.TryGetValue(part, out var value3); if (num > value3) { s_worst[part] = num; } } } public static void Clear() { s_total.Clear(); s_worst.Clear(); s_count.Clear(); } public static string Report() { double toMs = 1000.0 / (double)Stopwatch.Frequency; if (s_total.Count != 0) { return string.Join("; ", from p in s_total orderby p.Value descending select $"{p.Key} {s_count[p.Key]} times, {(double)p.Value * toMs:0.0} ms in all, slowest {(double)s_worst[p.Key] * toMs:0.0} ms"); } return "nothing measured"; } } [BepInPlugin("daalmyte.vikingcolonies", "VikingColonies", "1.5.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { public const string PluginGuid = "daalmyte.vikingcolonies"; public const string PluginName = "VikingColonies"; public const string PluginVersion = "1.5.1"; private Harmony _harmony; public static Plugin Instance { get; private set; } private void Awake() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown Instance = this; _harmony = new Harmony("daalmyte.vikingcolonies"); List<string> list = StartupSteps.Run(new StartupStep[1] { new StartupStep("settings", "the mod's settings", delegate { ColonyConfig.Bind(((BaseUnityPlugin)this).Config); }) }); if (list.Count > 0) { Log.Error("VikingColonies 1.5.1 didn't start: " + list[0]); return; } List<StartupStep> list2 = new List<StartupStep> { new StartupStep("placement keys", "turning and placing huts", PlacementPatches.RegisterKeys), new StartupStep("texts", "the mod's texts", Translations.Register), new StartupStep("settler deaths", "settler deaths told to players", SettlerDeaths.Register), new StartupStep("pieces and settlers", "the colony's buildings, settlers and the builder's ground work", delegate { PrefabManager.OnVanillaPrefabsAvailable += OnVanillaPrefabsAvailable; ItemManager.OnItemsRegistered += GroundShaper.Register; }) }; Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(typeof(Plugin).Assembly); foreach (Type type in typesFromAssembly) { list2.Add(new StartupStep("hook " + ((type.DeclaringType != null) ? (type.DeclaringType.Name + ".") : "") + type.Name, "a part of the mod (" + type.FullName + ")", delegate { Hook(type); })); } list2.Add(AddPart<AreaRings>("colony rings")); list2.Add(AddPart<FieldOutline>("field outlines")); list2.Add(AddPart<BuildOutline>("the placement outline")); list2.Add(AddPart<DevRunner>("the test tools' background work")); list2.AddRange(new StartupStep[46] { Command<SelfTestCommand>(), Command<BuildCommand>(), Command<BlueprintCommand>(), Command<ReportCommand>(), Command<GotoCommand>(), Command<ViewCommand>(), Command<DoorsCommand>(), Command<WalkCommand>(), Command<SettlersCommand>(), Command<BedsCommand>(), Command<UiCommand>(), Command<SetCommand>(), Command<RemoveCommand>(), Command<KillCommand>(), Command<PerfCommand>(), Command<PlaceCommand>(), Command<FindFlatCommand>(), Command<GroundCommand>(), Command<SpeedCommand>(), Command<QuitCommand>(), Command<WorldCommand>(), Command<PoseCommand>(), Command<LifeTestCommand>(), Command<PolishTestCommand>(), Command<WalkTestCommand>(), Command<WalkWatchCommand>(), Command<CropsCommand>(), Command<ObserveCommand>(), Command<WorkerTestCommand>(), Command<GuardTestCommand>(), Command<HallTestCommand>(), Command<WeakTestCommand>(), Command<StepsTestCommand>(), Command<MemorialTestCommand>(), Command<CreativeTestCommand>(), Command<StorehouseTestCommand>(), Command<WatchCommand>(), Command<ShotCommand>(), Command<BugReportCommand>(), Command<PieceDumpCommand>(), Command<SpawnInfoCommand>(), Command<CamCommand>(), Command<StampCommand>(), Command<PreviewCommand>(), Command<TurretCommand>(), Command<BuildRunCommand>() }); list2.Add(AddPart<SettlerWords>("words above settlers' heads and Hugin's night tip")); list2.Add(AddPart<FeedbackAsker>("asking once what the player thinks of the mod")); list2.Add(AddPart<SettlerBodies>("settlers passing through each other and players instead of shoving")); GameCheck.StartupFailures.Clear(); GameCheck.StartupFailures.AddRange(StartupSteps.Run(list2)); Log.Event("VikingColonies 1.5.1 loaded"); GameCheck.Run(); } private StartupStep AddPart<T>(string feature) where T : Component { return new StartupStep("component " + typeof(T).Name, feature, delegate { ((Component)this).gameObject.AddComponent<T>(); }); } private static StartupStep Command<T>() where T : ConsoleCommand, new() { return new StartupStep("command " + typeof(T).Name, "a console command", delegate { CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new T()); }); } private void Hook(Type type) { if (type.IsDefined(typeof(HarmonyPatch), inherit: false)) { Type type2 = type; while (type2 != null) { RuntimeHelpers.RunClassConstructor(type2.TypeHandle); type2 = type2.DeclaringType; } } _harmony.CreateClassProcessor(type).Patch(); } private void OnVanillaPrefabsAvailable() { PrefabManager.OnVanillaPrefabsAvailable -= OnVanillaPrefabsAvailable; BuildingPieces.Register(); CitizenPrefab.Register(); SettlerCairn.Register(); BlueprintLibrary.LogPlaceholderShapes(); } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } } namespace VikingColonies.Core { public static class BedSpot { public const float HalfWidth = 0.6f; public const float HalfLength = 1.4f; public const float Clearance = 0.6f; public static IReadOnlyList<Vec3> Candidates(Vec3 bed, Quat rotation, Vec3 chest) { Vec3 normalized = rotation.Rotate(new Vec3(1f, 0f, 0f)).Flat.Normalized; Vec3 normalized2 = rotation.Rotate(new Vec3(0f, 0f, 1f)).Flat.Normalized; float num = 1.2f; float num2 = 2f; return (from s in new Vec3[4] { bed + normalized * num, bed + normalized * (0f - num), bed + normalized2 * num2, bed + normalized2 * (0f - num2) } select new Vec3(s.X, chest.Y, s.Z) into s orderby Vec3.Distance(s.Flat, chest.Flat) select s).ToList(); } } public sealed class BlueprintPiece { public string Name { get; } public string Category { get; } public Vec3 Position { get; } public Quat Rotation { get; } public Vec3 Scale { get; } public string Info { get; } public BlueprintPiece(string name, string category, Vec3 position, Quat rotation, Vec3 scale, string info) { Name = name; Category = category ?? ""; Position = position; Rotation = rotation; Scale = scale; Info = info ?? ""; } } public sealed class BlueprintFile { private const string HeaderName = "#Name:"; private const string HeaderCreator = "#Creator:"; private const string HeaderDescription = "#Description:"; private const string HeaderCategory = "#Category:"; private const string SectionPieces = "#Pieces"; public string Name = ""; public string Creator = ""; public string Description = ""; public string Category = ""; public readonly List<BlueprintPiece> Pieces = new List<BlueprintPiece>(); public readonly List<string> Problems = new List<string>(); public static BlueprintFile Parse(IEnumerable<string> lines) { BlueprintFile blueprintFile = new BlueprintFile(); bool flag = false; int num = 0; foreach (string item in lines ?? Enumerable.Empty<string>()) { object obj = item; num++; if (obj == null) { obj = ""; } string text = ((string)obj).TrimStart(new char[1] { '\ufeff' }).Trim(); if (text.Length == 0) { continue; } if (text.StartsWith("#")) { if (text.StartsWith("#Name:")) { blueprintFile.Name = text.Substring("#Name:".Length); } else if (text.StartsWith("#Creator:")) { blueprintFile.Creator = text.Substring("#Creator:".Length); } else if (text.StartsWith("#Description:")) { blueprintFile.Description = Unquote(text.Substring("#Description:".Length)); } else if (text.StartsWith("#Category:")) { blueprintFile.Category = text.Substring("#Category:".Length); } else { flag = text != "#Pieces"; } } else if (!flag) { if (TryParsePiece(text, out var piece, out var why)) { blueprintFile.Pieces.Add(piece); } else { blueprintFile.Problems.Add($"line {num}: {why}"); } } } return blueprintFile; } public IEnumerable<string> ToLines() { yield return "#Name:" + Name; yield return "#Creator:" + Creator; yield return "#Description:" + Quote(Description); yield return "#Category:" + Category; yield return "#Pieces"; foreach (BlueprintPiece piece in Pieces) { yield return string.Join(";", piece.Name, piece.Category, F(piece.Position.X), F(piece.Position.Y), F(piece.Position.Z), F(piece.Rotation.X), F(piece.Rotation.Y), F(piece.Rotation.Z), F(piece.Rotation.W), Quote(piece.Info), F(piece.Scale.X), F(piece.Scale.Y), F(piece.Scale.Z)); } } private static bool TryParsePiece(string line, out BlueprintPiece piece, out string why) { piece = null; string[] array = ((line.IndexOf(',') >= 0) ? line.Replace(',', '.') : line).Split(new char[1] { ';' }); if (array.Length < 9) { why = "a piece line needs at least 9 fields"; return false; } string text = array[0].Split(new char[1] { '(' })[0].Trim(); if (text.Length == 0) { why = "no piece name"; return false; } float[] array2 = new float[7]; for (int i = 0; i < 7; i++) { if (!RecordCodec.TryParseFinite(array[i + 2], out array2[i])) { why = "'" + array[i + 2] + "' is not a number"; return false; } } string text2 = ((array.Length > 9) ? Unquote(array[9]) : ""); if (text2 == "null") { text2 = ""; } Vec3 scale = new Vec3(1f, 1f, 1f); if (array.Length > 12 && RecordCodec.TryParseFinite(array[10], out float value) && RecordCodec.TryParseFinite(array[11], out float value2) && RecordCodec.TryParseFinite(array[12], out float value3)) { scale = new Vec3(value, value2, value3); } piece = new BlueprintPiece(text, array[1], new Vec3(array2[0], array2[1], array2[2]), new Quat(array2[3], array2[4], array2[5], array2[6]).Normalized, scale, text2); why = null; return true; } private static string F(float value) { return value.ToString("0.######", CultureInfo.InvariantCulture); } private static string Quote(string text) { return "\"" + (text ?? "").Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\n", "\\n") + "\""; } private static string Unquote(string text) { if (text == null || text.Length < 2 || text[0] != '"' || text[text.Length - 1] != '"') { return text ?? ""; } StringBuilder stringBuilder = new StringBuilder(); for (int i = 1; i < text.Length - 1; i++) { char c = text[i]; if (c == '\\' && i + 1 < text.Length - 1) { char c2 = text[++i]; stringBuilder.Append((c2 == 'n') ? '\n' : c2); } else { stringBuilder.Append(c); } } return stringBuilder.ToString(); } } public sealed class PlannedPiece { public int Index { get; } public string Prefab { get; } public Vec3 Position { get; } public Quat Rotation { get; } public bool IsValid => Prefab.Length > 0; public PlannedPiece(int index, string prefab, Vec3 position, Quat rotation) { Index = index; Prefab = prefab ?? ""; Position = position; Rotation = rotation; } } public static class BlueprintPlan { public static bool TryPlace(BlueprintFile blueprint, string anchorPrefab, Vec3 chestPosition, Quat chestRotation, out List<PlannedPiece> pieces, out string problem) { pieces = new List<PlannedPiece>(); BlueprintPiece blueprintPiece = blueprint?.Pieces.FirstOrDefault((BlueprintPiece p) => p.Name == anchorPrefab); if (blueprintPiece == null) { problem = "no " + anchorPrefab + " line marks where the chest goes"; return false; } Quat inverse = blueprintPiece.Rotation.Normalized.Inverse; Quat normalized = chestRotation.Normalized; foreach (BlueprintPiece piece in blueprint.Pieces) { if (!(piece.Name == anchorPrefab) && !piece.Name.StartsWith("vc_")) { Vec3 v = inverse.Rotate(piece.Position - blueprintPiece.Position); Quat quat = inverse * piece.Rotation.Normalized; pieces.Add(new PlannedPiece(pieces.Count, piece.Name, chestPosition + normalized.Rotate(v), (normalized * quat).Normalized)); } } problem = ((pieces.Count == 0) ? "the blueprint has no pieces besides the chest" : null); return pieces.Count > 0; } public static List<PlannedPiece> PlaceAround(BlueprintFile blueprint, Vec3 origin, Quat turn) { List<PlannedPiece> list = new List<PlannedPiece>(); if (blueprint == null) { return list; } Quat normalized = turn.Normalized; foreach (BlueprintPiece piece in blueprint.Pieces) { if (!piece.Name.StartsWith("vc_")) { list.Add(new PlannedPiece(list.Count, piece.Name, origin + normalized.Rotate(piece.Position), (normalized * piece.Rotation.Normalized).Normalized)); } } return list; } } public sealed class StateHistory { private readonly int _capacity; private readonly List<string> _states = new List<string>(); private readonly List<double> _times = new List<double>(); public StateHistory(int capacity = 10) { _capacity = Math.Max(1, capacity); } public void Add(string state, double at) { state = state ?? ""; if (_states.Count <= 0 || !(_states[_states.Count - 1] == state)) { _states.Add(state); _times.Add(at); if (_states.Count > _capacity) { _states.RemoveAt(0); _times.RemoveAt(0); } } } public List<string> Lines(double now, Func<string, string> words) { List<string> list = new List<string>(); for (int num = _states.Count - 1; num >= 0; num--) { string text = ((_states[num].Length == 0) ? "-" : ((words != null) ? words(_states[num]) : _states[num])); list.Add(text + " (" + Ago(now - _times[num]) + ")"); } return list; } private static string Ago(double seconds) { if (!(seconds < 120.0)) { return string.Format(CultureInfo.InvariantCulture, "{0:0} min ago", seconds / 60.0); } return string.Format(CultureInfo.InvariantCulture, "{0:0} s ago", Math.Max(0.0, seconds)); } } public static class BugReport { public static string Stamp(DateTime at) { return at.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture); } public static string Clock(DateTime at) { return at.ToString("HH:mm:ss", CultureInfo.InvariantCulture); } public static string Header(string modVersion, string gameVersion, IEnumerable<string> otherMods, IReadOnlyCollection<string> gameCheckMissing, IReadOnlyCollection<string> startupFailures, string network) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("VikingColonies " + modVersion + " bug report\n"); stringBuilder.Append("Valheim " + gameVersion + "; " + network + "\n"); List<string> list = (otherMods ?? Enumerable.Empty<string>()).ToList(); stringBuilder.Append("Other mods: ").Append((list.Count == 0) ? "none" : string.Join(", ", list)).Append('\n'); stringBuilder.Append((gameCheckMissing == null || gameCheckMissing.Count == 0) ? "Game check: all found\n" : ("Game check: missing " + string.Join("; ", gameCheckMissing) + "\n")); int num = startupFailures?.Count ?? 0; stringBuilder.Append((num == 0) ? "Start-up: every part ran\n" : string.Format("Start-up: {0} part{1} failed: {2}\n", num, (num == 1) ? "" : "s", string.Join("; ", startupFailures))); return stringBuilder.ToString(); } } public static class BugForm { public const string IssuesPage = "DaAlmyte/VikingColonies-issues"; public const int MaxAddress = 2000; public const int MaxPaste = 60000; private const string PasteHere = "The whole report is copied: click below this line and press Ctrl+V to paste it. Drag in BepInEx/LogOutput.log too if you can."; private const string CutNote = "(The report is cut here: the whole of it is in BepInEx/VikingColonies-bugreport.txt.)\n"; private const int Fixed = 3; public static string Address(string modVersion, string gameVersion, string header, string openedFrom) { string text = "https://github.com/DaAlmyte/VikingColonies-issues/issues/new?template=bug_report.yml&versions=" + Uri.EscapeDataString("VikingColonies " + modVersion + ", Valheim " + gameVersion) + "&files="; List<string> list = new List<string> { "The whole report is copied: click below this line and press Ctrl+V to paste it. Drag in BepInEx/LogOutput.log too if you can.", "", "Opened from: " + openedFrom }; list.AddRange(from l in (header ?? "").Replace("\r", "").Split(new char[1] { '\n' }) where l.Length > 0 select l); string text2; while (true) { text2 = text + Uri.EscapeDataString(string.Join("\n", list) + "\n"); if (text2.Length <= 2000) { return text2; } int num = -1; for (int num2 = 3; num2 < list.Count; num2++) { if (num < 0 || list[num2].Length > list[num].Length) { num = num2; } } if (num >= 0 && list[num].Length > 80) { list[num] = list[num].Substring(0, list[num].Length * 9 / 10).TrimEnd(Array.Empty<char>()) + " …"; continue; } if (list.Count <= 3) { break; } list.RemoveAt(list.Count - 1); } return text2.Substring(0, 2000); } public static string Paste(string report) { report = report ?? ""; if (report.Length <= 60000) { return report; } string text = report.Substring(0, 60000 - "(The report is cut here: the whole of it is in BepInEx/VikingColonies-bugreport.txt.)\n".Length - 1); int num = text.LastIndexOf('\n'); text = ((num <= 0) ? (text + "\n") : text.Substring(0, num + 1)); return text + "(The report is cut here: the whole of it is in BepInEx/VikingColonies-bugreport.txt.)\n"; } } public sealed class ReportCooldown { public const double Seconds = 10.0; private double _last = double.NegativeInfinity; public bool Busy(double now) { return now - _last < 10.0; } public bool TryStart(double now) { if (Busy(now)) { return false; } _last = now; return true; } public int SecondsLeft(double now) { if (!Busy(now)) { return 0; } return (int)Math.Ceiling(10.0 - (now - _last)); } } public sealed class BuildablePieces { private readonly HashSet<string> _names = new HashSet<string>(StringComparer.Ordinal); public int Count => _names.Count; public BuildablePieces(IEnumerable<IEnumerable<string>> toolLists) { if (toolLists == null) { return; } foreach (IEnumerable<string> toolList in toolLists) { if (toolList == null) { continue; } foreach (string item in toolList) { if (!string.IsNullOrEmpty(item)) { _names.Add(item); } } } } public bool Allows(string prefab) { if (!string.IsNullOrEmpty(prefab)) { return _names.Contains(prefab); } return false; } } public static class BuildSwing { public const float GiveUpSeconds = 3f; public static bool PlaceAfterSwing(bool wasInAttack, bool inAttack, bool placed, float secondsSinceSwing) { if (!placed) { if (!wasInAttack || inAttack) { if (!wasInAttack && !inAttack) { return secondsSinceSwing >= 3f; } return false; } return true; } return false; } public static bool StaysPutWhileSwinging(bool isSettler, bool isBuilder) { return isSettler && isBuilder; } public static bool SkipsGameHit(bool isSettler, bool isBuilder) { return isSettler && isBuilder; } } public sealed class Cooldown { private readonly double _seconds; private double _started = double.NaN; public Cooldown(double seconds) { _seconds = seconds; } public bool Ready(double now) { if (!double.IsNaN(_started) && !(now < _started)) { return now - _started >= _seconds; } return true; } public void Start(double now) { _started = now; } public void Clear() { _started = double.NaN; } } public sealed class TextCache<T> { private readonly Func<string, T> _decode; private string _text; private T _value; private bool _have; public TextCache(Func<string, T> decode) { _decode = decode; } public T Get(string text) { text = text ?? ""; if (_have && ((object)text == _text || string.Equals(text, _text, StringComparison.Ordinal))) { return _value; } _text = text; _value = _decode(text); _have = true; return _value; } } public static class BuildStance { public const float BuriedTolerance = 0.15f; public static bool InPlace(Vec3 settler, Vec3 target, float reach, bool insideFootprint) { if (!insideFootprint) { return Vec3.Distance(settler + new Vec3(0f, 1f, 0f), target) <= reach; } return false; } public static bool Buried(float top, float ground) { return top < ground - 0.15f; } public static float WorkDistance(float reach) { return Math.Min(8f, reach); } } public sealed class RecentSpots { private readonly double _maxAge; private readonly List<KeyValuePair<Vec3, double>> _spots = new List<KeyValuePair<Vec3, double>>(); public RecentSpots(double maxAgeSeconds) { _maxAge = maxAgeSeconds; } public void Add(Vec3 point, double now) { _spots.RemoveAll((KeyValuePair<Vec3, double> s) => now - s.Value > _maxAge || now < s.Value); _spots.Add(new KeyValuePair<Vec3, double>(point, now)); } public bool Near(Vec3 point, float radius, double now) { return _spots.Any((KeyValuePair<Vec3, double> s) => now - s.Value <= _maxAge && now >= s.Value && Vec3.Distance(s.Key.Flat, point.Flat) <= radius); } } public enum RockOnSite { Clear, Blocks, LeftAlone } public static class SiteRules { public const float UnbreakableAcross = 1f; public const float UnbreakableTall = 0.5f; public static bool ClearsInFrontOfDoor(ClearKind kind) { if (kind != ClearKind.Item) { return kind != ClearKind.Leftover; } return false; } public static bool InTheWay(ClearKind kind, bool middleOnSite, bool reachesIn) { if (!middleOnSite) { if (reachesIn) { if (kind != ClearKind.Stump && kind != ClearKind.Log) { return kind == ClearKind.Rock; } return true; } return false; } return true; } public static bool OwnField(bool isFieldMarker, string fieldsFarm, string thisSite) { if (isFieldMarker && !string.IsNullOrEmpty(thisSite)) { return fieldsFarm == thisSite; } return false; } public static bool ClearsPlants(OrderKind kind) { if (kind != OrderKind.Build) { return kind == OrderKind.Upgrade; } return true; } public static bool IsInTheWay(bool isThisHutsChest, string pieceSite, string thisSite, bool matchesPlan) { if (!isThisHutsChest && pieceSite != thisSite) { return !matchesPlan; } return false; } public static bool KeepsGroundOff(bool isThisHutsChest, string pieceSite, string thisSite, bool matchesPlan, bool vegetation, bool looseItem) { if (!vegetation && !looseItem) { return IsInTheWay(isThisHutsChest, pieceSite, thisSite, matchesPlan); } return false; } public static bool IsLooseItem(bool isItem, bool placedAsPiece, bool placedByPlayer) { if (isItem && !placedAsPiece) { return !placedByPlayer; } return false; } public static bool IsLeftover(string pieceSite, string thisSite, bool siteHutExists) { if (!string.IsNullOrEmpty(pieceSite) && pieceSite != thisSite) { return !siteHutExists; } return false; } public static bool Counts(bool isTrigger, bool isPiece) { return !(isTrigger && isPiece); } public static bool Unbreakable(bool isTrigger, bool moves, float across, float aboveGround) { if (!isTrigger && !moves && across >= 1f) { return aboveGround >= 0.5f; } return false; } public static RockOnSite Rock(bool holdsAnother, bool holdsOwn, bool inTheBuilding) { if (!(holdsAnother && inTheBuilding)) { if (!(holdsAnother || holdsOwn)) { return RockOnSite.Clear; } return RockOnSite.LeftAlone; } return RockOnSite.Blocks; } public static bool CanCarryOff(int maxStack, int quality, bool worn, bool crafted) { if (maxStack > 1 && quality <= 1 && !worn) { return !crafted; } return false; } } public static class SitePrep { public static bool HoldsUpBuilding(BuildState state, bool busy, bool waiting) { if (!busy) { if (waiting) { return state != BuildState.Building; } return false; } return true; } } public static class BlueprintNames { public static string SaveProblem(string name, string chestTypeId, IEnumerable<string> hutTypeIds) { if (!IsPlainName(name)) { return "a design's name may hold only letters, digits, _ and - ('" + name + "'): nothing was saved."; } if (name == chestTypeId || !hutTypeIds.Contains(name)) { return null; } return "this was built around a " + chestTypeId + " chest, so it can't be the " + name + " design (that needs a " + name + " chest). Nothing was saved; use the name " + chestTypeId + " or another name."; } public static string EmptyProblem(int buildingPieces, float radius) { if (buildingPieces <= 0) { return $"nothing is built within {radius:0} m of the chest, so nothing was saved."; } return null; } public static bool IsPlainName(string name) { if (!string.IsNullOrEmpty(name)) { return name.All((char c) => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '-'); } return false; } public static string FileName(string typeId, int level) { level = HutLevels.Clamp(level); if (level > 1) { return typeId + "_" + level; } return typeId; } } public static class BuildingSafety { public static bool IgnoresHit(bool treeHit, bool impactHit, bool hasAttacker) { if (!treeHit) { if (impactHit) { return !hasAttacker; } return false; } return true; } public static bool IgnoresSettlerHit(bool attackerIsSettler) { return attackerIsSettler; } } public enum BuildState { Waiting, Building, Done, Clearing, Shaping } public enum OrderKind { Build, Upgrade, Repair, Remove } public sealed class BuildOrderRecord { public const int CurrentVersion = 1; public const double ClaimSeconds = 60.0; public const int MaxFailures = 3; public const int MaxRebuilds = 3; public const double RebuildMemorySeconds = 1800.0; public BuildState State; public int Total; public readonly SortedSet<int> Done = new SortedSet<int>(); public readonly SortedDictionary<int, int> Failures = new SortedDictionary<int, int>(); public string Builder = ""; public double ClaimTime; public double Created; public string Needs = ""; public string Blocked = ""; public float Ground = float.NaN; public OrderKind Kind; public int TargetLevel = 1; public int TakeAwayTotal; public readonly SortedSet<int> TakenAway = new SortedSet<int>(); public bool Changed; public double First; public bool Paused; public bool Watched; public readonly SortedDictionary<int, int> Rebuilt = new SortedDictionary<int, int>(); public const int MaxPieces = 20000; public int TakeAwayLeft => Math.Max(0, TakeAwayTotal - TakenAway.Count); public int DoneCount => Done.Count; public int SkippedCount => Failures.Count((KeyValuePair<int, int> f) => f.Value >= 3 && !Done.Contains(f.Key)); public bool BuildHandled => Enumerable.Range(0, Total).All((int i) => !IsOpen(i)); public bool AllHandled { get { if (BuildHandled) { return TakeAwayLeft == 0; } return false; } } public bool IsPreparing { get { if (State != BuildState.Clearing) { return State == BuildState.Shaping; } return true; } } public bool IsSkipped(int index) { if (!Done.Contains(index) && Failures.TryGetValue(index, out var value)) { return value >= 3; } return false; } public bool IsOpen(int index) { if (index >= 0 && index < Total && !Done.Contains(index)) { return !IsSkipped(index); } return false; } public static int Rank(BuildState state) { return state switch { BuildState.Clearing => 1, BuildState.Shaping => 2, BuildState.Building => 3, BuildState.Done => 4, _ => 0, }; } public bool Advance(BuildState to) { if (to == BuildState.Done || Rank(to) <= Rank(State)) { return false; } State = to; return true; } public bool HasLiveClaim(double now) { if (Builder.Length > 0) { return Math.Abs(now - ClaimTime) < 60.0; } return false; } public bool IsClaimedByOther(string builder, double now) { if (HasLiveClaim(now)) { return Builder != builder; } return false; } public bool TryClaim(string builder, double now) { if (string.IsNullOrEmpty(builder) || IsClaimedByOther(builder, now)) { return false; } if (!HasLiveClaim(now)) { Needs = ""; Blocked = ""; } Builder = builder; ClaimTime = now; return true; } public void Release(string builder) { if (!(Builder != builder)) { Builder = ""; ClaimTime = 0.0; } } public void MarkDone(int index) { if (index >= 0 && index < Total) { Done.Add(index); Failures.Remove(index); Changed = true; } } public void MarkKept(int index) { if (index >= 0 && index < Total) { Done.Add(index); } } public bool MarkFailed(int index) { if (index < 0 || index >= Total) { return false; } Done.Remove(index); Failures.TryGetValue(index, out var value); Failures[index] = value + 1; return value + 1 == 3; } public void MarkSkipped(int index) { if (index >= 0 && index < Total) { Done.Remove(index); Failures[index] = 3; } } public void MarkGone(int index) { Done.Remove(index); } public void MarkTaken(int index) { if (index >= 0 && index < TakeAwayTotal) { TakenAway.Add(index); Changed = true; } } public bool ReopenForRepair(IEnumerable<int> missing, double now) { List<int> list = (missing ?? Enumerable.Empty<int>()).Where((int i) => Done.Contains(i)).Distinct().ToList(); if (State != BuildState.Done || list.Count == 0) { return false; } if (Math.Abs(now - Created) > 1800.0) { Rebuilt.Clear(); } List<int> list2 = new List<int>(); foreach (int item in list) { Rebuilt.TryGetValue(item, out var value); if (value >= 3) { MarkSkipped(item); continue; } Rebuilt[item] = value + 1; list2.Add(item); } if (list2.Count == 0) { return false; } for (int num = 0; num < Total; num++) { if (IsOpen(num)) { MarkSkipped(num); } } foreach (int item2 in list2) { Done.Remove(item2); Failures.Remove(item2); } Kind = OrderKind.Repair; State = BuildState.Building; Builder = ""; ClaimTime = 0.0; Needs = ""; Blocked = ""; Created = now; First = 0.0; Paused = false; Changed = false; TakeAwayTotal = 0; TakenAway.Clear(); return true; } public bool SkipRemoved(int index) { if (!Done.Contains(index) || (State != BuildState.Done && Kind != OrderKind.Repair)) { return false; } MarkSkipped(index); return true; } public int TakeBaseline(IEnumerable<int> missing) { int num = 0; foreach (int item in (missing ?? Enumerable.Empty<int>()).Distinct().ToList()) { if (Done.Contains(item)) { MarkSkipped(item); num++; } } Watched = true; return num; } public int LeaveAsItIs() { if (Kind != OrderKind.Repair || State == BuildState.Done) { return 0; } List<int> list = Enumerable.Range(0, Total).Where(IsOpen).ToList(); foreach (int item in list) { MarkSkipped(item); } return list.Count; } public void MarkBuilt() { State = BuildState.Done; Builder = ""; ClaimTime = 0.0; Needs = ""; Blocked = ""; } public static BuildOrderRecord ForRemove(int pieces, int level, double now) { return new BuildOrderRecord { Kind = OrderKind.Remove, Total = 0, TakeAwayTotal = Math.Max(0, pieces), TargetLevel = Math.Max(1, level), State = BuildState.Waiting, Watched = false, Created = now }; } public string Encode() { return RecordCodec.Encode(new KeyValuePair<string, string>[20] { Pair("v", RecordCodec.FormatInt(1)), Pair("state", RecordCodec.FormatInt((int)State)), Pair("total", RecordCodec.FormatInt(Total)), Pair("done", string.Join(",", Done.Select(RecordCodec.FormatInt))), Pair("fail", string.Join(",", Failures.Select((KeyValuePair<int, int> f) => RecordCodec.FormatInt(f.Key) + ":" + RecordCodec.FormatInt(f.Value)))), Pair("builder", Builder), Pair("claim", ClaimTime.ToString("R", CultureInfo.InvariantCulture)), Pair("created", Created.ToString("R", CultureInfo.InvariantCulture)), Pair("needs", Needs), Pair("blocked", Blocked), Pair("ground", float.IsNaN(Ground) ? "" : Ground.ToString("R", CultureInfo.InvariantCulture)), Pair("kind", RecordCodec.FormatInt((int)Kind)), Pair("level", RecordCodec.FormatInt(TargetLevel)), Pair("take", RecordCodec.FormatInt(TakeAwayTotal)), Pair("taken", string.Join(",", TakenAway.Select(RecordCodec.FormatInt))), Pair("changed", Changed ? "1" : "0"), Pair("first", (First > 0.0) ? First.ToString("R", CultureInfo.InvariantCulture) : ""), Pair("paused", Paused ? "1" : "0"), Pair("watched", Watched ? "1" : "0"), Pair("rebuilt", string.Join(",", Rebuilt.Select((KeyValuePair<int, int> r) => RecordCodec.FormatInt(r.Key) + ":" + RecordCodec.FormatInt(r.Value)))) }); } public static BuildOrderRecord Decode(string text) { if (string.IsNullOrEmpty(text)) { return null; } Dictionary<string, string> fields = RecordCodec.Decode(text); if (RecordCodec.ParseInt(RecordCodec.Get(fields, "v"), 0) < 1) { return null; } int num = RecordCodec.ParseInt(RecordCodec.Get(fields, "state"), 0); int num2 = RecordCodec.ParseInt(RecordCodec.Get(fields, "kind"), 0); BuildOrderRecord buildOrderRecord = new BuildOrderRecord { State = (Enum.IsDefined(typeof(BuildState), num) ? ((BuildState)num) : BuildState.Waiting), Total = Math.Min(20000, Math.Max(0, RecordCodec.ParseInt(RecordCodec.Get(fields, "total"), 0))), Builder = RecordCodec.Get(fields, "builder"), ClaimTime = ParseDouble(RecordCodec.Get(fields, "claim")), Created = ParseDouble(RecordCodec.Get(fields, "created")), Needs = RecordCodec.Get(fields, "needs"), Blocked = RecordCodec.Get(fields, "blocked"), Ground = ParseFloat(RecordCodec.Get(fields, "ground")), Kind = (Enum.IsDefined(typeof(OrderKind), num2) ? ((OrderKind)num2) : OrderKind.Build), TargetLevel = HutLevels.Clamp(RecordCodec.ParseInt(RecordCodec.Get(fields, "level"), 1)), TakeAwayTotal = Math.Min(20000, Math.Max(0, RecordCodec.ParseInt(RecordCodec.Get(fields, "take"), 0))), Changed = (RecordCodec.Get(fields, "changed") == "1"), First = Math.Max(0.0, ParseDouble(RecordCodec.Get(fields, "first"))), Paused = (RecordCodec.Get(fields, "paused") == "1"), Watched = (RecordCodec.Get(fields, "watched") == "1") }; string[] array = RecordCodec.Get(fields, "done").Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { int num3 = RecordCodec.ParseInt(array[i], -1); if (num3 >= 0 && num3 < buildOrderRecord.Total) { buildOrderRecord.Done.Add(num3); } } array = RecordCodec.Get(fields, "fail").Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string[] array2 = array[i].Split(new char[1] { ':' }); if (array2.Length == 2) { int num4 = RecordCodec.ParseInt(array2[0], -1); int num5 = RecordCodec.ParseInt(array2[1], 0); if (num4 >= 0 && num4 < buildOrderRecord.Total && num5 > 0) { buildOrderRecord.Failures[num4] = num5; } } } array = RecordCodec.Get(fields, "rebuilt").Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string[] array3 = array[i].Split(new char[1] { ':' }); if (array3.Length == 2) { int num6 = RecordCodec.ParseInt(array3[0], -1); int num7 = RecordCodec.ParseInt(array3[1], 0); if (num6 >= 0 && num6 < buildOrderRecord.Total && num7 > 0) { buildOrderRecord.Rebuilt[num6] = num7; } } } array = RecordCodec.Get(fields, "taken").Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { int num8 = RecordCodec.ParseInt(array[i], -1); if (num8 >= 0 && num8 < buildOrderRecord.TakeAwayTotal) { buildOrderRecord.TakenAway.Add(num8); } } return buildOrderRecord; } private static double ParseDouble(string text) { if (!RecordCodec.TryParseFinite(text, out double value)) { return 0.0; } return value; } private static float ParseFloat(string text) { if (!RecordCodec.TryParseFinite(text, out float value)) { return float.NaN; } return value; } private static KeyValuePair<string, string> Pair(string key, string value) { return new KeyValuePair<string, string>(key, value); } } public sealed class PreviewView { public string Name { get; } public Vec3 Eye { get; } public Vec3 Look { get; } public PreviewView(string name, Vec3 eye, Vec3 look) { Name = name; Eye = eye; Look = look; } } public static class PreviewViews { public static readonly string[] LoadViews = new string[4] { "frontright", "backleft", "above", "inside" }; public const float Margin = 1.08f; public const float EyeHeight = 1.7f; public static List<PreviewView> For(Vec3 min, Vec3 max, float verticalFov, float aspect) { Vec3 centre = new Vec3((min.X + max.X) / 2f, (min.Y + max.Y) / 2f, (min.Z + max.Z) / 2f); double num = Math.Tan((double)verticalFov * Math.PI / 360.0); double tanH = num * (double)Math.Max(0.1f, aspect); List<PreviewView> obj = new List<PreviewView> { Outside("front", 0f, 10f, min, max, centre, num, tanH), Outside("back", 180f, 10f, min, max, centre, num, tanH), Outside("left", 270f, 10f, min, max, centre, num, tanH), Outside("right", 90f, 10f, min, max, centre, num, tanH), Outside("frontright", 45f, 25f, min, max, centre, num, tanH), Outside("backleft", 225f, 25f, min, max, centre, num, tanH), Outside("above", 20f, 70f, min, max, centre, num, tanH) }; float y = Math.Max(min.Y, 0f) + 1.7f; float num2 = max.X - min.X; float num3 = max.Z - min.Z; obj.Add((num3 >= num2) ? new PreviewView("inside", new Vec3(centre.X, y, max.Z - 0.2f * num3), new Vec3(centre.X, y, min.Z)) : new PreviewView("inside", new Vec3(max.X - 0.2f * num2, y, centre.Z), new Vec3(min.X, y, centre.Z))); return obj; } private static PreviewView Outside(string name, float azimuth, float elevation, Vec3 min, Vec3 max, Vec3 centre, double tanV, double tanH) { double num = (double)azimuth * Math.PI / 180.0; double num2 = (double)elevation * Math.PI / 180.0; Vec3 vec = new Vec3((float)(Math.Sin(num) * Math.Cos(num2)), (float)Math.Sin(num2), (float)(Math.Cos(num) * Math.Cos(num2))); Vec3 vec2 = vec * -1f; Vec3 normalized = Cross(vec2, new Vec3(0f, 1f, 0f)).Normalized; Vec3 b = Cross(normalized, vec2); double num3 = 3.0; foreach (Vec3 item in Corners(min, max)) { Vec3 a = item - centre; double val = Math.Max((double)Math.Abs(Vec3.Dot(a, normalized)) / tanH, (double)Math.Abs(Vec3.Dot(a, b)) / tanV) * 1.0800000429153442 + (double)Vec3.Dot(a, vec); num3 = Math.Max(num3, val); } return new PreviewView(name, centre + vec * (float)num3, centre); } private static IEnumerable<Vec3> Corners(Vec3 a, Vec3 b) { float[] array = new float[2] { a.X, b.X }; foreach (float x in array) { float[] array2 = new float[2] { a.Y, b.Y }; foreach (float y in array2) { float[] array3 = new float[2] { a.Z, b.Z }; foreach (float z in array3) { yield return new Vec3(x, y, z); } } } } private static Vec3 Cross(Vec3 a, Vec3 b) { return new Vec3(a.Y * b.Z - a.Z * b.Y, a.Z * b.X - a.X * b.Z, a.X * b.Y - a.Y * b.X); } } public sealed class PreviewSettle { public const int StableSamples = 4; public const float MinSeconds = 5f; public const float MaxSeconds = 120f; private float _start = float.NaN; private int _standing = -1; private int _same; private float _sum; public bool Settled { get; private set; } public bool GaveUp { get; private set; } public void Add(float time, int standing, float supportSum) { if (float.IsNaN(_start)) { _start = time; } else if (standing == _standing && Math.Abs(supportSum - _sum) < Math.Max(1f, Math.Abs(_sum) * 0.0005f)) { _same++; } else { _same = 0; } _standing = standing; _sum = supportSum; float num = time - _start; bool flag = _same >= 4; if (num >= 120f) { GaveUp = !flag; Settled = true; } else { Settled = flag && num >= 5f; } } } public static class PreviewName { public const int Longest = 22; public static bool IsSafe(string name) { if (!string.IsNullOrEmpty(name) && name.Length <= 22) { return name.All((char c) => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '-'); } return false; } } public static class PreviewLoad { public const string Header = "index\tprefab\tx\ty\tz\tstands\tsupport\tneeds\tmost\tcolour"; public static string Line(int index, string prefab, Vec3 at, bool stands, float? support, float? needs, float? most, float? colour) { return string.Join("\t", index.ToString(CultureInfo.InvariantCulture), prefab, N(at.X, "0.##"), N(at.Y, "0.##"), N(at.Z, "0.##"), stands ? "1" : "0", N(support, "0.#"), N(needs, "0.#"), N(most, "0.#"), N(colour, "0.###")); } private static string N(float? v, string format) { if (!v.HasValue) { return ""; } return v.Value.ToString(format, CultureInfo.InvariantCulture); } } public static class BuildSequence { public static List<int> Order(IReadOnlyList<PlannedPiece> pieces, Vec3 centre) { return (from p in pieces where p.IsValid orderby (int)Math.Round(p.Position.Y * 10f), Vec3.Distance(p.Position.Flat, centre.Flat), p.Index select p.Index).ToList(); } } public sealed class BuildCursor { private readonly IReadOnlyList<int> _order; private int _position; private int _failedSinceSuccess; public bool Stuck { get; private set; } public int LastTried { get; private set; } = -1; public BuildCursor(IReadOnlyList<int> order) { _order = order ?? new int[0]; } public int Next(Func<int, bool> isOpen, Func<int, bool> canPlace, int maxTries) { int num = _order.Count(isOpen); if (num == 0) { Stuck = false; return -1; } int num2 = 0; for (int i = 0; i < _order.Count; i++) { if (num2 >= maxTries) { break; } int num3 = _order[_position]; _position = (_position + 1) % _order.Count; if (isOpen(num3)) { num2++; LastTried = num3; if (canPlace(num3)) { Restart(); return num3; } _failedSinceSuccess++; } } Stuck = _failedSinceSuccess >= num; return -1; } public void Restart() { _position = 0; _failedSinceSuccess = 0; Stuck = false; } } public enum BuildOutcome { Placed, Fell } public sealed class BuildTally { public const int DetailedEvents = 10; public const int SummaryEvery = 50; public int Placed { get; private set; } public int Fell { get; private set; } public SwingLogKind Record(BuildOutcome outcome) { if (outcome == BuildOutcome.Placed) { Placed++; } else { Fell++; } int num = Placed + Fell; if (num <= 10) { return SwingLogKind.Detail; } if (num % 50 != 0) { return SwingLogKind.None; } return SwingLogKind.Summary; } } public static class BurnRules { public const float Seconds = 4f; public const float Patch = 2f; public static bool StuckBurning(bool burning, float seconds) { if (burning) { return seconds >= 4f; } return false; } public static bool StillThere(float dx, float dz) { return Math.Sqrt(dx * dx + dz * dz) <= 2.0; } } public static class CamFollow { public static Vec3 Offset(float facingYaw, float distance, float height, float angle) { double num = (double)(facingYaw + angle) * Math.PI / 180.0; return new Vec3((float)(Math.Sin(num) * (double)distance), height, (float)(Math.Cos(num) * (double)distance)); } public static bool KeepsHud(string[] args) { if (args == null || args.Length == 0) { return false; } string a = args[^1] ?? ""; if (!string.Equals(a, "hud", StringComparison.OrdinalIgnoreCase)) { return string.Equals(a, "tags", StringComparison.OrdinalIgnoreCase); } return true; } public static bool FollowsLastSpeaker(string word) { return string.Equals(word, "talking", StringComparison.OrdinalIgnoreCase); } public static bool Matches(string word, string name, string job) { if (string.IsNullOrEmpty(word)) { return false; } if (string.Equals(word, "any", StringComparison.OrdinalIgnoreCase)) { return true; } if (FollowsLastSpeaker(word)) { return false; } if (string.Equals(word, "jobless", StringComparison.OrdinalIgnoreCase)) { return string.IsNullOrEmpty(job); } if (!string.Equals(word, name, StringComparison.OrdinalIgnoreCase)) { return string.Equals(word, job, StringComparison.OrdinalIgnoreCase); } return true; } } public static class CampOverview { public static IReadOnlyList<string> FirstNames(IReadOnlyList<string> names, int shown, out int more) { names = names ?? new string[0]; int num = Math.Max(0, Math.Min(shown, names.Count)); more = names.Count - num; return names.Take(num).ToList(); } public static IReadOnlyList<MoodReason> WeightiestReasons(IEnumerable<IReadOnlyList<MoodReason>> perSettler, int max) { List<MoodReason> list = new List<MoodReason>(); foreach (IReadOnlyList<MoodReason> item in perSettler ?? Enumerable.Empty<IReadOnlyList<MoodReason>>()) { if (item == null) { continue; } foreach (MoodReason r in item) { int num = list.FindIndex((MoodReason s) => s.Token == r.Token && s.Name == r.Name); if (num < 0) { list.Add(new MoodReason(r.Token, r.Change, r.Name)); } else { list[num] = new MoodReason(r.Token, list[num].Change + r.Change, r.Name); } } } return (from x in (from x in list.Where((MoodReason s) => s.Change != 0).Select((MoodReason s, int order) => new { s, order }) orderby Math.Abs(x.s.Change) descending, x.order select x).Take(Math.Max(0, max)) select x.s).ToList(); } public static IReadOnlyList<SagaEntry> LinesNaming(SagaLog saga, string name, int max) { if (saga == null || string.IsNullOrEmpty(name) || max <= 0) { return new SagaEntry[0]; } return (from e in saga.Page(0, 60) where e.Args.Contains(name) select e).Take(max).ToList(); } } public static class CampRules { public const int FirstLevel = 1; public const double RedRecheckSeconds = 60.0; public static int NextLevel(int hallLevel, int campLevel, bool busy, int builderHutLevel) { int num = HallAllows(hallLevel, campLevel, busy); if (num <= 0 || builderHutLevel <= 0 || !HutLevels.MayBuild(builderHutLevel, num, ownHut: false)) { return 0; } return num; } public static bool WaitsForBuilder(int hallLevel, int campLevel, bool busy, int builderHutLevel) { if (HallAllows(hallLevel, campLevel, busy) > 0) { return NextLevel(hallLevel, campLevel, busy, builderHutLevel) == 0; } return false; } private static int HallAllows(int hallLevel, int campLevel, bool busy) { if (busy || campLevel < 1) { return 0; } int num = campLevel + 1; if (num > Math.Min(5, TownHallLevels.Clamp(hallLevel))) { return 0; } return num; } public static bool LookAgain(double lastRedAt, double now) { return now - lastRedAt >= 60.0; } } public static class CampWait { private const char Gap = '|'; public static string Store(string token, int number = 0) { string text; if (number <= 0) { text = token; if (text == null) { return ""; } } else { text = token + "|" + number; } return text; } public static bool Read(string stored, out string token, out int number) { number = 0; token = stored ?? ""; int num = token.LastIndexOf('|'); if (num <= 0 || !token.StartsWith("$", StringComparison.Ordinal)) { return false; } if (!int.TryParse(token.Substring(num + 1), out number)) { number = 0; return false; } token = token.Substring(0, num); return true; } } public struct RowButton { public readonly float X; public readonly float Width; public float Left => X - Width / 2f; public float Right => X + Width / 2f; public RowButton(float x, float width) { X = x; Width = width; } } public static class CampWindowLayout { public const float Width = 760f; public const float Height = 740f; public const float CloseWidth = 140f; public static readonly RowButton Alarm = new RowButton(-270f, 170f); public static readonly RowButton Close = new RowButton(0f, 140f); public static readonly RowButton Report = new RowButton(205f, 130f); public static readonly RowButton Feedback = new RowButton(325f, 90f); public static RowButton[] BottomRow => new RowButton[4] { Alarm, Close, Report, Feedback }; } public sealed class CarryLoad { private readonly SortedDictionary<string, int> _items = new SortedDictionary<string, int>(StringComparer.Ordinal); public const int MaxCount = 1000000; public int Total => (int)Math.Min(2147483647L, ((IEnumerable<int>)_items.Values).Sum((Func<int, long>)((int n) => n))); public IEnumerable<string> ItemNames => _items.Keys.ToList(); public int Count(string item) { if (item == null || !_items.TryGetValue(item, out var value)) { return 0; } return value; } public void Add(string item, int amount) { if (string.IsNullOrEmpty(item)) { throw new ArgumentException("Item name is required.", "item"); } if (amount > 0) { _items[item] = (int)Math.Min(1000000L, (long)Count(item) + (long)amount); } } public int Remove(string item, int amount) { int num = Count(item); int num2 = Math.Min(num, Math.Max(0, amount)); if (num2 == 0) { return 0; } if (num == num2) { _items.Remove(item); } else { _items[item] = num - num2; } return num2; } public void Clear() { _items.Clear(); } public CarryLoad Beyond(IReadOnlyDictionary<string, int> keep) { CarryLoad carryLoad = new CarryLoad(); foreach (KeyValuePair<string, int> item in _items) { carryLoad.Add(item.Key, item.Value - ((keep != null && keep.TryGetValue(item.Key, out var value)) ? value : 0)); } return carryLoad; } public static CarryLoad Together(CarryLoad a, CarryLoad b) { CarryLoad carryLoad = new CarryLoad(); CarryLoad[] array = new CarryLoad[2] { a, b }; foreach (CarryLoad carryLoad2 in array) { if (carryLoad2 == null) { continue; } foreach (KeyValuePair<string, int> item in carryLoad2._items) { carryLoad.Add(item.Key, item.Value); } } return carryLoad; } public string Serialize() { return string.Join(",", _items.Select((KeyValuePair<string, int> kv) => kv.Key + ":" + kv.Value)); } public static CarryLoad Deserialize(string text) { CarryLoad carryLoad = new CarryLoad(); if (string.IsNullOrEmpty(text)) { return carryLoad; } string[] array = text.Split(new char[1] { ',' }); foreach (string text2 in array) { int num = text2.LastIndexOf(':'); if (num > 0 && int.TryParse(text2.Substring(num + 1), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { carryLoad.Add(text2.Substring(0, num), result); } } return carryLoad; } } public enum ChestStep { Take, GoRound } public enum ChestGoal { OpenSide, Chest } public static class ChestSides { public static bool Behind(float fromX, float fromZ, float forwardX, float forwardZ) { double num = Math.Sqrt(fromX * fromX + fromZ * fromZ); double num2 = Math.Sqrt(forwardX * forwardX + forwardZ * forwardZ); if (num < 0.1 || num2 < 0.001) { return false; } return (double)(fromX * forwardX + fromZ * forwardZ) / (num * num2) < -0.5; } } public sealed class ChestRound { private const int Round = 1; private const int Failed_ = 2; private const int PlannedSide = 3; private const int PlannedChest = 4; private const int Direct = 5; private readonly Dictionary<string, int> _chests = new Dictionary<string, int>(); public ChestStep AtReach(string chest, bool wallBetween) { int num = State(chest); if (num != 0 && num != 3 && num != 4) { return ChestStep.Take; } if (wallBetween) { return ChestStep.GoRound; } if (num != 0) { _chests[chest] = ((num == 3) ? 1 : 5); } return ChestStep.Take; } public bool ChecksOnArrival(string chest) { int num = State(chest); if (num != 0 && num != 3) { return num == 4; } return true; } public void WentRound(string chest) { if (chest != null) { _chests[chest] = 1; } } public void Failed(string chest) { if (chest != null) { _chests[chest] = 2; } } public bool GoingRound(string chest) { if (State(chest) != 1) { return State(chest) == 3; } return true; } public bool IsNew(string chest) { return State(chest) == 0; } public ChestGoal FirstGoal(string chest, bool hasOpenSide) { if (chest != null && State(chest) == 0) { _chests[chest] = (hasOpenSide ? 3 : 4); } if (!GoingRound(chest)) { return ChestGoal.Chest; } return ChestGoal.OpenSide; } public void Leave() { _chests.Clear(); } public void Relook(bool heldSite, bool newSite) { if (heldSite || newSite) { Leave(); } } private int State(string chest) { if (chest == null || !_chests.TryGetValue(chest, out var value)) { return 0; } return value; } } public static class ChopArea { public const float LeftoverReach = 25f; public static bool IsLeftover(bool isTree, bool haveFelled, float fromFelled) { if (!isTree && haveFelled) { return fromFelled <= 25f; } return false; } public static bool Counts(bool isTree, float fromHut, float radius, bool haveFelled, float fromFelled) { if (!(fromHut <= radius)) { return IsLeftover(isTree, haveFelled, fromFelled); } return true; } } public static class CitizenLine { public static string Format(string name, string jobTitle, string status, string place) { string text = ((string.IsNullOrEmpty(jobTitle) || jobTitle == status) ? status : ((!string.IsNullOrEmpty(status)) ? (jobTitle + ", " + status) : jobTitle)); string text2 = "<b>" + name + "</b> — " + text; if (!string.IsNullOrEmpty(place)) { return text2 + " @ " + place; } return text2; } public static string SeenToken(int lastSeenDay, int today) { if (lastSeenDay <= 0 || lastSeenDay >= today) { return "$vc_far_away"; } return "$vc_seen_day"; } public static string DaysToken(int days) { if (days > 0) { if (days != 1) { return "$vc_here_days"; } return "$vc_here_day"; } return "$vc_came_today"; } public static string[] Details(string name, string calling, string trait, string origin, string daysHere, string home) { string text = string.Join(", ", new string[2] { calling, trait }.Where((string w) => !string.IsNullOrEmpty(w))); string text2 = name + ((text.Length > 0) ? (": " + text + ".") : "."); if (!string.IsNullOrEmpty(origin)) { text2 = text2 + " " + char.ToUpperInvariant(origin[0]) + origin.Substring(1) + "."; } string text3 = string.Join(" ", new string[2] { daysHere, home }.Where((string w) => !string.IsNullOrEmpty(w))); return new string[2] { text2, text3 }; } } public sealed class CitizenRecord { public const int CurrentVersion = 2; public string Id = ""; public string Name = ""; public int Model; public string Hair = ""; public string Beard = ""; public float[] Skin = new float[3] { 1f, 1f, 1f }; public float[] HairColor = new float[3] { 1f, 1f, 1f }; public string ColonyId = ""; public float[] ColonyPos; public string Workplace = ""; public float[] WorkplacePos; public string Job = ""; public string Carry = ""; public string Status = ""; public float Woodcutting; public float WoodcuttingXp; public float Farming; public float FarmingXp; public float Crafting; public float CraftingXp; public float Fighting; public float FightingXp; public float Hauling; public float HaulingXp; public int Kills; public int LastMealDay = -1; public bool Hungry; public string Home = ""; public float[] HomePos; public double HallLostAt = -1.0; public int Life; public const int ArrivedUnknown = -1; public string Calling = ""; public string Trait = ""; public string Origin = ""; public int Arrived = -1; public bool Arriving; public const int NoDay = -1; public int Mood = -1; public string WhyMood = ""; public List<string> LastMeals = new List<string>(); public int EveningDay = -1; public bool EveningCompany; public int HitDay = -1; public int JoblessSince = -1; public bool HasColony => !string.IsNullOrEmpty(ColonyId); public bool IsEmployed => !string.IsNullOrEmpty(Workplace); public bool HasHome => !string.IsNullOrEmpty(Home); public void ClearHome() { Home = ""; HomePos = null; } public void ClearWorkplace() { Workplace = ""; WorkplacePos = null; Job = ""; } public void ClearColony() { ColonyId = ""; ColonyPos = null; ClearWorkplace(); ClearHome(); } public string Encode() { List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>(); list.Add(Pair("v", RecordCodec.FormatInt(2))); list.Add(Pair("id", Id)); list.Add(Pair("name", Name)); list.Add(Pair("model", RecordCodec.FormatInt(Model))); list.Add(Pair("hair", Hair)); list.Add(Pair("beard", Beard)); list.Add(Pair("skin", RecordCodec.FormatFloats(Skin))); list.Add(Pair("haircolor", RecordCodec.FormatFloats(HairColor))); list.Add(Pair("colony", ColonyId)); list.Add(Pair("colonypos", RecordCodec.FormatFloats(ColonyPos))); list.Add(Pair("work", Workplace)); list.Add(Pair("workpos", RecordCodec.FormatFloats(WorkplacePos))); list.Add(Pair("job", Job)); list.Add(Pair("carry", Carry)); list.Add(Pair("status", Status)); list.Add(Pair("woodcutting", RecordCodec.FormatFloats(new float[2] { Woodcutting, WoodcuttingXp }))); list.Add(Pair("meal", RecordCodec.FormatInt(LastMealDay))); list.Add(Pair("hungry", Hungry ? "1" : "")); list.Add(Pair("home", Home)); list.Add(Pair("homepos", RecordCodec.FormatFloats(HomePos))); list.Add(Pair("halllost", ColonyGrace.IsLost(HallLostAt) ? HallLostAt.ToString("R", CultureInfo.InvariantCulture) : "")); list.Add(Pair("life", (Life > 0) ? RecordCodec.FormatInt(Life) : "")); List<KeyValuePair<string, string>> list2 = list; if (Calling.Length > 0) { list2.Add(Pair("calling", Calling)); } if (Trait.Length > 0) { list2.Add(Pair("trait", Trait)); } if (Origin.Length > 0) { list2.Add(Pair("origin", Origin)); } if (Arrived != -1) { list2.Add(Pair("arrived", RecordCodec.FormatInt(Arrived))); } if (Arriving) { list2.Add(Pair("arriving", "1")); } if (Mood != -1) { list2.Add(Pair("mood", RecordCodec.FormatInt(Mood))); } if (WhyMood.Length > 0) { list2.Add(Pair("whymood", WhyMood)); } if (LastMeals.Count > 0) { list2.Add(Pair("meals", string.Join(",", LastMeals.Select((string m) => (m ?? "").Replace(',', ' '))))); } if (EveningDay != -1) { list2.Add(Pair("evening", RecordCodec.FormatInt(EveningDay))); } if (EveningCompany) { list2.Add(Pair("company", "1")); } if (HitDay != -1) { list2.Add(Pair("hitday", RecordCodec.FormatInt(HitDay))); } if (JoblessSince != -1) { list2.Add(Pair("joblesssince", RecordCodec.FormatInt(JoblessSince))); } if (Farming > 0f || FarmingXp > 0f) { list2.Add(Pair("farming", RecordCodec.FormatFloats(new float[2] { Farming, FarmingXp }))); } if (Crafting > 0f || CraftingXp > 0f) { list2.Add(Pair("crafting", RecordCodec.FormatFloats(new float[2] { Crafting, CraftingXp }))); } if (Fighting > 0f || FightingXp > 0f) { list2.Add(Pair("fighting", RecordCodec.FormatFloats(new float[2] { Fighting, FightingXp }))); } if (Hauling > 0f || HaulingXp > 0f) { list2.Add(Pair("hauling", RecordCodec.FormatFloats(new float[2] { Hauling, HaulingXp }))); } if (Kills > 0) { list2.Add(Pair("kills", RecordCodec.FormatInt(Kills))); } return RecordCodec.Encode(list2); } public static CitizenRecord Decode(string text) { Dictionary<string, string> fields = RecordCodec.Decode(text); float[] array = RecordCodec.ParseFloats(RecordCodec.Get(fields, "woodcutting"), 2) ?? new float[2]; float[] array2 = RecordCodec.ParseFloats(RecordCodec.Get(fields, "farming"), 2) ?? new float[2]; float[] array3 = RecordCodec.ParseFloats(RecordCodec.Get(fields, "crafting"), 2) ?? new float[2]; float[] array4 = RecordCodec.ParseFloats(RecordCodec.Get(fields, "fighting"), 2) ?? new float[2]; float[] array5 = RecordCodec.ParseFloats(RecordCodec.Get(fields, "hauling"), 2) ?? new float[2]; CitizenRecord citizenRecord = new CitizenRecord(); citizenRecord.Id = RecordCodec.Get(fields, "id"); citizenRecord.Name = RecordCodec.Get(fields, "name"); citizenRecord.Model = RecordCodec.ParseInt(RecordCodec.Get(fields, "model"), 0); citizenRecord.Hair = RecordCodec.Get(fields, "hair"); citizenRecord.Beard = RecordCodec.Get(fields, "beard"); citizenRecord.Skin = RecordCodec.ParseFloats(RecordCodec.Get(fields, "skin"), 3) ?? new float[3] { 1f, 1f, 1f }; citizenRecord.HairColor = RecordCodec.ParseFloats(RecordCodec.Get(fields, "haircolor"), 3) ?? new float[3] { 1f, 1f, 1f }; citizenRecord.ColonyId = RecordCodec.Get(fields, "colony"); citizenRecord.ColonyPos = RecordCodec.ParseFloats(RecordCodec.Get(fields, "colonypos"), 3); citizenRecord.Workplace = RecordCodec.Get(fields, "work"); citizenRecord.WorkplacePos = RecordCodec.ParseFloats(RecordCodec.Get(fields, "workpos"), 3); citizenRecord.Job = RecordCodec.Get(fields, "job"); citizenRecord.Carry = RecordCodec.Get(fields, "carry"); citizenRecord.Status = RecordCodec.Get(fields, "status"); citizenRecord.Woodcutting = array[0]; citizenRecord.WoodcuttingXp = array[1]; citizenRecord.LastMealDay = RecordCodec.ParseInt(RecordCodec.Get(fields, "meal"), -1); citizenRecord.Hungry = RecordCodec.Get(fields, "hungry") == "1"; citizenRecord.Home = RecordCodec.Get(fields, "home"); citizenRecord.HomePos = RecordCodec.ParseFloats(RecordCodec.Get(fields, "homepos"), 3); citizenRecord.HallLostAt = (RecordCodec.TryParseFinite(RecordCodec.Get(fields, "halllost"), out double value) ? value : (-1.0)); citizenRecord.Life = Math.Max(0, RecordCodec.ParseInt(RecordCodec.Get(fields, "life"), 0)); citizenRecord.Calling = RecordCodec.Get(fields, "calling"); citizenRecord.Trait = RecordCodec.Get(fields, "trait"); citizenRecord.Origin = RecordCodec.Get(fields, "origin"); citizenRecord.Arrived = RecordCodec.ParseInt(RecordCodec.Get(fields, "arrived"), -1); citizenRecord.Arriving = RecordCodec.Get(fields, "arriving") == "1"; citizenRecord.Mood = ValidMood(RecordCodec.ParseInt(RecordCodec.Get(fields, "mood"), -1)); citizenRecord.WhyMood = RecordCodec.Get(fields, "whymood"); citizenRecord.LastMeals = (from m in RecordCodec.Get(fields, "meals").Split(new char[1] { ',' }) where m.Length > 0 select m).Reverse().Take(5).Reverse() .ToList(); citizenRecord.EveningDay = RecordCodec.ParseInt(RecordCodec.Get(fields, "evening"), -1); citizenRecord.EveningCompany = RecordCodec.Get(fields, "company") == "1"; citizenRecord.HitDay = RecordCodec.ParseInt(RecordCodec.Get(fields, "hitday"), -1); citizenRecord.JoblessSince = RecordCodec.ParseInt(RecordCodec.Get(fields, "joblesssince"), -1); citizenRecord.Farming = array2[0]; citizenRecord.FarmingXp = array2[1]; citizenRecord.Crafting = array3[0]; citizenRecord.CraftingXp = array3[1]; citizenRecord.Fighting = array4[0]; citizenRecord.FightingXp = array4[1]; citizenRecord.Hauling = array5[0]; citizenRecord.HaulingXp = array5[1]; citizenRecord.Kills = Math.Max(0, RecordCodec.ParseInt(RecordCodec.Get(fields, "kills"), 0)); return citizenRecord; } public float Level(WorkSkill skill) { return skill switch { WorkSkill.Woodcutting => Woodcutting, WorkSkill.Farming => Farming, WorkSkill.Crafting => Crafting, WorkSkill.Fighting => Fighting, WorkSkill.Hauling => Hauling, _ => 0f, }; } public bool Practise(WorkSkill skill, float amount) { return skill switch { WorkSkill.Woodcutting => SettlerSkill.Raise(ref Woodcutting, ref WoodcuttingXp, amount), WorkSkill.Farming => SettlerSkill.Raise(ref Farming, ref FarmingXp, amount), WorkSkill.Crafting => SettlerSkill.Raise(ref Crafting, ref CraftingXp, amount), WorkSkill.Fighting => SettlerSkill.Raise(ref Fighting, ref FightingXp, amount), WorkSkill.Hauling => SettlerSkill.Raise(ref Hauling, ref HaulingXp, amount), _ => false, }; } private static int ValidMood(int mood) { if (mood < 0 || mood > 100) { return -1; } return mood; } public static CitizenRecord FromV1(string colonyId, string name, int model, string hair, string beard, float[] skin, float[] hairColor, string carry, string status) { CitizenRecord citizenRecord = new CitizenRecord(); citizenRecord.Name = name ?? ""; citizenRecord.Model = model; citizenRecord.Hair = hair ?? ""; citizenRecord.Beard = beard ?? ""; citizenRecord.Skin = ((skin != null && skin.Length == 3) ? skin : new float[3] { 1f, 1f, 1f }); citizenRecord.HairColor = ((hairColor != null && hairColor.Length == 3) ? hairColor : new float[3] { 1f, 1f, 1f }); citizenRecord.ColonyId = colonyId ?? ""; citizenRecord.Carry = carry ?? ""; citizenRecord.Status = status ?? ""; return citizenRecord; } private static KeyValuePair<string, string> Pair(string key, string value) { return new KeyValuePair<string, string>(key, value); } } public sealed class ClaimBoard { private struct Claim { public string Owner; public double Touched; public GroundPoint Center; public float Radius; } public const double ExpirySeconds = 60.0; private readonly Dictionary<string, Claim> _things = new Dictionary<string, Claim>(); private readonly Dictionary<string, Claim> _areas = new Dictionary<string, Claim>(); public bool TryClaim(string id, GroundPoint pos, string owner, double now) { if (IsTakenByOther(id, pos, owner, now)) { return false; } _things[id] = new Claim { Owner = owner, Touched = now }; if (_areas.TryGetValue(owner, out var value) && ColonyRules.Distance(pos, value.Center) <= value.Radius) { value.Touched = now; _areas[owner] = value; } return true; } public void Release(string id, string owner) { if (_things.TryGetValue(id, out var value) && value.Owner == owner) { _things.Remove(id); } } public void ClaimArea(string owner, GroundPoint center, float radius, double now) { _areas[owner] = new Claim { Owner = owner, Touched = now, Center = center, Radius = radius }; } public bool IsTakenByOther(string id, GroundPoint pos, string owner, double now) { Prune(now); if (_things.TryGetValue(id, out var value) && value.Owner != owner) { return true; } foreach (Claim value2 in _areas.Values) { if (value2.Owner != owner && ColonyRules.Distance(pos, value2.Center) <= value2.Radius) { return true; } } return false; } private void Prune(double now) { foreach (string item in (from p in _things where now - p.Value.Touched >= 60.0 select p.Key).ToList()) { _things.Remove(item); } foreach (string item2 in (from p in _areas where now - p.Value.Touched >= 60.0 select p.Key).ToList()) { _areas.Remove(item2); } } } public static class DeedKinds { public const string Felled = "felled"; public const string Gathered = "gathered"; public const string Carried = "carried"; public const string Mended = "mended"; public const string Built = "built"; public const string Raids = "raids"; public const string Lost = "lost"; public static readonly IReadOnlyList<string> All = new string[7] { "felled", "gathered", "carried", "mended", "built", "raids", "lost" }; public static bool IsKnown(string kind) { if (kind != null) { return All.Contains(kind); } return false; } } public sealed class ColonyDeeds { public const int MaxAmount = 1000; private int _day = -1; private readonly Dictionary<string, int> _today = new Dictionary<string, int>(); private readonly Dictionary<string, int> _all = new Dictionary<string, int>(); public bool Add(string kind, int amount, int today) { if (!DeedKinds.IsKnown(kind) || amount < 1 || amount > 1000) { return false; } if (today != _day) { _today.Clear(); _day = today; } _today[kind] = Count(_today, kind) + amount; _all[kind] = Count(_all, kind) + amount; return true; } public int Today(string kind, int today) { if (_day < 0 || today != _day) { return 0; } return Count(_today, kind); } public int AllTime(string kind) { return Count(_all, kind); } private static int Count(Dictionary<string, int> counts, string kind) { if (kind == null || !counts.TryGetValue(kind, out var value)) { return 0; } return value; } public string Encode() { StringBuilder stringBuilder = new StringBuilder(_day.ToString(CultureInfo.InvariantCulture)).Append('|'); bool flag = true; foreach (string item in DeedKinds.All) { if (_all.ContainsKey(item)) { if (!flag) { stringBuilder.Append(';'); } flag = false; stringBuilder.Append(item).Append('=').Append(Count(_today, item).ToString(CultureInfo.InvariantCulture)) .Append(',') .Append(Count(_all, item).ToString(CultureInfo.InvariantCulture)); } } return stringBuilder.ToString(); } public static ColonyDeeds Decode(string text) { ColonyDeeds colonyDeeds = new ColonyDeeds(); if (string.IsNullOrEmpty(text)) { return colonyDeeds; } int num = text.IndexOf('|'); string s = ((num < 0) ? text : text.Substring(0, num)); colonyDeeds._day = (int.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) ? result : (-1)); if (num < 0) { return colonyDeeds; } string[] array = text.Substring(num + 1).Split(new char[1] { ';' }, StringSplitOptions.RemoveEmptyEntries); foreach (string text2 in array) { int num2 = text2.IndexOf('='); if (num2 > 0) { string text3 = text2.Substring(0, num2); string[] array2 = text2.Substring(num2 + 1).Split(new char[1] { ',' }); if (DeedKinds.IsKnown(text3) && array2.Length == 2 && int.TryParse(array2[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2) && result2 >= 0 && int.TryParse(array2[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result3) && result3 >= 0) { colonyDeeds._today[text3] = result2; colonyDeeds._all[text3] = result3; } } } return colonyDeeds; } } public static class ColonyManagers { public static bool MayManage(long owner, long player, bool onOwnersWard) { if (owner != 0L) { if (player != 0L) { return player == owner || onOwnersWard; } return false; } return true; } } public sealed class ReportBuilding { public string Type = ""; public string Id = ""; public int Level = 1; public Vec3 Position; public float Turn; public Vec3? Door; public string Order = ""; public IReadOnlyDictionary<string, int> Chest; public string ChestSize = ""; } public sealed class ReportSettler { public string Name = ""; public string Job = ""; public string Workplace = ""; public string Status = ""; public Vec3 Position; public string Carry = ""; public List<string> Recent; public string Troubles = ""; } public sealed class ReportColony { public string Id = ""; public Vec3? Hall; public List<ReportBuilding> Buildings { get; } = new List<ReportBuilding>(); public List<ReportSettler> Settlers { get; } = new List<ReportSettler>(); } public static class ColonyReport { public static string Format(string header, IReadOnlyList<ReportColony> colonies, IReadOnlyList<ReportSettler> withoutColony) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append(header).Append('\n'); if (colonies.Count == 0) { stringBuilder.Append("No colonies.\n"); } foreach (ReportColony colony in colonies) { stringBuilder.Append(string.Format("Colony {0}: {1}, {2} settlers\n", colony.Id, colony.Hall.HasValue ? ("Town Hall at " + P(colony.Hall.Value)) : "no Town Hall here", colony.Settlers.Count)); foreach (ReportBuilding building in colony.Buildings) { stringBuilder.Append(string.Format(" {0} {1} level {2} at {3} turned {4}", building.Type, building.Id, building.Level, P(building.Position), building.Turn.ToString("0", CultureInfo.InvariantCulture))); if (building.Door.HasValue) { stringBuilder.Append(", door at " + P(building.Door.Value)); } stringBuilder.Append('\n'); if (building.Order.Length > 0) { stringBuilder.Append(" order: " + building.Order + "\n"); } if (building.Chest != null) { stringBuilder.Append(" chest" + ((building.ChestSize.Length > 0) ? (" " + building.ChestSize) : "") + ": " + Items(building.Chest) + "\n"); } } foreach (ReportSettler settler in colony.Settlers) { AppendSettler(stringBuilder, settler); } } if (withoutColony.Count > 0) { stringBuilder.Append("Without a colony:\n"); foreach (ReportSettler item in withoutColony) { AppendSettler(stringBuilder, item); } } return stringBuilder.ToString(); } private static void AppendSettler(StringBuilder sb, ReportSettler s) { sb.Append(" " + Line(s) + "\n"); if (s.Recent != null && s.Recent.Count > 0) { sb.Append(" recently: " + string.Join("; ", s.Recent) + "\n"); } if (!string.IsNullOrEmpty(s.Troubles)) { sb.Append(" troubles: " + s.Troubles + "\n"); } } private static string Line(ReportSettler s) { string text = ((s.Job.Length == 0) ? "jobless" : ((s.Workplace.Length == 0) ? s.Job : (s.Job + " at " + s.Workplace))); string text2 = ((s.Status.Length == 0) ? "-" : s.Status); string text3 = ((s.Carry.Length == 0) ? "nothing" : s.Carry); return s.Name + ", " + text + ": " + text2 + ", at " + P(s.Position) + ", carrying " + text3; } private static string Items(IReadOnlyDictionary<string, int> items) { if (items.Count != 0) { return string.Join(", ", from i in items.OrderByDescending((KeyValuePair<string, int> i) => i.Value).ThenBy<KeyValuePair<string, int>, string>((KeyValuePair<string, int> i) => i.Key, StringComparer.Ordinal) select $"{i.Key} {i.Value}"); } return "empty"; } private static string P(Vec3 v) { return string.Format(CultureInfo.InvariantCulture, "({0:0.0}, {1:0.0}, {2:0.0})", v.X, v.Y, v.Z); } } public sealed class RosterEntry { public string CitizenId = ""; public string Name = ""; public string Job = ""; public string Workplace = ""; public string Status = ""; public int LastSeenDay; public float[] LastPos; public int MissingSinceDay = -1; public string Home = ""; public int Mood = -1; public bool IsMissing => MissingSinceDay >= 0; } public sealed class ColonyRoster { public const int CurrentVersion = 1; private readonly List<RosterEntry> _entries = new List<RosterEntry>(); public const int MaxDecoded = 1000; public IReadOnlyList<RosterEntry> Entries => _entries; public RosterEntry Get(string citizenId) { return _entries.FirstOrDefault((RosterEntry e) => e.CitizenId == citizenId); } public RosterEntry WorkerOf(string workplaceId) { if (!string.IsNullOrEmpty(workplaceId)) { return _entries.FirstOrDefault((RosterEntry e) => e.Workplace == workplaceId); } return null; } public List<RosterEntry> WorkersOf(string workplaceId) { if (!string.IsNullOrEmpty(workplaceId)) { return _entries.Where((RosterEntry e) => e.Workplace == workplaceId).ToList(); } return new List<RosterEntry>(); } public void Seen(string citizenId, string name, string job, string workplace, string status, float[] pos, int day, string home = "", int mood = -1) { if (!string.IsNullOrEmpty(citizenId)) { RosterEntry rosterEntry = Get(citizenId); if (rosterEntry == null) { rosterEntry = new RosterEntry { CitizenId = citizenId }; _entries.Add(rosterEntry); } rosterEntry.Name = name ?? ""; rosterEntry.Job = job ?? ""; rosterEntry.Workplace = workplace ?? ""; rosterEntry.Status = status ?? ""; rosterEntry.LastPos = pos; rosterEntry.LastSeenDay = day; rosterEntry.MissingSinceDay = -1; rosterEntry.Home = home ?? ""; if (mood != -1) { rosterEntry.Mood = mood; } } } public bool Remove(string citizenId) { return _entries.RemoveAll((RosterEntry e) => e.CitizenId == citizenId) > 0; } public void MarkMissing(string citizenId, int day) { RosterEntry rosterEntry = Get(citizenId); if (rosterEntry != null && rosterEntry.MissingSinceDay < 0) { rosterEntry.MissingSinceDay = day; } } public static bool MayBringBack(RosterEntry entry, int today) { if (entry != null && entry.IsMissing) { return today - entry.MissingSinceDay >= 1; } return false; } public int PruneMissing(int today, int maxDaysMissing) { return _entries.RemoveAll((RosterEntry e) => e.MissingSinceDay >= 0 && today - e.MissingSinceDay >= maxDaysMissing); } public string Signature() { StringBuilder stringBuilder = new StringBuilder(); foreach (RosterEntry entry in _entries) { stringBuilder.Append(entry.CitizenId).Append('|').Append(entry.Name) .Append('|') .Append(entry.Job) .Append('|') .Append(entry.Workplace) .Append('|') .Append(entry.Status) .Append('|') .Append(entry.MissingSinceDay) .Append('|') .Append(entry.Home) .Append('|') .Append(entry.Mood) .Append('\n'); } return stringBuilder.ToString(); } public string Encode() { List<string> list = new List<string>(); list.Add(RecordCodec.Encode(new KeyValuePair<string, string>[1] { new KeyValuePair<string, string>("v", RecordCodec.FormatInt(1)) })); List<string> list2 = list; foreach (RosterEntry entry in _entries) { List<KeyValuePair<string, string>> list3 = new List<KeyValuePair<string, string>> { new KeyValuePair<string, string>("id", entry.CitizenId), new KeyValuePair<string, string>("name", entry.Name), new KeyValuePair<string, string>("job", entry.Job), new KeyValuePair<string, string>("work", entry.Workplace), new KeyValuePair<string, string>("status", entry.Status), new KeyValuePair<string, string>("seen", RecordCodec.FormatInt(entry.LastSeenDay)), new KeyValuePair<string, string>("pos", RecordCodec.FormatFloats(entry.LastPos)), new KeyValuePair<string, string>("missing", RecordCodec.FormatInt(entry.MissingSinceDay)), new KeyValuePair<string, string>("home", entry.Home) }; if (entry.Mood != -1) { list3.Add(new KeyValuePair<string, string>("mood", RecordCodec.FormatInt(entry.Mood))); } list2.Add(RecordCodec.Encode(list3)); } return string.Join("\n", list2); } public static ColonyRoster Decode(string text) { ColonyRoster colonyRoster = new ColonyRoster(); if (string.IsNullOrEmpty(text)) { return colonyRoster; } HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); string[] array = text.Split(new char[1] { '\n' }); foreach (string text2 in array) { if (colonyRoster._entries.Count >= 1000) { break; } Dictionary<string, string> fields = RecordCodec.Decode(text2); string text3 = RecordCodec.Get(fields, "id"); if (!string.IsNullOrEmpty(text3) && hashSet.Add(text3)) { List<RosterEntry> entries = colonyRoster._entries; RosterEntry obj = new RosterEntry { CitizenId = text3, Name = RecordCodec.Get(fields, "name"), Job = RecordCodec.Get(fields, "job"), Workplace = RecordCodec.Get(fields, "work"), Status = RecordCodec.Get(fields, "status"), LastSeenDay = RecordCodec.ParseInt(RecordCodec.Get(fields, "seen"), 0), LastPos = RecordCodec.ParseFloats(RecordCodec.Get(fields, "pos"), 3), MissingSinceDay = RecordCodec.ParseInt(RecordCodec.Get(fields, "missing"), -1), Home = RecordCodec.Get(fields, "home") }; int num = RecordCodec.ParseInt(RecordCodec.Get(fields, "mood"), -1); obj.Mood = ((num >= 0 && num <= 100) ? num : (-1)); entries.Add(obj); } } return colonyRoster; } } public enum Presence { Present, Gone, Unknown } public sealed class HallInfo { public string ColonyId { get; } public GroundPoint Pos { get; } public HallInfo(string colonyId, GroundPoint pos) { ColonyId = colonyId; Pos = pos; } } public sealed class CitizenInfo { public string Calling { get; } public string Id { get; } public string ColonyId { get; } public string Workplace { get; } public GroundPoint Pos { get; } public CitizenInfo(string id, string colonyId, string workplace, GroundPoint pos, string calling = "") { Id = id; ColonyId = colonyId; Workplace = workplace; Pos = pos; Calling = calling ?? ""; } } public static class ColonyRules { public const int MaxStartingCitizens = 10; public const float GoneConfirmSeconds = 30f; public static string ChooseColony(GroundPoint building, IEnumerable<HallInfo> halls, float radius) { HallInfo hallInfo = null; float num = float.MaxValue; foreach (HallInfo hall in halls) { float num2 = Distance(building, hall.Pos); if (!(num2 > radius) && (hallInfo == null || num2 < num || (num2 == num && string.CompareOrdinal(hall.ColonyId, hallInfo.ColonyId) < 0))) { hallInfo = hall; num = num2; } } return hallInfo?.ColonyId; } public static int CitizensToSpawn(int configured, int members) { return Math.Max(0, Math.Min(Math.Max(configured, 0), 10) - Math.Max(members, 0)); } public static bool IsAdoptable(string colonyId, GroundPoint pos, GroundPoint hall, float radius) { if (string.IsNullOrEmpty(colonyId)) { return Distance(pos, hall) <= radius; } return false; } public static string PickWorker(GroundPoint workplace, string colonyId, IEnumerable<CitizenInfo> citizens, string job = null) { if (string.IsNullOrEmpty(colonyId)) { return null; } CitizenInfo citizenInfo = null; float num = float.MaxValue; bool flag = false; foreach (CitizenInfo citizen in citizens) { if (!(citizen.ColonyId != colonyId) && string.IsNullOrEmpty(citizen.Workplace) && !string.IsNullOrEmpty(citizen.Id)) { bool flag2 = Callings.Matches(citizen.Calling, job); float num2 = Distance(workplace, citizen.Pos); if (citizenInfo == null || (flag2 && !flag) || (flag2 == flag && (num2 < num || (num2 == num && string.CompareOrdinal(citizen.Id, citizenInfo.Id) < 0)))) { citizenInfo = citizen; num = num2; flag = flag2; } } } return citizenInfo?.Id; } public static Presence PresenceOf(bool found, bool areaLoaded) { if (!found) { if (!areaLoaded) { return Presence.Unknown; } return Presence.Gone; } return Presence.Present; } public static int GoneChecksFor(float checkIntervalSeconds) { if (!(checkIntervalSeconds <= 0f)) { return (int)Math.Ceiling(30f / checkIntervalSeconds); } return 30; } public static float[] CheckPoint(float[] colonyPos, float[] ownPos) { if (colonyPos == null || colonyPos.Length != 3) { return ownPos; } return colonyPos; } public static float HutBuildRange(float colonyRadius) { return Math.Max(colonyRadius - 6f, 5f); } public static bool CanEmploy(CitizenRecord citizen, string colonyId) { if (!string.IsNullOrEmpty(colonyId) && citizen.ColonyId == colonyId) { return !citizen.IsEmployed; } return false; } public static bool CanUnemploy(CitizenRecord citizen, string workplaceId) { if (!string.IsNullOrEmpty(workplaceId)) { return citizen.Workplace == workplaceId; } return false; } public static bool CanJoinColony(CitizenRecord citizen, string colonyId) { if (!string.IsNullOrEmpty(colonyId)) { return !citizen.HasColony; } return false; } public static float Distance(GroundPoint a, GroundPoint b) { float num = a.X - b.X; float num2 = a.Z - b.Z; return (float)Math.Sqrt(num * num + num2 * num2); } } public sealed class GoneTracker { private readonly int _needed; private int _streak; public int Streak => _streak; public Presence Last { get; private set; } = Presence.Unknown; public GoneTracker(int confirmations = 3) { _needed = Math.Max(1, confirmations); } public bool Observe(Presence presence) { Last = presence; if (presence != Presence.Gone) { _streak = 0; return false; } _streak++; return _streak >= _needed; } public void Reset() { _streak = 0; } } public static class ConfigNames { public const string TownHallSection = "Town Hall"; public const string ColonySection = "Colony"; public const string LumberjackHutSection = "Lumberjack Hut"; public const string LumberjackSection = "Lumberjack"; public const string BuilderHutSection = "Builder Hut"; public const string LonghouseSection = "Longhouse"; public const string ForagerHutSection = "Forager Hut"; public const string ForagerSection = "Forager"; public const string FarmSection = "Farm"; public const string FarmerSection = "Farmer"; public const string FieldSection = "Field"; public const string BuilderSection = "Builder"; public const string PlacingSection = "Placing"; public const string DeveloperSection = "Developer"; public const string CreativeSection = "Creative"; public const string StorageSection = "Storage"; public const string MessagesSection = "Messages"; public const string WatchtowerSection = "Watchtower"; public const string StorehouseSection = "Storehouse"; public const string MemorialSection = "Memorial"; public const string WindowsSection = "Windows"; public const string WindowScale = "Scale"; public const string CourierSection = "Courier"; public const string Carry = "Carry"; public const string CarryPerLevel = "CarryPerLevel"; public const string GuardsSection = "Guards"; public const string WoodCost = "WoodCost"; public const string StoneCost = "StoneCost"; public const string StartingCitizens = "StartingCitizens"; public const string ColonyRadius = "ColonyRadius"; public const string WorkRadius = "WorkRadius"; public const string CarryCapacity = "CarryCapacity"; public const string Replant = "Replant"; public const string ReplantNeedsCultivator = "ReplantNeedsCultivator"; public const string LearningRate = "LearningRate"; public const string Reach = "Reach"; public const string SecondsPerPiece = "SecondsPerPiece"; public const string RaiseKey = "RaiseKey"; public const string LowerKey = "LowerKey"; public const string CreativeMode = "CreativeMode"; public const string CreativeLevelUpKey = "LevelUpKey"; public const string CreativeLevelDownKey = "LevelDownKey"; public const string CommandFile = "CommandFile"; public const string RadiusPerLevel = "RadiusPerLevel"; public const string RowsPerLevel = "RowsPerLevel"; public const string SkipIntro = "SkipIntro"; public const string AutoStartCharacter = "AutoStartCharacter"; public const string AutoStartWorld = "AutoStartWorld"; public const string SettlersSafeFromTrees = "SettlersSafeFromTrees"; public const string DeathMessage = "DeathMessage"; public const string SettlersGoIndoorsAtNight = "SettlersGoIndoorsAtNight"; public const string SettlersShelterInRaids = "SettlersShelterInRaids"; public const string SettlersFightBack = "SettlersFightBack"; public const string TownHallGraceDays = "TownHallGraceDays"; public const string SettlersEat = "Set