Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of HorrorDirectorMod v0.1.3
BepInEx/plugins/HorrorDirectorMod/HorrorDirectorMod.dll
Decompiled 42 minutes 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.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using Dissonance; using GameNetcodeStuff; using HarmonyLib; using HorrorDirectorMod.Anomaly; using HorrorDirectorMod.Bootstrap; using HorrorDirectorMod.Content; using HorrorDirectorMod.Dev; using HorrorDirectorMod.Diagnostics; using HorrorDirectorMod.Director; using HorrorDirectorMod.Input; using HorrorDirectorMod.Lifecycle; using HorrorDirectorMod.Objectives; using HorrorDirectorMod.Observation; using HorrorDirectorMod.Progression; using HorrorDirectorMod.Terminal; using Microsoft.CodeAnalysis; using TerminalApi; using TerminalApi.Classes; using TerminalApi.Events; using Unity.Collections; using Unity.Netcode; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("HorrorDirectorMod")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("First feasibility probe for a Lethal Company horror director mod.")] [assembly: AssemblyFileVersion("0.1.3.0")] [assembly: AssemblyInformationalVersion("0.1.3+032cd2883379c17aa34138bb9e6452b21b785dc2")] [assembly: AssemblyProduct("HorrorDirectorMod")] [assembly: AssemblyTitle("HorrorDirectorMod")] [assembly: AssemblyVersion("0.1.3.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace HorrorDirectorMod { [BepInPlugin("linar.horrordirector", "Horror Director Mod", "0.1.3")] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "linar.horrordirector"; public const string PluginName = "Horror Director Mod"; public const string PluginVersion = "0.1.3"; private Harmony? _harmony; internal static ManualLogSource ModLogger { get; private set; } private void Awake() { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown ModLogger = ((BaseUnityPlugin)this).Logger; Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); ConfigService config = new ConfigService(((BaseUnityPlugin)this).Config); ModServices.Initialize(((BaseUnityPlugin)this).Logger, config); ((BaseUnityPlugin)this).Logger.LogInfo((object)"[HDM:Bootstrap] Horror Director Mod 0.1.3 loaded."); _harmony = new Harmony("linar.horrordirector"); _harmony.PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"[HDM:Bootstrap] Harmony patches applied."); ModServices.Diagnostics.StartBackgroundHeartbeat(); ModServices.Diagnostics.LogHeartbeat("Awake smoke test"); } private void OnEnable() { ((BaseUnityPlugin)this).Logger.LogInfo((object)"[HDM:Bootstrap] Plugin enabled."); } private void OnDestroy() { ((BaseUnityPlugin)this).Logger.LogInfo((object)"[HDM:Bootstrap] Plugin object destroyed; static services may continue for diagnostics."); } private void OnApplicationQuit() { ((BaseUnityPlugin)this).Logger.LogInfo((object)"[HDM:Bootstrap] Application quitting."); ModServices.Shutdown(); Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; } } public static class PluginInfo { public const string PLUGIN_GUID = "HorrorDirectorMod"; public const string PLUGIN_NAME = "HorrorDirectorMod"; public const string PLUGIN_VERSION = "0.1.3"; } } namespace HorrorDirectorMod.Terminal { internal sealed class HauntedCaseTerminalService { private const string CommandWord = "hdmcase"; private const string AliasCommandWord = "case"; private readonly ManualLogSource _logger; private readonly ConfigService _config; private readonly WrongPlaceEpisodeDirector _wrongPlaceEpisodeDirector; private bool _registered; public HauntedCaseTerminalService(ManualLogSource logger, ConfigService config, WrongPlaceEpisodeDirector wrongPlaceEpisodeDirector) { _logger = logger; _config = config; _wrongPlaceEpisodeDirector = wrongPlaceEpisodeDirector; } public void Initialize() { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Expected O, but got Unknown if (!_registered) { TerminalApi.AddCommand("hdmcase", new CommandInfo { Title = "HDM Case", Category = "Other", Description = "Displays the Horror Director case file.", DisplayTextSupplier = DisplayCaseFile }, (string)null, true); TerminalApi.AddCommand("case", new CommandInfo { Title = "Case", Category = "Other", Description = "Alias for the Horror Director case file.", DisplayTextSupplier = DisplayCaseFile }, (string)null, true); _registered = true; _logger.LogInfo((object)"[HDM:Terminal] Registered hdmcase/case commands."); } } public void Shutdown() { _registered = false; } private string DisplayCaseFile() { if (!_config.WrongPlaceAllowTerminalWrongness) { return "HDM CASE FILE\n\nTerminal wrongness is disabled by config.\n\n"; } return "HDM CASE FILE\n\n" + $"State: {_wrongPlaceEpisodeDirector.State}\n" + "Episode: " + FormatEpisodeId() + "\nSignal: " + _wrongPlaceEpisodeDirector.TerminalStatus + "\n\nNotes:\n- ship safety signal unreliable\n- facility return route under observation\n- do not trust repeated sounds\n\n"; } private string FormatEpisodeId() { if (!(_wrongPlaceEpisodeDirector.EpisodeId == Guid.Empty)) { return _wrongPlaceEpisodeDirector.EpisodeId.ToString("N").Substring(0, 8); } return "none"; } } internal sealed class TerminalAnomalyDebugService { private const string CommandWord = "hdmaudio"; private const string AliasCommandWord = "audio"; private readonly ManualLogSource _logger; private readonly GameObserver _observer; private readonly AnomalyEventBus _anomalyEventBus; private bool _registered; public TerminalAnomalyDebugService(ManualLogSource logger, GameObserver observer, AnomalyEventBus anomalyEventBus) { _logger = logger; _observer = observer; _anomalyEventBus = anomalyEventBus; } public void Initialize() { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Expected O, but got Unknown if (!_registered) { TerminalApi.AddCommand("hdmaudio", new CommandInfo { Title = "HDM Audio", Category = "Other", Description = "Debug trigger for the Horror Director fake audio cue.", DisplayTextSupplier = TriggerFakeAudioCue }, (string)null, true); TerminalApi.AddCommand("audio", new CommandInfo { Title = "Audio", Category = "Other", Description = "Alias for the Horror Director fake audio cue debug trigger.", DisplayTextSupplier = TriggerFakeAudioCue }, (string)null, true); _registered = true; _logger.LogInfo((object)"[HDM:Terminal] Registered hdmaudio/audio commands."); } } public void Shutdown() { _registered = false; } private string TriggerFakeAudioCue() { StartOfRound round = Object.FindObjectOfType<StartOfRound>(); GameObservation observation = _observer.Observe(round); AnomalyTriggerResult anomalyTriggerResult = _anomalyEventBus.TryTriggerDebugFakeAudioCue(observation, "terminal command hdmaudio/audio"); if (anomalyTriggerResult.Success) { return "HDM AUDIO PROBE\n\nFake audio cue dispatched.\n" + $"Event id: {anomalyTriggerResult.EventId}\n\n"; } return "HDM AUDIO PROBE\n\nFake audio cue was not dispatched.\nReason: " + anomalyTriggerResult.Message + "\n\n"; } } internal sealed class TerminalFacilityObjectiveService { private const string SpawnCommandWord = "objspawn"; private const string SpawnSelfCommandWord = "objself"; private const string StartCommandWord = "objstart"; private readonly ManualLogSource _logger; private readonly ConfigService _config; private readonly FacilityObjectiveService _objectives; private bool _registered; public TerminalFacilityObjectiveService(ManualLogSource logger, ConfigService config, FacilityObjectiveService objectives) { _logger = logger; _config = config; _objectives = objectives; } public void Initialize() { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: 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_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Expected O, but got Unknown //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_008b: 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_00af: Expected O, but got Unknown //IL_00b4: 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_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Expected O, but got Unknown if (!_registered) { if (!_config.FacilityObjectiveEnabled) { _logger.LogInfo((object)"[HDM:Terminal] Facility objective commands disabled by config."); return; } TerminalApi.AddCommand("objspawn", new CommandInfo { Title = "HDM Objective Spawn", Category = "Other", Description = "Spawns a local facility objective debug panel near the player.", DisplayTextSupplier = SpawnObjective }, (string)null, true); TerminalApi.AddCommand("objself", new CommandInfo { Title = "HDM Objective Self", Category = "Other", Description = "Spawns a local facility objective debug panel at the player position.", DisplayTextSupplier = SpawnObjectiveAtPlayer }, (string)null, true); TerminalApi.AddCommand("objstart", new CommandInfo { Title = "HDM Objective Start", Category = "Other", Description = "Starts the pipe-routing facility objective probe.", DisplayTextSupplier = StartPipePuzzle }, (string)null, true); _registered = true; _logger.LogInfo((object)"[HDM:Terminal] Registered objspawn/objself/objstart commands."); } } public void Shutdown() { if (_registered) { _registered = false; } } private string SpawnObjective() { return FormatSpawnResult(_objectives.SpawnDebugObjective("terminal command objspawn"), "near you"); } private string SpawnObjectiveAtPlayer() { return FormatSpawnResult(_objectives.SpawnDebugObjectiveAtPlayer("terminal command objself"), "at your position"); } private string StartPipePuzzle() { return FormatPuzzleOpened(_objectives.StartPipePuzzleFromDebugCommand("terminal command objstart")); } private static string FormatSpawnResult(ObjectiveProbeResult result, string locationText) { if (result.Success) { return "HDM FACILITY OBJECTIVE\n\nObjective panel spawned " + locationText + ".\nObject: " + result.ObjectiveName + "\n\nLook at the panel and press E, or type objstart to open its local terminal.\n\n"; } return "HDM FACILITY OBJECTIVE\n\nObjective panel was not spawned.\nReason: " + result.Message + "\n\n"; } private static string FormatPuzzleOpened(PipePuzzle puzzle) { return "HDM FACILITY OBJECTIVE\n\nLocal objective terminal opened.\nUse the panel UI that appeared on your screen.\n\n" + $"Reward: {puzzle.RewardCredits} credits.\n\n"; } } internal sealed class TerminalMathGameService { private sealed class MathPuzzle { public string Expression { get; } public int Answer { get; } public int RewardCredits { get; } public MathPuzzle(string expression, int answer, int rewardCredits) { Expression = expression; Answer = answer; RewardCredits = rewardCredits; } } private const string CommandWord = "math"; private readonly ManualLogSource _logger; private readonly ConfigService _config; private readonly Random _random = new Random(); private MathPuzzle? _pendingPuzzle; private bool _registered; public TerminalMathGameService(ManualLogSource logger, ConfigService config) { _logger = logger; _config = config; } public void Initialize() { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: 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_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Expected O, but got Unknown //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Expected O, but got Unknown if (!_registered) { if (!_config.TerminalMathEnabled) { _logger.LogInfo((object)"[HDM:Terminal] Math command disabled by config."); return; } TerminalApi.AddCommand("math", new CommandInfo { Title = "Math", Category = "Other", Description = "Solve a small arithmetic task for credits.", DisplayTextSupplier = StartPuzzle }, (string)null, true); Events.TerminalParsedSentence += new TerminalParseSentenceEventHandler(OnTerminalParsedSentence); _registered = true; _logger.LogInfo((object)"[HDM:Terminal] Registered math command."); } } public void Shutdown() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown if (_registered) { Events.TerminalParsedSentence -= new TerminalParseSentenceEventHandler(OnTerminalParsedSentence); _registered = false; _pendingPuzzle = null; } } private string StartPuzzle() { _pendingPuzzle = GeneratePuzzle(); _logger.LogInfo((object)$"[HDM:Terminal] Math puzzle generated: expression='{_pendingPuzzle.Expression}', answer={_pendingPuzzle.Answer}, reward={_pendingPuzzle.RewardCredits}."); return FormatPuzzlePrompt(_pendingPuzzle); } private void OnTerminalParsedSentence(object sender, TerminalParseSentenceEventArgs e) { if (_pendingPuzzle == null || e.ReturnedNode == null) { return; } string text = (e.SubmittedText ?? string.Empty).Trim(); if (!text.Equals("math", StringComparison.OrdinalIgnoreCase)) { if (!int.TryParse(text, out var result)) { e.ReturnedNode.displayText = FormatInvalidAnswer(_pendingPuzzle); e.ReturnedNode.clearPreviousText = true; return; } if (result != _pendingPuzzle.Answer) { _logger.LogInfo((object)$"[HDM:Terminal] Math puzzle answered incorrectly: submitted={result}, expected={_pendingPuzzle.Answer}."); e.ReturnedNode.displayText = FormatWrongAnswer(_pendingPuzzle); e.ReturnedNode.clearPreviousText = true; return; } int rewardCredits = _pendingPuzzle.RewardCredits; int num = AwardCredits(((TerminalEventArgs)e).Terminal, rewardCredits); _logger.LogInfo((object)$"[HDM:Terminal] Math puzzle solved. reward={rewardCredits}, credits={num}."); e.ReturnedNode.displayText = FormatSuccess(rewardCredits, num); e.ReturnedNode.clearPreviousText = true; _pendingPuzzle = null; } } private MathPuzzle GeneratePuzzle() { if (_random.Next(0, 2) == 0) { int num = _random.Next(2, 13); int num2 = _random.Next(2, 13); return new MathPuzzle($"{num} * {num2}", num * num2, _config.TerminalMathRewardCredits); } int num3 = _random.Next(1, 51); int num4 = _random.Next(1, 51); return new MathPuzzle($"{num3} + {num4}", num3 + num4, _config.TerminalMathRewardCredits); } private static int AwardCredits(Terminal terminal, int reward) { if (terminal == null || reward <= 0) { return terminal?.groupCredits ?? 0; } terminal.groupCredits += reward; return terminal.groupCredits; } private static string FormatPuzzlePrompt(MathPuzzle puzzle) { return "HORROR DIRECTOR MATH PROBE\n\nSolve: " + puzzle.Expression + " = ?\n\nType only the answer and press enter.\n" + $"Reward: {puzzle.RewardCredits} credits.\n\n"; } private static string FormatInvalidAnswer(MathPuzzle puzzle) { return "Enter a number to answer the active math task.\n\nSolve: " + puzzle.Expression + " = ?\n\n"; } private static string FormatWrongAnswer(MathPuzzle puzzle) { return "Incorrect. Try again.\n\nSolve: " + puzzle.Expression + " = ?\n\n"; } private static string FormatSuccess(int reward, int credits) { return "Correct.\n\n" + $"Awarded {reward} credits.\n" + $"Current group credits: {credits}.\n\n" + "Type math for another task.\n\n"; } } internal sealed class TerminalPropDebugService { private const string SpawnCommandWord = "propspawn"; private const string SpawnSelfCommandWord = "propself"; private const string AnomalyCommandWord = "propanomaly"; private const string AnomalyAliasCommandWord = "propgo"; private const string NearbyAnomalyCommandWord = "propnear"; private readonly ManualLogSource _logger; private readonly PropAnomalyService _propAnomaly; private bool _registered; public TerminalPropDebugService(ManualLogSource logger, PropAnomalyService propAnomaly) { _logger = logger; _propAnomaly = propAnomaly; } public void Initialize() { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Expected O, but got Unknown //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Expected O, but got Unknown //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: 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_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Expected O, but got Unknown //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Expected O, but got Unknown if (!_registered) { TerminalApi.AddCommand("propspawn", new CommandInfo { Title = "HDM Prop Spawn", Category = "Other", Description = "Spawns a local debug prop near the player.", DisplayTextSupplier = SpawnDebugProp }, (string)null, true); TerminalApi.AddCommand("propanomaly", new CommandInfo { Title = "HDM Prop Anomaly", Category = "Other", Description = "Triggers a local anomaly on the last debug prop.", DisplayTextSupplier = TriggerPropAnomaly }, (string)null, true); TerminalApi.AddCommand("propself", new CommandInfo { Title = "HDM Prop Self", Category = "Other", Description = "Spawns a local debug prop directly at the player position.", DisplayTextSupplier = SpawnDebugPropAtPlayer }, (string)null, true); TerminalApi.AddCommand("propnear", new CommandInfo { Title = "HDM Prop Near", Category = "Other", Description = "Triggers an anomaly on the nearest real grabbable item.", DisplayTextSupplier = TriggerNearestGrabbableAnomaly }, (string)null, true); TerminalApi.AddCommand("propgo", new CommandInfo { Title = "HDM Prop Go", Category = "Other", Description = "Alias for the debug prop anomaly.", DisplayTextSupplier = TriggerPropAnomaly }, (string)null, true); _registered = true; _logger.LogInfo((object)"[HDM:Terminal] Registered propspawn/propself/propanomaly/propgo/propnear commands."); } } public void Shutdown() { _registered = false; } private string SpawnDebugProp() { PropProbeResult propProbeResult = _propAnomaly.SpawnDebugProp("terminal command propspawn"); if (propProbeResult.Success) { return "HDM PROP PROBE\n\nDebug prop spawned near you.\nProp: " + propProbeResult.PropName + "\n\nRun propanomaly or propgo to make it move.\n\n"; } return "HDM PROP PROBE\n\nDebug prop was not spawned.\nReason: " + propProbeResult.Message + "\n\n"; } private string SpawnDebugPropAtPlayer() { PropProbeResult propProbeResult = _propAnomaly.SpawnDebugPropAtPlayerPosition("terminal command propself"); if (propProbeResult.Success) { return "HDM PROP PROBE\n\nDebug prop spawned at your position.\nProp: " + propProbeResult.PropName + "\n\nRun propanomaly or propgo to make it move.\n\n"; } return "HDM PROP PROBE\n\nDebug prop was not spawned at your position.\nReason: " + propProbeResult.Message + "\n\n"; } private string TriggerPropAnomaly() { PropProbeResult propProbeResult = _propAnomaly.TriggerDebugAnomaly("terminal command propanomaly/propgo"); if (propProbeResult.Success) { return "HDM PROP PROBE\n\nProp anomaly triggered.\nProp: " + propProbeResult.PropName + "\n\n"; } return "HDM PROP PROBE\n\nProp anomaly was not triggered.\nReason: " + propProbeResult.Message + "\n\n"; } private string TriggerNearestGrabbableAnomaly() { PropProbeResult propProbeResult = _propAnomaly.TriggerNearestGrabbableAnomaly("terminal command propnear"); if (propProbeResult.Success) { return "HDM PROP PROBE\n\nNearest real item anomaly triggered.\nItem: " + propProbeResult.PropName + "\n\n"; } return "HDM PROP PROBE\n\nNearest real item anomaly was not triggered.\nReason: " + propProbeResult.Message + "\n\n"; } } } namespace HorrorDirectorMod.Progression { internal interface IModifierApplier { void Apply(PlayerControllerB player, ProgressionTotals totals, string reason); void Restore(PlayerControllerB? player, string reason); } internal sealed class PlayerStatApplier : IModifierApplier { private readonly struct PlayerBaseline { public ulong PlayerClientId { get; } public int Health { get; } public float MovementSpeed { get; } public float SprintTime { get; } public float GrabDistance { get; } public PlayerBaseline(ulong playerClientId, int health, float movementSpeed, float sprintTime, float grabDistance) { PlayerClientId = playerClientId; Health = health; MovementSpeed = movementSpeed; SprintTime = sprintTime; GrabDistance = grabDistance; } } private readonly ManualLogSource _logger; private PlayerBaseline? _baseline; private bool _hasAppliedTotals; public PlayerStatApplier(ManualLogSource logger) { _logger = logger; } public void Apply(PlayerControllerB player, ProgressionTotals totals, string reason) { EnsureBaseline(player); if (_baseline.HasValue) { PlayerBaseline value = _baseline.Value; int num = Math.Max(1, value.Health + totals.HealthBonus); float num2 = value.MovementSpeed * Math.Max(0.1f, totals.MovementMultiplier); float num3 = value.SprintTime * Math.Max(0.1f, totals.SprintTimeMultiplier); float num4 = value.GrabDistance * Math.Max(0.1f, totals.GrabDistanceMultiplier); player.movementSpeed = num2; player.sprintTime = num3; player.grabDistance = num4; if (!player.isPlayerDead && player.health < num) { player.health = num; } if (!_hasAppliedTotals || reason != "tick") { _logger.LogInfo((object)$"[HDM:Progression] Applied stats: player={player.playerClientId}, healthTarget={num}, move={num2:0.00}, sprintTime={num3:0.00}, grab={num4:0.00}, reason='{reason}'."); _hasAppliedTotals = true; } } } public void Restore(PlayerControllerB? player, string reason) { if (player == null || !_baseline.HasValue) { return; } PlayerBaseline value = _baseline.Value; if (value.PlayerClientId == player.playerClientId) { player.movementSpeed = value.MovementSpeed; player.sprintTime = value.SprintTime; player.grabDistance = value.GrabDistance; if (!player.isPlayerDead && player.health > value.Health) { player.health = value.Health; } _hasAppliedTotals = false; _logger.LogInfo((object)$"[HDM:Progression] Restored baseline stats: player={player.playerClientId}, reason='{reason}'."); } } public void ForgetPlayer() { _baseline = null; _hasAppliedTotals = false; } private void EnsureBaseline(PlayerControllerB player) { if (!_baseline.HasValue || _baseline.Value.PlayerClientId != player.playerClientId) { _baseline = new PlayerBaseline(player.playerClientId, Math.Max(1, player.health), Math.Max(0.1f, player.movementSpeed), Math.Max(0.1f, player.sprintTime), Math.Max(0.1f, player.grabDistance)); _hasAppliedTotals = false; _logger.LogInfo((object)$"[HDM:Progression] Captured baseline stats: player={player.playerClientId}, health={player.health}, move={player.movementSpeed:0.00}, sprintTime={player.sprintTime:0.00}, grab={player.grabDistance:0.00}."); } } } internal enum ProgressionUpgradeId { None, ReinforcedSuit, EmergencyPadding, RunnerConditioning, BiggerLungs, SteadyHands, DeepPockets, AdrenalineStep, SteadyNerves, IronWill } internal enum ProgressionEffectKind { HealthBonus, MovementMultiplier, SprintTimeMultiplier, GrabDistanceMultiplier, Placeholder } internal sealed class UpgradeDefinition { public string Id { get; } public ProgressionUpgradeId NetworkId { get; } public string Branch { get; } public string Title { get; } public string Description { get; } public int Cost { get; } public ProgressionEffectKind EffectKind { get; } public float EffectValue { get; } public bool Available { get; } public UpgradeDefinition(string id, ProgressionUpgradeId networkId, string branch, string title, string description, int cost, ProgressionEffectKind effectKind, float effectValue, bool available) { Id = id; NetworkId = networkId; Branch = branch; Title = title; Description = description; Cost = cost; EffectKind = effectKind; EffectValue = effectValue; Available = available; } } internal enum ProgressionNetworkMessageKind { PurchaseRequest = 1, ResetRequest = 2, RequestState = 3, StateSnapshot = 10, PurchaseRejected = 11 } internal enum ProgressionRejectReason { None, UnknownUpgrade, Unavailable, AlreadyOwned, NotEnoughPoints, NotHost, NetworkUnavailable } internal readonly struct ProgressionNetworkSnapshot { public ulong TargetClientId { get; } public int AvailablePoints { get; } public int OwnedMask { get; } public ProgressionNetworkSnapshot(ulong targetClientId, int availablePoints, int ownedMask) { TargetClientId = targetClientId; AvailablePoints = availablePoints; OwnedMask = ownedMask; } } internal readonly struct ProgressionNetworkReject { public ulong TargetClientId { get; } public ProgressionUpgradeId UpgradeId { get; } public ProgressionRejectReason Reason { get; } public ProgressionNetworkReject(ulong targetClientId, ProgressionUpgradeId upgradeId, ProgressionRejectReason reason) { TargetClientId = targetClientId; UpgradeId = upgradeId; Reason = reason; } } internal readonly struct ProgressionTotals { public int HealthBonus { get; } public float MovementMultiplier { get; } public float SprintTimeMultiplier { get; } public float GrabDistanceMultiplier { get; } public ProgressionTotals(int healthBonus, float movementMultiplier, float sprintTimeMultiplier, float grabDistanceMultiplier) { HealthBonus = healthBonus; MovementMultiplier = movementMultiplier; SprintTimeMultiplier = sprintTimeMultiplier; GrabDistanceMultiplier = grabDistanceMultiplier; } } internal readonly struct ProgressionSnapshot { public int AvailablePoints { get; } public int StartingPoints { get; } public bool IsRequestPending { get; } public string LastRejectedReason { get; } public ProgressionSnapshot(int availablePoints, int startingPoints, bool isRequestPending, string lastRejectedReason) { AvailablePoints = availablePoints; StartingPoints = startingPoints; IsRequestPending = isRequestPending; LastRejectedReason = lastRejectedReason; } } internal sealed class ProgressionPlayerState { private readonly HashSet<ProgressionUpgradeId> _ownedUpgradeIds = new HashSet<ProgressionUpgradeId>(); public ulong ClientId { get; } public int AvailablePoints { get; private set; } public IEnumerable<ProgressionUpgradeId> OwnedUpgradeIds => _ownedUpgradeIds; public ProgressionPlayerState(ulong clientId, int availablePoints) { ClientId = clientId; AvailablePoints = availablePoints; } public bool IsOwned(ProgressionUpgradeId upgradeId) { return _ownedUpgradeIds.Contains(upgradeId); } public bool TryPurchase(ProgressionUpgradeId upgradeId, int cost) { if (_ownedUpgradeIds.Contains(upgradeId) || AvailablePoints < cost) { return false; } AvailablePoints -= cost; _ownedUpgradeIds.Add(upgradeId); return true; } public void Reset(int availablePoints) { AvailablePoints = availablePoints; _ownedUpgradeIds.Clear(); } public int ToOwnedMask() { int num = 0; foreach (ProgressionUpgradeId ownedUpgradeId in _ownedUpgradeIds) { num |= 1 << (int)ownedUpgradeId; } return num; } public void ReplaceFromSnapshot(int availablePoints, int ownedMask) { AvailablePoints = availablePoints; _ownedUpgradeIds.Clear(); for (int i = 1; i <= 9; i++) { if ((ownedMask & (1 << i)) != 0) { _ownedUpgradeIds.Add((ProgressionUpgradeId)i); } } } } internal sealed class ProgressionNetworkService { private const string MessageName = "HDM_Progression_v1"; private const int Version = 1; private const int PayloadBytes = 48; private readonly ManualLogSource _logger; private Action<ulong, ProgressionNetworkMessageKind, ProgressionUpgradeId>? _onHostRequestReceived; private Action<ProgressionNetworkSnapshot>? _onSnapshotReceived; private Action<ProgressionNetworkReject>? _onRejectReceived; private CustomMessagingManager? _registeredMessagingManager; public string LastMessage { get; private set; } public bool IsListening { get { NetworkManager singleton = NetworkManager.Singleton; if (singleton == null) { return false; } return singleton.IsListening; } } public bool IsHost { get { NetworkManager singleton = NetworkManager.Singleton; if (singleton == null) { return false; } return singleton.IsHost; } } public bool IsServer { get { NetworkManager singleton = NetworkManager.Singleton; if (singleton == null) { return false; } return singleton.IsServer; } } public ulong LocalClientId { get { NetworkManager singleton = NetworkManager.Singleton; if (singleton == null) { return 0uL; } return singleton.LocalClientId; } } public ProgressionNetworkService(ManualLogSource logger) { _logger = logger; LastMessage = "none"; } public void Initialize(Action<ulong, ProgressionNetworkMessageKind, ProgressionUpgradeId> onHostRequestReceived, Action<ProgressionNetworkSnapshot> onSnapshotReceived, Action<ProgressionNetworkReject> onRejectReceived) { _onHostRequestReceived = onHostRequestReceived; _onSnapshotReceived = onSnapshotReceived; _onRejectReceived = onRejectReceived; EnsureRegistered(); } public void Tick() { EnsureRegistered(); } public void Shutdown() { if (_registeredMessagingManager != null) { _registeredMessagingManager.UnregisterNamedMessageHandler("HDM_Progression_v1"); _registeredMessagingManager = null; } _onHostRequestReceived = null; _onSnapshotReceived = null; _onRejectReceived = null; } public bool TrySendPurchaseRequest(ProgressionUpgradeId upgradeId) { return TrySendRequest(ProgressionNetworkMessageKind.PurchaseRequest, upgradeId); } public bool TrySendResetRequest() { return TrySendRequest(ProgressionNetworkMessageKind.ResetRequest, ProgressionUpgradeId.None); } public bool TrySendStateRequest() { return TrySendRequest(ProgressionNetworkMessageKind.RequestState, ProgressionUpgradeId.None); } public bool TrySendSnapshot(ulong targetClientId, ProgressionPlayerState state) { NetworkManager singleton = NetworkManager.Singleton; if (((singleton != null) ? singleton.CustomMessagingManager : null) == null || !singleton.IsListening) { LastMessage = "snapshot blocked: network unavailable"; return false; } if (targetClientId == singleton.LocalClientId) { return false; } EnsureRegistered(); return TrySend(singleton, targetClientId, ProgressionNetworkMessageKind.StateSnapshot, ProgressionUpgradeId.None, state.ClientId, state.AvailablePoints, state.ToOwnedMask(), ProgressionRejectReason.None); } public bool TrySendReject(ulong targetClientId, ProgressionUpgradeId upgradeId, ProgressionRejectReason reason) { NetworkManager singleton = NetworkManager.Singleton; if (((singleton != null) ? singleton.CustomMessagingManager : null) == null || !singleton.IsListening) { LastMessage = "reject blocked: network unavailable"; return false; } if (targetClientId == singleton.LocalClientId) { return false; } EnsureRegistered(); return TrySend(singleton, targetClientId, ProgressionNetworkMessageKind.PurchaseRejected, upgradeId, targetClientId, 0, 0, reason); } public string DescribeRole() { NetworkManager singleton = NetworkManager.Singleton; if (singleton == null) { return "network=null"; } return $"listening={singleton.IsListening}, host={singleton.IsHost}, server={singleton.IsServer}, client={singleton.IsClient}, localClient={singleton.LocalClientId}"; } private bool TrySendRequest(ProgressionNetworkMessageKind kind, ProgressionUpgradeId upgradeId) { NetworkManager singleton = NetworkManager.Singleton; if (((singleton != null) ? singleton.CustomMessagingManager : null) == null || !singleton.IsListening) { LastMessage = $"{kind} blocked: network unavailable"; return false; } EnsureRegistered(); return TrySend(singleton, 0uL, kind, upgradeId, singleton.LocalClientId, 0, 0, ProgressionRejectReason.None); } private unsafe bool TrySend(NetworkManager networkManager, ulong clientId, ProgressionNetworkMessageKind kind, ProgressionUpgradeId upgradeId, ulong targetClientId, int availablePoints, int ownedMask, ProgressionRejectReason rejectReason) { //IL_000b: 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) try { FastBufferWriter val = default(FastBufferWriter); ((FastBufferWriter)(ref val))..ctor(48, (Allocator)2, -1); try { WritePayload(val, kind, upgradeId, targetClientId, availablePoints, ownedMask, rejectReason); networkManager.CustomMessagingManager.SendNamedMessage("HDM_Progression_v1", clientId, val, (NetworkDelivery)3); LastMessage = $"sent {kind}: to={clientId}, target={targetClientId}, upgrade={upgradeId}, points={availablePoints}, mask={ownedMask}, reason={rejectReason}"; _logger.LogInfo((object)("[HDM:ProgressionNet] " + LastMessage + ".")); return true; } finally { ((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose(); } } catch (Exception ex) { LastMessage = $"send failed {kind}: {ex.GetType().Name}: {ex.Message}"; _logger.LogWarning((object)("[HDM:ProgressionNet] " + LastMessage)); return false; } } private void EnsureRegistered() { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown NetworkManager singleton = NetworkManager.Singleton; CustomMessagingManager val = ((singleton != null) ? singleton.CustomMessagingManager : null); if (val != null && val != _registeredMessagingManager) { if (_registeredMessagingManager != null) { _registeredMessagingManager.UnregisterNamedMessageHandler("HDM_Progression_v1"); } val.RegisterNamedMessageHandler("HDM_Progression_v1", new HandleNamedMessageDelegate(OnNamedMessage)); _registeredMessagingManager = val; _logger.LogInfo((object)"[HDM:ProgressionNet] Registered progression named message handler."); } } private void OnNamedMessage(ulong senderClientId, FastBufferReader reader) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) try { ReadPayload(reader, out var kind, out var upgradeId, out var targetClientId, out var availablePoints, out var ownedMask, out var rejectReason); LastMessage = $"received {kind}: sender={senderClientId}, target={targetClientId}, upgrade={upgradeId}, points={availablePoints}, mask={ownedMask}, reason={rejectReason}"; _logger.LogInfo((object)("[HDM:ProgressionNet] " + LastMessage + ".")); switch (kind) { case ProgressionNetworkMessageKind.PurchaseRequest: case ProgressionNetworkMessageKind.ResetRequest: case ProgressionNetworkMessageKind.RequestState: _onHostRequestReceived?.Invoke(senderClientId, kind, upgradeId); break; case ProgressionNetworkMessageKind.StateSnapshot: _onSnapshotReceived?.Invoke(new ProgressionNetworkSnapshot(targetClientId, availablePoints, ownedMask)); break; case ProgressionNetworkMessageKind.PurchaseRejected: _onRejectReceived?.Invoke(new ProgressionNetworkReject(targetClientId, upgradeId, rejectReason)); break; } } catch (Exception ex) { LastMessage = "receive failed: " + ex.GetType().Name + ": " + ex.Message; _logger.LogWarning((object)("[HDM:ProgressionNet] " + LastMessage)); } } private static void WritePayload(FastBufferWriter writer, ProgressionNetworkMessageKind kind, ProgressionUpgradeId upgradeId, ulong targetClientId, int availablePoints, int ownedMask, ProgressionRejectReason rejectReason) { //IL_0008: 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_001c: 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_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0036: 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_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) int num = 1; ((FastBufferWriter)(ref writer)).WriteValueSafe<int>(ref num, default(ForPrimitives)); num = (int)kind; ((FastBufferWriter)(ref writer)).WriteValueSafe<int>(ref num, default(ForPrimitives)); num = (int)upgradeId; ((FastBufferWriter)(ref writer)).WriteValueSafe<int>(ref num, default(ForPrimitives)); ((FastBufferWriter)(ref writer)).WriteValueSafe<ulong>(ref targetClientId, default(ForPrimitives)); ((FastBufferWriter)(ref writer)).WriteValueSafe<int>(ref availablePoints, default(ForPrimitives)); ((FastBufferWriter)(ref writer)).WriteValueSafe<int>(ref ownedMask, default(ForPrimitives)); num = (int)rejectReason; ((FastBufferWriter)(ref writer)).WriteValueSafe<int>(ref num, default(ForPrimitives)); } private static void ReadPayload(FastBufferReader reader, out ProgressionNetworkMessageKind kind, out ProgressionUpgradeId upgradeId, out ulong targetClientId, out int availablePoints, out int ownedMask, out ProgressionRejectReason rejectReason) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) int num = default(int); ((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref num, default(ForPrimitives)); if (num != 1) { throw new InvalidOperationException($"Unsupported progression payload version {num}."); } int num2 = default(int); ((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref num2, default(ForPrimitives)); int num3 = default(int); ((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref num3, default(ForPrimitives)); ((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref targetClientId, default(ForPrimitives)); ((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref availablePoints, default(ForPrimitives)); ((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref ownedMask, default(ForPrimitives)); int num4 = default(int); ((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref num4, default(ForPrimitives)); kind = (ProgressionNetworkMessageKind)num2; upgradeId = (ProgressionUpgradeId)num3; rejectReason = (ProgressionRejectReason)num4; } } internal sealed class ProgressionRuntime : MonoBehaviour { private ProgressionService? _service; public void Initialize(ProgressionService service) { _service = service; } private void Update() { if (_service != null) { _service.HandleUiKeyInput("runtime"); } } } internal sealed class ProgressionService { private readonly ManualLogSource _logger; private readonly ConfigService _config; private readonly GameplayInputService _gameplayInput; private readonly ProgressionNetworkService _network; private readonly ModifierRegistry _modifierRegistry; private readonly PlayerStatApplier _statApplier; private readonly Dictionary<ulong, ProgressionPlayerState> _states = new Dictionary<ulong, ProgressionPlayerState>(); private readonly Dictionary<ulong, bool> _hostAliveByClientId = new Dictionary<ulong, bool>(); private ProgressionRuntime? _runtime; private ProgressionUiController? _ui; private GameObject? _runtimeHost; private bool? _wasLocalPlayerAlive; private ulong? _observedPlayerId; private ulong? _requestedStateClientId; private bool _localRequestPending; private string _lastRejectedReason = string.Empty; private int _lastOpenKeyFrame = -1; private int _lastCloseKeyFrame = -1; public IReadOnlyList<UpgradeDefinition> Upgrades => (from modifier in _modifierRegistry.GetAvailable(EffectTargetKind.Player) select modifier.ToUpgradeDefinition()).ToArray(); internal KeyCode OpenKey => _config.ProgressionOpenKey; public event Action? StateChanged; public ProgressionService(ManualLogSource logger, ConfigService config, GameplayInputService gameplayInput, ProgressionNetworkService network, ModifierRegistry modifierRegistry) { _logger = logger; _config = config; _gameplayInput = gameplayInput; _network = network; _modifierRegistry = modifierRegistry; _statApplier = new PlayerStatApplier(logger); } public void Initialize() { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown if (!_config.ProgressionEnabled) { _logger.LogInfo((object)"[HDM:Progression] Progression prototype disabled by config."); return; } _network.Initialize(OnHostRequestReceived, OnSnapshotReceived, OnRejectReceived); _runtimeHost = new GameObject("HDM_ProgressionRuntime"); Object.DontDestroyOnLoad((Object)(object)_runtimeHost); _runtime = _runtimeHost.AddComponent<ProgressionRuntime>(); _runtime.Initialize(this); _logger.LogInfo((object)"[HDM:Progression] Runtime initialized with host-authoritative networking."); } public void Shutdown() { CloseUi("shutdown"); _ui?.Destroy(); _ui = null; if (_runtimeHost != null) { Object.Destroy((Object)(object)_runtimeHost); _runtimeHost = null; _runtime = null; } _network.Shutdown(); _statApplier.ForgetPlayer(); _states.Clear(); _hostAliveByClientId.Clear(); } public void Tick(StartOfRound round, float deltaTime) { if (!_config.ProgressionEnabled) { return; } _network.Tick(); HandleUiKeyInput("lifecycle"); if (IsHostAuthority()) { TickHostPlayerStates(round); } PlayerControllerB val = ResolveLocalPlayer(round); if (val == null || !val.isPlayerControlled) { _wasLocalPlayerAlive = null; _observedPlayerId = null; _statApplier.ForgetPlayer(); return; } ulong localClientId = GetLocalClientId(val); EnsureLocalStateRequested(localClientId); if (_observedPlayerId != val.playerClientId) { _observedPlayerId = val.playerClientId; _wasLocalPlayerAlive = !val.isPlayerDead; _statApplier.ForgetPlayer(); _logger.LogInfo((object)$"[HDM:Progression] Observing local player {val.playerClientId}; clientId={localClientId}."); } bool flag = !val.isPlayerDead; if (_wasLocalPlayerAlive == true && !flag) { if (_config.ProgressionResetRunProgressOnDeath) { if (IsHostAuthority()) { ResetRunForClient(localClientId, "local death", notifyClient: true); } else { RequestResetRun("local death"); } } CloseUi("death"); } _wasLocalPlayerAlive = flag; if (flag) { ApplyLocalStateToPlayer(val, "tick"); } } public void ToggleUi() { if (!_config.ProgressionEnabled) { return; } ProgressionUiController ui = _ui; if (ui != null && ui.IsOpen) { CloseUi("toggle"); return; } PlayerControllerB player = ResolveLocalPlayer(Object.FindObjectOfType<StartOfRound>()); if (!CanOpenUi(player, out string reason)) { _logger.LogInfo((object)("[HDM:Progression] Progression UI blocked: " + reason + ".")); return; } if (_ui == null) { _ui = new ProgressionUiController(_logger, this, _gameplayInput); } _ui.Open(player); _logger.LogInfo((object)"[HDM:Progression] Progression UI opened."); } public void OnOpenKeyPressed() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) _logger.LogInfo((object)$"[HDM:Progression] Open key pressed: key={_config.ProgressionOpenKey}."); ToggleUi(); } public void HandleUiKeyInput(string source) { ProgressionUiController ui = _ui; if (ui == null || !ui.IsOpen || !HandleCloseKeyInput(source)) { HandleOpenKeyInput(source); } } public void HandleOpenKeyInput(string source) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) if (Time.frameCount != _lastOpenKeyFrame && WasOpenKeyPressed()) { _lastOpenKeyFrame = Time.frameCount; _logger.LogInfo((object)$"[HDM:Progression] Open key input detected: key={_config.ProgressionOpenKey}, source={source}."); ToggleUi(); } } public void CloseUi(string reason) { if (_ui != null && _ui.IsOpen) { _ui.Close(reason); _logger.LogInfo((object)("[HDM:Progression] Progression UI closed: reason='" + reason + "'.")); } } public bool TryPurchaseUpgrade(string upgradeId) { return RequestPurchaseForLocalPlayer(upgradeId); } public bool RequestPurchaseForLocalPlayer(string upgradeId) { UpgradeDefinition upgradeDefinition = FindUpgrade(upgradeId); if (upgradeDefinition == null) { _lastRejectedReason = "unknown upgrade " + upgradeId; _logger.LogWarning((object)("[HDM:Progression] Purchase request failed: unknown upgrade '" + upgradeId + "'.")); NotifyStateChanged(); return false; } return RequestPurchaseForLocalPlayer(upgradeDefinition.NetworkId); } public bool RequestPurchaseForLocalPlayer(ProgressionUpgradeId upgradeId) { if (_localRequestPending) { _logger.LogInfo((object)$"[HDM:Progression] Purchase request ignored while pending: upgrade={upgradeId}."); return false; } _lastRejectedReason = string.Empty; if (IsHostAuthority()) { ulong localClientId = GetLocalClientId(ResolveLocalPlayer(Object.FindObjectOfType<StartOfRound>())); return ApplyPurchase(localClientId, upgradeId); } if (!_network.TrySendPurchaseRequest(upgradeId)) { _lastRejectedReason = ProgressionRejectReason.NetworkUnavailable.ToString(); _logger.LogInfo((object)$"[HDM:Progression] Purchase request blocked: upgrade={upgradeId}, reason=network unavailable."); NotifyStateChanged(); return false; } _localRequestPending = true; _logger.LogInfo((object)$"[HDM:Progression] Sent purchase request: upgrade={upgradeId}, role={_network.DescribeRole()}."); NotifyStateChanged(); return true; } public bool ApplyPurchase(ulong clientId, ProgressionUpgradeId upgradeId) { if (!IsHostAuthority()) { Reject(clientId, upgradeId, ProgressionRejectReason.NotHost); return false; } UpgradeDefinition upgradeDefinition = FindUpgrade(upgradeId); if (upgradeDefinition == null) { Reject(clientId, upgradeId, ProgressionRejectReason.UnknownUpgrade); return false; } if (!upgradeDefinition.Available) { Reject(clientId, upgradeId, ProgressionRejectReason.Unavailable); return false; } ProgressionPlayerState progressionPlayerState = EnsureState(clientId); if (progressionPlayerState.IsOwned(upgradeId)) { Reject(clientId, upgradeId, ProgressionRejectReason.AlreadyOwned); return false; } if (!progressionPlayerState.TryPurchase(upgradeId, upgradeDefinition.Cost)) { Reject(clientId, upgradeId, ProgressionRejectReason.NotEnoughPoints); return false; } _logger.LogInfo((object)$"[HDM:Progression] Host accepted purchase: client={clientId}, upgrade={upgradeId}, points={progressionPlayerState.AvailablePoints}."); SyncStateToClient(clientId, "purchase"); return true; } public void ResetRunProgress(string reason) { if (IsHostAuthority()) { ResetRunForClient(GetLocalClientId(ResolveLocalPlayer(Object.FindObjectOfType<StartOfRound>())), reason, notifyClient: true); } else { RequestResetRun(reason); } } public void LogStatus(string reason) { ulong localClientId = GetLocalClientId(ResolveLocalPlayer(Object.FindObjectOfType<StartOfRound>())); ProgressionPlayerState stateOrDefault = GetStateOrDefault(localClientId); string[] value = stateOrDefault.OwnedUpgradeIds.Select((ProgressionUpgradeId upgradeId) => upgradeId.ToString()).ToArray(); PlayerControllerB val = ResolveLocalPlayer(Object.FindObjectOfType<StartOfRound>()); string text = ((val == null) ? "none" : $"id={val.playerClientId}, controlled={val.isPlayerControlled}, dead={val.isPlayerDead}, health={val.health}, move={val.movementSpeed:0.00}, sprintTime={val.sprintTime:0.00}, grab={val.grabDistance:0.00}"); _logger.LogInfo((object)string.Format("[HDM:Progression] Status: reason='{0}', role={1}, localClient={2}, points={3}/{4}, owned=[{5}], pending={6}, rejected='{7}', player={8}.", reason, _network.DescribeRole(), localClientId, stateOrDefault.AvailablePoints, _config.ProgressionDevStartingPoints, string.Join(",", value), _localRequestPending, _lastRejectedReason, text)); } public void LogSyncStatus(string reason) { string[] value = (from state in _states.Values orderby state.ClientId select $"{state.ClientId}:points={state.AvailablePoints},mask={state.ToOwnedMask()}").ToArray(); _logger.LogInfo((object)string.Format("[HDM:Progression] SyncStatus: reason='{0}', role={1}, states=[{2}], pending={3}, lastNetwork='{4}'.", reason, _network.DescribeRole(), string.Join("; ", value), _localRequestPending, _network.LastMessage)); } public ProgressionSnapshot CreateSnapshot() { return new ProgressionSnapshot(GetStateOrDefault(GetLocalClientId(ResolveLocalPlayer(Object.FindObjectOfType<StartOfRound>()))).AvailablePoints, _config.ProgressionDevStartingPoints, _localRequestPending, _lastRejectedReason); } public bool IsOwned(string upgradeId) { UpgradeDefinition upgradeDefinition = FindUpgrade(upgradeId); if (upgradeDefinition != null) { return IsOwned(upgradeDefinition.NetworkId); } return false; } public bool IsOwned(ProgressionUpgradeId upgradeId) { return GetStateOrDefault(GetLocalClientId(ResolveLocalPlayer(Object.FindObjectOfType<StartOfRound>()))).IsOwned(upgradeId); } public int GetRank(string upgradeId) { return IsOwned(upgradeId) ? 1 : 0; } private void OnHostRequestReceived(ulong senderClientId, ProgressionNetworkMessageKind kind, ProgressionUpgradeId upgradeId) { if (!IsHostAuthority()) { _logger.LogInfo((object)$"[HDM:Progression] Ignored host request on non-host: sender={senderClientId}, kind={kind}."); return; } _logger.LogInfo((object)$"[HDM:Progression] Host received request: sender={senderClientId}, kind={kind}, upgrade={upgradeId}."); switch (kind) { case ProgressionNetworkMessageKind.PurchaseRequest: ApplyPurchase(senderClientId, upgradeId); break; case ProgressionNetworkMessageKind.ResetRequest: ResetRunForClient(senderClientId, "network reset request", notifyClient: true); break; case ProgressionNetworkMessageKind.RequestState: SyncStateToClient(senderClientId, "network state request"); break; } } private void OnSnapshotReceived(ProgressionNetworkSnapshot snapshot) { ulong localClientId = GetLocalClientId(ResolveLocalPlayer(Object.FindObjectOfType<StartOfRound>())); if (snapshot.TargetClientId != localClientId) { _logger.LogInfo((object)$"[HDM:Progression] Ignored snapshot for another client: target={snapshot.TargetClientId}, local={localClientId}."); return; } EnsureState(snapshot.TargetClientId).ReplaceFromSnapshot(snapshot.AvailablePoints, snapshot.OwnedMask); _localRequestPending = false; _lastRejectedReason = string.Empty; _logger.LogInfo((object)$"[HDM:Progression] Applied host snapshot: client={snapshot.TargetClientId}, points={snapshot.AvailablePoints}, mask={snapshot.OwnedMask}."); ApplyLocalStateToCurrentPlayer("snapshot"); NotifyStateChanged(); } private void OnRejectReceived(ProgressionNetworkReject reject) { ulong localClientId = GetLocalClientId(ResolveLocalPlayer(Object.FindObjectOfType<StartOfRound>())); if (reject.TargetClientId == localClientId) { _localRequestPending = false; _lastRejectedReason = reject.Reason.ToString(); _logger.LogInfo((object)$"[HDM:Progression] Host rejected request: upgrade={reject.UpgradeId}, reason={reject.Reason}."); NotifyStateChanged(); } } private void TickHostPlayerStates(StartOfRound round) { if (round.allPlayerScripts == null) { return; } PlayerControllerB[] allPlayerScripts = round.allPlayerScripts; foreach (PlayerControllerB val in allPlayerScripts) { if (val != null && val.isPlayerControlled) { ulong playerClientId = val.playerClientId; EnsureState(playerClientId); bool flag = !val.isPlayerDead; if (_hostAliveByClientId.TryGetValue(playerClientId, out var value) && value && !flag && _config.ProgressionResetRunProgressOnDeath) { ResetRunForClient(playerClientId, "host observed death", notifyClient: true); } _hostAliveByClientId[playerClientId] = flag; } } } private void EnsureLocalStateRequested(ulong localClientId) { if (IsHostAuthority() || !_network.IsListening) { EnsureState(localClientId); } else if (_requestedStateClientId != localClientId && !_localRequestPending && _network.TrySendStateRequest()) { _requestedStateClientId = localClientId; _localRequestPending = true; _logger.LogInfo((object)$"[HDM:Progression] Requested host progression state: client={localClientId}."); NotifyStateChanged(); } } private void RequestResetRun(string reason) { if (!_localRequestPending) { _lastRejectedReason = string.Empty; if (!_network.TrySendResetRequest()) { _lastRejectedReason = ProgressionRejectReason.NetworkUnavailable.ToString(); _logger.LogInfo((object)("[HDM:Progression] Reset request blocked: reason='" + reason + "', network unavailable.")); NotifyStateChanged(); } else { _localRequestPending = true; _logger.LogInfo((object)("[HDM:Progression] Sent reset request: reason='" + reason + "'.")); NotifyStateChanged(); } } } private void ResetRunForClient(ulong clientId, string reason, bool notifyClient) { EnsureState(clientId).Reset(_config.ProgressionDevStartingPoints); _logger.LogInfo((object)$"[HDM:Progression] Host reset run progression: client={clientId}, reason='{reason}'."); if (notifyClient) { SyncStateToClient(clientId, reason); } } private void SyncStateToClient(ulong clientId, string reason) { ProgressionPlayerState state = EnsureState(clientId); if (clientId == _network.LocalClientId || !_network.IsListening) { _localRequestPending = false; _lastRejectedReason = string.Empty; ApplyLocalStateToCurrentPlayer("host local " + reason); NotifyStateChanged(); } else { _network.TrySendSnapshot(clientId, state); } } private void Reject(ulong clientId, ProgressionUpgradeId upgradeId, ProgressionRejectReason reason) { _logger.LogInfo((object)$"[HDM:Progression] Host rejected purchase: client={clientId}, upgrade={upgradeId}, reason={reason}."); if (clientId == _network.LocalClientId || !_network.IsListening) { _localRequestPending = false; _lastRejectedReason = reason.ToString(); NotifyStateChanged(); } else { _network.TrySendReject(clientId, upgradeId, reason); } } private void ApplyLocalStateToCurrentPlayer(string reason) { PlayerControllerB val = ResolveLocalPlayer(Object.FindObjectOfType<StartOfRound>()); if (val != null) { ApplyLocalStateToPlayer(val, reason); } } private void ApplyLocalStateToPlayer(PlayerControllerB player, string reason) { ProgressionPlayerState stateOrDefault = GetStateOrDefault(GetLocalClientId(player)); if (!stateOrDefault.OwnedUpgradeIds.Any()) { _statApplier.Restore(player, reason); } else { _statApplier.Apply(player, CalculateTotals(stateOrDefault), reason); } } private ProgressionPlayerState EnsureState(ulong clientId) { if (_states.TryGetValue(clientId, out ProgressionPlayerState value)) { return value; } value = new ProgressionPlayerState(clientId, _config.ProgressionDevStartingPoints); _states[clientId] = value; _logger.LogInfo((object)$"[HDM:Progression] Created session progression state: client={clientId}, points={value.AvailablePoints}."); return value; } private ProgressionPlayerState GetStateOrDefault(ulong clientId) { if (!_states.TryGetValue(clientId, out ProgressionPlayerState value)) { return new ProgressionPlayerState(clientId, _config.ProgressionDevStartingPoints); } return value; } private bool IsHostAuthority() { if (_network.IsListening && !_network.IsServer) { return _network.IsHost; } return true; } private ulong GetLocalClientId(PlayerControllerB? player) { if (_network.IsListening) { return _network.LocalClientId; } return player?.playerClientId ?? 0; } private bool HandleCloseKeyInput(string source) { if (Time.frameCount == _lastCloseKeyFrame || !WasCloseKeyPressed(out string keyName)) { return false; } _lastCloseKeyFrame = Time.frameCount; _logger.LogInfo((object)("[HDM:Progression] Close key input detected: key=" + keyName + ", source=" + source + ".")); CloseUi("key " + keyName); return true; } private bool WasOpenKeyPressed() { if (!WasOpenKeyPressedByInputSystem()) { return WasOpenKeyPressedByLegacyInput(); } return true; } private bool WasCloseKeyPressed(out string keyName) { if (WasCloseKeyPressedByInputSystem(out keyName)) { return true; } if (WasCloseKeyPressedByLegacyInput(out keyName)) { return true; } keyName = string.Empty; return false; } private bool WasOpenKeyPressedByInputSystem() { //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_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Invalid comparison between Unknown and I4 //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Invalid comparison between Unknown and I4 //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Invalid comparison between Unknown and I4 //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Invalid comparison between Unknown and I4 //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Invalid comparison between Unknown and I4 //IL_006d: 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_008b: Unknown result type (might be due to invalid IL or missing references) Keyboard current = Keyboard.current; if (current == null) { return false; } KeyCode progressionOpenKey = _config.ProgressionOpenKey; if ((int)progressionOpenKey <= 111) { if ((int)progressionOpenKey == 96) { return ((ButtonControl)current.backquoteKey).wasPressedThisFrame; } if ((int)progressionOpenKey == 111) { return ((ButtonControl)current.oKey).wasPressedThisFrame; } } else { if ((int)progressionOpenKey == 112) { return ((ButtonControl)current.pKey).wasPressedThisFrame; } if ((int)progressionOpenKey == 287) { return ((ButtonControl)current.f6Key).wasPressedThisFrame; } } if (Enum.TryParse<Key>(((object)_config.ProgressionOpenKey/*cast due to .constrained prefix*/).ToString(), ignoreCase: true, out Key result)) { return ((ButtonControl)current[result]).wasPressedThisFrame; } return false; } private bool WasOpenKeyPressedByLegacyInput() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) try { return Input.GetKeyDown(_config.ProgressionOpenKey); } catch { return false; } } private static bool WasCloseKeyPressedByInputSystem(out string keyName) { Keyboard current = Keyboard.current; if (current == null) { keyName = string.Empty; return false; } if (((ButtonControl)current.escapeKey).wasPressedThisFrame) { keyName = "Escape"; return true; } if (((ButtonControl)current.tabKey).wasPressedThisFrame) { keyName = "Tab"; return true; } keyName = string.Empty; return false; } private static bool WasCloseKeyPressedByLegacyInput(out string keyName) { try { if (Input.GetKeyDown((KeyCode)27)) { keyName = "Escape"; return true; } if (Input.GetKeyDown((KeyCode)9)) { keyName = "Tab"; return true; } } catch { } keyName = string.Empty; return false; } private ProgressionTotals CalculateTotals(ProgressionPlayerState state) { int num = 0; float num2 = 1f; float num3 = 1f; float num4 = 1f; foreach (ModifierDefinition item in _modifierRegistry.GetAvailable(EffectTargetKind.Player)) { if (state.IsOwned(item.NetworkId)) { switch (item.EffectKind) { case ProgressionEffectKind.HealthBonus: num += Mathf.RoundToInt(item.EffectValue); break; case ProgressionEffectKind.MovementMultiplier: num2 *= item.EffectValue; break; case ProgressionEffectKind.SprintTimeMultiplier: num3 *= item.EffectValue; break; case ProgressionEffectKind.GrabDistanceMultiplier: num4 *= item.EffectValue; break; } } } return new ProgressionTotals(num, num2, num3, num4); } private bool CanOpenUi(PlayerControllerB? player, out string reason) { if (player == null || !player.isPlayerControlled) { reason = "no controlled local player"; return false; } if (player.isPlayerDead) { reason = "player is dead"; return false; } if (player.inTerminalMenu) { reason = "terminal menu is open"; return false; } if (player.inSpecialMenu) { reason = "special menu is open"; return false; } if (player.quickMenuManager != null && player.quickMenuManager.isMenuOpen) { reason = "quick menu is open"; return false; } reason = string.Empty; return true; } private UpgradeDefinition? FindUpgrade(string upgradeId) { return _modifierRegistry.FindAvailableById(upgradeId)?.ToUpgradeDefinition(); } private UpgradeDefinition? FindUpgrade(ProgressionUpgradeId upgradeId) { return _modifierRegistry.FindAvailableByNetworkId(upgradeId)?.ToUpgradeDefinition(); } private static PlayerControllerB? ResolveLocalPlayer(StartOfRound? round) { object obj = round?.localPlayerController; if (obj == null) { GameNetworkManager instance = GameNetworkManager.Instance; if (instance == null) { return null; } obj = instance.localPlayerController; } return (PlayerControllerB?)obj; } private void NotifyStateChanged() { this.StateChanged?.Invoke(); } } internal sealed class ProgressionUiController { private sealed class UpgradeButtonBinding { public UpgradeDefinition Upgrade { get; } public Button Button { get; } public Image Background { get; } public Text Status { get; } public UpgradeButtonBinding(UpgradeDefinition upgrade, Button button, Image background, Text status) { Upgrade = upgrade; Button = button; Background = background; Status = status; } } private readonly ManualLogSource _logger; private readonly ProgressionService _service; private readonly GameplayInputService _gameplayInput; private readonly List<UpgradeButtonBinding> _buttons = new List<UpgradeButtonBinding>(); private GameObject? _root; private Text? _pointsText; private PlayerControllerB? _owningPlayer; private bool _subscribed; public bool IsOpen { get { if (_root != null) { return _root.activeSelf; } return false; } } public ProgressionUiController(ManualLogSource logger, ProgressionService service, GameplayInputService gameplayInput) { _logger = logger; _service = service; _gameplayInput = gameplayInput; } public void Open(PlayerControllerB player) { EnsureUi(); _owningPlayer = player; if (!_subscribed) { _service.StateChanged += Refresh; _subscribed = true; } _root.SetActive(true); _gameplayInput.LockUi(player, "progression"); Refresh(); } public void Close(string reason) { if (_root != null) { _root.SetActive(false); } _gameplayInput.UnlockUi("progression"); _owningPlayer = null; } public void Destroy() { if (_subscribed) { _service.StateChanged -= Refresh; _subscribed = false; } if (_root != null) { Object.Destroy((Object)(object)_root); _root = null; } _buttons.Clear(); _pointsText = null; _owningPlayer = null; } private void EnsureUi() { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01ce: Unknown result type (might be due to invalid IL or missing references) //IL_0206: Unknown result type (might be due to invalid IL or missing references) //IL_0215: Unknown result type (might be due to invalid IL or missing references) //IL_0239: Unknown result type (might be due to invalid IL or missing references) //IL_0248: Unknown result type (might be due to invalid IL or missing references) //IL_0278: Unknown result type (might be due to invalid IL or missing references) //IL_0287: Unknown result type (might be due to invalid IL or missing references) if (_root == null) { EnsureEventSystem(); _root = new GameObject("HDM_ProgressionCanvas"); Object.DontDestroyOnLoad((Object)(object)_root); Canvas obj = _root.AddComponent<Canvas>(); obj.renderMode = (RenderMode)0; obj.sortingOrder = 6500; CanvasScaler obj2 = _root.AddComponent<CanvasScaler>(); obj2.uiScaleMode = (ScaleMode)1; obj2.referenceResolution = new Vector2(1280f, 720f); obj2.matchWidthOrHeight = 0.5f; _root.AddComponent<GraphicRaycaster>(); Stretch(CreatePanel(_root.transform, "Dim", new Color(0f, 0f, 0f, 0.78f)).GetComponent<RectTransform>()); GameObject val = CreatePanel(_root.transform, "Tree", new Color(0.07f, 0.08f, 0.09f, 0.96f)); RectTransform component = val.GetComponent<RectTransform>(); component.anchorMin = new Vector2(0.5f, 0.5f); component.anchorMax = new Vector2(0.5f, 0.5f); component.pivot = new Vector2(0.5f, 0.5f); component.sizeDelta = new Vector2(980f, 560f); component.anchoredPosition = Vector2.zero; Place(((Graphic)CreateText(val.transform, "Title", "HDM RUN TRAINING", 28, (TextAnchor)3, Color.white)).rectTransform, 28f, -24f, 480f, 42f, new Vector2(0f, 1f), new Vector2(0f, 1f)); _pointsText = CreateText(val.transform, "Points", string.Empty, 18, (TextAnchor)5, new Color(0.8f, 0.95f, 1f, 1f)); Place(((Graphic)_pointsText).rectTransform, -210f, -26f, 170f, 34f, new Vector2(1f, 1f), new Vector2(1f, 1f)); CreateActionButton(val.transform, "Close", "X", new Vector2(-44f, -28f), new Vector2(34f, 34f), delegate { _service.CloseUi("button"); }); CreateActionButton(val.transform, "Reset", "Reset Run", new Vector2(-132f, -514f), new Vector2(134f, 38f), delegate { _service.ResetRunProgress("ui reset"); }); CreateBranch(val.transform, "Grit", 28f); CreateBranch(val.transform, "Legs", 340f); CreateBranch(val.transform, "Utility", 652f); _root.SetActive(false); _logger.LogInfo((object)"[HDM:Progression] Progression UI created."); } } private void CreateBranch(Transform parent, string branch, float x) { //IL_0040: 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_0078: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) Place(((Graphic)CreateText(parent, branch + "_Header", branch.ToUpperInvariant(), 20, (TextAnchor)4, new Color(1f, 0.86f, 0.54f, 1f))).rectTransform, x, -92f, 280f, 34f, new Vector2(0f, 1f), new Vector2(0f, 1f)); UpgradeDefinition[] array = _service.Upgrades.Where((UpgradeDefinition upgrade) => upgrade.Branch.Equals(branch, StringComparison.OrdinalIgnoreCase)).ToArray(); for (int num = 0; num < array.Length; num++) { CreateUpgradeButton(parent, array[num], new Vector2(x, -140f - (float)num * 128f)); } } private void CreateUpgradeButton(Transform parent, UpgradeDefinition upgrade, Vector2 position) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Expected O, but got Unknown //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_01e0: Unknown result type (might be due to invalid IL or missing references) //IL_01ef: Unknown result type (might be due to invalid IL or missing references) GameObject val = CreatePanel(parent, "Upgrade_" + upgrade.Id, new Color(0.15f, 0.17f, 0.18f, 1f)); Place(val.GetComponent<RectTransform>(), position.x, position.y, 280f, 104f, new Vector2(0f, 1f), new Vector2(0f, 1f)); Button val2 = val.AddComponent<Button>(); ((Selectable)val2).transition = (Transition)1; ((Selectable)val2).colors = CreateButtonColors(); ((UnityEvent)val2.onClick).AddListener((UnityAction)delegate { _service.RequestPurchaseForLocalPlayer(upgrade.Id); }); Place(((Graphic)CreateText(val.transform, "Title", upgrade.Title, 17, (TextAnchor)0, Color.white)).rectTransform, 14f, -10f, 252f, 28f, new Vector2(0f, 1f), new Vector2(0f, 1f)); Place(((Graphic)CreateText(val.transform, "Description", upgrade.Description, 13, (TextAnchor)0, new Color(0.78f, 0.84f, 0.86f, 1f))).rectTransform, 14f, -42f, 252f, 30f, new Vector2(0f, 1f), new Vector2(0f, 1f)); Text val3 = CreateText(val.transform, "Status", string.Empty, 13, (TextAnchor)6, new Color(0.8f, 0.95f, 1f, 1f)); Place(((Graphic)val3).rectTransform, 14f, -78f, 252f, 20f, new Vector2(0f, 1f), new Vector2(0f, 1f)); _buttons.Add(new UpgradeButtonBinding(upgrade, val2, val.GetComponent<Image>(), val3)); } private void Refresh() { //IL_0133: Unknown result type (might be due to invalid IL or missing references) if (_root == null) { return; } ProgressionSnapshot progressionSnapshot = _service.CreateSnapshot(); if (_pointsText != null) { string arg = (progressionSnapshot.IsRequestPending ? " - Syncing" : (string.IsNullOrWhiteSpace(progressionSnapshot.LastRejectedReason) ? string.Empty : (" - Rejected: " + progressionSnapshot.LastRejectedReason))); _pointsText.text = $"Points: {progressionSnapshot.AvailablePoints}/{progressionSnapshot.StartingPoints}{arg}"; } foreach (UpgradeButtonBinding button in _buttons) { bool flag = _service.IsOwned(button.Upgrade.Id); bool flag2 = progressionSnapshot.AvailablePoints >= button.Upgrade.Cost; bool interactable = button.Upgrade.Available && !flag && flag2 && !progressionSnapshot.IsRequestPending; ((Selectable)button.Button).interactable = interactable; button.Status.text = FormatStatus(button.Upgrade, flag, flag2, progressionSnapshot.IsRequestPending); ((Graphic)button.Background).color = SelectBackground(button.Upgrade, flag, flag2); } } private static string FormatStatus(UpgradeDefinition upgrade, bool owned, bool affordable, bool pending) { if (!upgrade.Available) { return "Unavailable - experimental"; } if (pending && !owned) { return "Waiting for host"; } if (owned) { return "Owned - rank 1/1"; } if (!affordable) { return $"Locked - cost {upgrade.Cost}"; } return $"Available - cost {upgrade.Cost}"; } private static Color SelectBackground(UpgradeDefinition upgrade, bool owned, bool affordable) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0070: 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) if (!upgrade.Available) { return new Color(0.11f, 0.11f, 0.12f, 0.88f); } if (owned) { return new Color(0.12f, 0.34f, 0.25f, 1f); } if (!affordable) { return new Color(0.18f, 0.15f, 0.14f, 1f); } return new Color(0.15f, 0.18f, 0.21f, 1f); } private static GameObject CreatePanel(Transform parent, string name, Color color) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: 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_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown GameObject val = new GameObject(name); val.transform.SetParent(parent, false); ((Transform)val.AddComponent<RectTransform>()).localScale = Vector3.one; ((Graphic)val.AddComponent<Image>()).color = color; return val; } private static Text CreateText(Transform parent, string name, string value, int fontSize, TextAnchor alignment, Color color) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name); val.transform.SetParent(parent, false); ((Transform)val.AddComponent<RectTransform>()).localScale = Vector3.one; Text obj = val.AddComponent<Text>(); obj.font = Resources.GetBuiltinResource<Font>("Arial.ttf"); obj.text = value; obj.fontSize = fontSize; obj.alignment = alignment; ((Graphic)obj).color = color; obj.horizontalOverflow = (HorizontalWrapMode)0; obj.verticalOverflow = (VerticalWrapMode)0; return obj; } private static void CreateActionButton(Transform parent, string name, string label, Vector2 position, Vector2 size, Action onClick) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Expected O, but got Unknown //IL_00a6: Unknown result type (might be due to invalid IL or missing references) GameObject obj = CreatePanel(parent, name, new Color(0.2f, 0.23f, 0.25f, 1f)); Place(obj.GetComponent<RectTransform>(), position.x, position.y, size.x, size.y, new Vector2(1f, 1f), new Vector2(1f, 1f)); Button obj2 = obj.AddComponent<Button>(); ((Selectable)obj2).colors = CreateButtonColors(); ((UnityEvent)obj2.onClick).AddListener((UnityAction)delegate { onClick(); }); Stretch(((Graphic)CreateText(obj.transform, "Label", label, 14, (TextAnchor)4, Color.white)).rectTransform); } private static ColorBlock CreateButtonColors() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: 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_0028: 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_0068: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) ColorBlock defaultColorBlock = ColorBlock.defaultColorBlock; ((ColorBlock)(ref defaultColorBlock)).normalColor = Color.white; ((ColorBlock)(ref defaultColorBlock)).highlightedColor = new Color(0.9f, 0.96f, 1f, 1f); ((ColorBlock)(ref defaultColorBlock)).pressedColor = new Color(0.68f, 0.82f, 0.94f, 1f); ((ColorBlock)(ref defaultColorBlock)).disabledColor = new Color(0.5f, 0.5f, 0.5f, 0.8f); ((ColorBlock)(ref defaultColorBlock)).colorMultiplier = 1f; return defaultColorBlock; } private static void Place(RectTransform rect, float x, float y, float width, float height, Vector2 anchor, Vector2 pivot) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0009: 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_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) rect.anchorMin = anchor; rect.anchorMax = anchor; rect.pivot = pivot; rect.anchoredPosition = new Vector2(x, y); rect.sizeDelta = new Vector2(width, height); } private static void Stretch(RectTransform rect) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0017: 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) rect.anchorMin = Vector2.zero; rect.anchorMax = Vector2.one; rect.offsetMin = Vector2.zero; rect.offsetMax = Vector2.zero; } private static void EnsureEventSystem() { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown //IL_0018: Unknown result type (might be due to invalid IL or missing references) if (Object.FindObjectOfType<EventSystem>() == null) { GameObject val = new GameObject("HDM_EventSystem"); Object.DontDestroyOnLoad((Object)val); val.AddComponent<EventSystem>(); val.AddComponent<StandaloneInputModule>(); } } } } namespace HorrorDirectorMod.Observation { internal sealed class GameObservation { public DateTime ObservedAt { get; } public RoundObservation Round { get; } public TeamObservation Team { get; } public VoiceObservation Voice { get; } public IReadOnlyList<PlayerObservation> Players { get; } public GameObservation(DateTime observedAt, RoundObservation round, TeamObservation team, VoiceObservation voice, IReadOnlyList<PlayerObservation> players) { ObservedAt = observedAt; Round = round; Team = team; Voice = voice; Players = players; } } internal sealed class GameObserver : IGameObserver { private readonly struct VoicePlayerObservation { public bool IsSpeaking { get; } public float Amplitude { get; } public VoicePlayerObservation(bool isSpeaking, float amplitude) { IsSpeaking = isSpeaking; Amplitude = amplitude; } } private readonly struct VoiceActivitySample { public DateTime ObservedAt { get; } public bool HasSpeakingPlayers { get; } public VoiceActivitySample(DateTime observedAt, bool hasSpeakingPlayers) { ObservedAt = observedAt; HasSpeakingPlayers = hasSpeakingPlayers; } } private readonly ManualLogSource _logger; private readonly ConfigService _config; private readonly Queue<VoiceActivitySample> _voiceSamples = new Queue<VoiceActivitySample>(); private bool _voiceAvailabilityLogged; private DateTime? _lastVoiceActivityAt; private float _previousSplitScore; private IReadOnlyList<ulong> _previousIsolatedPlayerIds = new ulong[0]; public GameObserver(ManualLogSource logger, ConfigService config) { _logger = logger; _config = config; } public GameObservation Observe(StartOfRound? round) { DateTime utcNow = DateTime.UtcNow; NetworkManager singleton = NetworkManager.Singleton; IReadOnlyDictionary<string, VoicePlayerObservation> voiceStates = ObserveVoiceStates(round); IReadOnlyList<PlayerObservation> players = ObservePlayers(round, voiceStates); TeamObservation teamObservation = ObserveTeam(players); VoiceObservation voice = ObserveVoice(utcNow, round, voiceStates); RoundObservation round2 = new RoundObservation(singleton != null && singleton.IsHost, singleton != null && singleton.IsServer, singleton != null && singleton.IsClient, round != null, Math.Max(0, (round != null) ? (round.connectedPlayersAmount + 1) : 0), GetLocalPlayerName(round)); _previousSplitScore = teamObservation.SplitScore; _previousIsolatedPlayerIds = teamObservation.IsolatedPlayerIds; return new GameObservation(utcNow, round2, teamObservation, voice, players); } public void LogObservation(GameObservation observation, string reason) { //IL_0185: Unknown result type (might be due to invalid IL or missing references) _logger.LogInfo((object)$"[HDM:Observer] {reason}: controlled={observation.Team.ControlledCount}, alive={observation.Team.AliveCount}, aliveInside={observation.Team.AliveInsideFactoryCount}, split={observation.Team.SplitScore:0.0}, previousSplit={_previousSplitScore:0.0}, isolated={observation.Team.IsolatedPlayerIds.Count}, talkRatio={observation.Voice.TeamTalkRatio:0.00}, silence={observation.Voice.SilenceSeconds:0.0}s, voiceSource={observation.Voice.Source}."); foreach (PlayerObservation player in observation.Players) { if (player.IsControlled) { string text = ((!player.IsSpeaking.HasValue) ? "unknown" : (player.IsSpeaking.Value ? "true" : "false")); _logger.LogInfo((object)$"[HDM:Observer] Player {player.PlayerId}: name='{player.Name}', alive={player.IsAlive}, insideFactory={player.IsInsideFactory}, inShip={player.IsInShip}, speaking={text}, amp={FormatNullable(player.VoiceAmplitude)}, pos={FormatVector(player.Position)}."); } } } private IReadOnlyList<PlayerObservation> ObservePlayers(StartOfRound? round, IReadOnlyDictionary<string, VoicePlayerObservation> voiceStates) { //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) List<PlayerObservation> list = new List<PlayerObservation>(); PlayerControllerB[] array = round?.allPlayerScripts; if (array == null) { return list; } PlayerControllerB[] array2 = array; foreach (PlayerControllerB val in array2) { if (val != null) { string playerName = GetPlayerName(val); VoicePlayerObservation value; bool flag = voiceStates.TryGetValue(playerName, out value); ulong playerClientId = val.playerClientId; bool isPlayerControlled = val.isPlayerControlled; bool isAlive = !val.isPlayerDead; bool isInsideFactory = val.isInsideFactory; bool isInHangarShipRoom = val.isInHangarShipRoom; Transform transform = ((Component)val).transform; list.Add(new PlayerObservation(playerClientId, playerName, isPlayerControlled, isAlive, isInsideFactory, isInHangarShipRoom, (transform != null) ? transform.position : Vector3.zero, flag ? new bool?(value.IsSpeaking) : ((bool?)null), flag ? new float?(value.Amplitude) : ((float?)null))); } } return list; } private TeamObservation ObserveTeam(IReadOnlyList<PlayerObservation> players) { int num = 0; int num2 = 0; int num3 = 0; List<PlayerObservation> list = new List<PlayerObservation>(); foreach (PlayerObservation player in players) { if (!player.IsControlled) { continue; } num++; if (player.IsAlive) { num2++; list.Add(player); if (player.IsInsideFactory) { num3++; } } } return new TeamObservation(num, num2, num3, CalculateTeamSplitScore(list), FindIsolatedPlayerIds(list, _config.IsolationDistance)); } private IReadOnlyDictionary<string, VoicePlayerObservation> ObserveVoiceStates(StartOfRound? round) { Dictionary<string, VoicePlayerObservation> dictionary = new Dictionary<string, VoicePlayerObservation>(StringComparer.OrdinalIgnoreCase); if (!_config.VoiceProbeEnabled) { return dictionary; } try { DissonanceComms val = Object.FindObjectOfType<DissonanceComms>(); if (val == null) { LogVoiceAvailabilityOnce("[HDM:Voice] DissonanceComms not found yet."); return dictionary; } LogVoiceAvailabilityOnce("[HDM:Voice] DissonanceComms found. LocalPlayerName='" + val.LocalPlayerName + "'."); for (int i = 0; i < val.Players.Count; i++) { VoicePlayerState val2 = val.Players[i]; if (val2 != null && !string.IsNullOrWhiteSpace(val2.Name)) { dictionary[val2.Name] = new VoicePlayerObservation(val2.IsSpeaking, val2.Amplitude); } } string text = ResolveLocalPlayerName(round, val); if (!string.IsNullOrWhiteSpace(text) && !dictionary.ContainsKey(text) && val.Players.Count == 1) { VoicePlayerState val3 = val.Players[0]; dictionary[text] = new VoicePlayerObservation(val3.IsSpeaking, val3.Amplitude); } } catch (Exception ex) { _logger.LogWarning((object)("[HDM:Voice] Dissonance observation failed: " + ex.GetType().Name + ": " + ex.Message)); } return dictionary; } private VoiceObservation ObserveVoice(DateTime observedAt, StartOfRound? round, IReadOnlyDictionary<string, VoicePlayerObservation> voiceStates) { if (!_config.VoiceProbeEnabled) { return VoiceObservation.Disabled; } int num = 0; foreach (VoicePlayerObservation value in voiceStates.Values) { if (value.IsSpeaking) { num++; } } bool flag = num > 0; _voiceSamples.Enqueue(new VoiceActivitySample(observedAt, flag)); TrimVoiceSamples(observedAt); if (flag) { _lastVoiceActivityAt = observedAt; } bool? localPlayerSpeaking = ResolveLocalSpeaking(round, voiceStates); string source = ((voiceStates.Count == 0) ? "dissonance_empty" : "dissonance"); return new VoiceObservation(isAvailable: true, num, localPlayerSpeaking, CalculateTeamTalkRatio(), CalculateSilenceSeconds(observedAt), source); } private void TrimVoiceSamples(DateTime observedAt) { DateTime dateTime = observedAt - TimeSpan.FromSeconds(_config.VoiceActivityWindowSeconds); while (_voiceSamples.Count > 0 && _voiceSamples.Peek().ObservedAt < dateTime) { _voiceSamples.Dequeue(); } } private float CalculateTeamTalkRatio() { if (_voiceSamples.Count == 0) { return 0f; } int num = 0; foreach (VoiceActivitySample voiceSample in _voiceSamples) { if (voiceSample.HasSpeakingPlayers) { num++; } } return (float)num / (float)_voiceSamples.Count; } private float CalculateSilenceSeconds(DateTime observedAt) { if (!_lastVoiceActivityAt.HasValue) { if (_voiceSamples.Count != 0) { return (float)(observedAt - _voiceSamples.Peek().ObservedAt).TotalSeconds; } return 0f; } return Math.Max(0f, (float)(observedAt - _lastVoiceActivityAt.Value).TotalSeconds); } private bool? ResolveLocalSpeaking(StartOfRound? round, IReadOnlyDictionary<string, VoicePlayerObservation> voiceStates) { string localPlayerName = GetLocalPlayerName(round); if (string.IsNullOrWhiteSpace(localPlayerName)) { return null; } if (!voiceStates.TryGetValue(localPlayerName, out var value)) { return null; } return value.IsSpeaking; } private static float CalculateTeamSplitScore(IReadOnlyList<PlayerObservation> players) { //IL_0029: 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 (players.Count < 2) { return 0f; } float num = 0f; int num2 = 0; for (int i = 0; i < players.Count; i++) { for (int j = i + 1; j < players.Count; j++) { num += Vector3.Distance(players[i].Position, players[j].Position); num2++; } } if (num2 != 0) { return num / (float)num2; } return 0f; } private static IReadOnlyList<ulong> FindIsolatedPlayerIds(IReadOnlyList<PlayerObservation> players, float isolationDistance) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) List<ulong> list = new List<ulong>(); if (players.Count < 2) { return list; } for (int i = 0; i < players.Count; i++) { float num = float.MaxValue; for (int j = 0; j < players.Count; j++) { if (i != j) { num = Math.Min(num, Vector3.Distance(players[i].Position, players[j].Position)); } } if (num > isolationDistance) { list.Add(players[i].PlayerId); } } return list; } private void LogVoiceAvailabilityOnce(string message) { if (!_voiceAvailabilityLogged && _config.VoiceProbeLogComponentShape) { _voiceAvailabilityLogged = true; _logger.LogInfo((object)message); } } private static string ResolveLocalPlayerName(StartOfRound? round, DissonanceComms comms) { if (!string.IsNullOrWhiteSpace(comms.LocalPlayerName)) { return comms.LocalPlayerName; } return GetLocalPlayerName(round); } private static string GetLocalPlayerName(StartOfRound? round) { PlayerControllerB val = round?.localPlayerController; if (val == null) { return string.Empty; } return GetPlayerName(val); } private static string GetPlayerName(PlayerControllerB player) { if (!string.IsNullOrWhiteSpace(player.playerUsername)) { retur