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 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 _allowedVolumeTypes; private List? _parsedVolumeTypes; private string _parsedFrom = string.Empty; internal ConfigEntry Enabled { get; } internal ConfigEntry ExtendedLogging { get; } internal ConfigEntry FixedSeed { get; } internal ConfigEntry MaxSpawnFailuresPerLevel { get; } internal ConfigEntry MaxExceptionsPerSession { get; } internal ConfigEntry RoomsPerMedkit { get; } internal ConfigEntry Rounding { get; } internal ConfigEntry MinMedkits { get; } internal ConfigEntry MaxMedkits { get; } internal ConfigEntry CountNormal { get; } internal ConfigEntry CountPassages { get; } internal ConfigEntry CountDeadEnds { get; } internal ConfigEntry CountExtraction { get; } internal ConfigEntry CountSpecial { get; } internal ConfigEntry SpawnInNormal { get; } internal ConfigEntry SpawnInPassages { get; } internal ConfigEntry SpawnInDeadEnds { get; } internal ConfigEntry SpawnInSpecial { get; } internal ConfigEntry PreferDistantRooms { get; } internal ConfigEntry WeightSmall { get; } internal ConfigEntry WeightMedium { get; } internal ConfigEntry WeightLarge { get; } internal ConfigEntry GuaranteeVariety { get; } internal ConfigEntry DifficultyScaling { get; } internal ConfigEntry DifficultyScalingFactor { get; } internal ConfigEntry AllowLevelPointFallback { get; } internal ConfigEntry LevelPointOffset { get; } internal ConfigEntry RaycastUp { get; } internal ConfigEntry RaycastDistance { get; } internal ConfigEntry GroundRaycastMask { get; } internal ConfigEntry GroundOffset { get; } internal ConfigEntry ClearanceRadius { get; } internal ConfigEntry SpawnDelayFrames { get; } internal IReadOnlyList 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 list = new List(); 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("General", "Enabled", true, "Полностью включить или выключить разброс аптечек."); ExtendedLogging = file.Bind("General", "ExtendedLogging", false, "Подробный лог плана и каждого спавна. Пишется уровнем Info, поэтому виден и в консоли, и в LogOutput.log."); FixedSeed = file.Bind("General", "FixedSeed", 0, "0 = сид выводится из забега и имени уровня. Любое другое значение — фиксированный сид: одинаковая раскладка при каждом прогоне."); MaxSpawnFailuresPerLevel = file.Bind("General", "MaxSpawnFailuresPerLevel", 3, new ConfigDescription("Сколько неудачных попыток размещения терпим за уровень, прежде чем прекратить расстановку на этом уровне. Счётчик сбрасывается каждый уровень.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 64), Array.Empty())); MaxExceptionsPerSession = file.Bind("General", "MaxExceptionsPerSession", 10, new ConfigDescription("После стольких пойманных исключений плагин выключается до перезапуска игры.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 1000), Array.Empty())); RoomsPerMedkit = file.Bind("Amount", "RoomsPerMedkit", 4, new ConfigDescription("Делитель формулы «комнат / N». Значение по умолчанию 4 — прямое прочтение требования заказчика.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 64), Array.Empty())); Rounding = file.Bind("Amount", "RoundingMode", (RoundingMode)0, "Округление результата деления: Floor (вниз), Round (к ближайшему, половина от нуля), Ceil (вверх)."); MinMedkits = file.Bind("Amount", "MinMedkits", 1, new ConfigDescription("Нижняя граница количества аптечек на уровне.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 64), Array.Empty())); MaxMedkits = file.Bind("Amount", "MaxMedkits", 0, new ConfigDescription("Верхняя граница количества. 0 = без лимита. Если Min больше Max, побеждает Min.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 128), Array.Empty())); CountNormal = file.Bind("Rooms", "CountNormal", true, "Учитывать обычные комнаты в знаменателе формулы."); CountPassages = file.Bind("Rooms", "CountPassages", true, "Учитывать коридоры в знаменателе. По умолчанию да: коридоры формируют размер уровня и должны влиять на количество аптечек."); CountDeadEnds = file.Bind("Rooms", "CountDeadEnds", true, "Учитывать тупики в знаменателе."); CountExtraction = file.Bind("Rooms", "CountExtraction", false, "Учитывать комнаты извлечения в знаменателе."); CountSpecial = file.Bind("Rooms", "CountSpecial", false, "Учитывать модули типа Special в знаменателе. Этот тип появился в игре после версии 0.1.2 и в исходной спецификации не описан, поэтому по умолчанию он не считается и не заселяется."); SpawnInNormal = file.Bind("Rooms", "SpawnInNormal", true, "Разрешить размещение в обычных комнатах."); SpawnInPassages = file.Bind("Rooms", "SpawnInPassages", false, "Разрешить размещение в коридорах. По умолчанию нет: лут в проходном коридоре плохо читается игроком."); SpawnInDeadEnds = file.Bind("Rooms", "SpawnInDeadEnds", true, "Разрешить размещение в тупиках."); SpawnInSpecial = file.Bind("Rooms", "SpawnInSpecial", false, "Разрешить размещение в модулях типа Special."); PreferDistantRooms = file.Bind("Rooms", "PreferDistantRooms", false, "Смещать выбор к комнатам подальше от стартовой."); WeightSmall = file.Bind("Tiers", "WeightSmall", 60, new ConfigDescription("Вес аптечки на 25 HP.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 1000), Array.Empty())); WeightMedium = file.Bind("Tiers", "WeightMedium", 30, new ConfigDescription("Вес аптечки на 50 HP.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 1000), Array.Empty())); WeightLarge = file.Bind("Tiers", "WeightLarge", 10, new ConfigDescription("Вес аптечки на 100 HP.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 1000), Array.Empty())); GuaranteeVariety = file.Bind("Tiers", "GuaranteeVariety", true, "При трёх и более аптечках гарантировать все три тира. ВНИМАНИЕ: это осознанно переопределяет веса для первых трёх позиций — при 5 аптечках фактическая доля Large будет не меньше 20 %, а не 10 %. Нужна честная пропорция — выключите."); DifficultyScaling = file.Bind("Tiers", "DifficultyScaling", false, "Смещать веса к крупным аптечкам с ростом номера уровня в забеге."); DifficultyScalingFactor = file.Bind("Tiers", "DifficultyScalingFactor", 0.15f, new ConfigDescription("Коэффициент k: вес Large умножается на (1 + k * пройденных уровней), вес Small на столько же делится.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 5f), Array.Empty())); _allowedVolumeTypes = file.Bind("Placement", "AllowedVolumeTypes", "Small,Medium,Tiny,Big", "Типы ValuableVolume, которые можно занимать, через запятую. ПОРЯДОК ЗАДАЁТ ПРИОРИТЕТ: чем левее, тем охотнее используется. Доступные значения: Tiny, Small, Medium, Big, Wide, Tall, VeryTall."); AllowLevelPointFallback = file.Bind("Placement", "AllowLevelPointFallback", true, "Использовать навигационные точки уровня, если свободных объёмов в комнате нет."); LevelPointOffset = file.Bind("Placement", "LevelPointOffset", 0.35f, new ConfigDescription("Горизонтальный сдвиг от узла навигации, метры — чтобы аптечка не стояла ровно в точке маршрута.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 3f), Array.Empty())); RaycastUp = file.Bind("Placement", "RaycastUp", 1f, new ConfigDescription("Высота начала луча над кандидатом, метры.", (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 10f), Array.Empty())); RaycastDistance = file.Bind("Placement", "RaycastDistance", 3f, new ConfigDescription("Длина луча вниз, метры.", (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 30f), Array.Empty())); GroundRaycastMask = file.Bind("Placement", "GroundRaycastMask", -1, "LayerMask для поиска пола и проверки пересечений. -1 = все слои."); GroundOffset = file.Bind("Placement", "GroundOffset", 0.08f, new ConfigDescription("Подъём над полом после луча, метры — чтобы аптечка не проваливалась.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); ClearanceRadius = file.Bind("Placement", "ClearanceRadius", 0.45f, new ConfigDescription("Радиус проверки свободного места, метры.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 5f), Array.Empty())); SpawnDelayFrames = file.Bind("Placement", "SpawnDelayFrames", 2, new ConfigDescription("Задержка после завершения генерации, кадры. Нужна, чтобы PhysGrabObject ванильных предметов успел настроить свои коллайдеры.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 120), Array.Empty())); } 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? _levelsCompletedFunc; private static Func? _runIsShop; private static Func? _runIsLobby; private static Func? _runIsLobbyMenu; private static Func? _runIsArena; private static Func? _runIsTutorial; private static Type? _itemHealthPackType; private static FieldInfo? _healAmountField; private static readonly List Report = new List(); 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 dictionary = new Dictionary(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? predicate) { return predicate?.Invoke() ?? false; } private static Func? 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)Delegate.CreateDelegate(typeof(Func), methodInfo); } private static Func? 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)Delegate.CreateDelegate(typeof(Func), 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(); } ((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? _cache; private StatsManager? _boundTo; internal Dictionary? 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 itemDictionary = instance.itemDictionary; if (itemDictionary == null || itemDictionary.Count == 0) { ModLog.Warning("StatsManager.itemDictionary пуст — уровень пропущен."); return null; } Dictionary dictionary = ResolveByExactNames(itemDictionary) ?? ResolveByItemType(itemDictionary); if (dictionary == null) { return null; } ValidateHealAmounts(dictionary); _cache = dictionary; _boundTo = instance; return dictionary; } private static Dictionary? ResolveByExactNames(Dictionary 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)0, value }, { (MedkitTier)1, value2 }, { (MedkitTier)2, value3 } }; } private static Dictionary? ResolveByItemType(Dictionary dictionary) { List list = new List(); 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> list2 = new List>(3); foreach (Item item in list) { if (item.prefab == null || !((PrefabRef)(object)item.prefab).IsValid()) { ModLog.DisableForSession("у аптечки \"" + ((Object)item).name + "\" невалидный PrefabRef."); return null; } GameObject prefab = ((PrefabRef)(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(num.Value, item)); } list2.Sort((KeyValuePair a, KeyValuePair 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)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(ref item.itemType)/*cast due to .constrained prefix*/).ToString(), "healthPack", StringComparison.Ordinal); } private static void ValidateHealAmounts(Dictionary 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 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)(object)value.prefab).IsValid()) { return; } GameObject prefab = ((PrefabRef)(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 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> candidates = spawnPointResolver.Prescan(list); ModLog.Detail("Candidates: volumes=" + spawnPointResolver.VolumeCandidates + " free, levelPoints=" + spawnPointResolver.LevelPointCandidates + " fallback"); List 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 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 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 queue = new Queue(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)(object)item.prefab).IsValid()) { ModLog.Warning("PrefabRef невалиден у предмета \"" + ((Object)item).name + "\", пропускаем."); return null; } if (!SemiFunc.IsMasterClientOrSingleplayer()) { return null; } if (SemiFunc.IsMultiplayer()) { return PhotonNetwork.InstantiateRoomObject(((PrefabRef)(object)item.prefab).ResourcePath, position, rotation, (byte)0, (object[])null); } GameObject prefab = ((PrefabRef)(object)item.prefab).Prefab; if ((Object)(object)prefab == (Object)null) { ModLog.Warning("Префаб не резолвится у предмета \"" + ((Object)item).name + "\", пропускаем."); return null; } return Object.Instantiate(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 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(); List list = new List(array.Length); HashSet hashSet = new HashSet(); 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 BuildDescriptors(List modules, Dictionary> 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 list = new List(modules.Count); foreach (ModuleEntry module in modules) { List 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 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 dictionary = new Dictionary(); foreach (ModuleEntry module in modules) { dictionary.TryGetValue(module.Kind, out var value); dictionary[module.Kind] = value + 1; } List list = new List(); 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> _byRoom = new Dictionary>(); internal int VolumeCandidates { get; private set; } internal int LevelPointCandidates { get; private set; } internal SpawnPointResolver(ModConfig config) { _config = config; } internal Dictionary> Prescan(List modules) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) _byRoom.Clear(); VolumeCandidates = 0; LevelPointCandidates = 0; Dictionary dictionary = new Dictionary(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 value in _byRoom.Values) { value.Sort(CompareCandidates); } return _byRoom; } private void CollectVolumes(Dictionary 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(); IReadOnlyList 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() != (Object)null || (Object)(object)((Component)((Component)val).transform).GetComponentInParent() != (Object)null) { continue; } int num = IndexOf(allowedVolumeTypes, val.VolumeType); if (num >= 0) { Module componentInParent = ((Component)val).GetComponentInParent(); 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 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 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()); if ((Object)(object)val == (Object)null) { val = ((Component)item).GetComponentInParent(); } 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 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() != (Object)null) && !((Object)(object)((Component)collider).GetComponentInParent() != (Object)null) && !((Object)(object)((Component)collider).GetComponentInParent() != (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() != (Object)null) { return false; } if ((Object)(object)((Component)val).GetComponentInParent() != (Object)null) { return false; } if ((Object)(object)((Component)val).GetComponentInParent() != (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 value)) { value = new List(); _byRoom[key] = value; } value.Add(candidate); } private static int IndexOf(IReadOnlyList 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() == (Object)null) { ((Component)val).gameObject.AddComponent(); } break; } } } catch (Exception exception) { ModLog.CountedError("ValuableDirectorPatch.MarkVolume", exception); } } } }