Decompiled source of DangerousRoads v0.1.1
plugins/DangerousRoads.Core.dll
Decompiled 9 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using Microsoft.CodeAnalysis; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")] [assembly: AssemblyCompany("DangerousRoads.Core")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+fb85873cdf7f439cbcb58ddab6d8ef85c9144594")] [assembly: AssemblyProduct("DangerousRoads.Core")] [assembly: AssemblyTitle("DangerousRoads.Core")] [assembly: AssemblyMetadata("BuildStamp", "fb85873 2026-08-02")] [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.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace DangerousRoads.Core { public readonly struct ClockState { public readonly bool Armed; public readonly double DueAt; public static readonly ClockState Disarmed = new ClockState(armed: false, 0.0); public ClockState(bool armed, double dueAt) { Armed = armed; DueAt = dueAt; } public override string ToString() { if (!Armed) { return "disarmed"; } return $"armed@{DueAt:F1}"; } } public static class AmbushClock { public static float NextDelay(double roll01, float min, float max) { if (min < 0f) { min = 0f; } if (max < 0f) { max = 0f; } if (max < min) { max = min; } if (roll01 < 0.0) { roll01 = 0.0; } if (roll01 > 1.0) { roll01 = 1.0; } return min + (float)((double)(max - min) * roll01); } public static ClockState Arm(double now, float delay) { return new ClockState(armed: true, now + (double)((delay < 0f) ? 0f : delay)); } public static bool IsDue(ClockState state, double now) { if (state.Armed) { return now >= state.DueAt; } return false; } public static ClockState AfterWave(double now, float cooldownFloor, float rolledDelay) { return Arm(now, Math.Max(cooldownFloor, rolledDelay)); } public static ClockState Retry(double now, float retrySeconds) { return Arm(now, retrySeconds); } } public static class BandMath { public const float MinCrowFlies = 0.5f; public static bool InBand(float distance, float min, float max) { if (distance >= min) { return distance <= max; } return false; } public static float Ratio(float pathLength, float crowFlies) { if (crowFlies < 0.5f) { return -1f; } if (pathLength < 0f) { return -1f; } return pathLength / crowFlies; } public static bool PathRatioOk(float pathLength, float crowFlies, float k) { if (k <= 0f) { return false; } float num = Ratio(pathLength, crowFlies); if (num < 0f) { return false; } return num <= k; } public static float Score(float distance, float ratio, float bandMin, float bandMax) { float num = bandMax - bandMin; float num2 = ((num <= 0f) ? 0f : ((distance - bandMin) / num)); if (num2 < 0f) { num2 = 0f; } if (num2 > 1f) { num2 = 1f; } float num3 = 1f / Math.Max(1f, (ratio < 0f) ? float.MaxValue : ratio); return 0.6f * num2 + 0.4f * num3; } } public static class Bearing { private static readonly string[] Labels = new string[8] { "north", "north-east", "east", "south-east", "south", "south-west", "west", "north-west" }; public static int Sector(float dx, float dz, float minDistance = 0.5f) { if (dx * dx + dz * dz < minDistance * minDistance) { return -1; } double num = Math.Atan2(dx, dz) * 180.0 / Math.PI; if (num < 0.0) { num += 360.0; } return (int)Math.Round(num / 45.0) % 8; } public static string Label(float dx, float dz, float minDistance = 0.5f) { int num = Sector(dx, dz, minDistance); if (num >= 0) { return Labels[num]; } return ""; } } public sealed class BlockCounters { private readonly Dictionary<string, int> _counts = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase); private readonly List<string> _order = new List<string>(); public int Total { get; private set; } public string Last { get; private set; } = "(none)"; public void Record(string reason) { string text = (Last = (string.IsNullOrWhiteSpace(reason) ? "(unknown)" : reason.Trim())); Total++; if (_counts.TryGetValue(text, out var value)) { _counts[text] = value + 1; return; } _counts[text] = 1; _order.Add(text); } public void Reset() { _counts.Clear(); _order.Clear(); Total = 0; Last = "(none)"; } public int CountOf(string reason) { if (!_counts.TryGetValue(reason ?? "", out var value)) { return 0; } return value; } public string Format() { if (_counts.Count == 0) { return "none"; } List<string> list = new List<string>(_order); list.Sort(delegate(string a, string b) { int num2 = _counts[b].CompareTo(_counts[a]); return (num2 == 0) ? _order.IndexOf(a).CompareTo(_order.IndexOf(b)) : num2; }); StringBuilder stringBuilder = new StringBuilder(); for (int num = 0; num < list.Count; num++) { if (num > 0) { stringBuilder.Append(' '); } stringBuilder.Append(list[num]).Append('=').Append(_counts[list[num]].ToString(CultureInfo.InvariantCulture)); } return stringBuilder.ToString(); } } public static class ClusterPlan { public static IReadOnlyList<(float x, float z)> Offsets(int count, float radius, double startAngle01) { if (count <= 0 || radius <= 0f) { return Array.Empty<(float, float)>(); } if (startAngle01 < 0.0) { startAngle01 = 0.0; } if (startAngle01 > 1.0) { startAngle01 = 1.0; } List<(float, float)> list = new List<(float, float)>(count); double num = startAngle01 * 2.0 * Math.PI; double num2 = Math.PI * 2.0 / (double)count; for (int i = 0; i < count; i++) { double num3 = num + num2 * (double)i; list.Add(((float)(Math.Cos(num3) * (double)radius), (float)(Math.Sin(num3) * (double)radius))); } return list; } public static float MaxSpread(IReadOnlyList<(float x, float z)> positions) { if (positions == null || positions.Count < 2) { return 0f; } float num = 0f; for (int i = 0; i < positions.Count; i++) { for (int j = i + 1; j < positions.Count; j++) { float num2 = positions[i].x - positions[j].x; float num3 = positions[i].z - positions[j].z; float num4 = (float)Math.Sqrt(num2 * num2 + num3 * num3); if (num4 > num) { num = num4; } } } return num; } public static bool MaxSpreadOk(IReadOnlyList<(float x, float z)> positions, float clusterRadius, float tolerance = 6f) { if (clusterRadius <= 0f) { return false; } return MaxSpread(positions) <= clusterRadius * 2f + tolerance; } } public static class CombatDefer { public const double NotDeferring = double.NegativeInfinity; public static bool ShouldHold(bool enabled, bool inCombat, double deferSince, double now, float maxDeferSeconds) { if (!enabled || !inCombat) { return false; } if (maxDeferSeconds <= 0f) { return true; } if (double.IsNegativeInfinity(deferSince)) { return true; } return now - deferSince < (double)maxDeferSeconds; } public static double Advance(bool inCombat, double deferSince, double now) { if (!inCombat) { return double.NegativeInfinity; } if (!double.IsNegativeInfinity(deferSince)) { return deferSince; } return now; } } public sealed class FactionTable { private readonly Dictionary<string, string> _byName = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); public int Count => _byName.Count; public static FactionTable Parse(string text) { FactionTable factionTable = new FactionTable(); if (string.IsNullOrEmpty(text)) { return factionTable; } string[] array = text.Split(new char[1] { '\n' }); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); if (text2.Length == 0 || text2[0] == '#') { continue; } int num = text2.IndexOf('='); if (num > 0 && num != text2.Length - 1) { string text3 = text2.Substring(0, num).Trim(); string text4 = text2.Substring(num + 1).Trim(); if (text3.Length != 0 && text4.Length != 0) { factionTable._byName[text3] = text4; } } } return factionTable; } public string FactionOf(string species) { string text = (species ?? "").Trim(); if (text.Length == 0) { return null; } if (_byName.TryGetValue(text, out string value)) { return value; } string result = null; int num = 0; foreach (KeyValuePair<string, string> item in _byName) { if (item.Key.Length > num && text.IndexOf(item.Key, StringComparison.OrdinalIgnoreCase) >= 0) { result = item.Value; num = item.Key.Length; } } return result; } public bool IsKnown(string species) { return FactionOf(species) != null; } public bool Observe(string species, string faction, out string previous) { previous = null; string text = (species ?? "").Trim(); string text2 = (faction ?? "").Trim(); if (text.Length == 0 || text2.Length == 0) { return false; } bool num = _byName.TryGetValue(text, out previous); _byName[text] = text2; if (num) { return !string.Equals(previous, text2, StringComparison.OrdinalIgnoreCase); } return true; } public List<string> SameFaction(string species, IEnumerable<string> pool) { List<string> list = new List<string>(); string text = FactionOf(species); if (text == null || pool == null) { return list; } foreach (string item in pool) { if (string.Equals(FactionOf(item), text, StringComparison.OrdinalIgnoreCase)) { list.Add(item); } } return list; } public List<string> ToLines() { List<string> list = new List<string>(_byName.Keys); list.Sort(StringComparer.OrdinalIgnoreCase); List<string> list2 = new List<string>(list.Count); foreach (string item in list) { list2.Add(item + "=" + _byName[item]); } return list2; } } public sealed class MeasureLedger { private sealed class SourceStats { internal int Offered; internal int Accepted; internal int WavesUsed; internal double DistanceSum; internal int DistanceCount; internal double RatioSum; internal int RatioCount; internal readonly Dictionary<RejectReason, int> Rejects = new Dictionary<RejectReason, int>(); } private readonly List<string> _sources = new List<string>(); private readonly Dictionary<string, Dictionary<string, SourceStats>> _areas = new Dictionary<string, Dictionary<string, SourceStats>>(StringComparer.OrdinalIgnoreCase); public IReadOnlyList<string> Sources => _sources; public IReadOnlyList<string> Areas { get { List<string> list = new List<string>(_areas.Keys); list.Sort(StringComparer.OrdinalIgnoreCase); return list; } } public void RegisterSource(string sourceId) { string text = (sourceId ?? "").Trim(); if (text.Length == 0) { return; } for (int i = 0; i < _sources.Count; i++) { if (string.Equals(_sources[i], text, StringComparison.OrdinalIgnoreCase)) { return; } } _sources.Add(text); } public void Candidate(string areaKey, string sourceId, RejectReason reason, float distance, float ratio) { SourceStats sourceStats = Stats(areaKey, sourceId); sourceStats.Offered++; if (reason == RejectReason.None) { sourceStats.Accepted++; } else { sourceStats.Rejects[reason] = ((!sourceStats.Rejects.TryGetValue(reason, out var value)) ? 1 : (value + 1)); } if (distance >= 0f) { sourceStats.DistanceSum += distance; sourceStats.DistanceCount++; } if (ratio >= 0f) { sourceStats.RatioSum += ratio; sourceStats.RatioCount++; } } public void WaveUsed(string areaKey, string sourceId) { Stats(areaKey, sourceId).WavesUsed++; } public void SourceEmpty(string areaKey, string sourceId) { SourceStats sourceStats = Stats(areaKey, sourceId); sourceStats.Rejects[RejectReason.SourceEmpty] = ((!sourceStats.Rejects.TryGetValue(RejectReason.SourceEmpty, out var value)) ? 1 : (value + 1)); } public void Reset() { _areas.Clear(); } public string Format(string areaKey) { string text = (areaKey ?? "").Trim(); if (text.Length == 0) { text = "(unknown)"; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("area ").Append(text).Append(" — anchor sources") .AppendLine(); if (!_areas.TryGetValue(text, out Dictionary<string, SourceStats> value)) { stringBuilder.Append(" (no candidates recorded here yet)"); return stringBuilder.ToString(); } stringBuilder.Append(" ").Append("source".PadRight(16)).Append("offered".PadLeft(8)) .Append("accept".PadLeft(8)) .Append("used".PadLeft(6)) .Append("avgDist".PadLeft(9)) .Append("avgRatio".PadLeft(10)) .Append(" rejects") .AppendLine(); for (int i = 0; i < _sources.Count; i++) { string text2 = _sources[i]; value.TryGetValue(text2, out var value2); if (value2 == null) { value2 = new SourceStats(); } stringBuilder.Append(" ").Append(text2.PadRight(16)).Append(value2.Offered.ToString(CultureInfo.InvariantCulture).PadLeft(8)) .Append(value2.Accepted.ToString(CultureInfo.InvariantCulture).PadLeft(8)) .Append(value2.WavesUsed.ToString(CultureInfo.InvariantCulture).PadLeft(6)) .Append(Avg(value2.DistanceSum, value2.DistanceCount).PadLeft(9)) .Append(Avg(value2.RatioSum, value2.RatioCount).PadLeft(10)) .Append(" ") .Append(FormatRejects(value2.Rejects)) .AppendLine(); } return stringBuilder.ToString().TrimEnd(Array.Empty<char>()); } public string FormatAll() { IReadOnlyList<string> areas = Areas; if (areas.Count == 0) { return "no candidates recorded in any area yet"; } StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < areas.Count; i++) { if (i > 0) { stringBuilder.AppendLine(); } stringBuilder.AppendLine(Format(areas[i])); } return stringBuilder.ToString().TrimEnd(Array.Empty<char>()); } private static string Avg(double sum, int count) { if (count != 0) { return (sum / (double)count).ToString("F1", CultureInfo.InvariantCulture); } return "-"; } private static string FormatRejects(Dictionary<RejectReason, int> rejects) { if (rejects.Count == 0) { return "-"; } List<KeyValuePair<RejectReason, int>> list = new List<KeyValuePair<RejectReason, int>>(rejects); list.Sort(delegate(KeyValuePair<RejectReason, int> a, KeyValuePair<RejectReason, int> b) { int num2 = b.Value.CompareTo(a.Value); return (num2 == 0) ? string.CompareOrdinal(a.Key.ToString(), b.Key.ToString()) : num2; }); StringBuilder stringBuilder = new StringBuilder(); for (int num = 0; num < list.Count; num++) { if (num > 0) { stringBuilder.Append(' '); } stringBuilder.Append(list[num].Key).Append('=').Append(list[num].Value.ToString(CultureInfo.InvariantCulture)); } return stringBuilder.ToString(); } private SourceStats Stats(string areaKey, string sourceId) { string key = (string.IsNullOrWhiteSpace(areaKey) ? "(unknown)" : areaKey.Trim()); string text = (string.IsNullOrWhiteSpace(sourceId) ? "(unknown)" : sourceId.Trim()); RegisterSource(text); if (!_areas.TryGetValue(key, out Dictionary<string, SourceStats> value)) { value = new Dictionary<string, SourceStats>(StringComparer.OrdinalIgnoreCase); _areas[key] = value; } if (!value.TryGetValue(text, out var value2)) { value2 = (value[text] = new SourceStats()); } return value2; } } public static class PlateauRule { public static bool Accept(bool corner0, bool corner1, bool corner2, bool corner3) { return corner0 && corner1 && corner2 && corner3; } public static bool Accept(float d0, float d1, float d2, float d3, float maxSpread) { if (d0 < 0f || d1 < 0f || d2 < 0f || d3 < 0f) { return false; } if (maxSpread <= 0f) { return true; } float num = Math.Min(Math.Min(d0, d1), Math.Min(d2, d3)); return Math.Max(Math.Max(d0, d1), Math.Max(d2, d3)) - num <= maxSpread; } } public enum GateVerdict { Ok, Disabled, NoArea, NotOverworld, TownOrCity } public static class RegionGate { public static readonly int[] OverworldAreaIds = new int[6] { 101, 201, 301, 401, 501, 602 }; public static bool IsOverworldArea(int areaId) { for (int i = 0; i < OverworldAreaIds.Length; i++) { if (OverworldAreaIds[i] == areaId) { return true; } } return false; } public static GateVerdict Evaluate(bool enabled, int? areaId, bool townOrCity) { if (!enabled) { return GateVerdict.Disabled; } if (!areaId.HasValue) { return GateVerdict.NoArea; } if (townOrCity) { return GateVerdict.TownOrCity; } if (!IsOverworldArea(areaId.Value)) { return GateVerdict.NotOverworld; } return GateVerdict.Ok; } } public enum RejectReason { None, OutOfBand, InSight, NoNavSample, NoPath, PathIncomplete, RatioTooHigh, Plateau, TooCloseToMember, SourceEmpty } public enum SpeciesSource { DonorTable, SquadReserve, Nearby } public sealed class SpeciesCandidate { public string Key { get; } public SpeciesSource Source { get; } public bool Blocked { get; } public bool ExpeditionOnly { get; } public bool Warm { get; } public SpeciesCandidate(string key, SpeciesSource source, bool blocked, bool expeditionOnly, bool warm) { Key = key; Source = source; Blocked = blocked; ExpeditionOnly = expeditionOnly; Warm = warm; } public override string ToString() { return string.Format("{0} ({1}{2}", Key, Source, Blocked ? ", blocked" : "") + (ExpeditionOnly ? ", expedition-only" : "") + (Warm ? ", warm" : ", cold") + ")"; } } public static class RosterFilter { public static readonly string[] DefaultBlocklist = new string[16] { "Ghost of Vanasse", "Luke the Pearlescent", "Scarlet Emissary", "Guardian of the Compass", "Josef", "Dorion", "Evangeline", "Calixa's Squire", "Simeon's Squire", "Concealed Knight", "Bonded Beastmaster", "Marsh Guardian", "Royal Manticore", "Hive Lord", "Immaculate", "Wolfgang" }; public static bool IsBlocked(string key, IEnumerable<string>? extraBlocklist) { if (string.IsNullOrWhiteSpace(key)) { return true; } string[] defaultBlocklist = DefaultBlocklist; foreach (string needle in defaultBlocklist) { if (Contains(key, needle)) { return true; } } if (extraBlocklist != null) { foreach (string item in extraBlocklist) { if (!string.IsNullOrWhiteSpace(item) && Contains(key, item.Trim())) { return true; } } } return false; } private static bool Contains(string haystack, string needle) { if (needle.Length > 0) { return haystack.IndexOf(needle, StringComparison.OrdinalIgnoreCase) >= 0; } return false; } public static List<SpeciesCandidate> Build(IEnumerable<(string key, SpeciesSource source)> raw, IEnumerable<string>? extraBlocklist, Func<string, bool> isExpeditionOnly, Func<string, bool> canMintNow) { List<SpeciesCandidate> list = new List<SpeciesCandidate>(); if (raw == null) { return list; } HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); foreach (var item3 in raw) { string item = item3.key; SpeciesSource item2 = item3.source; string text = (item ?? "").Trim(); if (text.Length != 0 && hashSet.Add(text)) { bool flag = IsBlocked(text, extraBlocklist); bool flag2 = !flag && isExpeditionOnly != null && isExpeditionOnly(text); bool warm = !flag && !flag2 && canMintNow != null && canMintNow(text); list.Add(new SpeciesCandidate(text, item2, flag, flag2, warm)); } } return list; } public static List<SpeciesCandidate> Spawnable(IReadOnlyList<SpeciesCandidate>? all, bool warmOnly) { List<SpeciesCandidate> list = new List<SpeciesCandidate>(); if (all == null) { return list; } for (int i = 0; i < all.Count; i++) { SpeciesCandidate speciesCandidate = all[i]; if (!speciesCandidate.Blocked && !speciesCandidate.ExpeditionOnly && (!warmOnly || speciesCandidate.Warm)) { list.Add(speciesCandidate); } } return list; } public static List<SpeciesCandidate> PrewarmTargets(IReadOnlyList<SpeciesCandidate>? all) { List<SpeciesCandidate> list = new List<SpeciesCandidate>(); if (all == null) { return list; } for (int i = 0; i < all.Count; i++) { SpeciesCandidate speciesCandidate = all[i]; if (!speciesCandidate.Blocked && !speciesCandidate.ExpeditionOnly && !speciesCandidate.Warm) { list.Add(speciesCandidate); } } return list; } } public static class SourceOrder { public static List<string> Parse(string? csv, IReadOnlyList<string> known, out List<string> unknown) { unknown = new List<string>(); List<string> list = new List<string>(); if (known == null) { return list; } if (!string.IsNullOrWhiteSpace(csv)) { HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); string[] array = csv.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length == 0) { continue; } string text2 = null; for (int j = 0; j < known.Count; j++) { if (string.Equals(known[j], text, StringComparison.OrdinalIgnoreCase)) { text2 = known[j]; break; } } if (text2 == null) { unknown.Add(text); } else if (hashSet.Add(text2)) { list.Add(text2); } } } if (list.Count == 0) { for (int k = 0; k < known.Count; k++) { list.Add(known[k]); } } return list; } } public static class ThrottlePolicy { public const double Never = double.NegativeInfinity; public static bool Allow(double now, double lastAt, float minGap) { if (!(minGap <= 0f)) { return now - lastAt >= (double)minGap; } return true; } } public static class ToastText { public const string UnknownSpecies = "Something roams nearby."; public static string Wave(string? species, int count) { return Wave(species, count, null); } public static string Wave(string? species, int count, string? bearing) { if (count <= 0) { return ""; } string text = (species ?? "").Trim(); string text2 = (bearing ?? "").Trim(); if (text.Length == 0) { if (text2.Length <= 0) { return "Something roams nearby."; } return "Something roams the wilds to the " + text2 + "."; } if (text2.Length > 0) { if (count != 1) { return $"{count} {Pluralize(text)} roam the wilds to the {text2}."; } return "A " + text + " roams the wilds to the " + text2 + "."; } if (count != 1) { return $"{count} {Pluralize(text)} roam nearby."; } return "A " + text + " roams nearby."; } public static string Pluralize(string name) { if (string.IsNullOrEmpty(name)) { return name; } if (!EndsWithSibilant(name)) { return name + "s"; } return name + "es"; } private static bool EndsWithSibilant(string s) { if (s.EndsWith("ch", StringComparison.OrdinalIgnoreCase) || s.EndsWith("sh", StringComparison.OrdinalIgnoreCase)) { return true; } char c = char.ToLowerInvariant(s[s.Length - 1]); if (c != 's' && c != 'x') { return c == 'z'; } return true; } } public enum WaveRefusal { None, NoSpecies, CapReached, NoAnchor, Disarmed } public enum WaveOrigin { Squad, SameSpecies } public readonly struct WavePlan { private readonly string _faction; private readonly IReadOnlyList<string> _members; public string Faction => _faction ?? ""; public IReadOnlyList<string> Members => _members ?? Array.Empty<string>(); public WaveOrigin Origin { get; } public WaveRefusal Refusal { get; } public bool IsUnset { get { if (_members != null) { if (_members.Count == 0) { return Refusal == WaveRefusal.None; } return false; } return true; } } public int Count => Members.Count; public bool Ok { get { if (Refusal == WaveRefusal.None) { return Members.Count > 0; } return false; } } public string PrimarySpecies { get { if (Members.Count == 0) { return ""; } string result = Members[0]; int num = 0; for (int i = 0; i < Members.Count; i++) { int num2 = 0; for (int j = 0; j < Members.Count; j++) { if (string.Equals(Members[i], Members[j], StringComparison.OrdinalIgnoreCase)) { num2++; } } if (num2 > num) { num = num2; result = Members[i]; } } return result; } } public bool IsMixed { get { for (int i = 1; i < Members.Count; i++) { if (!string.Equals(Members[0], Members[i], StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } } public WavePlan(string faction, IReadOnlyList<string> members, WaveOrigin origin, WaveRefusal refusal) { _faction = faction ?? ""; _members = members ?? Array.Empty<string>(); Origin = origin; Refusal = refusal; } public static WavePlan Refused(WaveRefusal why) { return new WavePlan("", Array.Empty<string>(), WaveOrigin.SameSpecies, why); } public override string ToString() { if (!IsUnset) { if (!Ok) { return $"refused({Refusal})"; } return string.Format("{0}x [{1}] ", Count, string.Join(", ", new List<string>(Members).ToArray())) + $"faction={Faction} via {Origin}"; } return "(no wave composed yet)"; } } public static class WavePlanner { public static WavePlan Compose(IReadOnlyList<SpeciesCandidate> spawnable, IReadOnlyList<string> squadRoster, FactionTable factions, double speciesRoll, double countRoll, int minCount, int maxCount, int ownActive, int maxOwnActive, IReadOnlyList<string> recentSpecies, int recentPenaltyWindow, int availableSpots) { int num = maxOwnActive - ownActive; if (num <= 0) { return WavePlan.Refused(WaveRefusal.CapReached); } if (availableSpots <= 0) { return WavePlan.Refused(WaveRefusal.NoAnchor); } if (spawnable == null || spawnable.Count == 0) { return WavePlan.Refused(WaveRefusal.NoSpecies); } int num2 = RollCount(countRoll, minCount, maxCount); if (num2 > num) { num2 = num; } if (num2 > availableSpots) { num2 = availableSpots; } if (num2 <= 0) { return WavePlan.Refused(WaveRefusal.CapReached); } string faction; List<string> list = SquadCohort(spawnable, squadRoster, factions, speciesRoll, out faction); if (list.Count >= 2) { List<string> list2 = new List<string>(num2); int num3 = IndexFor(speciesRoll, list.Count); for (int i = 0; i < num2; i++) { list2.Add(list[(num3 + i) % list.Count]); } return new WavePlan(faction, list2, WaveOrigin.Squad, WaveRefusal.None); } string text = PickSpecies(spawnable, speciesRoll, recentSpecies, recentPenaltyWindow); if (text.Length == 0) { return WavePlan.Refused(WaveRefusal.NoSpecies); } List<string> list3 = new List<string>(num2); for (int j = 0; j < num2; j++) { list3.Add(text); } return new WavePlan(factions?.FactionOf(text) ?? "", list3, WaveOrigin.SameSpecies, WaveRefusal.None); } public static List<string> SquadCohort(IReadOnlyList<SpeciesCandidate> spawnable, IReadOnlyList<string> squadRoster, FactionTable factions, double roll01, out string faction) { faction = ""; List<string> list = new List<string>(); if (squadRoster == null || squadRoster.Count == 0 || factions == null) { return list; } List<string> list2 = new List<string>(); HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); for (int i = 0; i < squadRoster.Count; i++) { string text = (squadRoster[i] ?? "").Trim(); if (text.Length == 0 || !hashSet.Add(text)) { continue; } for (int j = 0; j < spawnable.Count; j++) { if (string.Equals(spawnable[j].Key, text, StringComparison.OrdinalIgnoreCase)) { list2.Add(spawnable[j].Key); break; } } } if (list2.Count < 2) { return list; } string species = list2[IndexFor(roll01, list2.Count)]; string text2 = factions.FactionOf(species); if (string.IsNullOrEmpty(text2)) { return list; } for (int k = 0; k < list2.Count; k++) { if (string.Equals(factions.FactionOf(list2[k]), text2, StringComparison.OrdinalIgnoreCase)) { list.Add(list2[k]); } } if (list.Count < 2) { list.Clear(); return list; } faction = text2; return list; } public static int RollCount(double roll01, int minCount, int maxCount) { if (minCount < 0) { minCount = 0; } if (maxCount < minCount) { maxCount = minCount; } int count = maxCount - minCount + 1; return minCount + IndexFor(roll01, count); } private static int IndexFor(double roll01, int count) { if (count <= 0) { return 0; } if (roll01 < 0.0) { roll01 = 0.0; } if (roll01 > 1.0) { roll01 = 1.0; } int num = (int)(roll01 * (double)count); if (num < count) { return num; } return count - 1; } private static string PickSpecies(IReadOnlyList<SpeciesCandidate> spawnable, double roll01, IReadOnlyList<string> recentSpecies, int recentPenaltyWindow) { List<SpeciesCandidate> list = new List<SpeciesCandidate>(spawnable.Count); if (recentSpecies != null && recentPenaltyWindow > 0) { int num = Math.Min(recentPenaltyWindow, recentSpecies.Count); for (int i = 0; i < spawnable.Count; i++) { bool flag = false; for (int j = 0; j < num; j++) { if (string.Equals(recentSpecies[j], spawnable[i].Key, StringComparison.OrdinalIgnoreCase)) { flag = true; break; } } if (!flag) { list.Add(spawnable[i]); } } } IReadOnlyList<SpeciesCandidate> readOnlyList; if (list.Count <= 0) { readOnlyList = spawnable; } else { IReadOnlyList<SpeciesCandidate> readOnlyList2 = list; readOnlyList = readOnlyList2; } IReadOnlyList<SpeciesCandidate> readOnlyList3 = readOnlyList; if (readOnlyList3.Count != 0) { return readOnlyList3[IndexFor(roll01, readOnlyList3.Count)].Key; } return ""; } } }
plugins/DangerousRoads.dll
Decompiled 9 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.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using CompanionKit; using CompanionKit.Core; using DangerousRoads.Anchors; using DangerousRoads.Core; using DangerousRoads.Placement; using ForgeKit; using HarmonyLib; using SpawnKit; using SpawnKit.Core; using UnityEngine; using UnityEngine.AI; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("DangerousRoads")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.1.0")] [assembly: AssemblyInformationalVersion("0.1.1+fb85873cdf7f439cbcb58ddab6d8ef85c9144594")] [assembly: AssemblyProduct("DangerousRoads")] [assembly: AssemblyTitle("DangerousRoads")] [assembly: AssemblyMetadata("BuildStamp", "fb85873 2026-08-02")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.1.0")] [module: UnverifiableCode] namespace DangerousRoads { internal enum DirectorState { Disabled, Idle, Warming, Armed, WaveInFlight, Cooldown } internal sealed class AmbushDirector { private readonly Plugin _host; private readonly RegionWatch _region = new RegionWatch(); private readonly SpeciesRoster _roster = new SpeciesRoster(); private readonly Prewarmer _prewarmer = new Prewarmer(); private readonly AnchorRegistry _anchors = new AnchorRegistry(); private readonly WaveRunner _wave; private readonly List<string> _recentSpecies = new List<string>(); private ClockState _clock = ClockState.Disarmed; private DirectorState _state = DirectorState.Idle; private readonly BlockCounters _blocks = new BlockCounters(); private double _combatDeferSince = double.NegativeInfinity; private double _nextRegionPollAt; private double _nextLedgerDumpAt; internal RegionWatch Region => _region; internal SpeciesRoster Roster => _roster; internal Prewarmer Warmer => _prewarmer; internal AnchorRegistry Anchors => _anchors; internal WaveRunner Wave => _wave; internal DirectorState State => _state; internal ClockState Clock => _clock; internal string LastBlock => _blocks.Last; internal BlockCounters Blocks => _blocks; internal AmbushDirector(Plugin host) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Expected O, but got Unknown _host = host; _wave = new WaveRunner(_anchors); } internal void OnRegionReady(Character player, string sceneName) { //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) _region.Poll(DrConfig.Enabled.Value); _anchors.OnSceneChanged(sceneName); Toasts.ResetThrottle(); _recentSpecies.Clear(); _blocks.Reset(); _combatDeferSince = double.NegativeInfinity; CompassBlips.ClearAll(); if (!_region.IsOverworld) { Disarm($"not overworld ({_region.LastVerdict})"); ManualLogSource log = Plugin.Log; if (log != null) { log.LogMessage((object)("[AMBUSH] " + _region.Describe() + " — idle.")); } return; } _roster.Rebuild(player, sceneName); _prewarmer.Reset(_roster.PrewarmTargets(), DrConfig.PrewarmCount.Value); _clock = AmbushClock.Arm((double)Time.time, DrConfig.FirstArmDelaySeconds.Value); _state = DirectorState.Armed; ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogMessage((object)("[AMBUSH] armed in " + _region.Describe() + "; first wave in " + $"~{DrConfig.FirstArmDelaySeconds.Value:F0}s.")); } } internal void Tick() { //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) double num = Time.unscaledTime; if (PhotonNetwork.isNonMasterClientInRoom) { if (_state != DirectorState.Disabled) { Disarm("guest (master-only)"); } return; } if (!DrConfig.Enabled.Value) { if (_state != DirectorState.Disabled) { Disarm("[General] Enabled=false"); } return; } if (_state == DirectorState.Disabled) { _state = DirectorState.Idle; } if (num >= _nextRegionPollAt) { _nextRegionPollAt = num + 0.5; if (_region.Poll(DrConfig.Enabled.Value) && !_region.IsOverworld) { Disarm($"left the overworld ({_region.LastVerdict})"); } } _prewarmer.Tick(num); AutoDumpLedger(num); if (_region.IsOverworld && !_wave.Running && _state != DirectorState.Idle && AmbushClock.IsDue(_clock, (double)Time.time)) { TryWave(); } } private void TryWave() { Character localPlayer = Plugin.LocalPlayer; string text = SoftBlock(localPlayer); if (text != null) { SoftRetry(text); return; } List<SpeciesCandidate> list = _roster.SpawnableNow(); if (list.Count == 0) { SoftRetry("no warm species"); return; } _state = DirectorState.WaveInFlight; ((MonoBehaviour)_host).StartCoroutine(_wave.Run(localPlayer, _region.LedgerKey, list, _recentSpecies, Spawner.Active("dangerousroads").Count, 0, null, delegate(WaveOutcome outcome) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) if (outcome == WaveOutcome.Placed) { WavePlan lastPlan = _wave.LastPlan; RememberSpecies(((WavePlan)(ref lastPlan)).PrimarySpecies); _clock = AmbushClock.AfterWave((double)Time.time, DrConfig.CooldownAfterWaveSeconds.Value, AmbushClock.NextDelay((double)Random.value, DrConfig.MinIntervalSeconds.Value, DrConfig.MaxIntervalSeconds.Value)); _state = DirectorState.Cooldown; } else { SoftRetry(outcome.ToString()); } if (_state == DirectorState.Cooldown) { _state = DirectorState.Armed; } })); } private string SoftBlock(Character player) { if ((Object)(object)player == (Object)null) { return "no local player"; } if (!player.Alive) { return "player is dead"; } NetworkLevelLoader instance = NetworkLevelLoader.Instance; if ((Object)(object)instance != (Object)null && !instance.IsOverallLoadingDone) { return "level still loading"; } if ((Object)(object)AISquadManager.Instance == (Object)null) { return "no AISquadManager"; } if (Spawner.IsExpeditionRunning) { return "expedition in flight"; } bool inCombat = player.InCombat; _combatDeferSince = CombatDefer.Advance(inCombat, _combatDeferSince, (double)Time.unscaledTime); if (CombatDefer.ShouldHold(DrConfig.SkipWhileInCombat.Value, inCombat, _combatDeferSince, (double)Time.unscaledTime, DrConfig.CombatDeferMaxSeconds.Value)) { return "player in combat"; } if (Spawner.Active("dangerousroads").Count >= DrConfig.MaxOwnActive.Value) { return "own cap reached"; } return null; } private void SoftRetry(string why) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) _blocks.Record(why); _clock = AmbushClock.Retry((double)Time.time, DrConfig.RetrySeconds.Value); _state = DirectorState.Armed; bool flag = _blocks.CountOf(why) == 1; if (flag || DrConfig.LogVerbose.Value) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogMessage((object)("[AMBUSH] soft block: " + why + " — retrying in " + $"{DrConfig.RetrySeconds.Value:F0}s." + ((flag && !DrConfig.LogVerbose.Value) ? " (first of this kind here; further ones need [Diag] LogVerbose)" : ""))); } } } private void RememberSpecies(string key) { _recentSpecies.Insert(0, key); while (_recentSpecies.Count > 8) { _recentSpecies.RemoveAt(_recentSpecies.Count - 1); } } private void AutoDumpLedger(double unscaled) { float value = DrConfig.LedgerAutoDumpSeconds.Value; if (value <= 0f) { return; } if (_nextLedgerDumpAt == 0.0) { _nextLedgerDumpAt = unscaled + (double)value; } else if (!(unscaled < _nextLedgerDumpAt)) { _nextLedgerDumpAt = unscaled + (double)value; ManualLogSource log = Plugin.Log; if (log != null) { log.LogMessage((object)("[LEDGER] auto-dump\n" + _anchors.Ledger.FormatAll())); } } } internal void Disarm(string why) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) _clock = ClockState.Disarmed; _state = DirectorState.Disabled; _blocks.Record(why); } internal void ArmNow() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) _clock = AmbushClock.Arm((double)Time.time, 0f); _state = DirectorState.Armed; _combatDeferSince = double.NegativeInfinity; } internal void ForceWave(Character player, int count, string species) { if (_wave.Running) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)"[AMBUSH] a wave is already running."); } return; } _state = DirectorState.WaveInFlight; ((MonoBehaviour)_host).StartCoroutine(_wave.Run(player, _region.LedgerKey, _roster.SpawnableNow(), _recentSpecies, Spawner.Active("dangerousroads").Count, (count <= 0) ? 1 : count, species, delegate { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) _state = DirectorState.Armed; if (_wave.LastOutcome == WaveOutcome.Placed) { WavePlan lastPlan = _wave.LastPlan; RememberSpecies(((WavePlan)(ref lastPlan)).PrimarySpecies); } })); } internal float SecondsUntilDue() { if (!_clock.Armed) { return -1f; } return Mathf.Max(0f, (float)(_clock.DueAt - (double)Time.time)); } } [HarmonyPatch(typeof(CharacterUI), "Awake")] internal static class CharacterUI_Awake_Blips { private static void Postfix(CharacterUI __instance) { try { CompassBlips.Attach(__instance); } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("[ANCHOR] could not attach compass blips: " + ex.Message)); } } } } internal static class UiDump { internal static string Dump(int maxDepth) { //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) StringBuilder stringBuilder = new StringBuilder(); CharacterUI val = Plugin.LocalPlayer?.CharacterUI; if ((Object)(object)val == (Object)null) { return "no CharacterUI (no local player yet?)"; } UICompass componentInChildren = ((Component)val).GetComponentInChildren<UICompass>(true); stringBuilder.AppendLine("CharacterUI '" + ((Object)val).name + "' blips: " + CompassBlips.Describe()); if ((Object)(object)componentInChildren != (Object)null) { RectTransform component = ((Component)componentInChildren).GetComponent<RectTransform>(); stringBuilder.AppendLine(" UICompass FOUND at " + CompassBlips.Path(((Component)componentInChildren).transform)); string[] obj = new string[6] { $" active={((Component)componentInChildren).gameObject.activeInHierarchy} ", $"angleWidth={componentInChildren.CompassAngleWidth} ", "rect=", null, null, null }; object obj2; if (!((Object)(object)component != (Object)null)) { obj2 = "?"; } else { Rect rect = component.rect; obj2 = ((object)((Rect)(ref rect)).size/*cast due to .constrained prefix*/).ToString(); } obj[3] = (string)obj2; obj[4] = " "; obj[5] = $"dir={componentInChildren.CompassDir}"; stringBuilder.AppendLine(string.Concat(obj)); } else { stringBuilder.AppendLine(" UICompass NOT FOUND — blips cannot work; hierarchy follows."); } stringBuilder.AppendLine(" --- hierarchy ---"); Walk(((Component)val).transform, 0, maxDepth, stringBuilder); return stringBuilder.ToString().TrimEnd(Array.Empty<char>()); } private static void Walk(Transform t, int depth, int maxDepth, StringBuilder sb) { if (depth > maxDepth) { return; } List<string> list = new List<string>(); Component[] components = ((Component)t).GetComponents<Component>(); foreach (Component val in components) { if ((Object)(object)val != (Object)null && !(val is RectTransform) && !(val is Transform)) { list.Add(((object)val).GetType().Name); } } sb.Append(' ', 2 + depth * 2).Append(((Component)t).gameObject.activeSelf ? "" : "(inactive) ").Append(((Object)t).name); if (list.Count > 0) { sb.Append(" [").Append(string.Join(", ", list.ToArray())).Append(']'); } sb.AppendLine(); for (int j = 0; j < t.childCount; j++) { Walk(t.GetChild(j), depth + 1, maxDepth, sb); } } } internal static class CompassBlips { private sealed class Rig { internal UICompass Compass; internal readonly List<RectTransform> Dots = new List<RectTransform>(); internal Transform Root; } private static readonly Dictionary<CharacterUI, Rig> _rigs = new Dictionary<CharacterUI, Rig>(); private static int _consecutiveErrors; private const int ErrorsBeforeDisable = 10; private static bool _disabled; internal static bool? CompassFound { get; private set; } internal static int LiveBlips { get; private set; } internal static void Attach(CharacterUI ui) { //IL_00ab: Unknown result type (might be due to invalid IL or missing references) PruneDestroyed(); if ((Object)(object)ui == (Object)null || _rigs.ContainsKey(ui)) { return; } UICompass componentInChildren = ((Component)ui).GetComponentInChildren<UICompass>(true); if (!CompassFound.HasValue) { CompassFound = (Object)(object)componentInChildren != (Object)null; if ((Object)(object)componentInChildren == (Object)null) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)"[ANCHOR] no UICompass in this HUD — compass blips are off; the wave toast will name the direction in text instead. Run 'roadsui' to dump the live CharacterUI hierarchy and see what is actually there."); } } else { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogMessage((object)("[ANCHOR] compass found: " + Path(((Component)componentInChildren).transform))); } } } if (!((Object)(object)componentInChildren == (Object)null)) { Transform transform = new GameObject("DR_Blips", new Type[1] { typeof(RectTransform) }).transform; transform.SetParent(((Component)componentInChildren).transform, false); _rigs[ui] = new Rig { Compass = componentInChildren, Root = transform }; } } internal static void Forget(CharacterUI ui) { if ((Object)(object)ui != (Object)null) { _rigs.Remove(ui); } } private static void PruneDestroyed() { List<CharacterUI> list = null; foreach (KeyValuePair<CharacterUI, Rig> rig in _rigs) { if ((Object)(object)rig.Key == (Object)null || rig.Value == null || (Object)(object)rig.Value.Compass == (Object)null || (Object)(object)rig.Value.Root == (Object)null) { (list ?? (list = new List<CharacterUI>())).Add(rig.Key); } } if (list != null) { for (int i = 0; i < list.Count; i++) { _rigs.Remove(list[i]); } ManualLogSource log = Plugin.Log; if (log != null) { log.LogMessage((object)string.Format("{0} dropped {1} destroyed HUD rig(s) (zone load); {2} live.", "[ANCHOR]", list.Count, _rigs.Count)); } } } internal static void ClearAll() { foreach (KeyValuePair<CharacterUI, Rig> rig in _rigs) { HideFrom(rig.Value, 0); } LiveBlips = 0; } internal static void Tick(IReadOnlyList<Vector3> targets) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) if (_disabled || !DrConfig.ShowBlips.Value) { ClearAll(); return; } try { Character localPlayer = Plugin.LocalPlayer; if ((Object)(object)localPlayer == (Object)null) { ClearAll(); return; } Vector3 position = ((Component)localPlayer).transform.position; float value = DrConfig.BlipRangeMeters.Value; Color color = ParseColor(DrConfig.BlipColor.Value); int num = 0; Vector3 localPosition = default(Vector3); float num5 = default(float); foreach (KeyValuePair<CharacterUI, Rig> rig in _rigs) { Rig value2 = rig.Value; if ((Object)(object)value2.Compass == (Object)null || (Object)(object)value2.Root == (Object)null) { continue; } int num2 = 0; int num3 = 0; while (targets != null && num3 < targets.Count) { Vector3 val = targets[num3] - position; val.y = 0f; if (!(((Vector3)(ref val)).sqrMagnitude > value * value) && !(((Vector3)(ref val)).sqrMagnitude < 0.01f)) { Vector3 compassDir = value2.Compass.CompassDir; if (!(((Vector3)(ref compassDir)).sqrMagnitude < 0.001f)) { float num4 = Vector3.Angle(compassDir, val) * Mathf.Sign(Vector3.Dot(Vector3.up, Vector3.Cross(compassDir, val))); if (value2.Compass.IsVisibleOnCompass(num4, ref localPosition, ref num5)) { RectTransform val2 = DotAt(value2, num2, color); if (!((Object)(object)val2 == (Object)null)) { ((Transform)val2).localPosition = localPosition; ((Transform)val2).localScale = Vector3.one * num5; SetShown(val2, shown: true); num2++; } } } } num3++; } HideFrom(value2, num2); if (num2 > num) { num = num2; } } LiveBlips = num; _consecutiveErrors = 0; } catch (Exception ex) { if (++_consecutiveErrors >= 10) { _disabled = true; ManualLogSource log = Plugin.Log; if (log != null) { log.LogError((object)(string.Format("{0} compass blips threw {1}x in a row — ", "[ANCHOR]", 10) + $"disabling them for this session rather than flooding the log: {ex}")); } } else { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogWarning((object)("[ANCHOR] blip tick threw: " + ex.Message)); } } } } private static RectTransform DotAt(Rig rig, int index, Color color) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) while (rig.Dots.Count <= index) { GameObject val = new GameObject($"DR_Blip{rig.Dots.Count}", new Type[2] { typeof(RectTransform), typeof(Image) }); RectTransform component = val.GetComponent<RectTransform>(); ((Transform)component).SetParent(rig.Root, false); component.sizeDelta = new Vector2(10f, 10f); Image component2 = val.GetComponent<Image>(); ((Graphic)component2).raycastTarget = false; rig.Dots.Add(component); } RectTransform val2 = rig.Dots[index]; if ((Object)(object)val2 == (Object)null) { return null; } Image component3 = ((Component)val2).GetComponent<Image>(); if ((Object)(object)component3 != (Object)null && ((Graphic)component3).color != color) { ((Graphic)component3).color = color; } return val2; } private static void HideFrom(Rig rig, int firstUnused) { for (int i = firstUnused; i < rig.Dots.Count; i++) { if ((Object)(object)rig.Dots[i] != (Object)null) { SetShown(rig.Dots[i], shown: false); } } } private static void SetShown(RectTransform dot, bool shown) { if (((Component)dot).gameObject.activeSelf != shown) { ((Component)dot).gameObject.SetActive(shown); } } internal static Color ParseColor(string hex) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) Color result = default(Color); if (!ColorUtility.TryParseHtmlString((hex ?? "").Trim(), ref result)) { return Color.red; } return result; } internal static string Path(Transform t) { string text = ((Object)t).name; Transform parent = t.parent; while ((Object)(object)parent != (Object)null) { text = ((Object)parent).name + "/" + text; parent = parent.parent; } return text; } internal static string Describe() { if (CompassFound.HasValue) { if (CompassFound != false) { if (!_disabled) { return $"{_rigs.Count} HUD(s), {LiveBlips} blip(s) live"; } return "disabled after repeated errors"; } return "NO UICompass in this HUD (text bearing fallback)"; } return "not probed yet"; } } internal static class DrConfig { internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<float> MinIntervalSeconds; internal static ConfigEntry<float> MaxIntervalSeconds; internal static ConfigEntry<float> FirstArmDelaySeconds; internal static ConfigEntry<float> CooldownAfterWaveSeconds; internal static ConfigEntry<float> RetrySeconds; internal static ConfigEntry<int> MinCount; internal static ConfigEntry<int> MaxCount; internal static ConfigEntry<int> MaxOwnActive; internal static ConfigEntry<float> MemberSpacingMeters; internal static ConfigEntry<bool> SkipWhileInCombat; internal static ConfigEntry<float> CombatDeferMaxSeconds; internal static ConfigEntry<float> ClusterRadiusMeters; internal static ConfigEntry<float> MinDistanceMeters; internal static ConfigEntry<float> MaxDistanceMeters; internal static ConfigEntry<float> PathLengthRatioMax; internal static ConfigEntry<bool> RequireOutOfSight; internal static ConfigEntry<bool> SightCheckAllPlayers; internal static ConfigEntry<string> SourceOrder; internal static ConfigEntry<bool> EnableInteractableAnchors; internal static ConfigEntry<int> MaxCandidatesPerWave; internal static ConfigEntry<float> PlateauProbeRadius; internal static ConfigEntry<bool> WarmOnly; internal static ConfigEntry<int> PrewarmCount; internal static ConfigEntry<float> PrewarmIntervalSeconds; internal static ConfigEntry<string> ExtraBlocklist; internal static ConfigEntry<bool> ShowToast; internal static ConfigEntry<float> ToastMinGapSeconds; internal static ConfigEntry<bool> ToastBearing; internal static ConfigEntry<bool> ShowBlips; internal static ConfigEntry<float> BlipRangeMeters; internal static ConfigEntry<string> BlipColor; internal static ConfigEntry<bool> LogVerbose; internal static ConfigEntry<float> LedgerAutoDumpSeconds; internal static ConfigEntry<KeyboardShortcut> ForceWaveKey; internal static void Bind(ConfigFile cfg) { //IL_03f4: Unknown result type (might be due to invalid IL or missing references) Enabled = cfg.Bind<bool>("General", "Enabled", true, "Master kill-switch. Guards: an ambush loop misbehaving in a live session with no way to stop it short of a relaunch. Set false + run 'reloadcfg' to disarm instantly."); MinIntervalSeconds = cfg.Bind<float>("Schedule", "MinIntervalSeconds", 20f, "Shortest gap between ambushes, in seconds of ACTUAL PLAY (the clock runs on Time.time, so it freezes while paused or loading). Guards: banking up an ambush during ten minutes in the inventory and having it fire the instant you unpause."); MaxIntervalSeconds = cfg.Bind<float>("Schedule", "MaxIntervalSeconds", 60f, "Longest gap between ambushes. An inverted band (Min > Max) clamps to Min rather than throwing — a typo makes the mod boring, never crashes a timer tick."); FirstArmDelaySeconds = cfg.Bind<float>("Schedule", "FirstArmDelaySeconds", 60f, "Grace period after entering a region before the first ambush can fire. Guards: getting jumped in the first second after a save load or a zone transition, before you have your bearings or even full control."); CooldownAfterWaveSeconds = cfg.Bind<float>("Schedule", "CooldownAfterWaveSeconds", 45f, "Hard floor after a wave actually places, regardless of what the interval rolls. Guards: two unlucky 20s rolls stacking six creatures onto a player who is still fighting the first three."); RetrySeconds = cfg.Bind<float>("Schedule", "RetrySeconds", 15f, "Delay before re-checking after a SOFT BLOCK (loading, in combat, no warm species, no verified anchor, cap reached). Guards: a blocked director re-evaluating every frame — a log flood plus wasted synchronous NavMesh calls."); MinCount = cfg.Bind<int>("Wave", "MinCount", 1, "Fewest creatures in a wave."); MaxCount = cfg.Bind<int>("Wave", "MaxCount", 3, "Most creatures in a wave. NB the structural invariant is ONE FACTION, not one species — a shared faction is the only thing stopping an 'ambush' from fighting itself. A wave composed from a real vanilla squad's roster CAN be mixed-species (and is filtered to a single faction first); when no faction can be PROVEN it collapses to N of one species, which is same-faction by construction."); MaxOwnActive = cfg.Bind<int>("Wave", "MaxOwnActive", 6, "Cap on creatures this mod may have alive at once. Guards: monopolising SpawnKit's [Spawner] MaxActiveSpawns, which defaults to 8 and is GLOBAL ACROSS ALL CONSUMERS — exceeding it also makes SpawnKit toast the player on every refusal."); MemberSpacingMeters = cfg.Bind<float>("Wave", "MemberSpacingMeters", 4f, "Minimum spacing between members of one wave. Guards: three creatures minted at the same point (telefrag, physics pop, one visible blob instead of a group)."); SkipWhileInCombat = cfg.Bind<bool>("Wave", "SkipWhileInCombat", true, "Hold a wave back while the player is already fighting. Guards: piling an ambush onto a fight that is already going badly. BOUNDED by CombatDeferMaxSeconds — read that description before assuming this is a simple on/off."); CombatDeferMaxSeconds = cfg.Bind<float>("Wave", "CombatDeferMaxSeconds", 120f, "Longest CONTINUOUS stretch SkipWhileInCombat may hold a wave, after which it fires anyway. Guards: the mod switching itself off. Outward's Character.InCombat is 'anything is engaged with me', NOT 'I am swinging' — with a companion pet picking fights it is true almost permanently, which live produced TWO waves in twenty minutes with 'player in combat' blocking every single tick. 0 restores the old unbounded behaviour; that is the bug, not a feature."); ClusterRadiusMeters = cfg.Bind<float>("Wave", "ClusterRadiusMeters", 12f, "How tightly the members of one wave group around the lead spot. Guards: a 'wave' that is really N unrelated encounters — live, three creatures announced as one group landed ~100m apart and only one was ever found. Members are placed on a ring of this radius around the verified lead and each is still fully verified, so a wave SHRINKS rather than scattering when the ground won't take it."); MinDistanceMeters = cfg.Bind<float>("Placement", "MinDistanceMeters", 40f, "Closest a creature may appear. Guards: something materialising in your lap with no approach to notice — which would waste the out-of-sight rule entirely."); MaxDistanceMeters = cfg.Bind<float>("Placement", "MaxDistanceMeters", 200f, "Farthest a creature may appear. THE BAND IS CHOSEN TO OVERLAP VANILLA'S: AISquadManager deploys its own wandering squads at 50-400m, so its hand-placed AISquadSpawnPoints — the best thematic anchors in the game — barely exist below 50m. 40-200m reaches them while staying close enough that an encounter is actually felt; 400m would be more faithful and mostly invisible. NB this mod does its OWN placement and passes an explicit position to SpawnKit, precisely because SpawnKit's own SpawnDistance clamps to 50m and its ring probe is a near-field tool."); PathLengthRatioMax = cfg.Bind<float>("Placement", "PathLengthRatioMax", 1.8f, "Reject a spot whose WALKING distance exceeds this multiple of its straight-line distance. Guards: the gorge case — a spot 40m away needing a 300m detour around a cliff. It passes a plain reachability test and still never produces an ambush."); RequireOutOfSight = cfg.Bind<bool>("Placement", "RequireOutOfSight", true, "Only place where geometry blocks the player's view (vanilla AISquadManager's own rule). Guards: creatures visibly popping into existence, the classic mod tell."); SightCheckAllPlayers = cfg.Bind<bool>("Placement", "SightCheckAllPlayers", true, "In co-op, require the spot to be hidden from EVERY player, as vanilla does. Guards: a spot occluded from the host that a guest is staring straight at."); SourceOrder = cfg.Bind<string>("Placement", "SourceOrder", "liveai,gatherpoints,squadpoints,interactables,procedural", "Anchor sources to try, in order. Guards: needing a rebuild to reorder or disable the chain mid-session — reordering IS the experiment the ledger exists to inform. Unknown names are warned about, never silently dropped; an empty or all-unknown value falls back to the full default chain."); EnableInteractableAnchors = cfg.Bind<bool>("Placement", "EnableInteractableAnchors", true, "Use world interactables (gatherables, chests) as placement anchors. Guards: a half-mapped interactable type poisoning a session — turn it off and re-measure without losing the rest of the chain."); MaxCandidatesPerWave = cfg.Bind<int>("Placement", "MaxCandidatesPerWave", 24, "Ceiling on candidates verified per wave. Guards: a frame hitch — NavMesh.CalculatePath is synchronous, and an unbounded candidate list would run hundreds of them in one frame."); PlateauProbeRadius = cfg.Bind<float>("Placement", "PlateauProbeRadius", 1f, "Half-width of the four-corner ground check around a candidate. Guards: placing on a ledge, boulder top, tent roof or overhang — which NavMesh.SamplePosition actively invites, because it returns the 3D-NEAREST polygon and a roof is often nearest. Bigger = stricter (demands a wider flat patch)."); WarmOnly = cfg.Bind<bool>("Species", "WarmOnly", true, "Only spawn species whose body template is already resident. Guards: THE NEVER-STALL RULE — a cold species costs a 1-6s donor-scene harvest mid-play, and an expedition-only one costs a ~20s party teleport through two loading screens with a save on each leg. Turning this off makes ambushes hitch. Don't."); PrewarmCount = cfg.Bind<int>("Species", "PrewarmCount", 3, "How many of the region's cold species to warm in the background. Guards: an empty warm roster meaning the mod is silent for a whole region."); PrewarmIntervalSeconds = cfg.Bind<float>("Species", "PrewarmIntervalSeconds", 20f, "Gap between background prewarms. Guards: a harvest storm on region entry, and thrashing SpawnKit's 12-entry template LRU so that warming one species evicts the last one."); ExtraBlocklist = cfg.Bind<string>("Species", "ExtraBlocklist", "", "Comma-separated species to never spawn, appended to the built-in list of named story NPCs and set-piece bosses. Matched as a case-insensitive substring. Guards: discovering a bad actor mid-session and needing a rebuild to stop it."); ShowToast = cfg.Bind<bool>("Notify", "ShowToast", true, "Show an on-screen message when a wave arrives."); ToastMinGapSeconds = cfg.Bind<float>("Notify", "ToastMinGapSeconds", 30f, "Minimum gap between toasts. Guards: ForgeKit's Notify.Player has NO rate limit of its own — it pushes straight to the UI and the log on every single call."); ToastBearing = cfg.Bind<bool>("Notify", "ToastBearing", true, "Name the compass direction in the toast ('...from the north-east'). Guards: an announcement the player cannot act on — the live report that prompted this was 'I saw the toast for 3 bandits and only found one'."); ShowBlips = cfg.Bind<bool>("Compass", "ShowBlips", true, "Mark this mod's live spawns on the game's HUD compass. Falls back to the toast bearing alone if no UICompass is found in the HUD — run 'roadsui' to see which."); BlipRangeMeters = cfg.Bind<float>("Compass", "BlipRangeMeters", 250f, "Stop marking a creature past this distance. Guards: a creature walking toward you popping off the compass — keep this comfortably above MaxDistanceMeters."); BlipColor = cfg.Bind<string>("Compass", "BlipColor", "#FF3B30", "Blip colour, #RRGGBB or #RRGGBBAA. An unparseable value falls back to red rather than to an invisible blip."); LogVerbose = cfg.Bind<bool>("Diag", "LogVerbose", false, "Log one line per placement candidate with its full verdict chain. Guards: flooding an ordinary session's log, while keeping the detail one 'reloadcfg' away."); LedgerAutoDumpSeconds = cfg.Bind<float>("Diag", "LedgerAutoDumpSeconds", 300f, "Auto-print the anchor-source measurement table this often (0 = never). Guards: THE SPIKE'S ENTIRE DELIVERABLE evaporating because nobody remembered to type 'roadsledger' during a session. Leave this on until anchor coverage is understood."); ForceWaveKey = cfg.Bind<KeyboardShortcut>("Keys", "ForceWaveKey", new KeyboardShortcut((KeyCode)0, Array.Empty<KeyCode>()), "Force an ambush wave immediately (unbound by default). Keys are a CROSS-MOD resource — pick one ForgeKit.Keybinds doesn't already report as taken."); } internal static string Describe() { return $"[General] Enabled={Enabled.Value} · " + $"[Schedule] interval={MinIntervalSeconds.Value:F0}-{MaxIntervalSeconds.Value:F0}s " + $"firstArm={FirstArmDelaySeconds.Value:F0}s cooldown={CooldownAfterWaveSeconds.Value:F0}s " + $"retry={RetrySeconds.Value:F0}s · " + $"[Wave] count={MinCount.Value}-{MaxCount.Value} maxActive={MaxOwnActive.Value} " + $"cluster={ClusterRadiusMeters.Value:F0}m spacing={MemberSpacingMeters.Value:F0}m " + $"skipInCombat={SkipWhileInCombat.Value}/{CombatDeferMaxSeconds.Value:F0}s · " + $"[Placement] band={MinDistanceMeters.Value:F0}-{MaxDistanceMeters.Value:F0}m " + $"ratioMax={PathLengthRatioMax.Value:F2} outOfSight={RequireOutOfSight.Value} " + $"plateau={PlateauProbeRadius.Value:F1}m budget={MaxCandidatesPerWave.Value} " + "order=" + SourceOrder.Value + " · " + $"[Species] warmOnly={WarmOnly.Value} prewarm={PrewarmCount.Value}/" + $"{PrewarmIntervalSeconds.Value:F0}s blocklist='{ExtraBlocklist.Value}' · " + $"[Diag] verbose={LogVerbose.Value}"; } } internal static class FactionBook { internal static readonly SortedDictionary<string, string> Learned = new SortedDictionary<string, string>(StringComparer.OrdinalIgnoreCase); internal static FactionTable Table { get; private set; } = FactionTable.Parse(""); internal static void Load() { string text = EmbeddedRes.Text(typeof(FactionBook).Assembly, "SpeciesFactions.txt", "[ROSTER]", Plugin.Log); Table = FactionTable.Parse(text); ManualLogSource log = Plugin.Log; if (log != null) { log.LogMessage((object)string.Format("{0} faction table: {1} species.", "[ROSTER]", Table.Count)); } } internal unsafe static void Observe(string species, Factions faction) { string text = (species ?? "").Trim(); if (text.Length == 0) { return; } string text2 = ((object)(*(Factions*)(&faction))/*cast due to .constrained prefix*/).ToString(); string text3 = default(string); if (!Table.Observe(text, text2, ref text3)) { return; } Learned[text] = text2; if (text3 == null) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogMessage((object)("[ROSTER] learned '" + text + "' = " + text2 + " (not in the shipped table).")); } return; } ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogWarning((object)("[ROSTER] '" + text + "' reported " + text2 + " but the table said " + text3 + " — the shipped row is wrong or this species varies by donor. Worth investigating.")); } } internal static int ScanScene() { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Invalid comparison between Unknown and I4 //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) int count = Learned.Count; CharacterManager instance = CharacterManager.Instance; if ((Object)(object)instance != (Object)null && instance.Characters != null) { foreach (Character value in instance.Characters.Values) { if ((Object)(object)value != (Object)null && value.IsAI && (int)value.Faction != 1 && !string.IsNullOrEmpty(value.Name)) { UID uID = value.UID; if (!SpawnUid.IsSpawnUid(((UID)(ref uID)).Value)) { Observe(value.Name, value.Faction); } } } } AISquadManager instance2 = AISquadManager.Instance; if ((Object)(object)instance2 != (Object)null) { ScanSquads(instance2.SquadsInPlay); ScanSquads(instance2.SquadsInReserve); } return Learned.Count - count; } private static void ScanSquads(List<AISquad> squads) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Invalid comparison between Unknown and I4 //IL_005d: Unknown result type (might be due to invalid IL or missing references) if (squads == null) { return; } for (int i = 0; i < squads.Count; i++) { AISquad obj = squads[i]; List<AISquadMember> list = ((obj != null) ? obj.Members : null); if (list == null) { continue; } for (int j = 0; j < list.Count; j++) { AISquadMember obj2 = list[j]; Character val = ((obj2 != null) ? obj2.Character : null); if ((Object)(object)val != (Object)null && !string.IsNullOrEmpty(val.Name) && (int)val.Faction != 1) { Observe(val.Name, val.Faction); } } } } internal static void ObserveSpawn(SpawnHandle handle, string speciesKey) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) Character val = ((handle != null) ? handle.Character : null); if (!((Object)(object)val == (Object)null)) { Observe(string.IsNullOrEmpty(val.Name) ? speciesKey : val.Name, val.Faction); } } } internal static class Ident { internal const string CHANNEL = "DangerousRoads_cmd.txt"; internal const string OWNER_TAG = "dangerousroads"; internal const string T_AMBUSH = "[AMBUSH]"; internal const string T_ANCHOR = "[ANCHOR]"; internal const string T_ROSTER = "[ROSTER]"; internal const string T_LEDGER = "[LEDGER]"; } [BepInPlugin("cobalt.dangerousroads", "DangerousRoads", "0.1.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { public const string GUID = "cobalt.dangerousroads"; public const string NAME = "DangerousRoads"; public const string VERSION = "0.1.1"; internal static ManualLogSource Log; internal static Plugin Instance; private CommandRegistry _commands; private VerbHost _verbs; private CommandChannel _channel; private AmbushDirector _director; private readonly List<Vector3> _blipTargets = new List<Vector3>(); internal static Character LocalPlayer { get { CharacterManager instance = CharacterManager.Instance; if (instance == null) { return null; } return instance.GetFirstLocalCharacter(); } } internal void Awake() { //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Expected O, but got Unknown //IL_00d2: Unknown result type (might be due to invalid IL or missing references) Instance = this; Log = ((BaseUnityPlugin)this).Logger; Log.LogMessage((object)("[DangerousRoads] build " + BuildStamp.Read(((object)this).GetType().Assembly) + " @ " + ((object)this).GetType().Assembly.Location)); if (Notify.Log == null) { Notify.Log = ((BaseUnityPlugin)this).Logger; } DrConfig.Bind(((BaseUnityPlugin)this).Config); Log.LogMessage((object)("[AMBUSH] config: " + DrConfig.Describe())); Keybinds.Claim("DangerousRoads", "force an ambush wave", DrConfig.ForceWaveKey); FactionBook.Load(); _director = new AmbushDirector(this); RegisterVerbs(); _channel = new CommandChannel("DangerousRoads_cmd.txt", Log, _commands, 0.5f, true, true); new Harmony("cobalt.dangerousroads").PatchAll(); SceneManager.sceneLoaded += OnSceneLoaded; Log.LogMessage((object)"[AMBUSH] ready — verbs on BepInEx/config/DangerousRoads_cmd.txt ('help' lists them; 'roadsstatus' is the one to start with)."); } internal void Update() { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) _channel.Tick(); _director.Tick(); if (Time.frameCount % 2 == 0) { TickBlips(); } KeyboardShortcut value = DrConfig.ForceWaveKey.Value; if ((int)((KeyboardShortcut)(ref value)).MainKey != 0) { value = DrConfig.ForceWaveKey.Value; if (((KeyboardShortcut)(ref value)).IsDown()) { _channel.Run("roadsnow"); } } } private void TickBlips() { //IL_004b: Unknown result type (might be due to invalid IL or missing references) _blipTargets.Clear(); IReadOnlyList<SpawnHandle> readOnlyList = Spawner.Active("dangerousroads"); for (int i = 0; i < readOnlyList.Count; i++) { SpawnHandle obj = readOnlyList[i]; Character val = ((obj != null) ? obj.Character : null); if ((Object)(object)val != (Object)null && val.Alive) { _blipTargets.Add(((Component)val).transform.position); } } CompassBlips.Tick(_blipTargets); } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) if ((int)mode == 0 && Scenes.IsGameplay(((Scene)(ref scene)).name)) { string sceneName = ((Scene)(ref scene)).name; ((MonoBehaviour)this).StartCoroutine(Lifecycle.WhenPlayerReady((Func<Character>)(() => LocalPlayer), (Action<Character>)delegate(Character player) { _director.OnRegionReady(player, sceneName); }, (Action<string>)delegate(string why) { Log.LogWarning((object)("[AMBUSH] " + why + " in '" + sceneName + "'.")); }, 30f, (object)this)); } } private void RegisterVerbs() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Expected O, but got Unknown _commands = new CommandRegistry(Log); _verbs = new VerbHost(_commands, Log, (Func<Character>)(() => LocalPlayer)); _verbs.Register("selftest", "Run the DangerousRoads self-test ([SELFTEST] PASS/FAIL ... DONE).", (Action<VerbContext>)delegate { SelfTest(); }, "[DangerousRoads]", false, true, false, (string)null); Verbs.Register(_verbs, _director); CommonVerbs.RegisterAll(_verbs, Log, new CommonVerbsOptions { ConfigSource = () => ((BaseUnityPlugin)this).Config }); } private void SelfTest() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Invalid comparison between Unknown and I4 //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Invalid comparison between Unknown and I4 //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) SelfTestHarness val = new SelfTestHarness(Log); val.Begin("DangerousRoads 0.1.1"); val.Check("logger wired", Log != null); val.Check("config bound", DrConfig.Enabled != null && DrConfig.SourceOrder != null); val.Check("no cross-mod keybind conflicts", !Keybinds.HasConflicts()); val.Check("overworld whitelist has all six regions", RegionGate.OverworldAreaIds.Length == 6); val.Check("Caldera passes, New Sirocco does not", (int)RegionGate.Evaluate(true, (int?)602, false) == 0 && (int)RegionGate.Evaluate(true, (int?)601, false) == 3); val.Check("unknown scene fails closed", (int)RegionGate.Evaluate(true, (int?)null, false) == 2); val.Check("interval band rolls inside [min,max]", AmbushClock.NextDelay(0.5, 20f, 300f) == 160f); val.Check("gorge ratio rejects a 300m walk to a 40m spot", !BandMath.PathRatioOk(300f, 40f, 1.8f)); val.Check("region gate agrees with live AreaManager", _director.Region.LastVerdict == RegionGate.Evaluate(DrConfig.Enabled.Value, _director.Region.LastAreaId, (Object)(object)AreaManager.Instance != (Object)null && AreaManager.Instance.GetIsCurrentAreaTownOrCity())); val.Check("anchor chain resolves to at least one source", _director.Anchors.ActiveOrder().Count > 0); val.Check("every anchor source is registered in the ledger", _director.Anchors.Ledger.Sources.Count == _director.Anchors.KnownSourceIds.Count); val.Done(); } } internal sealed class Prewarmer { private readonly Queue<string> _queue = new Queue<string>(); private readonly HashSet<string> _attempted = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private bool _inFlight; private double _nextAt = double.NegativeInfinity; internal string InFlightKey { get; private set; } = ""; internal int Pending => _queue.Count; internal void Reset(IReadOnlyList<SpeciesCandidate> targets, int max) { _queue.Clear(); _attempted.Clear(); if (targets == null) { return; } int num = Mathf.Min(max, targets.Count); for (int i = 0; i < num; i++) { _queue.Enqueue(targets[i].Key); } if (num > 0) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogMessage((object)string.Format("{0} prewarm queue: {1} species.", "[ROSTER]", num)); } } } internal void Tick(double unscaledNow) { if (_inFlight || _queue.Count == 0) { return; } if (double.IsNegativeInfinity(_nextAt)) { _nextAt = unscaledNow; } if (unscaledNow < _nextAt) { return; } if (Spawner.IsExpeditionRunning) { _nextAt = unscaledNow + 5.0; return; } string key = _queue.Dequeue(); if (!_attempted.Add(key)) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogMessage((object)("[ROSTER] prewarm skipped '" + key + "' — already attempted this region " + $"({_queue.Count} left).")); } return; } if (Spawner.CanMintNow(key)) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogMessage((object)string.Format("{0} prewarm skipped '{1}' — already warm ({2} left).", "[ROSTER]", key, _queue.Count)); } return; } _inFlight = true; InFlightKey = key; ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogMessage((object)string.Format("{0} prewarming '{1}' ({2} left)...", "[ROSTER]", key, _queue.Count)); } Spawner.Prewarm(key, (Action<bool>)delegate(bool ok) { _inFlight = false; InFlightKey = ""; _nextAt = Time.unscaledTime + DrConfig.PrewarmIntervalSeconds.Value; ManualLogSource log4 = Plugin.Log; if (log4 != null) { log4.LogMessage((object)("[ROSTER] prewarm '" + key + "' " + (ok ? "OK" : "FAILED") + " " + $"({_queue.Count} left in queue).")); } }); } internal void RequestNow(string speciesKey) { string text = (speciesKey ?? "").Trim(); if (text.Length != 0) { _attempted.Remove(text); _queue.Enqueue(text); _nextAt = double.NegativeInfinity; } } internal string Describe() { if (!_inFlight) { if (_queue.Count <= 0) { return "idle, queue empty"; } return $"idle, {_queue.Count} queued"; } return $"warming '{InFlightKey}', {_queue.Count} queued"; } } internal sealed class RegionWatch { internal int? LastAreaId { get; private set; } internal GateVerdict LastVerdict { get; private set; } = (GateVerdict)2; internal string LastAreaName { get; private set; } = "(none)"; internal string LedgerKey => LastAreaName; internal bool IsOverworld => (int)LastVerdict == 0; internal bool Poll(bool enabled) { //IL_00b1: Unknown result type (might be due to invalid IL or missing references) int? num = null; string lastAreaName = "(none)"; bool flag = false; AreaManager instance = AreaManager.Instance; if ((Object)(object)instance != (Object)null) { Area currentArea = instance.CurrentArea; if (currentArea != null) { num = currentArea.ID; lastAreaName = (string.IsNullOrEmpty(currentArea.DefaultName) ? $"area#{currentArea.ID}" : currentArea.DefaultName); } flag = instance.GetIsCurrentAreaTownOrCity(); } bool result = num != LastAreaId; LastAreaId = num; LastAreaName = lastAreaName; LastVerdict = RegionGate.Evaluate(enabled, num, flag); return result; } internal string Describe() { //IL_0039: Unknown result type (might be due to invalid IL or missing references) return string.Format("{0} (id={1}) → {2}", LastAreaName, LastAreaId.HasValue ? LastAreaId.Value.ToString() : "none", LastVerdict); } } internal sealed class SpeciesRoster { private List<Character> _scratch = new List<Character>(); internal List<SpeciesCandidate> All { get; private set; } = new List<SpeciesCandidate>(); internal string BuiltForScene { get; private set; } = ""; internal void Rebuild(Character player, string sceneName) { BuiltForScene = sceneName ?? ""; List<(string, SpeciesSource)> list = new List<(string, SpeciesSource)>(); CollectNearby(player, list); CollectFromSquads(list); CollectFromDonorTable(sceneName, list); All = RosterFilter.Build((IEnumerable<ValueTuple<string, SpeciesSource>>)list, (IEnumerable<string>)ExtraBlocklist(), (Func<string, bool>)Spawner.IsExpeditionOnly, (Func<string, bool>)Spawner.CanMintNow); int count = RosterFilter.Spawnable((IReadOnlyList<SpeciesCandidate>)All, DrConfig.WarmOnly.Value).Count; ManualLogSource log = Plugin.Log; if (log != null) { log.LogMessage((object)(string.Format("{0} '{1}': {2} species considered, ", "[ROSTER]", BuiltForScene, All.Count) + $"{count} spawnable now (warmOnly={DrConfig.WarmOnly.Value}).")); } } internal List<SpeciesCandidate> SpawnableNow() { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Expected O, but got Unknown List<SpeciesCandidate> list = new List<SpeciesCandidate>(All.Count); for (int i = 0; i < All.Count; i++) { SpeciesCandidate val = All[i]; if (!val.Blocked && !val.ExpeditionOnly) { bool flag = Spawner.CanMintNow(val.Key); if (!DrConfig.WarmOnly.Value || flag) { list.Add(new SpeciesCandidate(val.Key, val.Source, val.Blocked, val.ExpeditionOnly, flag)); } } } return list; } internal List<SpeciesCandidate> PrewarmTargets() { List<SpeciesCandidate> list = new List<SpeciesCandidate>(); for (int i = 0; i < All.Count; i++) { SpeciesCandidate val = All[i]; if (!val.Blocked && !val.ExpeditionOnly && !Spawner.CanMintNow(val.Key)) { list.Add(val); } } return list; } internal static string[] ExtraBlocklist() { string value = DrConfig.ExtraBlocklist.Value; if (!string.IsNullOrEmpty(value)) { return value.Split(new char[1] { ',' }); } return new string[0]; } private void CollectNearby(Character player, List<(string, SpeciesSource)> into) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) CharacterManager instance = CharacterManager.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)player == (Object)null) { return; } _scratch.Clear(); instance.FindCharactersInRange(((Component)player).transform.position, 200f, ref _scratch); for (int i = 0; i < _scratch.Count; i++) { Character val = _scratch[i]; if (LiveAiSource.IsUsableAnchor(val) && !string.IsNullOrEmpty(val.Name)) { into.Add((val.Name, (SpeciesSource)2)); } } } private static void CollectFromSquads(List<(string, SpeciesSource)> into) { AISquadManager instance = AISquadManager.Instance; if (!((Object)(object)instance == (Object)null)) { AddSquads(instance.SquadsInPlay, into); AddSquads(instance.SquadsInReserve, into); } } private static void AddSquads(List<AISquad> squads, List<(string, SpeciesSource)> into) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Invalid comparison between Unknown and I4 if (squads == null) { return; } for (int i = 0; i < squads.Count; i++) { AISquad obj = squads[i]; List<AISquadMember> list = ((obj != null) ? obj.Members : null); if (list == null) { continue; } for (int j = 0; j < list.Count; j++) { AISquadMember obj2 = list[j]; Character val = ((obj2 != null) ? obj2.Character : null); if ((Object)(object)val != (Object)null && !string.IsNullOrEmpty(val.Name) && (int)val.Faction != 1) { into.Add((val.Name, (SpeciesSource)1)); } } } } private static void CollectFromDonorTable(string sceneName, List<(string, SpeciesSource)> into) { if (string.IsNullOrEmpty(sceneName)) { return; } Dictionary<string, List<string>> donorScenes = DonorHarvest.DonorScenes; if (donorScenes == null) { return; } List<string> list = DonorTable.KeysForScene(donorScenes, sceneName, true); if (list != null) { for (int i = 0; i < list.Count; i++) { into.Add((list[i], (SpeciesSource)0)); } } } } internal static class Toasts { private static double _lastAt = double.NegativeInfinity; internal static void Wave(Character player, string species, int count) { Wave(player, species, count, null); } internal static void Wave(Character player, string species, int count, Vector3? at) { //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) if (!DrConfig.ShowToast.Value || count <= 0) { return; } double num = Time.unscaledTime; if (!ThrottlePolicy.Allow(num, _lastAt, DrConfig.ToastMinGapSeconds.Value)) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogMessage((object)string.Format("{0} toast suppressed by throttle ({1} x{2}).", "[AMBUSH]", species, count)); } return; } _lastAt = num; string text = ""; if (DrConfig.ToastBearing.Value && at.HasValue && (Object)(object)player != (Object)null) { Vector3 val = at.Value - ((Component)player).transform.position; text = Bearing.Label(val.x, val.z, 0.5f); } string text2 = ToastText.Wave(species, count, text); if (text2.Length > 0) { Notify.Player(player, text2); } } internal static void ResetThrottle() { _lastAt = double.NegativeInfinity; } } internal static class Verbs { private const string Tag = "[DangerousRoads]"; internal static void Register(VerbHost verbs, AmbushDirector dir) { verbs.Register("roadsstatus", "Director state: region, gate verdict, time to next wave, roster/warm counts, last wave.", (Action<VerbContext>)delegate { Status(dir); }, "[DangerousRoads]", false, true, false, (string)null); verbs.Register("roadsroster", "Every species considered for this region: source, faction, blocked, expedition-only, warm.", (Action<VerbContext>)delegate { Roster(dir); }, "[DangerousRoads]", false, true, false, (string)null); verbs.Register("roadsfactions", "Harvest species->faction from the loaded scene AND its reserve squads (the whole region roster, no spawning), then print anything new for pasting into SpeciesFactions.txt. 'roadsfactions all' prints the entire merged table.", (Action<VerbContext>)delegate(VerbContext ctx) { Factions(ctx.Arg(1)); }, "[DangerousRoads]", false, true, false, (string)null); verbs.Register("roadsledger", "THE SPIKE TABLE — per-region, per-source candidate counts, accept rates and reject histogram. 'roadsledger reset' clears it.", (Action<VerbContext>)delegate(VerbContext ctx) { Ledger(dir, ctx.Arg(1)); }, "[DangerousRoads]", false, true, false, (string)null); verbs.Register("roadsanchors", "Run the WHOLE anchor chain now and print every candidate with its full verdict chain. Spawns nothing. Optional arg: how many spots to look for (default 3).", (Action<VerbContext>)delegate(VerbContext ctx) { Anchors(dir, ctx.Player, ctx.Arg(1)); }, "[DangerousRoads]", true, true, false, (string)null); verbs.Register("roadssquadpoints", "Raw census of vanilla AISquadSpawnPoints in this scene — position, distance, typeID, CheckValidSpawn, member species. Independent of our own filters.", (Action<VerbContext>)delegate(VerbContext ctx) { SquadPoints(dir, ctx.Player); }, "[DangerousRoads]", true, true, false, (string)null); verbs.Register("roadsui", "Dump the live CharacterUI hierarchy and report whether a UICompass was found — the recon that decides whether compass blips can work at all. 'roadsui <depth>' to go deeper (default 6).", (Action<VerbContext>)delegate(VerbContext ctx) { Log(UiDump.Dump(ParseInt(ctx.Arg(1), 6))); }, "[DangerousRoads]", true, true, false, (string)null); verbs.Register("roadsblips", "Compass blip status: whether a UICompass was found, how many HUDs are hooked, and how many blips are live right now.", (Action<VerbContext>)delegate { Log("[ANCHOR] blips: " + CompassBlips.Describe()); }, "[DangerousRoads]", false, true, false, (string)null); verbs.Register("roadsprobe", "Run the verification pipeline on one point and print every stage. 'roadsprobe' = where you stand; 'roadsprobe <x> <y> <z>' = an explicit spot.", (Action<VerbContext>)delegate(VerbContext ctx) { Probe(dir, ctx.Player, ctx); }, "[DangerousRoads]", true, true, false, (string)null); verbs.Register("roadsmark", "Pick the best anchor right now, log it and draw a marker ray — walk over and judge the terrain yourself. Spawns nothing.", (Action<VerbContext>)delegate(VerbContext ctx) { Mark(dir, ctx.Player); }, "[DangerousRoads]", true, true, false, (string)null); verbs.Register("roadsnow", "Force a wave immediately, through the real pipeline. 'roadsnow [count] [species...]'.", (Action<VerbContext>)delegate(VerbContext ctx) { Now(dir, ctx); }, "[DangerousRoads]", true, true, true, (string)null); verbs.Register("roadsarm", "Arm the director so the next wave is due immediately.", (Action<VerbContext>)delegate { dir.ArmNow(); Log("armed; next wave due now."); }, "[DangerousRoads]", false, true, false, (string)null); verbs.Register("roadsdisarm", "Stop the ambush timer until the next region change.", (Action<VerbContext>)delegate { dir.Disarm("roadsdisarm"); Log("disarmed."); }, "[DangerousRoads]", false, true, false, (string)null); verbs.Register("roadswarm", "Queue a species for background prewarm now ('roadswarm <species>').", (Action<VerbContext>)delegate(VerbContext ctx) { Warm(dir, ctx.Tail(1)); }, "[DangerousRoads]", false, true, true, (string)null); verbs.Register("roadssweep", "Despawn every creature THIS mod spawned. 'roadssweep kill' kills them instead (death animation + loot). The panic button.", (Action<VerbContext>)delegate(VerbContext ctx) { Sweep(ctx.Arg(1)); }, "[DangerousRoads]", false, true, true, (string)null); } private static void Status(AmbushDirector dir) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("[AMBUSH] status"); stringBuilder.AppendLine($" state {dir.State}"); stringBuilder.AppendLine(" region " + dir.Region.Describe()); stringBuilder.AppendLine($" clock {dir.Clock} (due in {Fmt(dir.SecondsUntilDue())})"); stringBuilder.AppendLine(" last block " + dir.LastBlock); stringBuilder.AppendLine($" blocks {dir.Blocks.Total} total — {dir.Blocks.Format()}"); int count = dir.Roster.SpawnableNow().Count; stringBuilder.AppendLine($" roster {dir.Roster.All.Count} considered, {count} spawnable now " + "(scene '" + dir.Roster.BuiltForScene + "')"); stringBuilder.AppendLine(" prewarm " + dir.Warmer.Describe()); stringBuilder.AppendLine(string.Format(" active {0} / ", Spawner.Active("dangerousroads").Count) + $"{DrConfig.MaxOwnActive.Value} (SpawnKit's global cap is shared)"); stringBuilder.AppendLine($" last wave {dir.Wave.LastOutcome} — placed {dir.Wave.LastPlaced} of " + string.Format("{0} via {1} ", dir.Wave.LastPlan, Or(dir.Wave.LastAnchorSource, "-")) + "@ " + Fmt(dir.Wave.LastAnchorDistance)); stringBuilder.AppendLine(" blips " + CompassBlips.Describe()); stringBuilder.AppendLine($" factions {FactionBook.Table.Count} known" + ((FactionBook.Learned.Count > 0) ? $", {FactionBook.Learned.Count} learned this session (roadsfactions to see them)" : "")); stringBuilder.AppendLine(" chain " + string.Join(" > ", dir.Anchors.ActiveOrder().ToArray())); stringBuilder.Append(" config " + DrConfig.Describe()); Log(stringBuilder.ToString()); } private static void Roster(AmbushDirector dir) { //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) IReadOnlyList<SpeciesCandidate> all = dir.Roster.All; if (all.Count == 0) { Log("[ROSTER] roster is empty (not in an overworld region?)."); return; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine(string.Format("{0} {1} species for '{2}'", "[ROSTER]", all.Count, dir.Roster.BuiltForScene)); for (int i = 0; i < all.Count; i++) { SpeciesCandidate val = all[i]; bool flag = !val.Blocked && !val.ExpeditionOnly && Spawner.CanMintNow(val.Key); string text = FactionBook.Table.FactionOf(val.Key) ?? "?"; stringBuilder.AppendLine(" " + val.Key.PadRight(26) + " " + ((object)val.Source/*cast due to .constrained prefix*/).ToString().PadRight(13) + text.PadRight(16) + (val.Blocked ? " BLOCKED" : "") + (val.ExpeditionOnly ? " EXPEDITION-ONLY" : "") + ((val.Blocked || val.ExpeditionOnly) ? "" : (flag ? " warm" : " cold"))); } Log(stringBuilder.ToString().TrimEnd(Array.Empty<char>())); } private static void Factions(string arg) { int num = FactionBook.ScanScene(); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine(string.Format("{0} faction scan: {1} new species this call; ", "[ROSTER]", num) + $"{FactionBook.Table.Count} known, {FactionBook.Learned.Count} learned at runtime."); if (string.Equals(arg, "all", StringComparison.OrdinalIgnoreCase)) { stringBuilder.AppendLine(" --- full merged table (paste into src/DangerousRoads/SpeciesFactions.txt) ---"); foreach (string item in FactionBook.Table.ToLines()) { stringBuilder.AppendLine(" " + item); } } else if (FactionBook.Learned.Count > 0) { stringBuilder.AppendLine(" --- new/changed rows (paste into src/DangerousRoads/SpeciesFactions.txt) ---"); foreach (KeyValuePair<string, string> item2 in FactionBook.Learned) { stringBuilder.AppendLine(" " + item2.Key + "=" + item2.Value); } } else { stringBuilder.Append(" nothing new — the shipped table already covers everything in this scene."); } Log(stringBuilder.ToString().TrimEnd(Array.Empty<char>())); } private static void Ledger(AmbushDirector dir, string arg) { if (string.Equals(arg, "reset", StringComparison.OrdinalIgnoreCase)) { dir.Anchors.Ledger.Reset(); Log("[LEDGER] reset."); } else { Log("[LEDGER]\n" + dir.Anchors.Ledger.FormatAll()); } } private static void Anchors(AmbushDirector dir, Character player, string countArg) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) int num = ParseInt(countArg, 3); List<VerifiedSpot> list = new List<VerifiedSpot>(); List<Vector3> viewerCenters = WaveRunner.ViewerCenters(player); List<VerifiedSpot> list2 = dir.Anchors.FindSpots(((Component)player).transform.position, viewerCenters, num, dir.Region.LedgerKey, list); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine(string.Format("{0} probe: {1} candidate(s) examined, ", "[ANCHOR]", list.Count) + $"{list2.Count} accepted (wanted {num})"); stringBuilder.AppendLine(" chain " + string.Join(" > ", dir.Anchors.ActiveOrder().ToArray())); for (int i = 0; i < list.Count; i++) { stringBuilder.AppendLine($" {list[i]}"); } if (list.Count == 0) { stringBuilder.AppendLine(" (no source offered anything in band — check roadssquadpoints and the ledger)"); } Log(stringBuilder.ToString().TrimEnd(Array.Empty<char>())); } private static void SquadPoints(AmbushDirector dir, Character player) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) if (!(dir.Anchors.Get("squadpoints") is SquadPointSource squadPointSource)) { Log("squadpoints source is not registered."); return; } Vector3 position = ((Component)player).transform.position; AISquadSpawnPoint[] points = squadPointSource.Points; StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine(string.Format("{0} squad spawn point census — {1} in scene ", "[ANCHOR]", points.Length) + "(found via " + squadPointSource.FoundVia + ")"); stringBuilder.AppendLine(" NB vanilla deploys these in a 50-400m band; ours is " + $"{DrConfig.MinDistanceMeters.Value:F0}-{DrConfig.MaxDistanceMeters.Value:F0}m. " + "The overlap is the whole question."); int num = 0; for (int i = 0; i < points.Length; i++) { AISquadSpawnPoint val = points[i]; if (!((Object)(object)val == (Object)null)) { float num2 = AnchorUtil.FlatDistance(position, ((Component)val).transform.position); bool flag = num2 >= DrConfig.MinDistanceMeters.Value && num2 <= DrConfig.MaxDistanceMeters.Value; if (flag) { num++; } stringBuilder.AppendLine(string.Format(" #{0,-3} d={1,7:F1}m {2} ", i, num2, flag ? "IN-BAND" : " ") + $"typeID={val.SquadSpawnTypeID,-3} valid={SquadPointSource.SafeCheckValid(val),-5} " + "species=[" + string.Join(", ", SquadPointSource.SpeciesAt(val).ToArray()) + "]"); } } stringBuilder.Append($" → {num} of {points.Length} in band from here."); Log(stringBuilder.ToString()); } private static void Probe(AmbushDirector dir, Character player, VerbContext ctx) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_0182: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Expected O, but got Unknown //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_023b: Unknown result type (might be due to invalid IL or missing references) Vector3 position = ((Component)player).transform.position; if (ctx.Arg(3) != null && float.TryParse(ctx.Arg(1), NumberStyles.Float, CultureInfo.InvariantCulture, out var result) && float.TryParse(ctx.Arg(2), NumberStyles.Float, CultureInfo.InvariantCulture, out var result2) && float.TryParse(ctx.Arg(3), NumberStyles.Float, CultureInfo.InvariantCulture, out var result3)) { ((Vector3)(ref position))..ctor(result, result2, result3); } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine(string.Format("{0} probe at {1}", "[ANCHOR]", position)); Vector3 onMesh; bool flag = NavProbe.SnapToWalkable(position, out onMesh); stringBuilder.AppendLine(" 1 snap-to-walkable " + (flag ? $"OK -> {onMesh} (moved {Vector3.Distance(position, onMesh):F2}m)" : "FAIL (no walkable navmesh in range)")); if (!flag) { Log(stringBuilder.ToString().TrimEnd(Array.Empty<char>())); return; } float num = AnchorUtil.FlatDistance(((Component)player).transform.position, onMesh); stringBuilder.AppendLine($" 2 band {num:F1}m — " + (BandMath.InBand(num, DrConfig.MinDistanceMeters.Value, DrConfig.MaxDistanceMeters.Value) ? "in" : "OUT OF") + " band"); bool flag2 = NavProbe.IsOccludedFrom(player.CenterPosition, onMesh); stringBuilder.AppendLine(" 3 out of sight " + (flag2 ? "OK (occluded)" : "FAIL (player can see it)")); NavMeshPath val = new NavMeshPath(); bool flag3 = NavProbe.CanWalk(((Component)player).transform.position, onMesh, val); float num2 = NavProbe.PathLength(val); stringBuilder.AppendLine(string.Format(" 4 reachable {0} pathLen={1:F1}m", flag3 ? "OK" : $"FAIL ({val.status})", num2)); float num3 = BandMath.Ratio(num2, num); stringBuilder.AppendLine($" 5 path ratio {num3:F2} (max {DrConfig.PathLengthRatioMax.Value:F2}) — " + (BandMath.PathRatioOk(num2, num, DrConfig.PathLengthRatioMax.Value) ? "OK" : "FAIL (detour / wrong side of a cliff)")); NavProbe.GroundCorners(onMesh, DrConfig.PlateauProbeRadius.Value, out var d, out var d2, out var d3, out var d4); stringBuilder.Append($" 6 plateau corners=[{d:F2} {d2:F2} {d3:F2} {d4:F2}] (-1 = void) — " + (PlateauRule.Accept(d, d2, d3, d4, 0f) ? "OK" : "FAIL (ledge/rock/roof)")); Log(stringBuilder.ToString()); } private static void Mark(AmbushDirector dir, Character player) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) List<Vector3> viewerCenters = WaveRunner.ViewerCenters(player); List<VerifiedSpot> list = dir.Anchors.FindSpots(((Component)player).transform.position, viewerCenters, 1, dir.Region.LedgerKey); if (list.Count == 0) { Log("[ANCHOR] mark: no verified anchor from here."); return; } VerifiedSpot verifiedSpot = list[0]; Vector3 val = verifiedSpot.Position - ((Component)player).transform.position; float num = Mathf.Atan2(val.x, val.z) * 57.29578f; if (num < 0f) { num += 360f; } Debug.DrawRay(verifiedSpot.Position, Vector3.up * 8f, Color.red, 60f); Log(string.Format("{0} mark: {1}\n", "[ANCHOR]", verifiedSpot) + $" at {verifiedSpot.Position} — bearing {num:F0}deg, {verifiedSpot.Distance:F0}m. " + "A red ray marks it for 60s; walk over and judge the ground."); } private static void Now(AmbushDirector dir, VerbContext ctx) { int num = ParseInt(ctx.Arg(1), 0); string text = ((num > 0) ? ctx.Tail(2) : ctx.Tail(1)); if (num <= 0) { num = 1; } dir.ForceWave(ctx.Player, num, string.IsNullOrEmpty(text) ? null : text.Trim()); } private static void Warm(AmbushDirector dir, string species) { if (string.IsNullOrEmpty(species)) { Log("usage: roadswarm <species>"); return; } dir.Warmer.RequestNow(species.Trim()); Log("[ROSTER] queued '" + species.Trim() + "' for prewarm."); } private static void Sweep(string arg) { bool flag = string.Equals(arg, "kill", StringComparison.OrdinalIgnoreCase); int count = Spawner.Active("dangerousroads").Count; Spawner.DespawnAll("dangerousroads", flag); Log(string.Format("{0} swept {1} spawn(s){2}.", "[AMBUSH]", count, flag ? " (killed)" : "")); } private static void Log(string message) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogMessage((object)message); } } private static int ParseInt(string s, int fallback) { if (!int.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return fallback; } return result; } private static string Fmt(float seconds) { if (!(seconds < 0f)) { return $"{seconds:F0}s"; } return "-"; } private static string Or(string s, string fallback) { if (!string.IsNullOrEmpty(s)) { return s; } return fallback; } } internal enum WaveOutcome { Pending, Placed, NoAnchor, NoSpecies, SpawnRefused, Aborted } internal sealed class WaveRunner { private readonly AnchorRegistry _anchors; internal WaveOutcome LastOutcome { get; private set; } internal WavePlan LastPlan { get; private set; } internal int LastPlaced { get; private set; } internal string LastAnchorSource { get; private set; } = ""; internal float LastAnchorDistance { get; private set; } = -1f; internal bool Running { get; private set; } internal string LastSpecies { get { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) WavePlan lastPlan = LastPlan; if (!((WavePlan)(ref lastPlan)).Ok) { return ""; } lastPlan = LastPlan; return ((WavePlan)(ref lastPlan)).PrimarySpecies; } } internal WaveRunner(AnchorRegistry anchors) { _anchors = anchors; } internal IEnumerator Run(Character player, string areaKey, IReadOnlyList<SpeciesCandidate> spawnable, IReadOnlyList<string> recentSpecies, int ownActive, int forcedCount, string forcedSpecies, Action<WaveOutcome> onDone) { Running = true; LastOutcome = WaveOutcome.Pending; LastPlaced = 0; LastAnchorSource = ""; LastAnchorDistance = -1f; try { if ((Object)(object)player == (Object)null) { Finish(WaveOutcome.Aborted, onDone); yield break; } int want = Mathf.Max(1, (forcedCount > 0) ? forcedCount : DrConfig.MaxCount.Value); List<Vector3> viewerCenters = ViewerCenters(player); List<VerifiedSpot> spots = _anchors.FindCluster(((Component)player).transform.position, viewerCenters, want, areaKey); yield return null; if (spots.Count == 0) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogMessage((object)("[AMBUSH] no verified anchor in " + areaKey + " — skipping.")); } Finish(WaveOutcome.NoAnchor, onDone); yield break; } LastAnchorSource = spots[0].SourceId; LastAnchorDistance = spots[0].Distance; _anchors.Ledger.WaveUsed(areaKey, spots[0].SourceId); WavePlan plan = (LastPlan = BuildPlan(spawnable, spots, recentSpecies, ownActive, forcedCount, forcedSpecies)); if (!((WavePlan)(ref plan)).Ok) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogMessage((object)string.Format("{0} composition refused: {1}.", "[AMBUSH]", ((WavePlan)(ref plan)).Refusal)); } Finish(((int)((WavePlan)(ref plan)).Refusal == 1) ? WaveOutcome.NoSpecies : WaveOutcome.SpawnRefused, onDone); yield break; } int n = Mathf.Min(((WavePlan)(ref plan)).Count, spots.Count); ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogMessage((object)(string.Format("{0} wave {1} in {2} via {3} ", "[AMBUSH]", plan, areaKey, spots[0].SourceId) + $"@ {spots[0].Distance:F0}m (ratio {spots[0].Ratio:F2}).")); } int placed = 0; int resolved = 0; for (int i = 0; i < n; i++) { VerifiedSpot verifiedSpot = spots[i]; string species = ((WavePlan)(ref plan)).Members[i]; Vector3 val2 = ((Component)player).transform.position - verifiedSpot.Position; val2.y = 0f; SpawnHandle val3 = Spawner.Spawn(species, new SpawnOptions { OwnerTag = "dangerousroads", Position = verifiedSpot.Position, Rotation = ((((Vector3)(ref val2)).sqrMagnitude > 0.001f) ? Quaternion.LookRotation(((Vector3)(ref val2)).normalized) : Quaternion.identity) }, (Action<SpawnHandle>)delegate(SpawnHandle handle) { //IL_005e: Unknown result type (might be due to invalid IL or missing references) int num2 = resolved; resolved = num2 + 1; if (handle.IsAlive) { num2 = placed; placed = num2 + 1; FactionBook.ObserveSpawn(handle, species); } else { ManualLogSource log5 = Plugin.Log; if (log5 != null) { log5.LogMessage((object)string.Format("{0} member refused: {1}.", "[AMBUSH]", handle.FailReason)); } } }); if (val3 == null) { int num = resolved; resolved = num + 1; } } float deadline = Time.unscaledTime + 20f; while (resolved < n && Time.unscaledTime < deadline) { yield return null; } LastPlaced = placed; if (placed > 0) { Toasts.Wave(player, ((WavePlan)(ref plan)).PrimarySpecies, placed, spots[0].Position); Finish(WaveOutcome.Placed, onDone); } else { ManualLogSource log4 = Plugin.Log; if (log4 != null) { log4.LogWarning((object)string.Format("{0} wave placed nothing ({1}).", "[AMBUSH]", plan)); } Finish(WaveOutcome.SpawnRefused, onDone); } plan = default(WavePlan); } finally { Running = false; } } private static WavePlan BuildPlan(IReadOnlyList<SpeciesCandidate> spawnable, List<VerifiedSpot> spots, IReadOnlyList<string> recentSpecies, int ownActive, int forcedCount, string forcedSpecies) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) if (!string.IsNullOrEmpty(forcedSpecies)) { int num = Mathf.Max(1, forcedCount); List<string> list = new List<string>(num); for (int i = 0; i < num; i++) { list.Add(forcedSpecies); } return new WavePlan(FactionBook.Table.FactionOf(forcedSpecies) ?? "", (IReadOnlyList<string>)list, (WaveOrigin)1, (WaveRefusal)0); } IReadOnlyList<string> readOnlyList = null; for (int j = 0; j < spots.Count; j++) { if (spots[j].ThematicSpecies != null && spots[j].ThematicSpecies.Count > 0) { readOnlyList = spots[j].ThematicSpecies; break; } } int num2 = ((forcedCount > 0) ? forcedCount : DrConfig.MinCount.Value); int num3 = ((forcedCount > 0) ? forcedCount : DrConfig.MaxCount.Value); return WavePlanner.Compose(spawnable, readOnlyList, FactionBook.Table, (double)Random.value, (double)Random.value, num2, num3, ownActive, DrConfig.MaxOwnActive.Value, recentSpecies, 3, spots.Count); } private void Finish(WaveOutcome outcome, Action<WaveOutcome> onDone) { LastOutcome = outcome; onDone?.Invoke(outcome); } internal static List<Vector3> ViewerCenters(Character player) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) List<Vector3> list = new List<Vector3>(); if ((Object)(object)player != (Object)null) { list.Add(player.CenterPosition); } if (!DrConfig.SightCheckAllPlayers.Value) { return list; } CharacterManager instance = CharacterManager.Instance; if ((Object)(object)instance == (Object)null || instance.Characters == null) { return list; } foreach (Character value in instance.Characters.Values) { if (!((Object)(object)value == (Object)null) && !((Object)(object)value == (Object)(object)player) && !value.IsAI && value.Alive) { list.Add(value.CenterPosition); } } return list; } } } namespace DangerousRoads.Placement { internal sealed class VerifiedSpot { internal Vector3 Position; internal string SourceId; internal string Label; internal RejectReason Reason; internal float Distance = -1f; internal float PathLength = -1f; internal float Ratio = -1f; internal float Score; internal IReadOnlyList<string> ThematicSpecies; internal bool Ok => (int)Reason == 0; public override string ToString() { //IL_005a: Unknown result type (might be due to invalid IL or missing references) return $"{SourceId} {Label} d={Distance:F1} path={PathLength:F1} ratio={Ratio:F2} " + (Ok ? $"ACCEPT score={Score:F2}" : $"reject={Reason}"); } } internal sealed class CandidateVerifier { private readonly NavMeshPath _path = new NavMeshPath(); internal VerifiedSpot Verify(AnchorCandidate cand, Vector3 playerPos, IReadOnlyList<Vector3> viewerCenters, IReadOnlyList<Vector3> accepted) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) VerifiedSpot verifiedSpot = new VerifiedSpot { Position = cand.Position, SourceId = cand.SourceId, Label = cand.Label, ThematicSpecies = cand.ThematicSpecies, Reason = (RejectReason)0 }; float value = DrConfig.MinDistanceMeters.Value; float value2 = DrConfig.MaxDistanceMeters.Value; if (!NavProbe.SnapToWalkable(cand.Position, out var onMesh)) { return Fail(verifiedSpot, (RejectReason)3); } verifiedSpot.Position = onMesh; verifiedSpot.Distance = AnchorUtil.FlatDistance(playerPos, onMesh); if (!BandMath.InBand(verifiedSpot.Distance, value, value2)) { return Fail(verifiedSpot, (RejectReason)1); } float value3 = DrConfig.MemberSpacingMeters.Value; if (accepted != null && value3 > 0f) { for (int i = 0; i < accepted.Count; i++) { if (Vector3.Distance(accepted[i], onMesh) < value3) { return Fail(verifiedSpot, (RejectReason)8); } } } if (DrConfig.RequireOutOfSight.Value && viewerCenters != null) { for (int j = 0; j < viewerCenters.Count; j++) { if (!NavProbe.IsOccludedFrom(viewerCenters[j], onMesh)) { return Fail(verifiedSpot, (RejectReason)2); } } } if (!NavMesh.CalculatePath(playerPos, onMesh, -1, _path)) { return Fail(verifiedSpot, (RejectReason)4); } if ((int)_path.status != 0) { return Fail(verifiedSpot, (RejectReason)5); } verifiedSpot.PathLength = NavProbe.PathLength(_path); verifiedSpot.Ratio = BandMath.Ratio(verifiedSpot.PathLength, verifiedSpot.Distance); if (!BandMath.PathRatioOk(verifiedSpot.PathLength, verifiedSpot.Distance, DrConfig.PathLengthRatioMax.Value)) { return Fail(verifiedSpot, (RejectReason)6); } NavProbe.GroundCorners(onMesh, DrConfig.PlateauProbeRadius.Value, out var d, out var d2, out var d3, out var d4); if (!PlateauRule.Accept(d, d2, d3, d4, 0f)) { return Fail(verifiedSpot, (RejectReason)7); } verifiedSpot.Score = BandMath.Score(verifiedSpot.Distance, verifiedSpot.Ratio, value, value2); return verifiedSpot; } private static VerifiedSpot Fail(VerifiedSpot v, RejectReason why) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) v.Reason = why; return v; } } internal static class NavProbe { internal const int WalkableMask = 1; internal const int AllAreasMask = -1; internal const float SampleRadius = 10f; internal const float GroundProbeLength = 1.6f; internal static bool SnapToWalkable(Vector3 raw, out Vector3 onMesh) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) NavMeshHit val = default(NavMeshHit); if (NavMesh.SamplePosition(raw, ref val, 10f, 1)) { onMesh = ((NavMeshHit)(ref val)).position; return true; } onMesh = raw; return false; } internal static bool CanWalk(Vector3 from, Vector3 to, NavMeshPath path) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Invalid comparison between Unknown and I4 if (NavMesh.CalculatePath(from, to, -1, path)) { return (int)path.status == 0; } return false; } internal static float PathLength(NavMeshPath path) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) if (path == null || path.corners == null || path.corners.Length < 2) { return -1f; } float num = 0f; Vector3[] corners = path.corners; for (int i = 1; i < corners.Length; i++) { num += Vector3.Distance(corners[i - 1], corners[i]); } return num; } internal static bool IsOccludedFrom(Vector3 viewerCenter, Vector3 candidate) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) Vector3 val = viewerCenter + Vector3.up * 2.5f; Vector3 val2 = candidate + Vector3.up * 3f; Vector3 val3 = val2 - val; float magnitude = ((Vector3)(ref val3)).magnitude; if (magnitude < 0.01f) { return false; } return Physics.SphereCast(new Ray(val, val3 / magnitude), 1f, magnitude, Global.LargeEnvironmentMask); } internal static float GroundDistanceAt(Vector3 corner) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) Vector3 val = corner + Vector3.up; RaycastHit val2 = default(RaycastHit); if (!Physics.Raycast(val, Vector3.down, ref val2, 1.6f, Global.LargeEnvironmentMask)) { return -1f; } return ((RaycastHit)(ref val2)).distance; } internal static void GroundCorners(Vector3 at, float radius, out float d0, out float d1, out float d2, out float d3) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) d0 = GroundDistanceAt(at + new Vector3(0f - radius, 0f, 0f - radius)); d1 = GroundDistanceAt(at + new Vector3(0f - radius, 0f, radius)); d2 = GroundDistanceAt(at + new Vector3(radius, 0f, 0f - radius)); d3 = GroundDistanceAt(at + new Vector3(radius, 0f, radius)); } } } namespace DangerousRoads.Anchors { internal sealed class AnchorRegistry { private readonly Dictionary<string, IAnchorSource> _byId = new Dictionary<string, IAnchorSource>(StringComparer.OrdinalIgnoreCase); private readonly List<string> _known = new List<string>(); private readonly CandidateVerifier _verifier = new CandidateVerifier(); private readonly List<AnchorCandidate> _raw = new List<AnchorCandidate>(); internal MeasureLedger Ledger { get; } = new MeasureLedger(); internal IReadOnlyList<string> KnownSourceIds => _known; internal AnchorRegistry() { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown Add(new LiveAiSource()); Add(new GatherPointSource()); Add(new SquadPointSource()); Add(new InteractableSource()); Add(new ProceduralSource()); } private void Add(IAnchorSource source) { _byId[source.Id] = source; _known.Add(source.Id); Ledger.RegisterSource(source.Id); } internal IAnchorSource Get(string id) { if (!_byId.TryGetValue(id ?? "", out var value)) { return null; } return value; } internal void OnSceneChanged(string sceneName) { for (int i = 0; i < _known.Count; i++) { _byId[_known[i]].OnSceneChanged(sceneName); } } internal List<string> ActiveOrder() { List<string> list = default(List<string>); List<string> result = SourceOrder.Parse(DrConfig.SourceOrder.Value, (IReadOnlyList<string>)_known, ref list); if (list.Count > 0) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("[ANCHOR] [Placement] SourceOrder names unknown source(s): " + string.Join(", ", list.ToArray()) + ". Known: " + string.Join(", ", _known.ToArray()) + ".")); } } return result; } internal List<VerifiedSpot> FindSpots(Vector3 playerPos, IReadOnlyList<Vector3> viewerCenters, int want, string areaKey, List<VerifiedSpot> auditInto = null) { //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) List<VerifiedSpot> list = new List<VerifiedSpot>(); List<Vector3> list2 = new List<Vector3>(); if (want <= 0) { return list; } int num = Mathf.Max(1, DrConfig.MaxCandidatesPerWave.Value); float value = DrConfig.MinDistanceMeters.Value; float value2 = DrConfig.MaxDistanceMeters.Value; bool value3 = DrConfig.LogVerbose.Value; List<string> list3 = ActiveOrder(); for (int i = 0; i < list3.Count; i++) { if (list.Count >= want) { break; } if (num <= 0) { break; } IAnchorSource anchorSource = Get(list3[i]); if (anchorSource == null) { continue; } _raw.Clear(); if (anchorSource.Collect(playerPos, value, value2, num, _raw) == 0) { Ledger.SourceEmpty(areaKey, anchorSource.Id); if (value3) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogMessage((object)("[ANCHOR] " + anchorSource.Id + ": no candidates in band.")); } } continue; } for (int j = 0; j < _raw.Count; j++) { if (list.Count >= want) { break; } if (num <= 0) { break; } num--; VerifiedSpot verifiedSpot = _verifier.Verify(_raw[j], playerPos, viewerCenters, list2); Ledger.Candidate(areaKey, anchorSource.Id, verifiedSpot.Reason, verifiedSpot.Distance, verifiedSpot.Ratio); auditInto?.Add(verifiedSpot); if (value3) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogMessage((object)string.Format("{0} {1}", "[ANCHOR]", verifiedSpot)); } } if (verifiedSpot.Ok) { list.Add(verifiedSpot); list2.Add(verifiedSpot.Position); } } } list.Sort((VerifiedSpot a, VerifiedSpot b) => b.Score.CompareTo(a.Score)); return list; } internal List<VerifiedSpot> FindCluster(Vector3 playerPos, IReadOnlyList<Vector3> viewerCenters, int want, string areaKey, List<VerifiedSpot> auditInto = null) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or mi