Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of MedkitScatter v0.0.1
MedkitScatter.Planning.dll
Decompiled 10 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; 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("AitServices")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("Medkit Scatter")] [assembly: AssemblyTitle("MedkitScatter.Planning")] [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 MedkitScatter.Planning { public interface IRandomSource { int Next(int minInclusive, int maxExclusive); double NextDouble(); } public readonly struct MedkitPlacement { public RoomKey Room { get; } public MedkitTier Tier { get; } public MedkitPlacement(RoomKey room, MedkitTier tier) { Room = room; Tier = tier; } public override string ToString() { return Room.ToString() + " " + Tier; } } public static class MedkitPlanner { private readonly struct Weights { public double Small { get; } public double Medium { get; } public double Large { get; } public double Total => Small + Medium + Large; public Weights(double small, double medium, double large) { Small = small; Medium = medium; Large = large; } } public const int MaxSupportedMedkits = 128; public static PlanResult Plan(PlanInput? input, IRandomSource? rng) { PlanInput planInput = input ?? new PlanInput(); IRandomSource rng2 = rng ?? new SystemRandomSource(0); List<string> list = new List<string>(); IReadOnlyList<RoomDescriptor> readOnlyList = planInput.Rooms ?? Array.Empty<RoomDescriptor>(); int num = 0; for (int i = 0; i < readOnlyList.Count; i++) { if (readOnlyList[i].CountsToTotal) { num++; } } List<RoomDescriptor> list2 = new List<RoomDescriptor>(); HashSet<RoomKey> hashSet = new HashSet<RoomKey>(); foreach (RoomDescriptor item in from r in readOnlyList where r.CanHostMedkit && r.CandidateSpots > 0 orderby r.Key select r) { if (hashSet.Add(item.Key)) { list2.Add(item); } else { list.Add("duplicate RoomKey " + item.Key.ToString() + " ignored"); } } int num2 = Math.Max(1, planInput.RoomsPerMedkit); double num3 = (double)num / (double)num2; int num4 = planInput.Rounding switch { RoundingMode.Ceil => (int)Math.Ceiling(num3), RoundingMode.Round => (int)Math.Round(num3, MidpointRounding.AwayFromZero), _ => (int)Math.Floor(num3), }; int num5 = Clamp(planInput.MinMedkits, 0, 128); int val = ((planInput.MaxMedkits == 0) ? int.MaxValue : planInput.MaxMedkits); val = Math.Max(num5, val); int num6 = Clamp(num4, num5, val); List<RoomDescriptor> list3 = OrderRooms(list2, rng2, planInput.PreferDistantRooms); int num7 = Math.Min(num6, list3.Count); List<MedkitPlacement> list4 = new List<MedkitPlacement>(num7); List<RoomKey> list5 = new List<RoomKey>(Math.Max(0, list3.Count - num7)); Weights weights = EffectiveWeights(planInput, list); List<MedkitTier> list6 = new List<MedkitTier>(num7); if (planInput.GuaranteeVariety && num7 >= 3) { List<MedkitTier> list7 = new List<MedkitTier> { MedkitTier.Small, MedkitTier.Medium, MedkitTier.Large }; Shuffle(list7, rng2); list6.AddRange(list7); } while (list6.Count < num7) { list6.Add(WeightedPick(weights, rng2)); } for (int num8 = 0; num8 < list3.Count; num8++) { if (num8 < num7) { list4.Add(new MedkitPlacement(list3[num8].Key, list6[num8])); } else { list5.Add(list3[num8].Key); } } string explanation = BuildExplanation(planInput, num, num2, num3, num4, num5, planInput.MaxMedkits, num6, list2.Count, num7, list5.Count, weights, list); return new PlanResult(num, num6, list4, list5, explanation, list); } private static Weights EffectiveWeights(PlanInput cfg, List<string> notes) { double num = Math.Max(0.0, cfg.WeightSmall); double num2 = Math.Max(0.0, cfg.WeightMedium); double num3 = Math.Max(0.0, cfg.WeightLarge); if (cfg.DifficultyScaling) { int num4 = Math.Max(0, cfg.LevelsCompleted); double num5 = 1.0 + cfg.DifficultyFactor * (double)num4; if (double.IsNaN(num5) || double.IsInfinity(num5) || num5 <= 0.0) { notes.Add("difficulty factor " + Fmt(num5) + " invalid, using 1.0"); num5 = 1.0; } num3 *= num5; num /= num5; } if (num + num2 + num3 <= 0.0) { notes.Add("all tier weights <= 0, degraded to Small-only"); return new Weights(1.0, 0.0, 0.0); } return new Weights(num, num2, num3); } private static MedkitTier WeightedPick(Weights weights, IRandomSource rng) { double total = weights.Total; if (total <= 0.0) { return MedkitTier.Small; } double num = rng.NextDouble(); if (double.IsNaN(num) || num < 0.0) { num = 0.0; } else if (num >= 1.0) { num = 0.999999999; } double num2 = num * total; if (num2 < weights.Small) { return MedkitTier.Small; } if (num2 < weights.Small + weights.Medium) { return MedkitTier.Medium; } return MedkitTier.Large; } private static List<RoomDescriptor> OrderRooms(List<RoomDescriptor> sortedByKey, IRandomSource rng, bool preferDistant) { List<RoomDescriptor> list = new List<RoomDescriptor>(sortedByKey); if (!preferDistant) { Shuffle(list, rng); return list; } List<RoomDescriptor> list2 = new List<RoomDescriptor>(list); List<double> list3 = new List<double>(list2.Count); foreach (RoomDescriptor item in list2) { list3.Add(1.0 + (double)Math.Max(0, item.DistanceFromStart)); } List<RoomDescriptor> list4 = new List<RoomDescriptor>(list2.Count); while (list2.Count > 0) { double num = 0.0; for (int i = 0; i < list3.Count; i++) { num += list3[i]; } double num2 = rng.NextDouble(); if (double.IsNaN(num2) || num2 < 0.0) { num2 = 0.0; } else if (num2 >= 1.0) { num2 = 0.999999999; } double num3 = num2 * num; int index = list2.Count - 1; double num4 = 0.0; for (int j = 0; j < list2.Count; j++) { num4 += list3[j]; if (num3 < num4) { index = j; break; } } list4.Add(list2[index]); list2.RemoveAt(index); list3.RemoveAt(index); } return list4; } private static void Shuffle<T>(IList<T> list, IRandomSource rng) { for (int num = list.Count - 1; num > 0; num--) { int num2 = rng.Next(0, num + 1); if (num2 < 0 || num2 > num) { num2 = 0; } T value = list[num]; list[num] = list[num2]; list[num2] = value; } } private static int Clamp(int value, int min, int max) { if (value < min) { return min; } if (value <= max) { return value; } return max; } private static string Fmt(double value) { return value.ToString("0.##", CultureInfo.InvariantCulture); } private static string BuildExplanation(PlanInput cfg, int roomCount, int divisor, double raw, int rounded, int effMin, int rawMax, int targetCount, int eligibleCount, int plannedCount, int reserveCount, Weights weights, List<string> notes) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("RoomCount(counted)=").Append(roomCount).Append(" -> ") .Append(roomCount) .Append(" / ") .Append(divisor) .Append(" = ") .Append(raw.ToString("0.00", CultureInfo.InvariantCulture)) .Append(" -> ") .Append(cfg.Rounding) .Append(" -> ") .Append(rounded) .Append(" -> clamp[") .Append(effMin) .Append("..") .Append((rawMax == 0) ? "inf" : rawMax.ToString(CultureInfo.InvariantCulture)) .Append("] -> TargetCount=") .Append(targetCount); stringBuilder.Append(" | eligible=").Append(eligibleCount).Append(" -> PlannedCount=") .Append(plannedCount) .Append(", Reserve=") .Append(reserveCount); stringBuilder.Append(" | weights S/M/L=").Append(Fmt(weights.Small)).Append('/') .Append(Fmt(weights.Medium)) .Append('/') .Append(Fmt(weights.Large)) .Append(" variety=") .Append(cfg.GuaranteeVariety ? "on" : "off") .Append(" distant=") .Append(cfg.PreferDistantRooms ? "on" : "off"); if (notes.Count > 0) { stringBuilder.Append(" | notes: ").Append(string.Join("; ", notes.ToArray())); } return stringBuilder.ToString(); } } public enum MedkitTier { Small, Medium, Large } public sealed class PlanInput { public IReadOnlyList<RoomDescriptor> Rooms { get; set; } = Array.Empty<RoomDescriptor>(); public int RoomsPerMedkit { get; set; } = 4; public RoundingMode Rounding { get; set; } public int MinMedkits { get; set; } = 1; public int MaxMedkits { get; set; } public int WeightSmall { get; set; } = 60; public int WeightMedium { get; set; } = 30; public int WeightLarge { get; set; } = 10; public bool GuaranteeVariety { get; set; } = true; public bool PreferDistantRooms { get; set; } public bool DifficultyScaling { get; set; } public double DifficultyFactor { get; set; } = 0.15; public int LevelsCompleted { get; set; } } public sealed class PlanResult { public int RoomCount { get; } public int TargetCount { get; } public int PlannedCount => Placements.Count; public IReadOnlyList<MedkitPlacement> Placements { get; } public IReadOnlyList<RoomKey> Reserve { get; } public string Explanation { get; } public IReadOnlyList<string> Notes { get; } public PlanResult(int roomCount, int targetCount, IReadOnlyList<MedkitPlacement>? placements, IReadOnlyList<RoomKey>? reserve, string? explanation, IReadOnlyList<string>? notes) { RoomCount = roomCount; TargetCount = targetCount; Placements = placements ?? Array.Empty<MedkitPlacement>(); Reserve = reserve ?? Array.Empty<RoomKey>(); Explanation = explanation ?? string.Empty; Notes = notes ?? Array.Empty<string>(); } } public readonly struct RoomDescriptor { public RoomKey Key { get; } public RoomKind Kind { get; } public bool CountsToTotal { get; } public bool CanHostMedkit { get; } public int CandidateSpots { get; } public int DistanceFromStart { get; } public RoomDescriptor(RoomKey key, RoomKind kind, bool countsToTotal, bool canHostMedkit, int candidateSpots, int distanceFromStart) { Key = key; Kind = kind; CountsToTotal = countsToTotal; CanHostMedkit = canHostMedkit; CandidateSpots = candidateSpots; DistanceFromStart = distanceFromStart; } public override string ToString() { return Key.ToString() + " " + Kind.ToString() + " spots=" + CandidateSpots + " dist=" + DistanceFromStart; } } public readonly struct RoomKey : IEquatable<RoomKey>, IComparable<RoomKey> { public int GridX { get; } public int GridY { get; } public RoomKey(int gridX, int gridY) { GridX = gridX; GridY = gridY; } public int CompareTo(RoomKey other) { int num = GridY.CompareTo(other.GridY); if (num == 0) { return GridX.CompareTo(other.GridX); } return num; } public bool Equals(RoomKey other) { if (GridX == other.GridX) { return GridY == other.GridY; } return false; } public override bool Equals(object? obj) { if (obj is RoomKey other) { return Equals(other); } return false; } public override int GetHashCode() { return (GridX * 397) ^ GridY; } public static bool operator ==(RoomKey left, RoomKey right) { return left.Equals(right); } public static bool operator !=(RoomKey left, RoomKey right) { return !left.Equals(right); } public static bool operator <(RoomKey left, RoomKey right) { return left.CompareTo(right) < 0; } public static bool operator >(RoomKey left, RoomKey right) { return left.CompareTo(right) > 0; } public static bool operator <=(RoomKey left, RoomKey right) { return left.CompareTo(right) <= 0; } public static bool operator >=(RoomKey left, RoomKey right) { return left.CompareTo(right) >= 0; } public override string ToString() { return "(" + GridX + "," + GridY + ")"; } } public enum RoomKind { Normal, Passage, DeadEnd, Extraction, StartRoom, Unknown, Special } public enum RoundingMode { Floor, Round, Ceil } public sealed class SystemRandomSource : IRandomSource { private readonly Random _random; public int Seed { get; } public SystemRandomSource(int seed) { Seed = seed; _random = new Random(seed); } public int Next(int minInclusive, int maxExclusive) { if (maxExclusive <= minInclusive) { return minInclusive; } return _random.Next(minInclusive, maxExclusive); } public double NextDouble() { return _random.NextDouble(); } } }
MedkitScatter.dll
Decompiled 10 hours agousing 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 HarmonyLib; using MedkitScatter.Patches; using MedkitScatter.Planning; using MedkitScatter.Runtime; using MedkitScatter.Util; using Microsoft.CodeAnalysis; using Photon.Pun; using Photon.Realtime; using UnityEngine; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("Assembly-CSharp-firstpass")] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: IgnoresAccessChecksTo("Autodesk.Fbx")] [assembly: IgnoresAccessChecksTo("Discord.Sdk")] [assembly: IgnoresAccessChecksTo("Domain_Reload")] [assembly: IgnoresAccessChecksTo("Facepunch.Steamworks.Win64")] [assembly: IgnoresAccessChecksTo("FbxBuildTestAssets")] [assembly: IgnoresAccessChecksTo("Klattersynth")] [assembly: IgnoresAccessChecksTo("Photon3Unity3D")] [assembly: IgnoresAccessChecksTo("PhotonChat")] [assembly: IgnoresAccessChecksTo("PhotonRealtime")] [assembly: IgnoresAccessChecksTo("PhotonUnityNetworking")] [assembly: IgnoresAccessChecksTo("PhotonUnityNetworking.Utilities")] [assembly: IgnoresAccessChecksTo("PhotonVoice.API")] [assembly: IgnoresAccessChecksTo("PhotonVoice")] [assembly: IgnoresAccessChecksTo("PhotonVoice.PUN")] [assembly: IgnoresAccessChecksTo("SingularityGroup.HotReload.Runtime.Public")] [assembly: IgnoresAccessChecksTo("Sirenix.OdinInspector.Attributes")] [assembly: IgnoresAccessChecksTo("Sirenix.OdinInspector.Modules.Unity.Addressables")] [assembly: IgnoresAccessChecksTo("Sirenix.OdinInspector.Modules.UnityLocalization")] [assembly: IgnoresAccessChecksTo("Sirenix.Serialization.Config")] [assembly: IgnoresAccessChecksTo("Sirenix.Serialization")] [assembly: IgnoresAccessChecksTo("Sirenix.Utilities")] [assembly: IgnoresAccessChecksTo("Unity.Addressables")] [assembly: IgnoresAccessChecksTo("Unity.AI.Navigation")] [assembly: IgnoresAccessChecksTo("Unity.Burst")] [assembly: IgnoresAccessChecksTo("Unity.Burst.Unsafe")] [assembly: IgnoresAccessChecksTo("Unity.Collections")] [assembly: IgnoresAccessChecksTo("Unity.Collections.LowLevel.ILSupport")] [assembly: IgnoresAccessChecksTo("Unity.Formats.Fbx.Runtime")] [assembly: IgnoresAccessChecksTo("Unity.InputSystem")] [assembly: IgnoresAccessChecksTo("Unity.InputSystem.ForUI")] [assembly: IgnoresAccessChecksTo("Unity.InternalAPIEngineBridge.013")] [assembly: IgnoresAccessChecksTo("Unity.Localization")] [assembly: IgnoresAccessChecksTo("Unity.Mathematics")] [assembly: IgnoresAccessChecksTo("Unity.MemoryProfiler")] [assembly: IgnoresAccessChecksTo("Unity.Postprocessing.Runtime")] [assembly: IgnoresAccessChecksTo("Unity.Profiling.Core")] [assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.Core.Runtime")] [assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.Core.ShaderLibrary")] [assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.ShaderGraph.ShaderGraphLibrary")] [assembly: IgnoresAccessChecksTo("Unity.ResourceManager")] [assembly: IgnoresAccessChecksTo("Unity.ScriptableBuildPipeline")] [assembly: IgnoresAccessChecksTo("Unity.Splines")] [assembly: IgnoresAccessChecksTo("Unity.TextMeshPro")] [assembly: IgnoresAccessChecksTo("Unity.Timeline")] [assembly: IgnoresAccessChecksTo("Unity.VisualScripting.Antlr3.Runtime")] [assembly: IgnoresAccessChecksTo("Unity.VisualScripting.Core")] [assembly: IgnoresAccessChecksTo("Unity.VisualScripting.Flow")] [assembly: IgnoresAccessChecksTo("Unity.VisualScripting.State")] [assembly: IgnoresAccessChecksTo("UnityEngine.ARModule")] [assembly: IgnoresAccessChecksTo("UnityEngine.NVIDIAModule")] [assembly: IgnoresAccessChecksTo("UnityEngine.UI")] [assembly: IgnoresAccessChecksTo("websocket-sharp")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("AitServices")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("Medkit Scatter")] [assembly: AssemblyTitle("MedkitScatter")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [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 MedkitScatter { internal sealed class ModConfig { private const string SectionGeneral = "General"; private const string SectionAmount = "Amount"; private const string SectionRooms = "Rooms"; private const string SectionTiers = "Tiers"; private const string SectionPlacement = "Placement"; private readonly ConfigEntry<string> _allowedVolumeTypes; private List<Type>? _parsedVolumeTypes; private string _parsedFrom = string.Empty; internal ConfigEntry<bool> Enabled { get; } internal ConfigEntry<bool> ExtendedLogging { get; } internal ConfigEntry<int> FixedSeed { get; } internal ConfigEntry<int> MaxSpawnFailuresPerLevel { get; } internal ConfigEntry<int> MaxExceptionsPerSession { get; } internal ConfigEntry<int> RoomsPerMedkit { get; } internal ConfigEntry<RoundingMode> Rounding { get; } internal ConfigEntry<int> MinMedkits { get; } internal ConfigEntry<int> MaxMedkits { get; } internal ConfigEntry<bool> CountNormal { get; } internal ConfigEntry<bool> CountPassages { get; } internal ConfigEntry<bool> CountDeadEnds { get; } internal ConfigEntry<bool> CountExtraction { get; } internal ConfigEntry<bool> CountSpecial { get; } internal ConfigEntry<bool> SpawnInNormal { get; } internal ConfigEntry<bool> SpawnInPassages { get; } internal ConfigEntry<bool> SpawnInDeadEnds { get; } internal ConfigEntry<bool> SpawnInSpecial { get; } internal ConfigEntry<bool> PreferDistantRooms { get; } internal ConfigEntry<int> WeightSmall { get; } internal ConfigEntry<int> WeightMedium { get; } internal ConfigEntry<int> WeightLarge { get; } internal ConfigEntry<bool> GuaranteeVariety { get; } internal ConfigEntry<bool> DifficultyScaling { get; } internal ConfigEntry<float> DifficultyScalingFactor { get; } internal ConfigEntry<bool> AllowLevelPointFallback { get; } internal ConfigEntry<float> LevelPointOffset { get; } internal ConfigEntry<float> RaycastUp { get; } internal ConfigEntry<float> RaycastDistance { get; } internal ConfigEntry<int> GroundRaycastMask { get; } internal ConfigEntry<float> GroundOffset { get; } internal ConfigEntry<float> ClearanceRadius { get; } internal ConfigEntry<int> SpawnDelayFrames { get; } internal IReadOnlyList<Type> AllowedVolumeTypes { get { //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) string text = _allowedVolumeTypes.Value ?? string.Empty; if (_parsedVolumeTypes != null && string.Equals(text, _parsedFrom, StringComparison.Ordinal)) { return _parsedVolumeTypes; } List<Type> list = new List<Type>(); string[] array = text.Split(','); foreach (string text2 in array) { string text3 = text2.Trim(); if (text3.Length == 0) { continue; } bool flag = false; string[] names = Enum.GetNames(typeof(Type)); foreach (string text4 in names) { if (string.Equals(text4, text3, StringComparison.OrdinalIgnoreCase)) { Type item = (Type)Enum.Parse(typeof(Type), text4); if (!list.Contains(item)) { list.Add(item); } flag = true; break; } } if (!flag) { ModLog.Warning("AllowedVolumeTypes: значение \"" + text3 + "\" не является членом ValuableVolume.Type и проигнорировано."); } } if (list.Count == 0) { ModLog.Warning("AllowedVolumeTypes пуст или полностью нераспознан — объёмы использоваться не будут, останется только LevelPoint."); } _parsedFrom = text; _parsedVolumeTypes = list; return list; } } internal ModConfig(ConfigFile file) { //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Expected O, but got Unknown //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Expected O, but got Unknown //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Expected O, but got Unknown //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Expected O, but got Unknown //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Expected O, but got Unknown //IL_02a9: Unknown result type (might be due to invalid IL or missing references) //IL_02b3: Expected O, but got Unknown //IL_02db: Unknown result type (might be due to invalid IL or missing references) //IL_02e5: Expected O, but got Unknown //IL_030d: Unknown result type (might be due to invalid IL or missing references) //IL_0317: Expected O, but got Unknown //IL_037e: Unknown result type (might be due to invalid IL or missing references) //IL_0388: Expected O, but got Unknown //IL_03f3: Unknown result type (might be due to invalid IL or missing references) //IL_03fd: Expected O, but got Unknown //IL_042c: Unknown result type (might be due to invalid IL or missing references) //IL_0436: Expected O, but got Unknown //IL_0465: Unknown result type (might be due to invalid IL or missing references) //IL_046f: Expected O, but got Unknown //IL_04ba: Unknown result type (might be due to invalid IL or missing references) //IL_04c4: Expected O, but got Unknown //IL_04f3: Unknown result type (might be due to invalid IL or missing references) //IL_04fd: Expected O, but got Unknown //IL_0521: Unknown result type (might be due to invalid IL or missing references) //IL_052b: Expected O, but got Unknown Enabled = file.Bind<bool>("General", "Enabled", true, "Полностью включить или выключить разброс аптечек."); ExtendedLogging = file.Bind<bool>("General", "ExtendedLogging", false, "Подробный лог плана и каждого спавна. Пишется уровнем Info, поэтому виден и в консоли, и в LogOutput.log."); FixedSeed = file.Bind<int>("General", "FixedSeed", 0, "0 = сид выводится из забега и имени уровня. Любое другое значение — фиксированный сид: одинаковая раскладка при каждом прогоне."); MaxSpawnFailuresPerLevel = file.Bind<int>("General", "MaxSpawnFailuresPerLevel", 3, new ConfigDescription("Сколько неудачных попыток размещения терпим за уровень, прежде чем прекратить расстановку на этом уровне. Счётчик сбрасывается каждый уровень.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 64), Array.Empty<object>())); MaxExceptionsPerSession = file.Bind<int>("General", "MaxExceptionsPerSession", 10, new ConfigDescription("После стольких пойманных исключений плагин выключается до перезапуска игры.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 1000), Array.Empty<object>())); RoomsPerMedkit = file.Bind<int>("Amount", "RoomsPerMedkit", 4, new ConfigDescription("Делитель формулы «комнат / N». Значение по умолчанию 4 — прямое прочтение требования заказчика.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 64), Array.Empty<object>())); Rounding = file.Bind<RoundingMode>("Amount", "RoundingMode", (RoundingMode)0, "Округление результата деления: Floor (вниз), Round (к ближайшему, половина от нуля), Ceil (вверх)."); MinMedkits = file.Bind<int>("Amount", "MinMedkits", 1, new ConfigDescription("Нижняя граница количества аптечек на уровне.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 64), Array.Empty<object>())); MaxMedkits = file.Bind<int>("Amount", "MaxMedkits", 0, new ConfigDescription("Верхняя граница количества. 0 = без лимита. Если Min больше Max, побеждает Min.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 128), Array.Empty<object>())); CountNormal = file.Bind<bool>("Rooms", "CountNormal", true, "Учитывать обычные комнаты в знаменателе формулы."); CountPassages = file.Bind<bool>("Rooms", "CountPassages", true, "Учитывать коридоры в знаменателе. По умолчанию да: коридоры формируют размер уровня и должны влиять на количество аптечек."); CountDeadEnds = file.Bind<bool>("Rooms", "CountDeadEnds", true, "Учитывать тупики в знаменателе."); CountExtraction = file.Bind<bool>("Rooms", "CountExtraction", false, "Учитывать комнаты извлечения в знаменателе."); CountSpecial = file.Bind<bool>("Rooms", "CountSpecial", false, "Учитывать модули типа Special в знаменателе. Этот тип появился в игре после версии 0.1.2 и в исходной спецификации не описан, поэтому по умолчанию он не считается и не заселяется."); SpawnInNormal = file.Bind<bool>("Rooms", "SpawnInNormal", true, "Разрешить размещение в обычных комнатах."); SpawnInPassages = file.Bind<bool>("Rooms", "SpawnInPassages", false, "Разрешить размещение в коридорах. По умолчанию нет: лут в проходном коридоре плохо читается игроком."); SpawnInDeadEnds = file.Bind<bool>("Rooms", "SpawnInDeadEnds", true, "Разрешить размещение в тупиках."); SpawnInSpecial = file.Bind<bool>("Rooms", "SpawnInSpecial", false, "Разрешить размещение в модулях типа Special."); PreferDistantRooms = file.Bind<bool>("Rooms", "PreferDistantRooms", false, "Смещать выбор к комнатам подальше от стартовой."); WeightSmall = file.Bind<int>("Tiers", "WeightSmall", 60, new ConfigDescription("Вес аптечки на 25 HP.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 1000), Array.Empty<object>())); WeightMedium = file.Bind<int>("Tiers", "WeightMedium", 30, new ConfigDescription("Вес аптечки на 50 HP.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 1000), Array.Empty<object>())); WeightLarge = file.Bind<int>("Tiers", "WeightLarge", 10, new ConfigDescription("Вес аптечки на 100 HP.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 1000), Array.Empty<object>())); GuaranteeVariety = file.Bind<bool>("Tiers", "GuaranteeVariety", true, "При трёх и более аптечках гарантировать все три тира. ВНИМАНИЕ: это осознанно переопределяет веса для первых трёх позиций — при 5 аптечках фактическая доля Large будет не меньше 20 %, а не 10 %. Нужна честная пропорция — выключите."); DifficultyScaling = file.Bind<bool>("Tiers", "DifficultyScaling", false, "Смещать веса к крупным аптечкам с ростом номера уровня в забеге."); DifficultyScalingFactor = file.Bind<float>("Tiers", "DifficultyScalingFactor", 0.15f, new ConfigDescription("Коэффициент k: вес Large умножается на (1 + k * пройденных уровней), вес Small на столько же делится.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f), Array.Empty<object>())); _allowedVolumeTypes = file.Bind<string>("Placement", "AllowedVolumeTypes", "Small,Medium,Tiny,Big", "Типы ValuableVolume, которые можно занимать, через запятую. ПОРЯДОК ЗАДАЁТ ПРИОРИТЕТ: чем левее, тем охотнее используется. Доступные значения: Tiny, Small, Medium, Big, Wide, Tall, VeryTall."); AllowLevelPointFallback = file.Bind<bool>("Placement", "AllowLevelPointFallback", true, "Использовать навигационные точки уровня, если свободных объёмов в комнате нет."); LevelPointOffset = file.Bind<float>("Placement", "LevelPointOffset", 0.35f, new ConfigDescription("Горизонтальный сдвиг от узла навигации, метры — чтобы аптечка не стояла ровно в точке маршрута.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), Array.Empty<object>())); RaycastUp = file.Bind<float>("Placement", "RaycastUp", 1f, new ConfigDescription("Высота начала луча над кандидатом, метры.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 10f), Array.Empty<object>())); RaycastDistance = file.Bind<float>("Placement", "RaycastDistance", 3f, new ConfigDescription("Длина луча вниз, метры.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 30f), Array.Empty<object>())); GroundRaycastMask = file.Bind<int>("Placement", "GroundRaycastMask", -1, "LayerMask для поиска пола и проверки пересечений. -1 = все слои."); GroundOffset = file.Bind<float>("Placement", "GroundOffset", 0.08f, new ConfigDescription("Подъём над полом после луча, метры — чтобы аптечка не проваливалась.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); ClearanceRadius = file.Bind<float>("Placement", "ClearanceRadius", 0.45f, new ConfigDescription("Радиус проверки свободного места, метры.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f), Array.Empty<object>())); SpawnDelayFrames = file.Bind<int>("Placement", "SpawnDelayFrames", 2, new ConfigDescription("Задержка после завершения генерации, кадры. Нужна, чтобы PhysGrabObject ванильных предметов успел настроить свои коллайдеры.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 120), Array.Empty<object>())); } internal bool ShouldCount(RoomKind kind) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected I4, but got Unknown return (int)kind switch { 0 => CountNormal.Value, 1 => CountPassages.Value, 2 => CountDeadEnds.Value, 3 => CountExtraction.Value, 6 => CountSpecial.Value, _ => false, }; } internal bool ShouldHost(RoomKind kind) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected I4, but got Unknown return (int)kind switch { 0 => SpawnInNormal.Value, 1 => SpawnInPassages.Value, 2 => SpawnInDeadEnds.Value, 6 => SpawnInSpecial.Value, _ => false, }; } } [BepInPlugin("AitServices.MedkitScatter", "Medkit Scatter", "1.0.0")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "AitServices.MedkitScatter"; public const string PluginName = "Medkit Scatter"; public const string PluginVersion = "1.0.0"; private const string GenerateDoneMethod = "GenerateDone"; private const string SpawnValuableMethod = "SpawnValuable"; private const string LegacySpawnMethod = "Spawn"; private Harmony? _harmony; internal static Plugin? Instance { get; private set; } private void Awake() { //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Expected O, but got Unknown Instance = this; ModConfig config = new ModConfig(((BaseUnityPlugin)this).Config); ModLog.Initialize(((BaseUnityPlugin)this).Logger, config.MaxExceptionsPerSession.Value); ModLog.Extended = config.ExtendedLogging.Value; config.ExtendedLogging.SettingChanged += delegate { ModLog.Extended = config.ExtendedLogging.Value; }; GameApi.Initialize(); ModLog.Info(GameApi.Describe()); MedkitScatterService.Initialize(config); _harmony = new Harmony("AitServices.MedkitScatter"); PatchLevelGenerator(_harmony); PatchValuableDirector(_harmony); ModLog.Info("Medkit Scatter 1.0.0 загружен (host-only, game v0.4.4)."); } private static void PatchLevelGenerator(Harmony harmony) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Expected O, but got Unknown try { MethodInfo methodInfo = AccessTools.Method(typeof(LevelGenerator), "GenerateDone", (Type[])null, (Type[])null); if (methodInfo == null) { ModLog.Error("LevelGenerator.GenerateDone не найден — плагин не сможет расставлять аптечки. Версия игры не 0.4.4?"); return; } harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(LevelGeneratorPatch), "GenerateDonePostfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ModLog.Detail("Патч LevelGenerator.GenerateDone применён."); } catch (Exception exception) { ModLog.CountedError("PatchLevelGenerator", exception); } } private static void PatchValuableDirector(Harmony harmony) { //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Expected O, but got Unknown try { string text = "SpawnValuable"; MethodInfo methodInfo = AccessTools.Method(typeof(ValuableDirector), "SpawnValuable", (Type[])null, (Type[])null); if (methodInfo == null) { text = "Spawn"; methodInfo = AccessTools.Method(typeof(ValuableDirector), "Spawn", (Type[])null, (Type[])null); } if (methodInfo == null) { ModLog.Warning("Метод спавна ценностей у ValuableDirector не найден — работаем без маркеров занятости объёмов."); return; } harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(AccessTools.Method(typeof(ValuableDirectorPatch), "MarkVolume", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ModLog.Detail("Патч ValuableDirector." + text + " применён."); } catch (Exception exception) { ModLog.CountedError("PatchValuableDirector", exception); } } } } namespace MedkitScatter.Util { internal static class GameApi { private const BindingFlags AnyInstance = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static FieldInfo? _moduleTypeField; private static PropertyInfo? _moduleTypeProperty; private static FieldInfo? _levelGridField; private static FieldInfo? _tileTypeField; private static FieldInfo? _runSeedField; private static FieldInfo? _levelsCompletedField; private static FieldInfo? _levelCurrentField; private static Func<int>? _levelsCompletedFunc; private static Func<bool>? _runIsShop; private static Func<bool>? _runIsLobby; private static Func<bool>? _runIsLobbyMenu; private static Func<bool>? _runIsArena; private static Func<bool>? _runIsTutorial; private static Type? _itemHealthPackType; private static FieldInfo? _healAmountField; private static readonly List<string> Report = new List<string>(); internal static string RunSeedFieldName { get; private set; } = string.Empty; internal static string ModuleTypeSource { get; private set; } = "none"; internal static void Initialize() { Report.Clear(); ResolveModuleType(); ResolveRunState(); ResolveRunPredicates(); ResolveHealthPack(); } internal static string Describe() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("Game API: moduleType=").Append(ModuleTypeSource); stringBuilder.Append(", runSeed=").Append((RunSeedFieldName.Length == 0) ? "none" : RunSeedFieldName); stringBuilder.Append(", levelsCompleted=").Append((_levelsCompletedField != null) ? "RunManager.levelsCompleted" : ((_levelsCompletedFunc != null) ? "SemiFunc.RunGetLevelsCompleted" : "none")); stringBuilder.Append(", healAmount=").Append((_healAmountField != null) ? "ok" : "none"); stringBuilder.Append(", predicates=[").Append((_runIsShop != null) ? "Shop " : string.Empty).Append((_runIsLobby != null) ? "Lobby " : string.Empty) .Append((_runIsLobbyMenu != null) ? "LobbyMenu " : string.Empty) .Append((_runIsArena != null) ? "Arena " : string.Empty) .Append((_runIsTutorial != null) ? "Tutorial" : string.Empty) .Append(']'); if (Report.Count > 0) { stringBuilder.Append(" | ").Append(string.Join("; ", Report.ToArray())); } return stringBuilder.ToString(); } private static void ResolveModuleType() { Type typeFromHandle = typeof(Type); FieldInfo[] fields = typeof(Module).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.FieldType == typeFromHandle) { _moduleTypeField = fieldInfo; ModuleTypeSource = "Module." + fieldInfo.Name; return; } } PropertyInfo[] properties = typeof(Module).GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (PropertyInfo propertyInfo in properties) { if (propertyInfo.PropertyType == typeFromHandle && propertyInfo.CanRead) { _moduleTypeProperty = propertyInfo; ModuleTypeSource = "Module." + propertyInfo.Name + " (property)"; return; } } _levelGridField = AccessTools.Field(typeof(LevelGenerator), "LevelGrid"); if (_levelGridField == null) { Report.Add("LevelGenerator.LevelGrid не найден"); ModuleTypeSource = "name heuristic"; return; } Type elementType = _levelGridField.FieldType.GetElementType(); if (elementType == null) { Report.Add("LevelGrid не массив: " + _levelGridField.FieldType.Name); ModuleTypeSource = "name heuristic"; return; } _tileTypeField = AccessTools.Field(elementType, "type"); if (_tileTypeField == null) { Report.Add(elementType.Name + ".type не найден"); ModuleTypeSource = "name heuristic"; } else { ModuleTypeSource = "LevelGrid[x,y]." + _tileTypeField.Name; } } internal static string? TryGetModuleTypeName(Module module, int gridX, int gridY) { if ((Object)(object)module == (Object)null) { return null; } FieldInfo moduleTypeField = _moduleTypeField; if (moduleTypeField != null) { return moduleTypeField.GetValue(module)?.ToString(); } PropertyInfo moduleTypeProperty = _moduleTypeProperty; if (moduleTypeProperty != null) { return moduleTypeProperty.GetValue(module, null)?.ToString(); } FieldInfo levelGridField = _levelGridField; FieldInfo tileTypeField = _tileTypeField; if (levelGridField == null || tileTypeField == null) { return null; } LevelGenerator instance = LevelGenerator.Instance; if ((Object)(object)instance == (Object)null) { return null; } if (!(levelGridField.GetValue(instance) is Array { Rank: 2 } array)) { return null; } if (gridX < array.GetLowerBound(0) || gridX > array.GetUpperBound(0) || gridY < array.GetLowerBound(1) || gridY > array.GetUpperBound(1)) { return null; } object value = array.GetValue(gridX, gridY); if (value != null) { return tileTypeField.GetValue(value)?.ToString(); } return null; } private static void ResolveRunState() { _levelsCompletedField = AccessTools.Field(typeof(RunManager), "levelsCompleted"); if (_levelsCompletedField != null && _levelsCompletedField.FieldType != typeof(int)) { Report.Add("RunManager.levelsCompleted не int: " + _levelsCompletedField.FieldType.Name); _levelsCompletedField = null; } _levelsCompletedFunc = ResolveIntFunc("RunGetLevelsCompleted"); if (_levelsCompletedField == null && _levelsCompletedFunc == null) { Report.Add("число пройденных уровней недоступно, считаем 0"); } _levelCurrentField = AccessTools.Field(typeof(RunManager), "levelCurrent"); Dictionary<string, FieldInfo> dictionary = new Dictionary<string, FieldInfo>(StringComparer.Ordinal); FieldInfo[] fields = typeof(RunManager).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.FieldType == typeof(int) && !dictionary.ContainsKey(fieldInfo.Name)) { dictionary[fieldInfo.Name] = fieldInfo; } } string[] array = new string[5] { "runSeed", "levelSeed", "saveLevelSeed", "seed", "RandomSeed" }; string[] array2 = array; foreach (string text in array2) { if (dictionary.TryGetValue(text, out var value)) { _runSeedField = value; RunSeedFieldName = text; break; } } } internal static int GetLevelsCompleted() { FieldInfo levelsCompletedField = _levelsCompletedField; if (levelsCompletedField != null) { RunManager instance = RunManager.instance; if ((Object)(object)instance != (Object)null) { object value = levelsCompletedField.GetValue(instance); if (value is int) { return (int)value; } } } return _levelsCompletedFunc?.Invoke() ?? 0; } internal static string GetLevelName() { //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) FieldInfo levelCurrentField = _levelCurrentField; if (levelCurrentField != null) { RunManager instance = RunManager.instance; if ((Object)(object)instance != (Object)null) { object? value = levelCurrentField.GetValue(instance); Object val = (Object)((value is Object) ? value : null); if (val != null && val != (Object)null) { return val.name; } } } Scene activeScene = SceneManager.GetActiveScene(); return ((Scene)(ref activeScene)).name; } internal static int? TryGetRunSeed() { FieldInfo runSeedField = _runSeedField; if (runSeedField == null) { return null; } RunManager instance = RunManager.instance; if ((Object)(object)instance == (Object)null) { return null; } object value = runSeedField.GetValue(instance); if (!(value is int)) { return null; } return (int)value; } private static void ResolveRunPredicates() { _runIsShop = ResolveBoolFunc("RunIsShop"); _runIsLobby = ResolveBoolFunc("RunIsLobby"); _runIsLobbyMenu = ResolveBoolFunc("RunIsLobbyMenu"); _runIsArena = ResolveBoolFunc("RunIsArena"); _runIsTutorial = ResolveBoolFunc("RunIsTutorial"); } internal static bool IsNonLevelContext() { if (!Invoke(_runIsShop) && !Invoke(_runIsLobby) && !Invoke(_runIsLobbyMenu) && !Invoke(_runIsArena)) { return Invoke(_runIsTutorial); } return true; } private static bool Invoke(Func<bool>? predicate) { return predicate?.Invoke() ?? false; } private static Func<bool>? ResolveBoolFunc(string name) { MethodInfo methodInfo = AccessTools.Method(typeof(SemiFunc), name, (Type[])null, (Type[])null); if (methodInfo == null || !methodInfo.IsStatic || methodInfo.ReturnType != typeof(bool) || methodInfo.GetParameters().Length != 0) { Report.Add("SemiFunc." + name + " отсутствует"); return null; } return (Func<bool>)Delegate.CreateDelegate(typeof(Func<bool>), methodInfo); } private static Func<int>? ResolveIntFunc(string name) { MethodInfo methodInfo = AccessTools.Method(typeof(SemiFunc), name, (Type[])null, (Type[])null); if (methodInfo == null || !methodInfo.IsStatic || methodInfo.ReturnType != typeof(int) || methodInfo.GetParameters().Length != 0) { return null; } return (Func<int>)Delegate.CreateDelegate(typeof(Func<int>), methodInfo); } private static void ResolveHealthPack() { _itemHealthPackType = AccessTools.TypeByName("ItemHealthPack"); if (_itemHealthPackType == null) { Report.Add("тип ItemHealthPack не найден"); return; } _healAmountField = AccessTools.Field(_itemHealthPackType, "healAmount"); if (_healAmountField == null) { Report.Add("ItemHealthPack.healAmount не найден"); } } internal static double? TryGetHealAmount(GameObject prefab) { Type itemHealthPackType = _itemHealthPackType; FieldInfo healAmountField = _healAmountField; if ((Object)(object)prefab == (Object)null || itemHealthPackType == null || healAmountField == null) { return null; } Component component = prefab.GetComponent(itemHealthPackType); if ((Object)(object)component == (Object)null) { return null; } object value = healAmountField.GetValue(component); if (value is int num) { return num; } if (value is float num2) { return num2; } if (value is double) { return (double)value; } return null; } } internal static class ModLog { private static ManualLogSource? _source; private static int _maxExceptionsPerSession = 10; private static int _exceptionCount; internal static bool Extended { get; set; } internal static bool SessionDisabled { get; private set; } internal static void Initialize(ManualLogSource source, int maxExceptionsPerSession) { _source = source; _maxExceptionsPerSession = Math.Max(1, maxExceptionsPerSession); } internal static void Info(string message) { ManualLogSource? source = _source; if (source != null) { source.LogInfo((object)message); } } internal static void Detail(string message) { if (Extended) { ManualLogSource? source = _source; if (source != null) { source.LogInfo((object)message); } } } internal static void Warning(string message) { ManualLogSource? source = _source; if (source != null) { source.LogWarning((object)message); } } internal static void Error(string message) { ManualLogSource? source = _source; if (source != null) { source.LogError((object)message); } } internal static void Debug(string message) { ManualLogSource? source = _source; if (source != null) { source.LogDebug((object)message); } } internal static void CountedError(string where, Exception exception) { _exceptionCount++; ManualLogSource? source = _source; if (source != null) { source.LogError((object)("[" + where + "] " + exception)); } if (_exceptionCount >= _maxExceptionsPerSession && !SessionDisabled) { DisableForSession("поймано исключений: " + _exceptionCount + " (лимит " + _maxExceptionsPerSession + ")"); } } internal static void DisableForSession(string reason) { SessionDisabled = true; ManualLogSource? source = _source; if (source != null) { source.LogError((object)("MedkitScatter выключен до перезапуска игры: " + reason)); } } } internal static class SeedFactory { private const uint FnvOffsetBasis = 2166136261u; private const uint FnvPrime = 16777619u; internal static int Create(int fixedSeed, string levelName, int levelsCompleted, out string saltSource) { if (fixedSeed != 0) { saltSource = "FixedSeed"; return fixedSeed; } int? num = GameApi.TryGetRunSeed(); int value; if (num.HasValue) { value = num.Value; saltSource = "RunManager." + GameApi.RunSeedFieldName; } else { Room currentRoom = PhotonNetwork.CurrentRoom; string value2 = ((currentRoom != null) ? currentRoom.Name : null); if (string.IsNullOrEmpty(value2)) { value = 0; saltSource = "singleplayer"; } else { value = (int)StableHash(value2); saltSource = "photonRoom"; } } uint hash = 2166136261u; hash = Mix(hash, StableHash(levelName ?? string.Empty)); hash = Mix(hash, (uint)levelsCompleted); return (int)Mix(hash, (uint)value); } private static uint StableHash(string value) { uint num = 2166136261u; for (int i = 0; i < value.Length; i++) { num ^= value[i]; num *= 16777619; } return num; } private static uint Mix(uint hash, uint value) { hash ^= value; hash *= 16777619; return hash; } } } namespace MedkitScatter.Runtime { internal enum CandidateSource { ValuableVolume, LevelPoint } internal readonly struct Candidate { internal RoomKey Room { get; } internal CandidateSource Source { get; } internal Vector3 Position { get; } internal Quaternion Rotation { get; } internal int Priority { get; } internal Candidate(RoomKey room, CandidateSource source, Vector3 position, Quaternion rotation, int priority) { //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) //IL_000f: 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_0016: 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) Room = room; Source = source; Position = position; Rotation = rotation; Priority = priority; } } internal sealed class CoroutineRunner : MonoBehaviour { private static CoroutineRunner? _instance; internal static bool TryRun(IEnumerator routine) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown if (routine == null) { return false; } if ((Object)(object)_instance == (Object)null) { GameObject val = new GameObject("MedkitScatter_CoroutineRunner"); ((Object)val).hideFlags = (HideFlags)61; Object.DontDestroyOnLoad((Object)(object)val); _instance = val.AddComponent<CoroutineRunner>(); } ((MonoBehaviour)_instance).StartCoroutine(routine); return true; } } internal sealed class MedkitCatalog { private const string SmallItemName = "Item Health Pack Small"; private const string MediumItemName = "Item Health Pack Medium"; private const string LargeItemName = "Item Health Pack Large"; private const string HealthPackItemTypeName = "healthPack"; private const double ExpectedSmallHeal = 25.0; private const double ExpectedMediumHeal = 50.0; private const double ExpectedLargeHeal = 100.0; private Dictionary<MedkitTier, Item>? _cache; private StatsManager? _boundTo; internal Dictionary<MedkitTier, Item>? Resolve() { StatsManager instance = StatsManager.instance; if ((Object)(object)instance == (Object)null) { ModLog.Warning("StatsManager.instance отсутствует — уровень пропущен."); return null; } if (_cache != null && (Object)(object)_boundTo != (Object)null && (Object)(object)_boundTo == (Object)(object)instance) { return _cache; } Dictionary<string, Item> itemDictionary = instance.itemDictionary; if (itemDictionary == null || itemDictionary.Count == 0) { ModLog.Warning("StatsManager.itemDictionary пуст — уровень пропущен."); return null; } Dictionary<MedkitTier, Item> dictionary = ResolveByExactNames(itemDictionary) ?? ResolveByItemType(itemDictionary); if (dictionary == null) { return null; } ValidateHealAmounts(dictionary); _cache = dictionary; _boundTo = instance; return dictionary; } private static Dictionary<MedkitTier, Item>? ResolveByExactNames(Dictionary<string, Item> dictionary) { if (!dictionary.TryGetValue("Item Health Pack Small", out Item value) || (Object)(object)value == (Object)null) { return null; } if (!dictionary.TryGetValue("Item Health Pack Medium", out Item value2) || (Object)(object)value2 == (Object)null) { return null; } if (!dictionary.TryGetValue("Item Health Pack Large", out Item value3) || (Object)(object)value3 == (Object)null) { return null; } ModLog.Detail("Каталог: три аптечки найдены по точным именам."); return new Dictionary<MedkitTier, Item> { { (MedkitTier)0, value }, { (MedkitTier)1, value2 }, { (MedkitTier)2, value3 } }; } private static Dictionary<MedkitTier, Item>? ResolveByItemType(Dictionary<string, Item> dictionary) { List<Item> list = new List<Item>(); foreach (Item value in dictionary.Values) { if ((Object)(object)value != (Object)null && IsHealthPack(value)) { list.Add(value); } } if (list.Count != 3) { ModLog.DisableForSession("ожидалось ровно 3 ванильных аптечки, найдено " + list.Count + " (вероятно, установлен мод, добавляющий свои). Автоопределение отключено."); return null; } List<KeyValuePair<double, Item>> list2 = new List<KeyValuePair<double, Item>>(3); foreach (Item item in list) { if (item.prefab == null || !((PrefabRef<GameObject>)(object)item.prefab).IsValid()) { ModLog.DisableForSession("у аптечки \"" + ((Object)item).name + "\" невалидный PrefabRef."); return null; } GameObject prefab = ((PrefabRef<GameObject>)(object)item.prefab).Prefab; if ((Object)(object)prefab == (Object)null) { ModLog.DisableForSession("не удалось получить префаб аптечки \"" + ((Object)item).name + "\"."); return null; } double? num = GameApi.TryGetHealAmount(prefab); if (!num.HasValue) { ModLog.DisableForSession("не удалось прочитать healAmount у \"" + ((Object)item).name + "\" — порядок тиров определить невозможно."); return null; } list2.Add(new KeyValuePair<double, Item>(num.Value, item)); } list2.Sort((KeyValuePair<double, Item> a, KeyValuePair<double, Item> b) => a.Key.CompareTo(b.Key)); ModLog.Warning("Каталог собран автоопределением по типу предмета: " + ((Object)list2[0].Value).name + "=" + Fmt(list2[0].Key) + ", " + ((Object)list2[1].Value).name + "=" + Fmt(list2[1].Key) + ", " + ((Object)list2[2].Value).name + "=" + Fmt(list2[2].Key) + "."); return new Dictionary<MedkitTier, Item> { { (MedkitTier)0, list2[0].Value }, { (MedkitTier)1, list2[1].Value }, { (MedkitTier)2, list2[2].Value } }; } private static bool IsHealthPack(Item item) { return string.Equals(((object)Unsafe.As<itemType, itemType>(ref item.itemType)/*cast due to .constrained prefix*/).ToString(), "healthPack", StringComparison.Ordinal); } private static void ValidateHealAmounts(Dictionary<MedkitTier, Item> items) { Check(items, (MedkitTier)0, 25.0); Check(items, (MedkitTier)1, 50.0); Check(items, (MedkitTier)2, 100.0); } private unsafe static void Check(Dictionary<MedkitTier, Item> items, MedkitTier tier, double expected) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) if (!items.TryGetValue(tier, out Item value) || (Object)(object)value == (Object)null || value.prefab == null || !((PrefabRef<GameObject>)(object)value.prefab).IsValid()) { return; } GameObject prefab = ((PrefabRef<GameObject>)(object)value.prefab).Prefab; if ((Object)(object)prefab == (Object)null) { return; } double? num = GameApi.TryGetHealAmount(prefab); if (num.HasValue) { if (Math.Abs(num.Value - expected) > 0.01) { ModLog.Warning("Аптечка " + ((object)(*(MedkitTier*)(&tier))/*cast due to .constrained prefix*/).ToString() + " (\"" + ((Object)value).name + "\") лечит на " + Fmt(num.Value) + ", а не на " + Fmt(expected) + ". Логика не меняется, но README устарел."); } else { ModLog.Detail("Аптечка " + ((object)(*(MedkitTier*)(&tier))/*cast due to .constrained prefix*/).ToString() + " = " + Fmt(num.Value) + " HP."); } } } private static string Fmt(double value) { return value.ToString("0.##", CultureInfo.InvariantCulture); } } internal static class MedkitScatterService { private static readonly MedkitCatalog Catalog = new MedkitCatalog(); private static ModConfig? _config; private static bool _running; internal static void Initialize(ModConfig config) { _config = config; } internal static void OnLevelGenerated() { if (ModLog.SessionDisabled) { return; } ModConfig config = _config; if (config == null || !config.Enabled.Value || !SemiFunc.IsMasterClientOrSingleplayer() || !SemiFunc.RunIsLevel() || GameApi.IsNonLevelContext() || (Object)(object)LevelGenerator.Instance == (Object)null) { return; } if ((Object)(object)StatsManager.instance == (Object)null) { ModLog.Warning("StatsManager.instance отсутствует — уровень пропущен."); return; } if (_running) { ModLog.Detail("Размещение уже идёт, повторный вызов GenerateDone проигнорирован."); return; } _running = true; try { if (!CoroutineRunner.TryRun(Run(config))) { _running = false; ModLog.Warning("Не удалось запустить корутину размещения — уровень пропущен."); } } catch (Exception exception) { _running = false; ModLog.CountedError("MedkitScatterService.StartCoroutine", exception); } } private static IEnumerator Run(ModConfig config) { int frames = Math.Max(0, config.SpawnDelayFrames.Value); for (int i = 0; i < frames; i++) { yield return null; } try { Execute(config); } catch (Exception exception) { ModLog.CountedError("MedkitScatterService.Execute", exception); } finally { _running = false; } } private static void Execute(ModConfig config) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_018f: 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) //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_01c2: Unknown result type (might be due to invalid IL or missing references) //IL_01d3: Unknown result type (might be due to invalid IL or missing references) //IL_01e4: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Unknown result type (might be due to invalid IL or missing references) //IL_0206: Unknown result type (might be due to invalid IL or missing references) //IL_0217: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_0232: Expected O, but got Unknown Stopwatch stopwatch = Stopwatch.StartNew(); LevelGenerator instance = LevelGenerator.Instance; if ((Object)(object)instance == (Object)null) { return; } if (!instance.Generated) { ModLog.Warning("LevelGenerator.Generated == false после GenerateDone — продолжаем."); } int levelsCompleted = GameApi.GetLevelsCompleted(); string levelName = GameApi.GetLevelName(); string saltSource; int num = SeedFactory.Create(config.FixedSeed.Value, levelName, levelsCompleted, out saltSource); SystemRandomSource val = new SystemRandomSource(num); ModLog.Info("Level generated: \"" + levelName + "\" (levelsCompleted=" + levelsCompleted + ", seed=" + num + " via " + saltSource + ")"); RoomKey? startRoomKey; List<ModuleEntry> list = RoomIndexBuilder.CollectModules(out startRoomKey); if (list.Count == 0) { ModLog.Info("Модулей на сцене не найдено — 0 аптечек размещено."); return; } ModLog.Detail("Rooms: " + list.Count + " modules | " + RoomIndexBuilder.Histogram(list)); SpawnPointResolver spawnPointResolver = new SpawnPointResolver(config); Dictionary<RoomKey, List<Candidate>> candidates = spawnPointResolver.Prescan(list); ModLog.Detail("Candidates: volumes=" + spawnPointResolver.VolumeCandidates + " free, levelPoints=" + spawnPointResolver.LevelPointCandidates + " fallback"); List<RoomDescriptor> rooms = RoomIndexBuilder.BuildDescriptors(list, candidates, config, startRoomKey); PlanInput val2 = new PlanInput { Rooms = rooms, RoomsPerMedkit = config.RoomsPerMedkit.Value, Rounding = config.Rounding.Value, MinMedkits = config.MinMedkits.Value, MaxMedkits = config.MaxMedkits.Value, WeightSmall = config.WeightSmall.Value, WeightMedium = config.WeightMedium.Value, WeightLarge = config.WeightLarge.Value, GuaranteeVariety = config.GuaranteeVariety.Value, PreferDistantRooms = config.PreferDistantRooms.Value, DifficultyScaling = config.DifficultyScaling.Value, DifficultyFactor = config.DifficultyScalingFactor.Value, LevelsCompleted = levelsCompleted }; PlanResult val3 = MedkitPlanner.Plan(val2, (IRandomSource)(object)val); ModLog.Detail(val3.Explanation); foreach (string note in val3.Notes) { ModLog.Warning("Планирование: " + note); } if (val3.PlannedCount == 0) { ModLog.Info("Spawned 0/" + val3.TargetCount + " medkits: подходящих комнат нет."); return; } ModLog.Detail("Plan: " + Describe(val3)); Dictionary<MedkitTier, Item> dictionary = Catalog.Resolve(); if (dictionary == null) { ModLog.Info("Spawned 0/" + val3.TargetCount + " medkits: каталог аптечек недоступен."); } else { PlaceAll(config, val3, spawnPointResolver, dictionary, (IRandomSource)(object)val, stopwatch); } } private unsafe static void PlaceAll(ModConfig config, PlanResult plan, SpawnPointResolver resolver, Dictionary<MedkitTier, Item> items, IRandomSource rng, Stopwatch stopwatch) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0048: 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_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: 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_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_01c0: Unknown result type (might be due to invalid IL or missing references) //IL_01c5: Unknown result type (might be due to invalid IL or missing references) //IL_020f: Unknown result type (might be due to invalid IL or missing references) //IL_0211: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Invalid comparison between Unknown and I4 //IL_0150: Unknown result type (might be due to invalid IL or missing references) Queue<RoomKey> queue = new Queue<RoomKey>(plan.Reserve); int value = config.MaxSpawnFailuresPerLevel.Value; int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; int num5 = 0; bool flag = false; bool flag2 = false; foreach (MedkitPlacement placement in plan.Placements) { MedkitPlacement current = placement; RoomKey room = ((MedkitPlacement)(ref current)).Room; MedkitTier tier = ((MedkitPlacement)(ref current)).Tier; while (true) { bool flag3 = false; if (resolver.TryResolvePosition(room, rng, out var position, out var rotation, out var source)) { if (!items.TryGetValue(tier, out Item value2) || (Object)(object)value2 == (Object)null) { if (!flag2) { flag2 = true; ModLog.Warning("Нет предмета для тира " + ((object)(*(MedkitTier*)(&tier))/*cast due to .constrained prefix*/).ToString() + " — позиции пропускаются."); } break; } GameObject val = NetworkSpawner.Spawn(value2, position, rotation); if ((Object)(object)val != (Object)null) { num++; flag3 = true; if ((int)tier != 0) { if ((int)tier == 1) { num4++; } else { num5++; } } else { num3++; } ModLog.Detail(" room " + ((object)(*(RoomKey*)(&room))/*cast due to .constrained prefix*/).ToString() + " -> " + source.ToString() + " " + ((object)(*(MedkitTier*)(&tier))/*cast due to .constrained prefix*/).ToString() + " at " + Format(position)); } } if (flag3) { break; } num2++; if (num2 > value) { ModLog.Warning("Превышен MaxSpawnFailuresPerLevel (" + value + ") — расстановка на этом уровне прекращена."); flag = true; break; } if (queue.Count == 0) { ModLog.Warning("Комната " + ((object)(*(RoomKey*)(&room))/*cast due to .constrained prefix*/).ToString() + ": валидной позиции нет, резерв исчерпан."); break; } RoomKey val2 = queue.Dequeue(); ModLog.Warning("Комната " + ((object)(*(RoomKey*)(&room))/*cast due to .constrained prefix*/).ToString() + ": валидной позиции нет, переходим к резервной " + ((object)(*(RoomKey*)(&val2))/*cast due to .constrained prefix*/).ToString() + "."); room = val2; } if (flag) { break; } } stopwatch.Stop(); ModLog.Info("Spawned " + num + "/" + plan.TargetCount + " medkits (Small=" + num3 + " Medium=" + num4 + " Large=" + num5 + ") in " + stopwatch.ElapsedMilliseconds + " ms"); } private static string Describe(PlanResult plan) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: 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_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < plan.Placements.Count; i++) { if (i > 0) { stringBuilder.Append(" | "); } MedkitPlacement val = plan.Placements[i]; StringBuilder stringBuilder2 = stringBuilder.Append(((MedkitPlacement)(ref val)).Room).Append(' '); val = plan.Placements[i]; stringBuilder2.Append(((MedkitPlacement)(ref val)).Tier); } return stringBuilder.ToString(); } private static string Format(Vector3 position) { return "(" + position.x.ToString("0.00", CultureInfo.InvariantCulture) + ", " + position.y.ToString("0.00", CultureInfo.InvariantCulture) + ", " + position.z.ToString("0.00", CultureInfo.InvariantCulture) + ")"; } } internal static class NetworkSpawner { internal static GameObject? Spawn(Item item, Vector3 position, Quaternion rotation) { //IL_0057: 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_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)item == (Object)null) { return null; } if (item.prefab == null || !((PrefabRef<GameObject>)(object)item.prefab).IsValid()) { ModLog.Warning("PrefabRef невалиден у предмета \"" + ((Object)item).name + "\", пропускаем."); return null; } if (!SemiFunc.IsMasterClientOrSingleplayer()) { return null; } if (SemiFunc.IsMultiplayer()) { return PhotonNetwork.InstantiateRoomObject(((PrefabRef<GameObject>)(object)item.prefab).ResourcePath, position, rotation, (byte)0, (object[])null); } GameObject prefab = ((PrefabRef<GameObject>)(object)item.prefab).Prefab; if ((Object)(object)prefab == (Object)null) { ModLog.Warning("Префаб не резолвится у предмета \"" + ((Object)item).name + "\", пропускаем."); return null; } return Object.Instantiate<GameObject>(prefab, position, rotation); } } internal readonly struct ModuleEntry { internal RoomKey Key { get; } internal Module ModuleRef { get; } internal RoomKind Kind { get; } internal ModuleEntry(RoomKey key, Module moduleRef, RoomKind kind) { //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) //IL_000f: 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) Key = key; ModuleRef = moduleRef; Kind = kind; } } internal static class RoomIndexBuilder { private const string TypeNormal = "Normal"; private const string TypePassage = "Passage"; private const string TypeDeadEnd = "DeadEnd"; private const string TypeExtraction = "Extraction"; private const string TypeSpecial = "Special"; private const string HintPassage = "Passage"; private const string HintDeadEndSpaced = "Dead End"; private const string HintDeadEnd = "DeadEnd"; private const string HintExtraction = "Extraction"; private const string HintStartRoom = "Start Room"; internal static List<ModuleEntry> CollectModules(out RoomKey? startRoomKey) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0068: 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_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Invalid comparison between Unknown and I4 //IL_00ee: Unknown result type (might be due to invalid IL or missing references) startRoomKey = null; Module[] array = Object.FindObjectsOfType<Module>(); List<ModuleEntry> list = new List<ModuleEntry>(array.Length); HashSet<RoomKey> hashSet = new HashSet<RoomKey>(); bool heuristicWarned = false; int num = 0; Module[] array2 = array; RoomKey val2 = default(RoomKey); foreach (Module val in array2) { if (!((Object)(object)val == (Object)null)) { ((RoomKey)(ref val2))..ctor(val.GridX, val.GridY); if (!hashSet.Add(val2)) { num++; } else { list.Add(new ModuleEntry(val2, val, Classify(val, val2, ref heuristicWarned))); } } } if (num > 0) { ModLog.Warning("Пропущено модулей с дублирующимся RoomKey: " + num + "."); } list.Sort(delegate(ModuleEntry a, ModuleEntry b) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) RoomKey key = a.Key; return ((RoomKey)(ref key)).CompareTo(b.Key); }); foreach (ModuleEntry item in list) { if ((int)item.Kind == 4) { startRoomKey = item.Key; break; } } return list; } private static RoomKind Classify(Module module, RoomKey key, ref bool heuristicWarned) { if (!module.StartRoom) { string text = GameApi.TryGetModuleTypeName(module, ((RoomKey)(ref key)).GridX, ((RoomKey)(ref key)).GridY); if (!string.IsNullOrEmpty(text)) { switch (text) { case "Normal": return (RoomKind)0; case "Passage": return (RoomKind)1; case "DeadEnd": return (RoomKind)2; case "Extraction": return (RoomKind)3; case "Special": return (RoomKind)6; default: if (!heuristicWarned) { heuristicWarned = true; ModLog.Warning("Неизвестный член Module.Type: \"" + text + "\". Такие комнаты не считаются и не заселяются."); } return (RoomKind)5; } } if (!heuristicWarned) { heuristicWarned = true; ModLog.Warning("Тип модуля не читается через игровой API (" + GameApi.ModuleTypeSource + "), включена эвристика по имени объекта. Точность раскладки снижена."); } string name = ((Object)((Component)module).gameObject).name; if (name.IndexOf("Start Room", StringComparison.OrdinalIgnoreCase) < 0) { if (name.IndexOf("Extraction", StringComparison.OrdinalIgnoreCase) < 0) { if (name.IndexOf("Dead End", StringComparison.OrdinalIgnoreCase) < 0 && name.IndexOf("DeadEnd", StringComparison.OrdinalIgnoreCase) < 0) { if (name.IndexOf("Passage", StringComparison.OrdinalIgnoreCase) < 0) { return (RoomKind)5; } return (RoomKind)1; } return (RoomKind)2; } return (RoomKind)3; } return (RoomKind)4; } return (RoomKind)4; } internal static List<RoomDescriptor> BuildDescriptors(List<ModuleEntry> modules, Dictionary<RoomKey, List<Candidate>> candidates, ModConfig config, RoomKey? startRoomKey) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004b: 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_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) List<RoomDescriptor> list = new List<RoomDescriptor>(modules.Count); foreach (ModuleEntry module in modules) { List<Candidate> value; int num = (candidates.TryGetValue(module.Key, out value) ? value.Count : 0); list.Add(new RoomDescriptor(module.Key, module.Kind, config.ShouldCount(module.Kind), config.ShouldHost(module.Kind), num, GridDistanceToStart(module.Key, startRoomKey))); } return list; } private static int GridDistanceToStart(RoomKey key, RoomKey? startRoomKey) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) if (!startRoomKey.HasValue) { return 0; } RoomKey value = startRoomKey.Value; return Math.Abs(((RoomKey)(ref key)).GridX - ((RoomKey)(ref value)).GridX) + Math.Abs(((RoomKey)(ref key)).GridY - ((RoomKey)(ref value)).GridY); } internal unsafe static string Histogram(List<ModuleEntry> modules) { //IL_001a: 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_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) Dictionary<RoomKind, int> dictionary = new Dictionary<RoomKind, int>(); foreach (ModuleEntry module in modules) { dictionary.TryGetValue(module.Kind, out var value); dictionary[module.Kind] = value + 1; } List<string> list = new List<string>(); RoomKind[] array = (RoomKind[])Enum.GetValues(typeof(RoomKind)); for (int i = 0; i < array.Length; i++) { RoomKind key = array[i]; if (dictionary.TryGetValue(key, out var value2) && value2 > 0) { list.Add(((object)(*(RoomKind*)(&key))/*cast due to .constrained prefix*/).ToString() + "=" + value2); } } return string.Join(" ", list.ToArray()); } } internal sealed class SpawnPointResolver { private const int LevelPointPriority = 1000; private readonly ModConfig _config; private readonly Dictionary<RoomKey, List<Candidate>> _byRoom = new Dictionary<RoomKey, List<Candidate>>(); internal int VolumeCandidates { get; private set; } internal int LevelPointCandidates { get; private set; } internal SpawnPointResolver(ModConfig config) { _config = config; } internal Dictionary<RoomKey, List<Candidate>> Prescan(List<ModuleEntry> modules) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) _byRoom.Clear(); VolumeCandidates = 0; LevelPointCandidates = 0; Dictionary<int, RoomKey> dictionary = new Dictionary<int, RoomKey>(modules.Count); foreach (ModuleEntry module in modules) { if ((Object)(object)module.ModuleRef != (Object)null) { dictionary[((Object)module.ModuleRef).GetInstanceID()] = module.Key; } } CollectVolumes(dictionary); if (_config.AllowLevelPointFallback.Value) { CollectLevelPoints(dictionary); } foreach (List<Candidate> value in _byRoom.Values) { value.Sort(CompareCandidates); } return _byRoom; } private void CollectVolumes(Dictionary<int, RoomKey> moduleKeys) { //IL_0078: 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_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) ValuableVolume[] array = Object.FindObjectsOfType<ValuableVolume>(); IReadOnlyList<Type> allowedVolumeTypes = _config.AllowedVolumeTypes; ValuableVolume[] array2 = array; foreach (ValuableVolume val in array2) { if ((Object)(object)val == (Object)null || !((Behaviour)val).isActiveAndEnabled || !((Component)val).gameObject.activeInHierarchy || (Object)(object)((Component)val).gameObject.GetComponent<UsedVolumeMarker>() != (Object)null || (Object)(object)((Component)((Component)val).transform).GetComponentInParent<ValuablePropSwitch>() != (Object)null) { continue; } int num = IndexOf(allowedVolumeTypes, val.VolumeType); if (num >= 0) { Module componentInParent = ((Component)val).GetComponentInParent<Module>(); if (!((Object)(object)componentInParent == (Object)null) && moduleKeys.TryGetValue(((Object)componentInParent).GetInstanceID(), out var value)) { Transform transform = ((Component)val).transform; Add(value, new Candidate(value, CandidateSource.ValuableVolume, transform.position, transform.rotation, num)); VolumeCandidates++; } } } } private void CollectLevelPoints(Dictionary<int, RoomKey> moduleKeys) { //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) List<LevelPoint> list = SemiFunc.LevelPointsGetAll(); if (list == null) { return; } foreach (LevelPoint item in list) { if (!((Object)(object)item == (Object)null) && !item.Truck && !item.inStartRoom && !item.ModuleConnect) { RoomVolume room = item.Room; Module val = (((Object)(object)room != (Object)null) ? room.Module : ((Component)item).GetComponentInParent<Module>()); if ((Object)(object)val == (Object)null) { val = ((Component)item).GetComponentInParent<Module>(); } if (!((Object)(object)val == (Object)null) && moduleKeys.TryGetValue(((Object)val).GetInstanceID(), out var value)) { Add(value, new Candidate(value, CandidateSource.LevelPoint, ((Component)item).transform.position, Quaternion.identity, 1000)); LevelPointCandidates++; } } } } internal bool TryResolvePosition(RoomKey room, IRandomSource rng, out Vector3 position, out Quaternion rotation, out CandidateSource source) { //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_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: 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_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: 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) position = Vector3.zero; rotation = Quaternion.identity; source = CandidateSource.ValuableVolume; if (!_byRoom.TryGetValue(room, out List<Candidate> value)) { return false; } foreach (Candidate item in value) { Vector3 val = item.Position; if (item.Source == CandidateSource.LevelPoint && _config.LevelPointOffset.Value > 0f) { double num = rng.NextDouble() * Math.PI * 2.0; float value2 = _config.LevelPointOffset.Value; val += new Vector3((float)Math.Cos(num), 0f, (float)Math.Sin(num)) * value2; } if (TryFindGround(val, out var grounded) && IsClear(grounded)) { position = grounded; rotation = Quaternion.Euler(0f, (float)rng.Next(0, 360), 0f); source = item.Source; return true; } } return false; } private bool TryFindGround(Vector3 candidate, out Vector3 grounded) { //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_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_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: 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_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) grounded = Vector3.zero; Vector3 val = candidate + Vector3.up * _config.RaycastUp.Value; RaycastHit[] array = Physics.RaycastAll(val, Vector3.down, _config.RaycastDistance.Value, _config.GroundRaycastMask.Value, (QueryTriggerInteraction)1); if (array.Length == 0) { return false; } Array.Sort(array, (RaycastHit a, RaycastHit b) => ((RaycastHit)(ref a)).distance.CompareTo(((RaycastHit)(ref b)).distance)); RaycastHit[] array2 = array; for (int num = 0; num < array2.Length; num++) { RaycastHit val2 = array2[num]; Collider collider = ((RaycastHit)(ref val2)).collider; if (!((Object)(object)collider == (Object)null) && !collider.isTrigger && !((Object)(object)((Component)collider).GetComponentInParent<PhysGrabObject>() != (Object)null) && !((Object)(object)((Component)collider).GetComponentInParent<ValuableObject>() != (Object)null) && !((Object)(object)((Component)collider).GetComponentInParent<PlayerAvatar>() != (Object)null)) { grounded = ((RaycastHit)(ref val2)).point + Vector3.up * _config.GroundOffset.Value; return true; } } return false; } private bool IsClear(Vector3 position) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) float value = _config.ClearanceRadius.Value; if (value <= 0f) { return true; } Collider[] array = Physics.OverlapSphere(position, value, _config.GroundRaycastMask.Value, (QueryTriggerInteraction)1); Collider[] array2 = array; foreach (Collider val in array2) { if (!((Object)(object)val == (Object)null) && !val.isTrigger) { if ((Object)(object)((Component)val).GetComponentInParent<ValuableObject>() != (Object)null) { return false; } if ((Object)(object)((Component)val).GetComponentInParent<ItemAttributes>() != (Object)null) { return false; } if ((Object)(object)((Component)val).GetComponentInParent<PlayerAvatar>() != (Object)null) { return false; } } } return true; } private void Add(RoomKey key, Candidate candidate) { //IL_0006: 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) if (!_byRoom.TryGetValue(key, out List<Candidate> value)) { value = new List<Candidate>(); _byRoom[key] = value; } value.Add(candidate); } private static int IndexOf(IReadOnlyList<Type> allowed, Type value) { //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) for (int i = 0; i < allowed.Count; i++) { if (allowed[i] == value) { return i; } } return -1; } private static int CompareCandidates(Candidate left, Candidate right) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002e: 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_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0055: 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) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) int num = left.Priority.CompareTo(right.Priority); if (num != 0) { return num; } int num2 = left.Position.x.CompareTo(right.Position.x); if (num2 != 0) { return num2; } int num3 = left.Position.y.CompareTo(right.Position.y); if (num3 == 0) { return left.Position.z.CompareTo(right.Position.z); } return num3; } } internal sealed class UsedVolumeMarker : MonoBehaviour { } } namespace MedkitScatter.Patches { internal static class LevelGeneratorPatch { internal static void GenerateDonePostfix() { try { MedkitScatterService.OnLevelGenerated(); } catch (Exception exception) { ModLog.CountedError("LevelGeneratorPatch.GenerateDonePostfix", exception); } } } internal static class ValuableDirectorPatch { internal static void MarkVolume(object[] __args) { try { if (__args == null) { return; } foreach (object obj in __args) { ValuableVolume val = (ValuableVolume)((obj is ValuableVolume) ? obj : null); if (val != null && !((Object)(object)val == (Object)null)) { if ((Object)(object)((Component)val).gameObject.GetComponent<UsedVolumeMarker>() == (Object)null) { ((Component)val).gameObject.AddComponent<UsedVolumeMarker>(); } break; } } } catch (Exception exception) { ModLog.CountedError("ValuableDirectorPatch.MarkVolume", exception); } } } }