Decompiled source of DynamicPressure v2.0.0
Dynamic Pressure.dll
Decompiled a week ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Serialization.Json; using System.Runtime.Versioning; using System.Text; using System.Threading.Tasks; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using I2.Loc; using LevelGeneration; using PerfectRandom.Sulfur.Core; using PerfectRandom.Sulfur.Core.Items; using PerfectRandom.Sulfur.Core.LevelGeneration; using PerfectRandom.Sulfur.Core.Stats; using PerfectRandom.Sulfur.Core.UI; using PerfectRandom.Sulfur.Core.Units; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("Dynamic Pressure")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Dynamic Pressure")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("bf243145-c378-4bad-8447-4747dbf03372")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("1.0.0.0")] namespace Ryuka.Sulfur.DynamicPressure; [BepInPlugin("ryuka.sulfur.dynamicpressure", "Dynamic Pressure", "2.0.0")] public sealed class DynamicPressurePlugin : BaseUnityPlugin { private sealed class WeaponRecord { public float Damage; public int Hits; public int Kills; } private sealed class LevelRecord { public float StartTime; public readonly float[] PhaseSeconds = new float[4]; public float DamageTaken; public float DamageTakenFromAdded; public int Hits; public float LowestHealth = 1f; public bool Died; public int AddedKills; public int OriginalKills; public int Added; public int Surges; public readonly Dictionary<string, WeaponRecord> Weapons = new Dictionary<string, WeaponRecord>(); public readonly HashSet<int> DeadUnits = new HashSet<int>(); } private sealed class Snapshot { public bool hasGameManager; public bool hasPlayer; public bool playerAlive; public bool inSafeZone; public string gameState = "Unknown"; public string currentEnvironment = "Unknown"; public float playerHp; public string playerRoomName = "null"; public int routeRoomIndex; public int threatsPresent; public bool engaged; public float weightedThreat; public int modAlive; public float modAliveCost; public bool spawningAllowed; public SpawnBlockReason blockReason; } private enum SpawnZone { Ahead, Side, Rear } private sealed class ZoneBuckets { public readonly List<SpawnPointChoice> Ahead = new List<SpawnPointChoice>(); public readonly List<SpawnPointChoice> Side = new List<SpawnPointChoice>(); public readonly List<SpawnPointChoice> Rear = new List<SpawnPointChoice>(); public List<SpawnPointChoice> For(SpawnZone zone) { return zone switch { SpawnZone.Ahead => Ahead, SpawnZone.Side => Side, _ => Rear, }; } } private struct SpawnPointChoice { public NPCSpawn spawn; public Vector3 position; public Room room; public float distanceToPlayer; public SpawnZone zone; } private const float SampleIntervalSeconds = 5f; private const float FeedbackMessageSeconds = 6f; private CombatRecorder _recorder; private LevelRecord _levelRecord; private float _nextSampleTime; private string _feedbackMessage; private float _feedbackMessageUntil; private bool _sessionRecorded; private float _levelDensity; private int _levelSpawnableRooms; private int _levelRouteRooms; private float _levelAverageCost; private bool _levelMeasured; private static bool _hitIsFromPlayer; private static DamageSourceData _hitSource; public const string PluginGuid = "ryuka.sulfur.dynamicpressure"; public const string PluginName = "Dynamic Pressure"; public const string PluginVersion = "2.0.0"; private const float ScanIntervalSeconds = 0.5f; private const float DiagLogIntervalSeconds = 5f; private const float NearbyThreatDistance = 40f; private const float ModUnitTrackedDistance = 60f; private const float OriginalThreatWeight = 0.5f; private const float FallbackPackBudget = 12f; private const float FallbackPackDensity = 1f; private static DynamicPressurePlugin _instance; private static int _levelGeneration; private static int _capturedGeneration = -1; private static float _capturedPackBudget; private static float _capturedPackDensity; private Harmony _harmony; private Settings _settings; private Director _director; private readonly List<string> _recentEvents = new List<string>(); private int _seenGeneration; private bool _levelStarted; private PressureStyle _appliedStyle; private DirectorTuning _appliedTuning; private float _packBudget; private bool _lateGame; private float _nextScanTime; private float _lastTickTime; private float _nextDiagLogTime; private int _diagWindowBatches; private int _diagWindowSpawned; private string _pluginDirectory; private float _nextLanguageRetryTime; private GUIStyle _overlayStyle; private Vector2 _overlayScroll; private OnLanguageChange _reloadOverlayLanguage; private bool _spawnInProgress; private bool _inLevelTransition; private bool _hitPatchActive; private bool _pendingMigrationNotice; private float _pendingSurgeBudget; private int _waveId; private string _lastUpdateFailure; private int _updateFailureCount; private Snapshot _snapshot = new Snapshot(); private SpawnBlockReason _lastBlockReason; private string _lastDecision = "None"; private string _lastSpawnSummary = "None"; private const int TrickleMaxUnits = 3; private const int SurgeMaxUnits = 12; private const float LegacyDistanceMin = 8f; private const float DistanceMax = 60f; private const float FairDistanceMin = 15f; private const float RearDistanceMin = 25f; private const float SurgeClusterRadius = 14f; private const float RearDotThreshold = -0.25f; private const int MaxLineChecksPerPick = 16; private const int NearestPointsToChooseFrom = 5; private const float ViewportMargin = 0.05f; private const float SightCheckHeight = 1.2f; private const float BigSightCheckHeight = 2.5f; private static readonly MutationDefinitions[] AllowedMutations; private readonly List<UnitSO> _eligible = new List<UnitSO>(); private readonly List<MutationDefinitions> _mutationChoices = new List<MutationDefinitions>(); private readonly HashSet<NPCSpawn> _usedThisBatch = new HashSet<NPCSpawn>(); private bool IsRecording { get { if (_recorder != null) { return _recorder.IsRecording; } return false; } } private PressurePreset CurrentPreset => PressurePreset.For(_settings.EffectiveStyle); private void CreateRecorder() { _recorder = new CombatRecorder(Path.Combine(Paths.BepInExRootPath, "DynamicPressure", "feedback"), ((BaseUnityPlugin)this).Logger); } private void SyncRecording() { bool flag = _settings.EnableMod.Value && _settings.RecordCombatData.Value; if (flag && !_recorder.NeedsStop) { _recorder.Start(); _sessionRecorded = false; ResumeLevelRecord(); } else if (!flag && _recorder.NeedsStop) { RecordLevelEnd(); _recorder.Stop(); } } private void ResumeLevelRecord() { GameManager instance = StaticInstance<GameManager>.Instance; if (IsRecording && (Object)(object)instance != (Object)null && (Object)(object)instance.PlayerUnit != (Object)null && _levelStarted && !_inLevelTransition) { RecordLevelStart(instance, joinedPartway: true); } } private void RecordSession() { List<object> list = new List<object>(); foreach (PluginInfo value in Chainloader.PluginInfos.Values) { list.Add(new Dictionary<string, object> { { "guid", value.Metadata.GUID }, { "name", value.Metadata.Name }, { "version", value.Metadata.Version.ToString() } }); } _recorder.Record("session", Time.time, "mod", "2.0.0", "game", Application.version, "plugins", list); } private void RememberLevelCalibration(float density, int spawnableRooms, int routeRooms, float averageCost, bool measured) { _levelDensity = density; _levelSpawnableRooms = spawnableRooms; _levelRouteRooms = routeRooms; _levelAverageCost = averageCost; _levelMeasured = measured; } private void RecordLevelStart(GameManager gm, bool joinedPartway) { if (IsRecording && !gm.InSafeZone && !gm.IsPlayerAtChurch) { if (!_sessionRecorded) { _sessionRecorded = true; RecordSession(); } _levelRecord = new LevelRecord { StartTime = Time.time }; _nextSampleTime = Time.time + 5f; _recorder.Record("level_start", Time.time, "env", ((Object)(object)gm.currentEnvironment != (Object)null) ? ((object)Unsafe.As<WorldEnvironmentIds, WorldEnvironmentIds>(ref gm.currentEnvironment.id)/*cast due to .constrained prefix*/).ToString() : null, "level", gm.currentLevelIndex, "partial", joinedPartway, "difficulty", SaveDifficulty(), "style", _appliedStyle.ToString(), "position", _settings.Position.Value.ToString(), "reinforcement", _settings.ReinforcementMultiplier.Value, "rest", _settings.RestMultiplier.Value, "elite", _settings.EliteMultiplier.Value, "pack_budget", _packBudget, "density", _levelDensity, "rooms", _levelSpawnableRooms, "route_rooms", _levelRouteRooms, "avg_cost", _levelAverageCost, "measured", _levelMeasured, "late", _lateGame, "budget", _director.TotalBudget, "target", _director.TargetThreat, "gear", ReadGear(gm)); } } private void RecordLevelEnd() { LevelRecord levelRecord = _levelRecord; _levelRecord = null; if (levelRecord == null || !IsRecording) { return; } Dictionary<string, object> dictionary = new Dictionary<string, object>(); foreach (KeyValuePair<string, WeaponRecord> weapon in levelRecord.Weapons) { dictionary[weapon.Key] = new Dictionary<string, object> { { "damage", weapon.Value.Damage }, { "hits", weapon.Value.Hits }, { "kills", weapon.Value.Kills } }; } GameManager instance = StaticInstance<GameManager>.Instance; _recorder.Record("level_end", Time.time, "seconds", Time.time - levelRecord.StartTime, "build_up", levelRecord.PhaseSeconds[0], "sustain", levelRecord.PhaseSeconds[1], "fade", levelRecord.PhaseSeconds[2], "relax", levelRecord.PhaseSeconds[3], "hits", levelRecord.Hits, "damage_taken", levelRecord.DamageTaken, "damage_from_added", levelRecord.DamageTakenFromAdded, "lowest_health", levelRecord.LowestHealth, "died", levelRecord.Died, "added", levelRecord.Added, "surges", levelRecord.Surges, "added_kills", levelRecord.AddedKills, "original_kills", levelRecord.OriginalKills, "spent", _director.SpentThreat, "budget", _director.TotalBudget, "pace", _director.Pace, "weapons", dictionary, "gear", ((Object)(object)instance != (Object)null) ? ReadGear(instance) : null); _recorder.Flush(); } private void RecordTick(Snapshot snapshot, float delta) { if (IsRecording && _levelRecord != null && !((Object)(object)StaticInstance<GameManager>.Instance == (Object)null)) { _levelRecord.PhaseSeconds[(int)_director.Phase] += delta; if (snapshot.hasPlayer) { _levelRecord.LowestHealth = Mathf.Min(_levelRecord.LowestHealth, snapshot.playerHp); _levelRecord.Died |= !snapshot.playerAlive; } if (!(Time.time < _nextSampleTime)) { _nextSampleTime = Time.time + 5f; _recorder.Record("sample", Time.time, "phase", _director.Phase.ToString(), "intensity", _director.Intensity, "pace", _director.Pace, "hp", snapshot.playerHp, "room", snapshot.routeRoomIndex, "engaged", snapshot.engaged, "present", snapshot.threatsPresent, "threat", snapshot.weightedThreat, "target", _director.TargetThreat, "added_alive", snapshot.modAlive, "added_cost", snapshot.modAliveCost, "spent", _director.SpentThreat, "unlocked", _director.UnlockedBudget, "blocked", snapshot.spawningAllowed ? null : snapshot.blockReason.ToString()); } } } private void RecordSpawn(UnitSO unit, SpawnPointChoice point, string reason, string mutation) { if (IsRecording && _levelRecord != null) { _levelRecord.Added++; if (reason == "Surge") { _levelRecord.Surges++; } _recorder.Record("spawn", Time.time, "unit", ((Object)unit).name, "cost", unit.SpawnCost, "kind", reason, "zone", point.zone.ToString(), "distance", point.distanceToPlayer, "mutation", mutation, "wave", _waveId); } } private void RecordPlayerHit(Unit player, float lost, float maxHealth, DamageSourceData source) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) if (IsRecording && _levelRecord != null && !(lost <= 0f)) { Unit sourceUnit = source.sourceUnit; Npc val = (Npc)(object)((sourceUnit is Npc) ? sourceUnit : null); bool flag = (Object)(object)val != (Object)null && (Object)(object)((Component)val).GetComponent<DynamicPressureSpawnMarker>() != (Object)null; _levelRecord.Hits++; _levelRecord.DamageTaken += lost; if (flag) { _levelRecord.DamageTakenFromAdded += lost; } _recorder.Record("hit", Time.time, "damage", lost, "hp", (maxHealth > 0f) ? (player.GetCurrentHealth() / maxHealth) : 0f, "periodic", source.states.isPeriodicModifier, "from", ((Object)(object)val != (Object)null && (Object)(object)((Unit)val).unitSO != (Object)null) ? ((Object)((Unit)val).unitSO).name : null, "from_added", flag); } } private void RecordDamageDealt(float damage, DamageSourceData source) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) if (_levelRecord != null && !(damage <= 0f) && IsRecording) { WeaponRecord weaponRecord = WeaponEntry(source); weaponRecord.Damage += damage; weaponRecord.Hits++; } } private WeaponRecord WeaponEntry(DamageSourceData source) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) string key = (((Object)(object)source.sourceWeapon != (Object)null && (Object)(object)source.sourceWeapon.weaponDefinition != (Object)null) ? ((Object)source.sourceWeapon.weaponDefinition).name : "other"); if (!_levelRecord.Weapons.TryGetValue(key, out var value)) { value = new WeaponRecord(); _levelRecord.Weapons[key] = value; } return value; } private void RecordDeath(Npc npc) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) if (!IsRecording || _levelRecord == null || !_levelRecord.DeadUnits.Add(((Object)npc).GetInstanceID())) { return; } bool hitIsFromPlayer = _hitIsFromPlayer; DynamicPressureSpawnMarker component = ((Component)npc).GetComponent<DynamicPressureSpawnMarker>(); if (hitIsFromPlayer) { WeaponEntry(_hitSource).Kills++; } if ((Object)(object)component == (Object)null) { if (IsHostileToPlayer(npc)) { _levelRecord.OriginalKills++; } return; } _levelRecord.AddedKills++; _recorder.Record("added_death", Time.time, "unit", ((Object)(object)component.sourceUnitSo != (Object)null) ? ((Object)component.sourceUnitSo).name : null, "alive_for", Time.time - component.spawnTime, "reached_player_after", (component.firstTargetTime >= 0f) ? ((object)(component.firstTargetTime - component.spawnTime)) : null, "by_player", hitIsFromPlayer, "wave", component.waveId); } private static bool IsFromPlayer(DamageSourceData source) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0008: 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) if (!source.isPlayer) { if ((Object)(object)source.sourceUnit != (Object)null) { return source.sourceUnit.isPlayer; } return false; } return true; } private static List<object> ReadGear(GameManager gm) { //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) List<object> list = new List<object>(); try { if ((Object)(object)gm.EquipmentManager == (Object)null) { return list; } foreach (KeyValuePair<InventorySlot, InventoryItem> equippedItem in gm.EquipmentManager.equippedItems) { InventoryItem value = equippedItem.Value; if ((Object)(object)value == (Object)null || (Object)(object)value.itemDefinition == (Object)null) { continue; } List<object> list2 = new List<object>(); foreach (EnchantmentDefinition enchantment in value.enchantments) { if ((Object)(object)enchantment != (Object)null) { list2.Add(((Object)enchantment).name); } } Dictionary<string, object> dictionary = new Dictionary<string, object> { { "slot", ((object)equippedItem.Key/*cast due to .constrained prefix*/).ToString() }, { "item", ((Object)value.itemDefinition).name } }; if ((Object)(object)value.weaponDefinition != (Object)null) { dictionary["rank"] = value.GetRankLevel(); } if (list2.Count > 0) { dictionary["enchantments"] = list2; } list.Add(dictionary); } } catch (Exception) { } return list; } private static string SaveDifficulty() { try { return ((object)Unsafe.As<Difficulty, Difficulty>(ref SulfurSaveState.active.DIFFICULTY)/*cast due to .constrained prefix*/).ToString(); } catch (Exception) { return null; } } private void ShowFeedbackMessage(string text) { _feedbackMessage = text; _feedbackMessageUntil = Time.unscaledTime + 6f; } private void DrawFeedbackMessage() { //IL_0030: Unknown result type (might be due to invalid IL or missing references) if (_feedbackMessage != null && Time.unscaledTime < _feedbackMessageUntil) { GUI.Label(new Rect(20f, 60f, (float)Screen.width - 40f, 40f), OverlayPalette.Tint("#D89CFF", _feedbackMessage), _overlayStyle); } } private void ExportCombatData() { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) if (!_settings.RecordCombatData.Value && !_recorder.HasRecordedData) { return; } GameManager instance = StaticInstance<GameManager>.Instance; if ((Object)(object)instance != (Object)null && ((object)instance.gameState/*cast due to .constrained prefix*/).ToString() != "Paused" && !instance.InSafeZone) { ShowFeedbackMessage(OverlayText.Get("alert.exportWhere", "Pause the game or go to the hub to export combat data.")); return; } if (!_recorder.TryExport(out var exportPath)) { ShowFeedbackMessage(OverlayText.Get("alert.exportEmpty", "No combat data to export yet.")); return; } Log("Combat data exported to " + exportPath); ShowFeedbackMessage(OverlayText.Get("alert.exported", "Combat data exported. Send the highlighted file to the mod author.")); try { using (Process.Start("explorer.exe", "/select,\"" + exportPath + "\"")) { } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not open the file manager (" + ex.GetType().Name + "). The export is at the path logged above.")); } } private static string Label(string key, string english) { return OverlayPalette.Tint("#B9B9B9", OverlayText.Get(key, english) + ": "); } private static string Header(string key, string english) { return OverlayPalette.Tint("#7EC8FF", OverlayText.Get(key, english)); } private static string Separator() { return OverlayPalette.Tint("#8A8A8A", " | "); } private static string TintDecision(string decision) { return decision switch { "SPAWNED" => OverlayPalette.Tint("#7BE07B", decision), "FAILED" => OverlayPalette.Tint("#FF6B6B", decision), "BLOCKED" => OverlayPalette.Tint("#FFC451", decision), _ => OverlayPalette.Tint("#8A8A8A", decision), }; } private static string TintReason(SpawnBlockReason reason) { switch (reason) { case SpawnBlockReason.None: return OverlayPalette.Tint("#7BE07B", reason); case SpawnBlockReason.NoCandidateUnits: case SpawnBlockReason.NoFairSpawnPoint: case SpawnBlockReason.SpawnAsyncFailed: case SpawnBlockReason.UpdateFailed: return OverlayPalette.Tint("#FF6B6B", reason); default: return OverlayPalette.Tint("#FFC451", reason); } } private void OnGUI() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) if (_overlayStyle == null) { _overlayStyle = new GUIStyle(GUI.skin.label); _overlayStyle.richText = true; } DrawFeedbackMessage(); if (_settings.EnableOverlay.Value) { float num = Mathf.Min(680f, (float)Screen.width - 40f); float num2 = Mathf.Max(240f, (float)Screen.height - 120f - 20f); Rect val = default(Rect); ((Rect)(ref val))..ctor(20f, 120f, num, num2); GUI.Box(val, OverlayText.Get("overlay.title", "Dynamic Pressure Debug")); GUILayout.BeginArea(new Rect(((Rect)(ref val)).x + 10f, ((Rect)(ref val)).y + 25f, ((Rect)(ref val)).width - 20f, ((Rect)(ref val)).height - 35f)); _overlayScroll = GUILayout.BeginScrollView(_overlayScroll, Array.Empty<GUILayoutOption>()); Row(Label("overlay.enabled", "Enabled") + OverlayPalette.Flag(_settings.EnableMod.Value, nominal: true) + Separator() + Label("overlay.style", "Style") + OverlayPalette.Tint("#D89CFF", _settings.EffectiveStyle) + Separator() + Label("overlay.position", "Position") + OverlayPalette.Tint("#D89CFF", _settings.Position.Value)); Row(Label("overlay.gameState", "GameState") + OverlayPalette.Tint((_snapshot.gameState == "Running") ? "#7BE07B" : "#FFC451", _snapshot.gameState) + Separator() + Label("overlay.safeZone", "SafeZone") + OverlayPalette.Flag(_snapshot.inSafeZone, nominal: false) + Separator() + Label("overlay.env", "Env") + OverlayPalette.Tint("#FFFFFF", _snapshot.currentEnvironment)); Row(Label("overlay.player", "Player") + (_snapshot.hasPlayer ? OverlayPalette.Tint("#7BE07B", OverlayText.Get("overlay.ok", "OK")) : OverlayPalette.Tint("#FF6B6B", OverlayText.Get("overlay.missing", "Missing"))) + Separator() + Label("overlay.hp", "HP") + OverlayPalette.Tint((_snapshot.playerHp < 0.5f) ? "#FFC451" : "#7BE07B", Mathf.RoundToInt(_snapshot.playerHp * 100f) + "%") + (_hitPatchActive ? string.Empty : (Separator() + OverlayPalette.Tint("#FF6B6B", OverlayText.Get("overlay.noHitSensor", "no hit sensor"))))); if (_lastUpdateFailure != null) { Row(Label("overlay.updateFailure", "Update Error") + OverlayPalette.Tint("#FF6B6B", _lastUpdateFailure) + OverlayPalette.Tint("#8A8A8A", " x" + _updateFailureCount)); } Row(Label("overlay.room", "Room") + OverlayPalette.Tint("#FFFFFF", _snapshot.playerRoomName) + Separator() + Label("overlay.route", "Route") + OverlayPalette.Tint("#FFFFFF", _snapshot.routeRoomIndex)); GUILayout.Space(8f); Row(Header("overlay.section.director", "Director")); Row(Label("overlay.phase", "Phase") + OverlayPalette.Tint("#D89CFF", _director.Phase) + Separator() + Label("overlay.intensity", "Intensity") + OverlayPalette.Tint("#FFFFFF", _director.Intensity.ToString("0.00")) + Separator() + Label("overlay.pace", "Pace") + OverlayPalette.Tint("#FFFFFF", _director.Pace.ToString("0.00"))); Row(Label("overlay.threat", "Threat") + OverlayPalette.Tint("#FFFFFF", _snapshot.weightedThreat.ToString("0.0")) + OverlayPalette.Tint("#8A8A8A", " / " + _director.TargetThreat.ToString("0.0")) + Separator() + Label("overlay.engaged", "Engaged") + OverlayPalette.Flag(_snapshot.engaged, nominal: true) + Separator() + Label("overlay.present", "Present") + OverlayPalette.Count(_snapshot.threatsPresent)); Row(Label("overlay.modAlive", "Mod Alive") + OverlayPalette.Tint("#FFFFFF", _snapshot.modAlive + " (" + _snapshot.modAliveCost.ToString("0") + "/" + _director.AliveCap.ToString("0") + ")") + Separator() + Label("overlay.budget", "Budget") + OverlayPalette.Tint("#FFFFFF", _director.SpentThreat.ToString("0") + " / " + _director.UnlockedBudget.ToString("0") + " / " + _director.TotalBudget.ToString("0"))); GUILayout.Space(8f); Row(Header("overlay.section.spawnDecision", "Spawn Decision")); Row(Label("overlay.lastDecision", "Last Decision") + TintDecision(_lastDecision) + Separator() + Label("overlay.reason", "Reason") + TintReason(_lastBlockReason)); Row(Label("overlay.lastSpawn", "Last Spawn") + OverlayPalette.Tint("#FFFFFF", _lastSpawnSummary)); GUILayout.Space(8f); Row(OverlayPalette.Tint("#8A8A8A", OverlayText.Get("overlay.keys", "Keys: F8 Manual Spawn | F9 Toggle Overlay"))); GUILayout.Space(8f); Row(Header("overlay.section.recentEvents", "Recent Events")); for (int i = 0; i < _recentEvents.Count; i++) { Row(OverlayPalette.Tint("#8A8A8A", _recentEvents[i])); } GUILayout.EndScrollView(); GUILayout.EndArea(); } } private void Row(string text) { GUILayout.Label(text, _overlayStyle, Array.Empty<GUILayoutOption>()); } private void EnsureLanguageSubscription() { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Expected O, but got Unknown if (_reloadOverlayLanguage != null) { if (OverlayText.GameCode == null && Time.unscaledTime >= _nextLanguageRetryTime) { _nextLanguageRetryTime = Time.unscaledTime + 1f; LoadOverlayLanguage(); } return; } try { AsyncAssetLoading instance = StaticInstance<AsyncAssetLoading>.Instance; if (!((Object)(object)instance == (Object)null)) { _reloadOverlayLanguage = new OnLanguageChange(LoadOverlayLanguage); instance.onLanguageChange = (OnLanguageChange)Delegate.Combine((Delegate?)(object)instance.onLanguageChange, (Delegate?)(object)_reloadOverlayLanguage); LoadOverlayLanguage(); } } catch (Exception ex) { _reloadOverlayLanguage = null; ((BaseUnityPlugin)this).Logger.LogWarning((object)("Overlay language will not follow the game language: " + ex.Message)); } } private void StopFollowingLanguage() { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Expected O, but got Unknown OnLanguageChange reloadOverlayLanguage = _reloadOverlayLanguage; _reloadOverlayLanguage = null; if (reloadOverlayLanguage == null) { return; } try { AsyncAssetLoading instance = StaticInstance<AsyncAssetLoading>.Instance; if ((Object)(object)instance != (Object)null) { instance.onLanguageChange = (OnLanguageChange)Delegate.Remove((Delegate?)(object)instance.onLanguageChange, (Delegate?)(object)reloadOverlayLanguage); } } catch (Exception) { } } private void LoadOverlayLanguage() { if (OverlayText.Reload(_pluginDirectory, out var resolvedCode)) { string text = "Overlay language: " + resolvedCode + " (game language code: " + (OverlayText.GameCode ?? "not loaded yet") + ") - " + OverlayText.LoadedCount + " strings from " + (OverlayText.LoadedFrom ?? _pluginDirectory); if (OverlayText.LoadedCount == 0) { ((BaseUnityPlugin)this).Logger.LogWarning((object)(text + " | " + (OverlayText.LastLoadNote ?? "no reason recorded"))); } else { ((BaseUnityPlugin)this).Logger.LogInfo((object)text); } } } 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; _settings = Settings.Bind(((BaseUnityPlugin)this).Config); _pendingMigrationNotice = ConfigMigration.Run(((BaseUnityPlugin)this).Config, _settings, ((BaseUnityPlugin)this).Logger); _director = new Director((float min, float max) => Random.Range(min, max)); CreateRecorder(); _pluginDirectory = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location) ?? string.Empty; _harmony = new Harmony("ryuka.sulfur.dynamicpressure"); TryPatchTransitions(); TryPatchAllDeadTrigger(); TryPatchPlayerHits(); TryPatchNpcDeath(); TryPatchLevelBudget(); Log("Loaded."); } private void OnDestroy() { StopFollowingLanguage(); RecordLevelEnd(); _recorder?.Stop(); try { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } catch { } if ((Object)(object)_instance == (Object)(object)this) { _instance = null; } } private void Update() { HandleKeys(); EnsureLanguageSubscription(); try { SyncRecording(); } catch (Exception e) { ReportUpdateFailure(e); } if (!_settings.EnableMod.Value) { _lastDecision = "BLOCKED"; _lastBlockReason = SpawnBlockReason.Disabled; return; } try { UpdateDirector(); } catch (Exception e2) { ReportUpdateFailure(e2); } LogDirectorDiagnostics(); } private void UpdateDirector() { GameManager instance = StaticInstance<GameManager>.Instance; if (_seenGeneration != _levelGeneration) { _seenGeneration = _levelGeneration; ResetLevelState("New level"); _inLevelTransition = false; } if (!(Time.time < _nextScanTime)) { _nextScanTime = Time.time + 0.5f; if (!_levelStarted && (Object)(object)instance != (Object)null && CanBeginLevel(instance)) { BeginLevel(instance); } else if (_levelStarted && (_appliedStyle != _settings.EffectiveStyle || TuningChanged())) { _appliedStyle = _settings.EffectiveStyle; _appliedTuning = _settings.Tuning; _director.ChangeStyle(CurrentPreset, _appliedTuning); } Snapshot snapshot = (_snapshot = ScanWorld(instance)); float num = ((_lastTickTime > 0f) ? (Time.time - _lastTickTime) : 0f); _lastTickTime = Time.time; DirectorOrders orders = _director.Tick(new DirectorInput { Now = Time.time, DeltaSeconds = num, SpawningAllowed = snapshot.spawningAllowed, Engaged = snapshot.engaged, PlayerHealthFraction = (snapshot.hasPlayer ? snapshot.playerHp : 1f), RouteRoomIndex = snapshot.routeRoomIndex, WeightedThreat = snapshot.weightedThreat, ModAliveCost = snapshot.modAliveCost }); CarryOutOrders(orders); RecordTick(snapshot, num); } } private bool TuningChanged() { DirectorTuning tuning = _settings.Tuning; if (Mathf.Approximately(tuning.Reinforcement, _appliedTuning.Reinforcement)) { return !Mathf.Approximately(tuning.Rest, _appliedTuning.Rest); } return true; } private void CarryOutOrders(DirectorOrders orders) { if (!_snapshot.spawningAllowed) { _pendingSurgeBudget = 0f; return; } if (orders.SurgeWarning) { ShowAlert(OverlayText.Get("alert.surge", "Something is coming...")); AddEvent("Surge warned"); } _pendingSurgeBudget += orders.SurgeBudget; if (!_spawnInProgress) { if (_pendingSurgeBudget > 0f) { float pendingSurgeBudget = _pendingSurgeBudget; _pendingSurgeBudget = 0f; SpawnBatchAsync(pendingSurgeBudget, 12, surge: true, manual: false, "Surge"); } else if (orders.TrickleBudget > 0f) { SpawnBatchAsync(orders.TrickleBudget, 3, surge: false, manual: false, "Trickle"); } } } private bool CanBeginLevel(GameManager gm) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) if (!_inLevelTransition && (Object)(object)gm.PlayerUnit != (Object)null && (Object)(object)gm.graphContext != (Object)null && gm.orderedRooms != null && gm.orderedRooms.Count > 0 && ((object)gm.gameState/*cast due to .constrained prefix*/).ToString() == "Running") { return !gm.awaitingStartLevel; } return false; } private void BeginLevel(GameManager gm) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Unknown result type (might be due to invalid IL or missing references) List<Room> orderedRooms = gm.orderedRooms; int num = 0; int num2 = 0; for (int i = 0; i < orderedRooms.Count; i++) { Room val = orderedRooms[i]; if (!((Object)(object)val == (Object)null)) { num2 = Mathf.Max(num2, val.lodRoomIndex + 1); if ((int)val.roomSize != 0 && !val.disallowEnemySpawn) { num++; } } } bool flag = _capturedGeneration == _levelGeneration && _capturedPackBudget > 0f; _packBudget = (flag ? _capturedPackBudget : 12f); float num3 = (flag ? _capturedPackDensity : 1f); _lateGame = IsLateGame(gm); _appliedStyle = _settings.EffectiveStyle; _appliedTuning = _settings.Tuning; PressurePreset currentPreset = CurrentPreset; List<UnitSO> list = new List<UnitSO>(); Roster.CollectEligible(gm, currentPreset, _packBudget, _lateGame, _settings.EliteMultiplier.Value, list); float num4 = ((list.Count > 0) ? ((float)list.Average((UnitSO u) => u.SpawnCost)) : 0f); _director.StartLevel(new LevelCalibration { PackBudget = _packBudget, PackDensity = num3, SpawnableRooms = num, RouteRooms = num2, AverageUnitCost = num4 }, currentPreset, _appliedTuning); _levelStarted = true; _lastTickTime = 0f; for (int num5 = 0; num5 < list.Count; num5++) { list[num5].FetchAndLoadUnitLoader(); } RememberLevelCalibration(num3, num, num2, num4, flag); RecordLevelStart(gm, joinedPartway: false); if (_pendingMigrationNotice) { _pendingMigrationNotice = false; ShowAlert(OverlayText.Get("alert.migrated", "Dynamic Pressure: your old tuning values were replaced by the new director.")); } AddEvent("Level start: budget " + _director.TotalBudget.ToString("0") + (flag ? string.Empty : " (estimated)")); if (_settings.LogSpawnDecisions.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Level start | style " + _appliedStyle.ToString() + " | packBudget " + _packBudget + (flag ? string.Empty : " (fallback)") + " | density " + num3 + " | rooms " + num + "/" + num2 + " | eligible " + list.Count + " avg " + num4.ToString("0.0") + " | total budget " + _director.TotalBudget.ToString("0") + " | target " + _director.TargetThreat.ToString("0.0") + " | late " + _lateGame)); } } private static bool IsLateGame(GameManager gm) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)gm.environmentsInOrder == (Object)null || (Object)(object)gm.currentEnvironment == (Object)null) { return false; } List<WorldEnvironmentIds> environmentsOnOrder = gm.environmentsInOrder.environmentsOnOrder; int num = environmentsOnOrder.IndexOf(gm.currentEnvironment.id); int num2 = environmentsOnOrder.IndexOf((WorldEnvironmentIds)9); if (num >= 0 && num2 >= 0) { return num >= num2; } return false; } private static void ShowAlert(string text) { UIManager instance = StaticInstance<UIManager>.Instance; if ((Object)(object)instance != (Object)null) { instance.ShowSmallAlert(text, 2.5f, (UISounds)0); } } private void HandleKeys() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_0079: 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_008c: Unknown result type (might be due to invalid IL or missing references) Keyboard current = Keyboard.current; if (current != null) { if ((int)_settings.OverlayToggleKey.Value != 0 && ((ButtonControl)current[_settings.OverlayToggleKey.Value]).wasPressedThisFrame) { _settings.EnableOverlay.Value = !_settings.EnableOverlay.Value; } if (_settings.EnableOverlay.Value && (int)_settings.ManualSpawnKey.Value != 0 && ((ButtonControl)current[_settings.ManualSpawnKey.Value]).wasPressedThisFrame && !_spawnInProgress) { SpawnBatchAsync(0f, 1, surge: false, manual: true, "Manual debug spawn"); } if ((int)_settings.ExportCombatDataKey.Value != 0 && ((ButtonControl)current[_settings.ExportCombatDataKey.Value]).wasPressedThisFrame) { ExportCombatData(); } } } private Snapshot ScanWorld(GameManager gm) { //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_0166: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) Snapshot snapshot = new Snapshot(); if ((Object)(object)gm == (Object)null) { snapshot.blockReason = SpawnBlockReason.NoGameManager; return snapshot; } snapshot.hasGameManager = true; snapshot.gameState = ((object)gm.gameState/*cast due to .constrained prefix*/).ToString(); snapshot.inSafeZone = gm.InSafeZone; snapshot.currentEnvironment = (((Object)(object)gm.currentEnvironment != (Object)null) ? ((object)Unsafe.As<WorldEnvironmentIds, WorldEnvironmentIds>(ref gm.currentEnvironment.id)/*cast due to .constrained prefix*/).ToString() : "null"); if ((Object)(object)gm.PlayerUnit == (Object)null || (Object)(object)gm.PlayerObject == (Object)null) { snapshot.blockReason = SpawnBlockReason.NoPlayer; return snapshot; } Unit playerUnit = gm.PlayerUnit; Room unitRoom = GetUnitRoom(playerUnit); snapshot.hasPlayer = true; snapshot.playerAlive = playerUnit.IsAlive; snapshot.playerHp = SafeGetPlayerHp(playerUnit); snapshot.playerRoomName = (((Object)(object)unitRoom != (Object)null) ? ((Object)unitRoom).name : "null"); snapshot.routeRoomIndex = (((Object)(object)unitRoom != (Object)null) ? unitRoom.lodRoomIndex : 0); bool flag = false; List<Npc> aliveNpcs = gm.aliveNpcs; if (aliveNpcs != null) { for (int i = 0; i < aliveNpcs.Count; i++) { Npc val = aliveNpcs[i]; if (!IsActiveThreat(val)) { continue; } bool flag2 = SafeTargetIsPlayer(val); bool flag3 = SafeHasKnownPlayerPosition(val, playerUnit); if (((Unit)val).IsBoss) { flag |= flag2 || val.hasBeenAggroedOnce; continue; } float num = Vector3.Distance(((Component)val).transform.position, gm.PlayerPosition); DynamicPressureSpawnMarker component = ((Component)val).GetComponent<DynamicPressureSpawnMarker>(); bool flag4 = (Object)(object)component != (Object)null; if (flag4 && flag2 && component.firstTargetTime < 0f) { component.firstTargetTime = Time.time; } int spawnCost = ((Unit)val).unitSO.SpawnCost; if (flag4 && (num <= 60f || flag2)) { snapshot.modAliveCost += spawnCost; snapshot.modAlive++; } if (IsSameOrConnectedRoom(GetUnitRoom((Unit)(object)val), unitRoom) || (flag2 && num <= 40f)) { snapshot.weightedThreat += (flag4 ? ((float)spawnCost) : ((float)spawnCost * 0.5f)); snapshot.threatsPresent++; snapshot.engaged |= flag2 || flag3; } } } snapshot.blockReason = FindBlockReason(gm, playerUnit, unitRoom, flag); snapshot.spawningAllowed = snapshot.blockReason == SpawnBlockReason.None; if (!snapshot.spawningAllowed) { _lastDecision = "BLOCKED"; _lastBlockReason = snapshot.blockReason; } return snapshot; } private SpawnBlockReason FindBlockReason(GameManager gm, Unit player, Room playerRoom, bool bossFight) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) if (_inLevelTransition) { return SpawnBlockReason.LevelTransition; } if (!_levelStarted) { return SpawnBlockReason.WaitingForLevel; } if (((object)gm.gameState/*cast due to .constrained prefix*/).ToString() != "Running" || gm.awaitingStartLevel) { return SpawnBlockReason.GameStateNotRunning; } if (gm.InSafeZone || gm.IsPlayerAtChurch || gm.IsOnboarding) { return SpawnBlockReason.SafeZone; } if (gm.IsEndless || CryptUI.IsRunning) { return SpawnBlockReason.ChallengeMode; } if (!player.IsAlive) { return SpawnBlockReason.PlayerDead; } if (bossFight) { return SpawnBlockReason.BossFight; } if ((Object)(object)playerRoom != (Object)null && playerRoom.IsEndRoom) { return SpawnBlockReason.EndRoomBlocked; } return SpawnBlockReason.None; } private static bool IsActiveThreat(Npc npc) { if ((Object)(object)npc != (Object)null && ((Unit)npc).IsAlive && (Object)(object)((Unit)npc).unitSO != (Object)null && ((Component)npc).gameObject.activeInHierarchy && !((Unit)npc).unitSO.isCivilian && ((Unit)npc).unitSO.id.value != UnitIds.BlackGuildMachinegunEmplacement.value) { return IsHostileToPlayer(npc); } return false; } private static bool IsHostileToPlayer(Npc npc) { try { return ((Unit)npc).IsHostileTo((FactionIds)16); } catch { return false; } } private static bool SafeTargetIsPlayer(Npc npc) { try { return (Object)(object)npc != (Object)null && npc.targetIsPlayer; } catch { return false; } } private static bool SafeHasKnownPlayerPosition(Npc npc, Unit playerUnit) { try { return (Object)(object)npc != (Object)null && (Object)(object)npc.AiAgent != (Object)null && (Object)(object)playerUnit != (Object)null && npc.AiAgent.HasKnownPosition(playerUnit); } catch { return false; } } private static float SafeGetPlayerHp(Unit playerUnit) { try { return ((Object)(object)playerUnit != (Object)null) ? playerUnit.GetNormalizedHealth() : 0f; } catch { return 0f; } } private static Room GetUnitRoom(Unit unit) { if ((Object)(object)unit == (Object)null) { return null; } if (!((Object)(object)unit.currentRoom != (Object)null)) { return unit.lastValidCurrentRoom; } return unit.currentRoom; } private static bool IsSameOrConnectedRoom(Room npcRoom, Room playerRoom) { if ((Object)(object)npcRoom == (Object)null || (Object)(object)playerRoom == (Object)null) { return false; } if ((Object)(object)npcRoom == (Object)(object)playerRoom) { return true; } if (playerRoom.connectedRooms != null) { return Array.IndexOf(playerRoom.connectedRooms, npcRoom) >= 0; } return false; } private void ReportUpdateFailure(Exception e) { string text = e.GetType().Name + ": " + e.Message; if (text != _lastUpdateFailure) { _lastUpdateFailure = text; _updateFailureCount = 0; AddEvent("Director update failed: " + e.GetType().Name); ((BaseUnityPlugin)this).Logger.LogError((object)"Director update failed. Repeats of this same failure are counted, not logged."); ((BaseUnityPlugin)this).Logger.LogError((object)e); } _updateFailureCount++; _lastDecision = "FAILED"; _lastBlockReason = SpawnBlockReason.UpdateFailed; } private void LogDirectorDiagnostics() { if (!_settings.LogSpawnDecisions.Value) { _nextDiagLogTime = Time.time + 5f; _diagWindowBatches = 0; _diagWindowSpawned = 0; } else if (!(Time.time < _nextDiagLogTime)) { _nextDiagLogTime = Time.time + 5f; ((BaseUnityPlugin)this).Logger.LogInfo((object)("Director " + _director.Phase.ToString() + " | intensity " + _director.Intensity.ToString("0.00") + " | pace " + _director.Pace.ToString("0.00") + " | threat " + _snapshot.weightedThreat.ToString("0.0") + "/" + _director.TargetThreat.ToString("0.0") + " | mod alive " + _snapshot.modAliveCost + "/" + _director.AliveCap.ToString("0") + " | spent " + _director.SpentThreat.ToString("0") + "/" + _director.UnlockedBudget.ToString("0") + "/" + _director.TotalBudget.ToString("0") + " | batches " + _diagWindowBatches + " placed " + _diagWindowSpawned + " | last " + _lastDecision + "/" + _lastBlockReason)); _diagWindowBatches = 0; _diagWindowSpawned = 0; } } private void ResetLevelState(string reason) { RecordLevelEnd(); _recentEvents.Clear(); _waveId = 0; _levelStarted = false; _lastTickTime = 0f; _pendingSurgeBudget = 0f; _lastDecision = "Reset"; _lastBlockReason = SpawnBlockReason.None; _lastSpawnSummary = "None"; AddEvent("Reset: " + reason); } private static void OnSpawnEnemiesExecutePrefix(SpawnEnemiesNode __instance) { if (!((Object)(object)__instance == (Object)null)) { _capturedGeneration = _levelGeneration; _capturedPackBudget = __instance.packBudget; _capturedPackDensity = __instance.packDensity; } } private static void OnAllDeadTriggerCheckAllDeadPrefix(List<Npc> ___allAliveNpcs) { if (___allAliveNpcs != null && ___allAliveNpcs.Count != 0) { ___allAliveNpcs.RemoveAll(IsDynamicPressureSpawnedNpc); } } private static bool OnAllDeadTriggerRegisterDeathPrefix(Unit __0) { return !IsDynamicPressureSpawnedNpc((Npc)(object)((__0 is Npc) ? __0 : null)); } private static bool IsDynamicPressureSpawnedNpc(Npc npc) { if ((Object)(object)npc != (Object)null) { return (Object)(object)((Component)npc).GetComponent<DynamicPressureSpawnMarker>() != (Object)null; } return false; } private static void OnStartLevelRoutinePrefix() { _levelGeneration++; } private static void OnGoToMainMenuPrefix() { if ((Object)(object)_instance != (Object)null) { _instance.RecordLevelEnd(); } } private static void OnLevelTransitionPrefix() { if (!((Object)(object)_instance == (Object)null)) { _instance.ResetLevelState("Level transition"); _instance._inLevelTransition = true; } } private static void OnPlayerDamagePrefix(Unit __instance, DamageSourceData sourceData, out HitState __state) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) __state = new HitState { Health = -1f, OuterFromPlayer = _hitIsFromPlayer, OuterSource = _hitSource }; if (!((Object)(object)__instance == (Object)null) && !((Object)(object)_instance == (Object)null)) { if (__instance.isPlayer) { __state.Health = __instance.GetCurrentHealth(); } else if (_instance.IsRecording && __instance is Npc) { __state.Health = __instance.GetCurrentHealth(); _hitIsFromPlayer = IsFromPlayer(sourceData); _hitSource = sourceData; } } } private static void OnPlayerDamagePostfix(Unit __instance, bool __result, HitState __state, DamageSourceData sourceData) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) _hitIsFromPlayer = __state.OuterFromPlayer; _hitSource = __state.OuterSource; if (!__result || __state.Health < 0f || (Object)(object)_instance == (Object)null || (Object)(object)__instance == (Object)null) { return; } float num = __state.Health - __instance.GetCurrentHealth(); if (!__instance.isPlayer) { if (IsFromPlayer(sourceData)) { _instance.RecordDamageDealt(num, sourceData); } return; } float attribute = __instance.Stats.GetAttribute((EntityAttributes)60); if (attribute > 0f) { _instance._director.ReportPlayerHit(num / attribute, sourceData.states.isPeriodicModifier); _instance.RecordPlayerHit(__instance, num, attribute, sourceData); } } private void TryPatchTransitions() { //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Expected O, but got Unknown MethodInfo prefix = AccessTools.Method(typeof(DynamicPressurePlugin), "OnLevelTransitionPrefix", (Type[])null, (Type[])null); TryPatchMethod(typeof(NextLevelTrigger), "MakeTransition", prefix); TryPatchMethod(typeof(GameManager), "CompleteLevel", prefix); TryPatchAllNamedMethods(typeof(GameManager), "GoToLevel", prefix); TryPatchAllNamedMethods(typeof(GameManager), "GoToChurchHub", prefix); TryPatchAllNamedMethods(typeof(GameManager), "GoToCarHub", prefix); TryPatchAllNamedMethods(typeof(GameManager), "GoToMainMenu", AccessTools.Method(typeof(DynamicPressurePlugin), "OnGoToMainMenuPrefix", (Type[])null, (Type[])null)); MethodInfo methodInfo = AccessTools.Method(typeof(GameManager), "StartLevelRoutineGraph", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(DynamicPressurePlugin), "OnStartLevelRoutinePrefix", (Type[])null, (Type[])null); if (methodInfo == null || methodInfo2 == null) { ((BaseUnityPlugin)this).Logger.LogError((object)"GameManager.StartLevelRoutineGraph was not found. The director cannot tell levels apart and will not add enemies after the first transition."); return; } _harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log("Patched GameManager.StartLevelRoutineGraph (level counter)."); } private void TryPatchPlayerHits() { //IL_00b3: 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_00c6: Expected O, but got Unknown //IL_00c6: Expected O, but got Unknown try { MethodInfo methodInfo = AccessTools.Method(typeof(Unit), "ReceiveDamage", new Type[4] { typeof(float), typeof(DamageSourceData), typeof(Data), typeof(Vector3?) }, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(DynamicPressurePlugin), "OnPlayerDamagePrefix", (Type[])null, (Type[])null); MethodInfo methodInfo3 = AccessTools.Method(typeof(DynamicPressurePlugin), "OnPlayerDamagePostfix", (Type[])null, (Type[])null); if (methodInfo == null || methodInfo2 == null || methodInfo3 == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Failed to patch Unit.ReceiveDamage: method not found. The director will not feel incoming damage."); return; } _harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo2), new HarmonyMethod(methodInfo3), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _hitPatchActive = true; Log("Patched Unit.ReceiveDamage (player hits)."); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Failed to patch Unit.ReceiveDamage: " + ex.Message + " | The director will not feel incoming damage.")); } } private static void OnNpcDiePostfix(Npc __instance) { if ((Object)(object)_instance != (Object)null && (Object)(object)__instance != (Object)null && _instance.IsRecording) { _instance.RecordDeath(__instance); } } private void TryPatchNpcDeath() { //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Expected O, but got Unknown try { MethodInfo methodInfo = AccessTools.Method(typeof(Npc), "Die", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(DynamicPressurePlugin), "OnNpcDiePostfix", (Type[])null, (Type[])null); if (methodInfo == null || methodInfo2 == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Failed to patch Npc.Die: method not found. Combat data will not include kills."); return; } _harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log("Patched Npc.Die (combat data)."); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Failed to patch Npc.Die: " + ex.Message + " | Combat data will not include kills.")); } } private void TryPatchLevelBudget() { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Expected O, but got Unknown try { MethodInfo methodInfo = AccessTools.Method(typeof(SpawnEnemiesNode), "Execute", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(DynamicPressurePlugin), "OnSpawnEnemiesExecutePrefix", (Type[])null, (Type[])null); if (methodInfo == null || methodInfo2 == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Failed to patch SpawnEnemiesNode.Execute: method not found. Budgets will use estimates."); return; } _harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log("Patched SpawnEnemiesNode.Execute (level budget)."); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Failed to patch SpawnEnemiesNode.Execute: " + ex.Message + " | Budgets will use estimates.")); } } private void TryPatchAllDeadTrigger() { //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Expected O, but got Unknown //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Expected O, but got Unknown try { MethodInfo methodInfo = AccessTools.Method(typeof(AllDeadTrigger), "CheckAllDead", Type.EmptyTypes, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(AllDeadTrigger), "RegisterDeath", new Type[1] { typeof(Unit) }, (Type[])null); MethodInfo methodInfo3 = AccessTools.Method(typeof(DynamicPressurePlugin), "OnAllDeadTriggerCheckAllDeadPrefix", (Type[])null, (Type[])null); MethodInfo methodInfo4 = AccessTools.Method(typeof(DynamicPressurePlugin), "OnAllDeadTriggerRegisterDeathPrefix", (Type[])null, (Type[])null); if (methodInfo == null || methodInfo2 == null || methodInfo3 == null || methodInfo4 == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Failed to patch AllDeadTrigger: expected methods were not found."); return; } _harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo3), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(methodInfo4), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log("Patched AllDeadTrigger progression filtering."); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Failed to patch AllDeadTrigger: " + ex.Message)); } } private void TryPatchMethod(Type type, string methodName, MethodInfo prefix) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Expected O, but got Unknown try { MethodInfo methodInfo = AccessTools.Method(type, methodName, (Type[])null, (Type[])null); if (methodInfo == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Patch skipped: " + type.Name + "." + methodName)); return; } _harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(prefix), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log("Patched " + type.Name + "." + methodName); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Patch failed: " + type.Name + "." + methodName + " / " + ex.Message)); } } private void TryPatchAllNamedMethods(Type type, string methodName, MethodInfo prefix) { //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Expected O, but got Unknown try { MethodInfo[] array = (from m in AccessTools.GetDeclaredMethods(type) where m.Name == methodName select m).ToArray(); if (array.Length == 0) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Patch skipped: " + type.Name + "." + methodName)); return; } for (int num = 0; num < array.Length; num++) { _harmony.Patch((MethodBase)array[num], new HarmonyMethod(prefix), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Log("Patched " + type.Name + "." + methodName + " x" + array.Length); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Patch failed: " + type.Name + "." + methodName + " / " + ex.Message)); } } private void AddEvent(string text) { _recentEvents.Add(DateTime.Now.ToString("HH:mm:ss") + " " + text); while (_recentEvents.Count > 8) { _recentEvents.RemoveAt(0); } } private void Log(string text) { ((BaseUnityPlugin)this).Logger.LogInfo((object)text); } private async Task SpawnBatchAsync(float budget, int maxUnits, bool surge, bool manual, string reason) { if (_spawnInProgress) { return; } GameManager gm = StaticInstance<GameManager>.Instance; if ((Object)(object)gm == (Object)null || (Object)(object)gm.graphContext == (Object)null) { return; } _spawnInProgress = true; _waveId++; int generation = _levelGeneration; int placed = 0; try { Roster.CollectEligible(gm, CurrentPreset, _packBudget, _lateGame, _settings.EliteMultiplier.Value, _eligible); if (_eligible.Count == 0) { Block(SpawnBlockReason.NoCandidateUnits); return; } KeepOneFaction(gm, _eligible); _usedThisBatch.Clear(); float spent = 0f; bool anchored = false; Vector3 anchor = Vector3.zero; while (placed < maxUnits && (manual || spent < budget)) { if (!TryChooseUnitAndPoint(gm, anchored, anchor, out var unit, out var point)) { Block(SpawnBlockReason.NoFairSpawnPoint); break; } _usedThisBatch.Add(point.spawn); if (!(await SpawnOneAsync(gm, generation, unit, point, reason, !manual))) { break; } placed++; spent += (float)unit.SpawnCost; if (surge && !anchored) { anchored = true; anchor = point.position; } await Task.Yield(); unit = null; point = default(SpawnPointChoice); } if (placed > 0) { _lastDecision = "SPAWNED"; _lastBlockReason = SpawnBlockReason.None; } } catch (Exception ex) { Block(SpawnBlockReason.SpawnAsyncFailed); _lastDecision = "FAILED"; AddEvent("Spawn exception: " + ex.GetType().Name); ((BaseUnityPlugin)this).Logger.LogError((object)ex); } finally { _diagWindowBatches++; _diagWindowSpawned += placed; _spawnInProgress = false; } } private void Block(SpawnBlockReason reason) { _lastDecision = "BLOCKED"; _lastBlockReason = reason; } private void KeepOneFaction(GameManager gm, List<UnitSO> units) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0042: 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) FactionIds faction = FactionFightingPlayer(gm); if ((int)faction == 0 || !units.Exists((UnitSO u) => u.factionId == faction)) { faction = units[Random.Range(0, units.Count)].factionId; } units.RemoveAll((UnitSO u) => u.factionId != faction); } private FactionIds FactionFightingPlayer(GameManager gm) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003b: 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_005b: Unknown result type (might be due to invalid IL or missing references) FactionIds result = (FactionIds)0; float num = float.MaxValue; List<Npc> aliveNpcs = gm.aliveNpcs; for (int i = 0; i < aliveNpcs.Count; i++) { Npc val = aliveNpcs[i]; if (IsActiveThreat(val) && SafeTargetIsPlayer(val)) { float num2 = Vector3.Distance(((Component)val).transform.position, gm.PlayerPosition); if (num2 < num) { num = num2; result = ((Unit)val).unitSO.factionId; } } } return result; } private bool TryChooseUnitAndPoint(GameManager gm, bool anchored, Vector3 anchor, out UnitSO unit, out SpawnPointChoice point) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) int num = Random.Range(0, _eligible.Count); for (int i = 0; i < _eligible.Count; i++) { unit = _eligible[(num + i) % _eligible.Count]; if (anchored && TryFindPointNear(gm, unit, anchor, out point)) { return true; } if (TryFindSpawnPoint(gm, unit, out point)) { return true; } } unit = null; point = default(SpawnPointChoice); return false; } private async Task<bool> SpawnOneAsync(GameManager gm, int generation, UnitSO unit, SpawnPointChoice point, string reason, bool chargeBudget) { Unit val = await unit.SpawnUnitAsync((MonoBehaviour)(object)this, point.position, Quaternion.identity); if ((Object)(object)val == (Object)null) { return false; } Npc val2 = (Npc)(object)((val is Npc) ? val : null); if ((Object)(object)val2 == (Object)null) { val2 = ((Component)val).GetComponent<Npc>(); } if ((Object)(object)val2 == (Object)null) { return false; } DynamicPressureSpawnMarker dynamicPressureSpawnMarker = ((Component)val2).gameObject.AddComponent<DynamicPressureSpawnMarker>(); dynamicPressureSpawnMarker.sourceUnitSo = unit; dynamicPressureSpawnMarker.spawnTime = Time.time; dynamicPressureSpawnMarker.spawnPosition = point.position; dynamicPressureSpawnMarker.spawnRoom = point.room; dynamicPressureSpawnMarker.spawnReason = reason; dynamicPressureSpawnMarker.waveId = _waveId; if (generation != _levelGeneration) { return false; } ((Unit)val2).currentRoom = point.room; ((Unit)val2).lastValidCurrentRoom = point.room; ((Unit)val2).lastRoomCalcPosition = ((Component)val2).transform.position; string text = TryApplyMutation(gm, val2, unit); if (chargeBudget) { _director.ReportSpent(unit.SpawnCost); } string text2 = (_lastSpawnSummary = ((Object)unit).name + ((text != null) ? (" [" + text + "]") : string.Empty) + " " + point.zone.ToString() + " " + point.distanceToPlayer.ToString("0") + "m"); AddEvent("Spawned: " + text2); if (_settings.LogSpawnDecisions.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Spawned " + text2 + " | cost " + unit.SpawnCost + " | " + reason)); } RecordSpawn(unit, point, reason, text); ((MonoBehaviour)this).StartCoroutine(ReportPlayerPositionLater(val2)); return true; } private unsafe string TryApplyMutation(GameManager gm, Npc npc, UnitSO unit) { //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: 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_00f8: 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) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) PressurePreset currentPreset = CurrentPreset; float num = currentPreset.MutationChance * _settings.EliteMultiplier.Value; if (num <= 0f || (currentPreset.MutationsOnlyLate && !_lateGame) || unit.disableMutations || ((Unit)npc).PreventMutations || unit.availableMutations == null || Random.value > num) { return null; } _mutationChoices.Clear(); List<MutationDefinitions> list = (((Object)(object)gm.Settings != (Object)null) ? gm.Settings.disabledMutations : null); for (int i = 0; i < AllowedMutations.Length; i++) { MutationDefinitions val = AllowedMutations[i]; if (Array.IndexOf(unit.availableMutations, val) >= 0 && (list == null || !list.Contains(val))) { _mutationChoices.Add(val); } } if (_mutationChoices.Count == 0) { return null; } MutationDefinitions val2 = _mutationChoices[Random.Range(0, _mutationChoices.Count)]; ((Unit)npc).ToggleMutation(val2, true); return ((object)(*(MutationDefinitions*)(&val2))/*cast due to .constrained prefix*/).ToString(); } private IEnumerator ReportPlayerPositionLater(Npc npc) { yield return (object)new WaitForSeconds(0.5f); GameManager instance = StaticInstance<GameManager>.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance.PlayerObject == (Object)null || (Object)(object)instance.PlayerUnit == (Object)null || (Object)(object)npc == (Object)null || !((Unit)npc).IsAlive || (Object)(object)npc.AiAgent == (Object)null) { yield break; } try { npc.AiAgent.ReportLastSeen(instance.PlayerUnit, ((Component)instance.PlayerUnit).transform.position, ((Component)npc.AiAgent).transform.position, npc.AiAgent.propagateToGroup); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)ex); } } private bool TryFindSpawnPoint(GameManager gm, UnitSO unit, out SpawnPointChoice result) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) result = default(SpawnPointChoice); List<Room> orderedRooms = gm.orderedRooms; if (orderedRooms == null || orderedRooms.Count == 0 || (Object)(object)gm.PlayerUnit == (Object)null) { return false; } bool flag = _settings.Position.Value == SpawnPosition.Anywhere; float num = (flag ? 8f : 15f); Room unitRoom = GetUnitRoom(gm.PlayerUnit); Camera camera = PlayerCamera(gm); Vector3 playerPosition = gm.PlayerPosition; ZoneBuckets zoneBuckets = new ZoneBuckets(); ZoneBuckets zoneBuckets2 = new ZoneBuckets(); for (int i = 0; i < orderedRooms.Count; i++) { Room val = orderedRooms[i]; if (!IsValidSpawnRoom(val, flag)) { continue; } NPCSpawn[] nPCSpawns = val.GetNPCSpawns(); if (nPCSpawns == null) { continue; } foreach (NPCSpawn val2 in nPCSpawns) { if ((Object)(object)val2 == (Object)null || _usedThisBatch.Contains(val2) || (val2.usableByTypes & unit.unitType) == 0) { continue; } Vector3 position = ((Component)val2).transform.position; float num2 = Vector3.Distance(position, playerPosition); if (!(num2 < num) && !(num2 > 120f)) { SpawnZone spawnZone = ((!flag) ? ClassifyZone(position, val, unitRoom, playerPosition, camera) : SpawnZone.Ahead); if (spawnZone != SpawnZone.Rear || !(num2 < 25f)) { SpawnPointChoice item = new SpawnPointChoice { spawn = val2, position = position, room = val, distanceToPlayer = num2, zone = spawnZone }; ((num2 <= 60f) ? zoneBuckets : zoneBuckets2).For(spawnZone).Add(item); } } } } if (flag) { if (!PickNearest(zoneBuckets.Ahead, out result)) { return PickNearest(zoneBuckets2.Ahead, out result); } return true; } return PickByPlacement(zoneBuckets, zoneBuckets2, camera, unit, out result); } private bool PickByPlacement(ZoneBuckets strict, ZoneBuckets loose, Camera camera, UnitSO unit, out SpawnPointChoice result) { bool flag = _settings.Position.Value == SpawnPosition.Ahead; float value = Random.value; SpawnZone spawnZone = ((!flag) ? ((!(value < 0.6f)) ? ((value < 0.9f) ? SpawnZone.Side : SpawnZone.Rear) : SpawnZone.Ahead) : ((!(value < 0.7f)) ? SpawnZone.Side : SpawnZone.Ahead)); SpawnZone[] array = OrderFrom(spawnZone, flag); ZoneBuckets[] array2 = new ZoneBuckets[2] { strict, loose }; foreach (ZoneBuckets zoneBuckets in array2) { SpawnZone[] array3 = array; foreach (SpawnZone spawnZone2 in array3) { if ((spawnZone2 != SpawnZone.Rear || spawnZone == SpawnZone.Rear) && PickHidden(zoneBuckets.For(spawnZone2), camera, unit, out result)) { return true; } } } if (!flag) { array2 = new ZoneBuckets[2] { strict, loose }; foreach (ZoneBuckets zoneBuckets2 in array2) { if (PickHidden(zoneBuckets2.Rear, camera, unit, out result)) { return true; } } } result = default(SpawnPointChoice); return false; } private static SpawnZone[] OrderFrom(SpawnZone first, bool neverBehind) { if (neverBehind) { if (first == SpawnZone.Ahead) { return new SpawnZone[2] { SpawnZone.Ahead, SpawnZone.Side }; } return new SpawnZone[2] { SpawnZone.Side, SpawnZone.Ahead }; } return first switch { SpawnZone.Side => new SpawnZone[3] { SpawnZone.Side, SpawnZone.Ahead, SpawnZone.Rear }, SpawnZone.Rear => new SpawnZone[3] { SpawnZone.Rear, SpawnZone.Ahead, SpawnZone.Side }, _ => new SpawnZone[3] { SpawnZone.Ahead, SpawnZone.Side, SpawnZone.Rear }, }; } private static SpawnZone ClassifyZone(Vector3 position, Room room, Room playerRoom, Vector3 playerPosition, Camera camera) { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0055: 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) if ((Object)(object)camera != (Object)null) { Vector3 forward = ((Component)camera).transform.forward; forward.y = 0f; Vector3 val = position - playerPosition; val.y = 0f; if (((Vector3)(ref forward)).sqrMagnitude > 0.0001f && ((Vector3)(ref val)).sqrMagnitude > 0.0001f && Vector3.Dot(((Vector3)(ref forward)).normalized, ((Vector3)(ref val)).normalized) < -0.25f) { return SpawnZone.Rear; } } if (!IsAheadOnRoute(position, room, playerRoom, playerPosition)) { return SpawnZone.Side; } return SpawnZone.Ahead; } private static bool IsAheadOnRoute(Vector3 position, Room room, Room playerRoom, Vector3 playerPosition) { //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_0049: 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_0050: 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) if ((Object)(object)playerRoom == (Object)null) { return false; } if (room.lodRoomIndex != playerRoom.lodRoomIndex) { return room.lodRoomIndex > playerRoom.lodRoomIndex; } if ((Object)(object)playerRoom.nextRoomConnector == (Object)null) { return false; } Vector3 position2 = ((Component)playerRoom.nextRoomConnector).transform.position; return Vector3.Distance(position, position2) < Vector3.Distance(playerPosition, position2); } private bool PickHidden(List<SpawnPointChoice> choices, Camera camera, UnitSO unit, out SpawnPointChoice result) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) result = default(SpawnPointChoice); if (choices.Count == 0) { return false; } choices.Sort((SpawnPointChoice a, SpawnPointChoice b) => a.distanceToPlayer.CompareTo(b.distanceToPlayer)); List<SpawnPointChoice> list = new List<SpawnPointChoice>(5); int num = Mathf.Min(choices.Count, 16); for (int num2 = 0; num2 < num; num2++) { if (list.Count >= 5) { break; } if (!IsExposed(camera, choices[num2].position, unit)) { list.Add(choices[num2]); } } if (list.Count == 0) { return false; } result = list[Random.Range(0, list.Count)]; return true; } private bool TryFindPointNear(GameManager gm, UnitSO unit, Vector3 anchor, out SpawnPointChoice result) { //IL_00bf: 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) //IL_00ca: 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_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) result = default(SpawnPointChoice); Camera camera = PlayerCamera(gm); bool flag = _settings.Position.Value == SpawnPosition.Anywhere; bool flag2 = _settings.Position.Value == SpawnPosition.Ahead; float num = (flag ? 8f : 15f); Room unitRoom = GetUnitRoom(gm.PlayerUnit); int num2 = 0; List<Room> orderedRooms = gm.orderedRooms; for (int i = 0; i < orderedRooms.Count; i++) { Room val = orderedRooms[i]; if (!IsValidSpawnRoom(val, flag)) { continue; } NPCSpawn[] nPCSpawns = val.GetNPCSpawns(); if (nPCSpawns == null) { continue; } for (int j = 0; j < nPCSpawns.Length; j++) { if (num2 >= 16) { break; } NPCSpawn val2 = nPCSpawns[j]; if ((Object)(object)val2 == (Object)null || _usedThisBatch.Contains(val2) || (val2.usableByTypes & unit.unitType) == 0) { continue; } Vector3 position = ((Component)val2).transform.position; float num3 = Vector3.Distance(position, gm.PlayerPosition); if (Vector3.Distance(position, anchor) > 14f || num3 < num) { continue; } SpawnZone spawnZone = ((!flag) ? ClassifyZone(position, val, unitRoom, gm.PlayerPosition, camera) : SpawnZone.Ahead); if (spawnZone != SpawnZone.Rear || (!flag2 && !(num3 < 25f))) { num2++; if (flag || !IsExposed(camera, position, unit)) { result = new SpawnPointChoice { spawn = val2, position = position, room = val, distanceToPlayer = num3, zone = spawnZone }; return true; } } } } return false; } private static bool PickNearest(List<SpawnPointChoice> choices, out SpawnPointChoice result) { result = default(SpawnPointChoice); if (choices.Count == 0) { return false; } choices.Sort((SpawnPointChoice a, SpawnPointChoice b) => a.distanceToPlayer.CompareTo(b.distanceToPlayer)); result = choices[Random.Range(0, Mathf.Min(choices.Count, 5))]; return true; } private static bool IsValidSpawnRoom(Room room, bool anywhere) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Invalid comparison between Unknown and I4 if ((Object)(object)room == (Object)null || room.IsStartRoom || room.IsEndRoom || room.disallowEnemySpawn) { return false; } if (!anywhere) { return (int)room.roomSize > 0; } return true; } private static Camera PlayerCamera(GameManager gm) { if (!((Object)(object)gm.PlayerScript != (Object)null)) { return null; } return gm.PlayerScript.playerCamera; } private static bool IsExposed(Camera camera, Vector3 position, UnitSO unit) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0028: 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_003d: 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_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)camera == (Object)null) { return false; } GameManager instance = StaticInstance<GameManager>.Instance; if ((Object)(object)instance == (Object)null) { return false; } if (CanSee(camera, position + Vector3.up * 1.2f, instance)) { return true; } if ((unit.unitType & 0x10) != 0) { return CanSee(camera, position + Vector3.up * 2.5f, instance); } return false; } private static bool CanSee(Camera camera, Vector3 point, GameManager gm) { //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_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_003c: 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_0058: Unknown result type (might be due to invalid IL or missing references) Vector3 val = camera.WorldToViewportPoint(point); if (val.z <= 0f || val.x < -0.05f || val.x > 1.05f || val.y < -0.05f || val.y > 1.05f) { return false; } return !Physics.Linecast(((Component)camera).transform.position, point, LayerMask.op_Implicit(gm.enemyOcclusionLayers), (QueryTriggerInteraction)1); } static DynamicPressurePlugin() { MutationDefinitions[] array = new MutationDefinitions[5]; RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); AllowedMutations = (MutationDefinitions[])(object)array; } } internal sealed class CombatRecorder { private const string SessionPrefix = "session-"; private const string ExportPrefix = "dynamicpressure-feedback-"; private const string Extension = ".jsonl"; private const int FlushAtLines = 2000; private const int FlushRetryLines = 500; private const int KeepSessions = 20; private const int KeepExports = 3; private const int ExportSessions = 5; private const int MaxFlushFailures = 3; private const int StampLength = 15; private const long MaxSessionBytes = 5242880L; private const long MaxTotalBytes = 20971520L; private readonly string _directory; private readonly ManualLogSource _log; private readonly Func<DateTime> _now; private readonly List<string> _buffer = new List<string>(); private string _sessionPath; private long _sessionBytes; private bool _stopped; private bool _unavailable; private int _flushFailures; private int _flushAtLines = 2000; public bool IsRecording { get { if (_sessionPath != null) { return !_stopped; } return false; } } public bool NeedsStop { get { if (_sessionPath == null) { return _unavailable; } return true; } } public bool HasRecordedData { get { try { return Directory.Exists(_directory) && Files("session-").Any((FileInfo f) => f.Length > 0); } catch (Exception) { return false; } } } public CombatRecorder(string directory, ManualLogSource log, Func<DateTime> now = null) { _directory = directory; _log = log; _now = now ?? ((Func<DateTime>)(() => DateTime.Now)); } public void Start() { if (_sessionPath != null || _unavailable) { return; } try { Directory.CreateDirectory(_directory); _sessionPath = UniquePath("session-"); _sessionBytes = 0L; _flushFailures = 0; _stopped = false; } catch (Exception ex) { _sessionPath = null; _unavailable = true; ManualLogSource log = _log; if (log != null) { log.LogWarning((object)("Combat data recording is unavailable: " + ex.GetType().Name)); } return; } PruneQuietly("session-", 19, null); } public void Stop() { Flush(); _buffer.Clear(); _sessionPath = null; _sessionBytes = 0L; _stopped = false; _unavailable = false; _flushFailures = 0; } public void Record(string kind, float time, params object[] fields) { if (IsRecording) { if (fields == null) { fields = new object[0]; } StringBuilder stringBuilder = new StringBuilder(128); stringBuilder.Append("{\"e\":"); AppendValue(stringBuilder, kind); stringBuilder.Append(",\"t\":"); AppendValue(stringBuilder, time); for (int i = 0; i + 1 < fields.Length; i += 2) { stringBuilder.Append(','); AppendValue(stringBuilder, Convert.ToString(fields[i], CultureInfo.InvariantCulture)); stringBuilder.Append(':'); AppendValue(stringBuilder, fields[i + 1]); } stringBuilder.Append('}'); _buffer.Add(stringBuilder.ToString()); if (_buffer.Count >= _flushAtLines) { Flush(); } } } public void Flush() { if (_buffer.Count == 0 || _sessionPath == null || _stopped) { return; } StringBuilder stringBuilder = new StringBuilder(); long num = 0L; int num2 = 0; bool flag = false; foreach (string item in _buffer) { long num3 = (long)Encoding.UTF8.GetByteCount(item) + 1L; if (_sessionBytes + num + num3 > 5242880) { flag = true; break; } stringBuilder.Append(item).Append('\n'); num += num3; num2++; } try { if (num2 > 0) { File.AppendAllText(_sessionPath, stringBuilder.ToString(), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); } } catch (Exception ex) { _flushAtLines = _buffer.Count + 500; if (++_flushFailures >= 3) { Silence("Combat data recording stopped: " + ex.GetType().Name); } return; } _flushFailures = 0; _flushAtLines = 2000; _sessionBytes += num; _buffer.RemoveRange(0, num2); if (flag) { Silence("Combat data recording stopped: this session reached its size limit."); } else { EnforceTotalLimit(); } } public bool TryExport(out string exportPath) { exportPath = null; Flush(); string text = null; try { if (!Directory.Exists(_directory)) { return false; } List<FileInfo> list = new List<FileInfo>(); long num = 20971520L; foreach (FileInfo item in Files("session-").Take(5)) { if (item.Length > 0 && item.Length <= num) { list.Add(item); num -= item.Length; } } if (list.Count == 0) { return false; } list.Reverse(); string text2 = UniquePath("dynamicpressure-feedback-"); text = text2 + ".tmp"; using (FileStream destination = new FileStream(text, FileMode.Create, FileAccess.Write)) { foreach (FileInfo item2 in list) { using FileStream fileStream = new FileStream(item2.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); fileStream.CopyTo(destination); } } File.Move(text, text2); text = null; exportPath = text2; } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)("Combat data could not be exported: " + ex.GetType().Name)); } DeleteQuietly(text); return false; } PruneQuietly("dynamicpressure-feedback-", 3, exportPath); return true; } private void Silence(string reason) { _buffer.Clear(); _stopped = true; ManualLogSource log = _log; if (log != null) { log.LogWarning((object)reason); } } private string UniquePath(string prefix) { string text = _now().ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture); string text2 = Path.Combine(_directory, prefix + text + ".jsonl"); int num = 2; while (File.Exists(text2)) { text2 = Path.Combine(_directory, prefix + text + "-" + num.ToString(CultureInfo.InvariantCulture) + ".jsonl"); num++; } return text2; } private FileInfo[] Files(string prefix) { return new DirectoryInfo(_directory).GetFiles(prefix + "*.jsonl").OrderByDescending<FileInfo, string>((FileInfo f) => Stamp(f.Name, prefix), StringComparer.Ordinal).ThenByDescending((FileInfo f) => Suffix(f.Name, prefix)) .ToArray(); } private static string Stamp(string fileName, string prefix) { string text = Path.GetFileNameWithoutExtension(fileName).Substring(prefix.Length); if (text.Length <= 15) { return text; } return text.Substring(0, 15); } private static int Suffix(string fileName, string prefix) { string text = Path.GetFileNameWithoutExtension(fileName).Substring(prefix.Length); if (text.Length <= 16 || !int.TryParse(text.Substring(16), NumberStyles.None, CultureInfo.InvariantCulture, out var result)) { return 1; } return result; } private void PruneQuietly(string prefix, int keep, string alwaysKeep) { try { IEnumerable<FileInfo> source = Files(prefix); if (alwaysKeep != null) { keep--; source = source.Where((FileInfo f) => !SamePath(f.FullName, alwaysKeep)); } FileInfo[] array = source.Skip(Math.Max(0, keep)).ToArray(); for (int num = 0; num < array.Length; num++) { DeleteQuietly(array[num].FullName); } } catch (Exception) { } } private void EnforceTotalLimit() { try { long num = 0L; FileInfo[] array = Files("session-"); foreach (FileInfo fileInfo in array) { num += fileInfo.Length; if (num > 20971520 && !SamePath(fileInfo.FullName, _sessionPath)) { DeleteQuietly(fileInfo.FullName); } } } catch (Exception) { } } private static bool SamePath(string a, string b) { return string.Equals(Path.GetFullPath(a), Path.GetFullPath(b), StringComparison.OrdinalIgnoreCase); } private static void DeleteQuietly(string path) { try { if (path != null) { File.Delete(path); } } catch (Exception) { } } internal static void AppendValue(StringBuilder line, object value) { if (value == null) { line.Append("null"); } else if (value is string) { AppendString(line, (string)value); } else if (value is bool) { line.Append(((bool)value) ? "true" : "false"); } else if (value is float || value is double || value is decimal) { double d = Convert.ToDouble(value, CultureInfo.InvariantCulture); if (double.IsNaN(d) || double.IsInfinity(d)) { line.Append("null"); } else { line.Append(d.ToString("0.###", CultureInfo.InvariantCulture)); } } else if (value is IDictionary) { line.Append('{'); bool flag = true; foreach (DictionaryEntry item in (IDictionary)value) { if (!flag) { line.Append(','); } flag = false; AppendString(line, Convert.ToString(item.Key, CultureInfo.InvariantCulture)); line.Append(':'); AppendValue(line, item.Value); } line.Append('}'); } else if (value is IEnumerable) { line.Append('['); bool flag2 = true; foreach (object item2 in (IEnumerable)value) { if (!flag2) { line.Append(','); } flag2 = false; AppendValue(line, item2); } line.Append(']'); } else if (IsWholeNumber(value)) { line.Append(Convert.ToString(value, CultureInfo.InvariantCulture)); } else { AppendString(line, Convert.ToString(value, CultureInfo.InvariantCulture)); } } private static bool IsWholeNumber(object value) { if (!(value is int) && !(value is long) && !(value is short) && !(value is byte) && !(value is uint) && !(value is ulong) && !(value is ushort)) { return value is sbyte; } return true; } private static void AppendString(StringBuilder line, string text) { line.Append('"'); foreach (char c in text) { switch (c) { case '"': line.Append("\\\""); continue; case '\\': line.Append("\\\\"); continue; case '\n': line.Append("\\n"); continue; case '\r': line.Append("\\r"); continue; case '\t': line.Append("\\t"); continue; } if (c < ' ') { StringBuilder stringBuilder = line.Append("\\u"); int num = c; stringBuilder.Append(num.ToString("x4", CultureInfo.InvariantCulture)); } else { line.Append(c); } } line.Append('"'); } } internal static class ConfigMigration { private struct LegacyEntry { public readonly string Section; public readonly string Key; public readonly string Default; public LegacyEntry(string section, string key, string defaultValue) { Section = section; Key = key; Default = defaultValue; } } private const string LegacyMarker = "[Style 1 - Light]"; private const string BackupSuffix = ".pre-2.0.bak"; private const string AbsentValue = "__dynamic_pressure_absent__"; private const string AutoSpawnKey = "EnableAutoSpawn"; private const string GeneralSection = "General"; private static readonly LegacyEntry[] LegacyEntries = BuildLegacyEntries(); private static LegacyEntry[] BuildLegacyEntries() { string[] array = new string[7] { "Style 1 - Light", "6", "16", "1", "2", "3", "8" }; string[] array2 = new string[7] { "Style 2 - Heavy", "9", "10", "1", "4", "5", "16" }; string[] array3 = new string[7] { "Style 3 - Nightmare", "13", "7", "2", "6", "8", "28" }; List<LegacyEntry> list = new List<LegacyEntry> { new LegacyEntry("General", "EnableAutoSpawn", "true"), new LegacyEntry("Pressure", "ScanInterval", "1"), new LegacyEntry("Pressure", "EnemyScanRadius", "25"), new LegacyEntry("Pressure", "CloseEnemyDistance", "8"), new LegacyEntry("Safety", "LowHealthStopThreshold", "0.35"), new LegacyEntry("Safety", "RecentDamageStopWindow", "4"), new LegacyEntry("Anti Loop", "MinOriginalEngagedHostilesForSpawn", "1"), new LegacyEntry("Anti Loop", "CooldownAfterModKillOnly", "8"), new LegacyEntry("Anti Loop", "MaxSecondsWithoutOriginalKill", "30"), new LegacyEntry("Anti Loop", "MaxModSpawnsPerOriginalEngaged", "2"), new LegacyEntry("Candidates", "AllowEnvironmentFallbackCandidates", "false"), new LegacyEntry("Spawn", "SpawnDistanceMin", "8"), new LegacyEntry("Spawn", "SpawnDistanceMax", "60") }; string[][] array4 = new string[3][] { array, array2, array3 }; foreach (string[] array5 in array4) { list.Add(new LegacyEntry(array5[0], "TargetPressure", array5[1])); list.Add(new LegacyEntry(array5[0], "SpawnCooldown", array5[2])); list.Add(new LegacyEntry(array5[0], "MaxSpawnPerWave", array5[3])); list.Add(new LegacyEntry(array5[0], "MaxModSpawnedAlive", array5[4])); list.Add(new LegacyEntry(array5[0], "MaxModSpawnedPerRoom", array5[5])); list.Add(new LegacyEntry(array5[0], "MaxModSpawnedPerLevel", array5[6])); } return list.ToArray(); } public static bool Run(ConfigFile config, Settings settings, ManualLogSource log) { string configFilePath = config.ConfigFilePath; if (!NeedsMigration(configFilePath) || !TryBackup(configFilePath, log)) { return false; } bool flag = false; bool saveOnConfigSet = config.SaveOnConfigSet; config.SaveOnConfigSet = false; try { LegacyEntry[] legacyEntries = LegacyEntries; for (int i = 0; i < legacyEntries.Length; i++) { LegacyEntry legacyEntry = legacyEntries[i]; ConfigEntry<string> val = config.Bind<string>(legacyEntry.Section, legacyEntry.Key, "__dynamic_pressure_absent__", string.Empty); string value = val.Value; config.Remove(((ConfigEntryBase)val).Definition); if (value == "__dynamic_pressure_absent__") { continue; } if (legacyEntry.Key == "EnableAutoSpawn") { if (IsFalse(value)) { settings.EnableMod.Value = false; } } else { flag |= !SameValue(value, legacyEntry.Default); } } settings.Style.Value = Settings.ClampStyle(settings.Style.Value); config.Save(); } finally { config.SaveOnConfigSet = saveOnConfigSet; } log.LogInfo((object)("Moved an older configuration onto the simplified settings. Backup: " + configFilePath + ".pre-2.0.bak")); if (flag) { log.LogWarning((object)"Custom tuning values from the older version no longer apply. They are kept in the backup file."); } return flag; } private static bool NeedsMigration(string path) { try { return File.Exists(path) && File.ReadAllText(path).Contains("[Style 1 - Light]"); } catch (Exception) { return false; } } private static bool TryBackup(string path, ManualLogSource log) { string text = path + ".pre-2.0.bak"; try { if (!File.Exists(text)) { File.Copy(path, text); } return true; } catch (Exception ex) { log.LogWarning((object)("Could not back up the older configuration; leaving it untouched: " + ex.Message)); return false; } } private static bool IsFalse(string value) { return string.Equals(value.Trim(), "false", StringComparison.OrdinalIgnoreCase); } private static bool SameValue(string stored, string expected) { if (double.TryParse(stored, NumberStyles.Float, CultureInfo.InvariantCulture, out var result) && double.TryParse(expected, NumberStyles.Float, CultureInfo.InvariantCulture, out var result2)) { return Math.Abs(result - result2) < 0.0001; } return string.Equals(stored.Trim(), expected, StringComparison.OrdinalIgnoreCase); } } public enum PressureStyle { Light = 1, Heavy, Nightmare } public enum SpawnPosition { Balanced, Ahead, Anywhere } internal enum DirectorPhase { BuildUp, Sustain, Fade, Relax } internal sealed class PressurePreset { public float PeakIntensity; public float InitialUnlockedFraction; public float FadeExitIntensity; public float FadeMaxSeconds; public float RelaxSeconds; public float BuildUpTimeoutSeconds; public float TargetThreatPerRoom; public float LevelBudgetFraction; public int MinimumUnitsPerLevel; public float AliveCapPerRoom; public float AliveCapFloor; public float SurgeIntervalMin; public float SurgeIntervalMax; public float MutationChance; public bool MutationsOnlyLate; public bool AllowTanks; public bool TanksOnlyLate; private static readonly PressurePreset Light = new PressurePreset { PeakIntensity = 0.5f, InitialUnlockedFraction = 0.25f, FadeExitIntensity = 0.25f, FadeMaxSeconds = 30f, RelaxSeconds = 45f, BuildUpTimeoutSeconds = 40f, TargetThreatPerRoom = 1f, LevelBudgetFraction = 0.25f, MinimumUnitsPerLevel = 4, AliveCapPerRoom = 0.8f, AliveCapFloor = 6f, SurgeIntervalMin = 150f, SurgeIntervalMax = 210f, MutationChance = 0f, MutationsOnlyLate = true, AllowTanks = false, TanksOnlyLate = true }; private static readonly PressurePreset Heavy = new PressurePreset { PeakIntensity = 0.65f, InitialUnlockedFraction = 0.32f, FadeExitIntensity = 0.32f, FadeMaxSeconds = 20f, RelaxSeconds = 24f, BuildUpTimeoutSeconds = 60f, TargetThreatPerRoom = 1.5f, LevelBudgetFraction = 0.5f, MinimumUnitsPerLevel = 7, AliveCapPerRoom = 1.2f, AliveCapFloor = 10f, SurgeIntervalMin = 100f, SurgeIntervalMax = 150f, MutationChance = 0.15f, MutationsOnlyLate = true, AllowTanks = true, TanksOnlyLate = true }; private static readonly PressurePreset Nightmare = new PressurePreset { PeakIntensity = 0.8f, InitialUnlockedFraction = 0.4f, FadeExitIntensity = 0.4f, FadeMaxSeconds = 12f, RelaxSeconds = 12f, BuildUpTimeoutSeconds = 90f, TargetThreatPerRoom = 2.2f, LevelBudgetFraction = 0.8f, MinimumUnitsPerLevel = 11, AliveCapPerRoom = 2f, AliveCapFloor = 14f, SurgeIntervalMin = 60f, SurgeIntervalMax = 100f, MutationChance = 0.3f, MutationsOnlyLate = false, AllowTanks = true, TanksOnlyLate = false }; public static PressurePreset For(PressureStyle style) { return style switch { PressureStyle.Heavy => Heavy, PressureStyle.Nightmare => Nightmare, _ => Light, }; } } internal struct LevelCalibration { public float PackBudget; public float PackDensity; public int SpawnableRooms; public int RouteRooms; public float AverageUnitCost; public float LevelThreat { get { if (SpawnableRooms <= 0) { return 0f; } return (float)Math.Ceiling((float)SpawnableRooms * PackDensity) * PackBudget; } } public float RoomThreat => LevelThreat / (float)Math.Max(1, SpawnableRooms); } internal struct DirectorTuning { public float Reinforcement; public float Rest; public static readonly DirectorTuning Neutral = new DirectorTuning { Reinforcement = 1f, Rest = 1f }; } internal struct DirectorInput { public float Now; public float DeltaSeconds; public bool SpawningAllowed; public bool Engaged; public float PlayerHealthFraction; public int RouteRoomIndex; public float WeightedThreat; public float ModAliveCost; } internal struct DirectorOrders { public float TrickleBudget; public float SurgeBudget; public bool SurgeWarning; } internal sealed class Director { private const float MinimumHitIntensity = 0.04f; private const float DecayPerSecond = 0.05f; private const float DecayHoldAfterHitSeconds = 15f; private const float CriticalHealthFraction = 0.25f; private const float SustainSeconds = 4f; private const int RelaxEndsAfterRooms = 2; private const float TrickleIntervalMin = 3f; private const float TrickleIntervalMax = 6f; private const float SurgeWarningSeconds = 2.5f; private const float EngagementGraceSeconds = 10f; private const float PaceStepPerTimeout = 0.15f; private const float MinimumPace = 0.5f; private const float TrickleShareOfUnlocked = 0.7f; private readonly Func<float, float, float> _randomRange; private PressurePreset _preset; private LevelCalibration _level; private DirectorTuning _tuning = DirectorTuning.Neutral; private float _phaseElapsed; private float _secondsSinceHit; private float _secondsSinceEngaged; private float _nextTrickleAt; private float _nextSurgeAt; private float _surgeFireAt; private bool _surgeWarned; private float _surgeReserved; private bool _sawEngagementThisBuildUp; private bool _hasEngaged; private int _timeoutsWithoutPeak; private int _relaxStartRoom; private int _highestRoute; public DirectorPhase Phase { get; private set; } public float Intensity { get; private set; } public float SpentThreat { get; private set; } public float TotalBudget { get; private set; } public bool HasEngaged => _hasEngaged; public float Pace => Math.Max(0.5f, 1f - 0.15f * (float)_timeoutsWithoutPeak); public float TargetThreat => _level.RoomThreat * _preset.TargetThreatPerRoom * _tuning.Reinforcement; public float AliveCap => Math.Max(_preset.AliveCapFloor, _level.RoomThreat * _preset.AliveCapPerRoom) * _tuning.Reinforcement; publi