Decompiled source of UltimateLethal CrewMorale v2.2.0

BepInEx/plugins/UltimateLethal.CrewMorale.dll

Decompiled a month ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using GameNetcodeStuff;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[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("UltimateLethal.CrewMorale")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("UltimateLethal.CrewMorale")]
[assembly: AssemblyTitle("UltimateLethal.CrewMorale")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[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 UltimateLethal.CrewMorale
{
	[BepInPlugin("ultimatelethal.crewmorale", "Ultimate Lethal Crew Morale", "2.2.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class CrewMoralePlugin : BaseUnityPlugin
	{
		private sealed class HudEvent
		{
			internal string Title;

			internal string Detail;

			internal float Duration;

			internal bool RevealOutsideShip;

			internal bool ShipOnly;

			internal int Priority;
		}

		private enum ThreatTier
		{
			Minor,
			Standard,
			Dangerous,
			Extreme
		}

		public const string Guid = "ultimatelethal.crewmorale";

		public const string Name = "Ultimate Lethal Crew Morale";

		public const string Version = "2.2.0";

		internal static CrewMoralePlugin Instance;

		internal static ConfigEntry<float> StartingMorale;

		internal static ConfigEntry<float> MinimumMorale;

		internal static ConfigEntry<float> MaximumMorale;

		internal static ConfigEntry<float> DecorationPriceScaleCredits;

		internal static ConfigEntry<float> DecorationPriceExponent;

		internal static ConfigEntry<float> DecorationMaxMoraleEach;

		internal static ConfigEntry<float> DecorationBonusCap;

		internal static ConfigEntry<float> DeathPenalty;

		internal static ConfigEntry<float> ReunionReward;

		internal static ConfigEntry<float> SurvivorReward;

		internal static ConfigEntry<float> QuotaFulfilledReward;

		internal static ConfigEntry<float> FullCrewShipRecoveryPerSecond;

		internal static ConfigEntry<float> OrbitRecoveryPerSecond;

		internal static ConfigEntry<float> EmptyShipDecayPerSecond;

		internal static ConfigEntry<float> CriticalInjuryPenalty;

		internal static ConfigEntry<int> CriticalInjuryHealthThreshold;

		internal static ConfigEntry<int> CriticalInjuryResetHealth;

		internal static ConfigEntry<float> ZeroScrapExpeditionPenalty;

		internal static ConfigEntry<float> LeftBehindPenalty;

		internal static ConfigEntry<float> MonsterKillReward;

		internal static ConfigEntry<bool> PlayerCreditRewardsEnabled;

		internal static ConfigEntry<int> MonsterKillPlayerCredits;

		internal static ConfigEntry<float> MinorThreatMorale;

		internal static ConfigEntry<float> StandardThreatMorale;

		internal static ConfigEntry<float> DangerousThreatMorale;

		internal static ConfigEntry<float> ExtremeThreatMorale;

		internal static ConfigEntry<int> MinorThreatPc;

		internal static ConfigEntry<int> StandardThreatPc;

		internal static ConfigEntry<int> DangerousThreatPc;

		internal static ConfigEntry<int> ExtremeThreatPc;

		internal static ConfigEntry<string> MinorThreatEnemies;

		internal static ConfigEntry<string> DangerousThreatEnemies;

		internal static ConfigEntry<string> ExtremeThreatEnemies;

		internal static ConfigEntry<int> SuccessfulHaulTier1Credits;

		internal static ConfigEntry<int> SuccessfulHaulTier2Credits;

		internal static ConfigEntry<int> SuccessfulHaulTier3Credits;

		internal static ConfigEntry<int> SuccessfulHaulTier4Credits;

		internal static ConfigEntry<int> ApparatusRecoveryCredits;

		internal static ConfigEntry<int> FullCrewExtractionCredits;

		internal static ConfigEntry<int> AgainstOddsCredits;

		internal static ConfigEntry<int> AgainstOddsHealthThreshold;

		internal static ConfigEntry<int> DangerousMoonTier1Credits;

		internal static ConfigEntry<int> DangerousMoonTier2Credits;

		internal static ConfigEntry<int> DangerousMoonTier3Credits;

		internal static ConfigEntry<bool> HallucinationsEnabled;

		internal static ConfigEntry<float> HallucinationMinimumInsanity;

		internal static ConfigEntry<float> HallucinationCheckSeconds;

		internal static ConfigEntry<float> HallucinationBaseChance;

		internal static ConfigEntry<float> HallucinationGlobalCooldown;

		internal static ConfigEntry<bool> HallucinationAudioEnabled;

		internal static ConfigEntry<bool> HallucinationVisualEnabled;

		internal static ConfigEntry<bool> HallucinationEnvironmentEnabled;

		internal static ConfigEntry<float> HallucinationAudioThreshold;

		internal static ConfigEntry<float> HallucinationVisualThreshold;

		internal static ConfigEntry<float> HallucinationEnvironmentThreshold;

		internal static ConfigEntry<float> HighMoraleSanityGainMultiplier;

		internal static ConfigEntry<float> LowMoraleSanityGainMultiplier;

		internal static ConfigEntry<float> HighMoraleRecoveryMultiplier;

		internal static ConfigEntry<float> LowMoraleRecoveryMultiplier;

		internal static ConfigEntry<float> TZPRecoveryMultiplier;

		internal static ConfigEntry<bool> SanityDebuffsEnabled;

		internal static ConfigEntry<float> SanityDebuffTier1Threshold;

		internal static ConfigEntry<float> SanityDebuffTier2Threshold;

		internal static ConfigEntry<float> SanityDebuffTier3Threshold;

		internal static ConfigEntry<float> SanityDebuffTier1Percent;

		internal static ConfigEntry<float> SanityDebuffTier2Percent;

		internal static ConfigEntry<float> SanityDebuffTier3Percent;

		internal static ConfigEntry<bool> SanityVisualEffectsEnabled;

		internal static ConfigEntry<float> SanityVisualMinimumInsanity;

		internal static ConfigEntry<float> SanityVisualMinimumInterval;

		internal static ConfigEntry<float> SanityVisualMaximumInterval;

		internal static ConfigEntry<float> SanityVisualMaximumDuration;

		internal static ConfigEntry<float> SanityVisualIntensity;

		internal static ConfigEntry<float> SanityVisualHudJitterPixels;

		internal static ConfigEntry<bool> SanityVisualDebugForceEffect;

		internal static ConfigEntry<float> ConfidentLguBonus;

		internal static ConfigEntry<float> ThrivingLguBonus;

		internal static ConfigEntry<float> PeakLguBonus;

		internal static ConfigEntry<float> BrokenInteractionPercent;

		internal static ConfigEntry<float> ShakenInteractionPercent;

		internal static ConfigEntry<float> UneasyInteractionPercent;

		internal static ConfigEntry<float> StableInteractionPercent;

		internal static ConfigEntry<float> ConfidentInteractionPercent;

		internal static ConfigEntry<float> ThrivingInteractionPercent;

		internal static ConfigEntry<float> PeakInteractionPercent;

		internal static ConfigEntry<float> BrokenStaminaPercent;

		internal static ConfigEntry<float> ShakenStaminaPercent;

		internal static ConfigEntry<float> UneasyStaminaPercent;

		internal static ConfigEntry<float> StableStaminaPercent;

		internal static ConfigEntry<float> ConfidentStaminaPercent;

		internal static ConfigEntry<float> ThrivingStaminaPercent;

		internal static ConfigEntry<float> PeakStaminaPercent;

		internal static ConfigEntry<int> HallucinationAudioWeight;

		internal static ConfigEntry<int> HallucinationVisualWeight;

		internal static ConfigEntry<int> HallucinationEnvironmentWeight;

		internal static ConfigEntry<int> HallucinationDebugForceType;

		internal static ConfigEntry<bool> LastStandEnabled;

		internal static ConfigEntry<float> LastStandTriggerMorale;

		internal static ConfigEntry<float> LastStandAfterMorale;

		internal static ConfigEntry<float> LastStandProtectionSeconds;

		internal static ConfigEntry<int> LastStandMinimumHealth;

		internal static ConfigEntry<float> EventHudSeconds;

		internal static ConfigEntry<bool> DebugV2Logging;

		internal static ConfigEntry<bool> ShowHud;

		internal static ConfigEntry<float> HudX;

		internal static ConfigEntry<float> HudY;

		internal static ConfigEntry<float> HudWidth;

		internal static ConfigEntry<float> HudHeight;

		internal static ConfigEntry<float> PlayerCreditsOffsetX;

		internal static ConfigEntry<float> PlayerCreditsOffsetY;

		internal static ConfigEntry<float> SanityHudRaisePixels;

		internal static ConfigEntry<bool> UseStatusColors;

		internal static ConfigEntry<bool> ShowTierDividers;

		internal static ConfigEntry<bool> FakeInsanityHudEnabled;

		internal static ConfigEntry<float> FakeInsanityStartThreshold;

		internal static ConfigEntry<float> FakeInsanityMaxOffset;

		internal static ConfigEntry<float> FakeInsanityUpdateInterval;

		internal static ConfigEntry<float> FakeInsanitySpikeChance;

		internal static ConfigEntry<float> FakeInsanityMaxDuration;

		internal static ConfigEntry<float> FakeInsanityReturnSpeed;

		private Harmony _harmony;

		private float _nextStateTick;

		private bool _wasFullLivingCrewInShip;

		private bool _wasLanded;

		private bool _someoneLeftShipThisExpedition;

		private bool _expeditionHadDeath;

		private bool _reunionAwardedThisExpedition;

		private int _expeditionStartingCrewCount;

		private readonly HashSet<PlayerControllerB> _expeditionRoster = new HashSet<PlayerControllerB>();

		private readonly HashSet<PlayerControllerB> _countedDeadPlayers = new HashSet<PlayerControllerB>();

		private readonly HashSet<PlayerControllerB> _criticalInjuryActive = new HashSet<PlayerControllerB>();

		private readonly HashSet<int> _shipScrapAtLanding = new HashSet<int>();

		private bool _recoveredScrapThisExpedition;

		private readonly HashSet<int> _creditedLocalScrap = new HashSet<int>();

		private int _localRecoveredScrapValue;

		private bool _localTookFieldDamage;

		private bool _apparatusRecoveredByLocal;

		private float _nextHallucinationCheck;

		private float _hallucinationCooldownUntil;

		private GameObject _hallucinationVisual;

		private float _hallucinationVisualUntil;

		private readonly Dictionary<Light, float> _hallucinationLightIntensity = new Dictionary<Light, float>();

		private float _hallucinationLightsUntil;

		private readonly List<Object> _hallucinationClips = new List<Object>();

		private float _fakeInsanityDisplayed;

		private float _nextFakeInsanityUpdate;

		private float _fakeInsanityUntil;

		private bool _fakeInsanityInitialized;

		private bool _insanityHudPresentationLocated;

		private readonly Random _sanityVisualRandom = new Random();

		private float _nextSanityVisualEffect;

		private float _sanityVisualEffectStarted;

		private float _sanityVisualEffectUntil;

		private float _sanityVisualSeverity;

		private int _sanityVisualSeed;

		private bool _quotaStateInitialized;

		private bool _quotaWasFulfilled;

		private float _previousDecorationBonus = -1f;

		private float _eventUntil;

		private string _eventTitle = "";

		private string _eventDetail = "";

		private bool _eventRevealOutsideShip;

		private int _eventPriority;

		private readonly List<HudEvent> _eventQueue = new List<HudEvent>();

		private bool _wasQuotaFailureSequence;

		private readonly Dictionary<PlayerControllerB, float> _lastStandProtectedUntil = new Dictionary<PlayerControllerB, float>();

		private GUIStyle _labelStyle;

		private GUIStyle _smallStyle;

		private GUIStyle _pcStyle;

		private GUIStyle _buffStyle;

		private GUIStyle _eventTitleStyle;

		private GUIStyle _eventDetailStyle;

		private Texture2D _pixel;

		internal static ManualLogSource PluginLogger
		{
			get
			{
				CrewMoralePlugin instance = Instance;
				if (instance == null)
				{
					return null;
				}
				return ((BaseUnityPlugin)instance).Logger;
			}
		}

		internal static float Morale { get; private set; } = 55f;

		internal static float DecorationBaseline { get; private set; } = 0f;

		internal static int DecorationCount { get; private set; } = 0;

		private void Awake()
		{
			//IL_107c: Unknown result type (might be due to invalid IL or missing references)
			//IL_1086: Expected O, but got Unknown
			Instance = this;
			StartingMorale = ((BaseUnityPlugin)this).Config.Bind<float>("Morale", "StartingMorale", 55f, "Morale at the beginning of a session.");
			MinimumMorale = ((BaseUnityPlugin)this).Config.Bind<float>("Morale", "MinimumMorale", 0f, "Minimum morale.");
			MaximumMorale = ((BaseUnityPlugin)this).Config.Bind<float>("Morale", "MaximumMorale", 100f, "Maximum morale.");
			DecorationPriceScaleCredits = ((BaseUnityPlugin)this).Config.Bind<float>("Decorations", "PriceScaleCredits", 200f, "Price divisor used by the decoration morale curve.");
			DecorationPriceExponent = ((BaseUnityPlugin)this).Config.Bind<float>("Decorations", "PriceExponent", 1.15f, "Exponent used by the decoration morale curve. Values above 1 make expensive decorations more efficient.");
			DecorationMaxMoraleEach = ((BaseUnityPlugin)this).Config.Bind<float>("Decorations", "MaxMoralePerDecoration", 10f, "Maximum morale a single decoration can contribute regardless of price.");
			DecorationBonusCap = ((BaseUnityPlugin)this).Config.Bind<float>("Decorations", "DecorationBonusCap", 20f, "Maximum total baseline morale supplied by all decorations.");
			DeathPenalty = ((BaseUnityPlugin)this).Config.Bind<float>("Events", "DeathPenalty", 20f, "Immediate morale loss per crew death.");
			ReunionReward = ((BaseUnityPlugin)this).Config.Bind<float>("Events", "PerfectReunionReward", 10f, "Morale gained when everyone who started the expedition returns to the ship alive after someone has left it.");
			SurvivorReward = ((BaseUnityPlugin)this).Config.Bind<float>("Events", "SurvivorRewardPerPlayer", 2f, "Morale gained per surviving member of the expedition starting roster when the expedition ends.");
			QuotaFulfilledReward = ((BaseUnityPlugin)this).Config.Bind<float>("Events", "QuotaFulfilledReward", 15f, "Morale gained once when the current quota becomes fulfilled.");
			FullCrewShipRecoveryPerSecond = ((BaseUnityPlugin)this).Config.Bind<float>("Events", "FullCrewShipRecoveryPerSecond", 0.004166667f, "Passive morale recovery while the full living crew is together inside the ship (+0.25 morale/minute before decor scaling).");
			OrbitRecoveryPerSecond = ((BaseUnityPlugin)this).Config.Bind<float>("Events", "OrbitRecoveryPerSecond", 0.004166667f, "Additional passive morale recovery while the ship is safely in orbit (+0.25 morale/minute before decor scaling).");
			EmptyShipDecayPerSecond = ((BaseUnityPlugin)this).Config.Bind<float>("Events", "EmptyShipDecayPerSecond", 0f, "Legacy setting retained for config compatibility. TEST 2 disables passive empty-ship morale decay.");
			CriticalInjuryPenalty = ((BaseUnityPlugin)this).Config.Bind<float>("Events", "CriticalInjuryPenalty", 5f, "Morale lost when a living crew member first drops into critical condition.");
			CriticalInjuryHealthThreshold = ((BaseUnityPlugin)this).Config.Bind<int>("Events", "CriticalInjuryHealthThreshold", 25, "Health at or below which a living player enters the critical-injury morale state.");
			CriticalInjuryResetHealth = ((BaseUnityPlugin)this).Config.Bind<int>("Events", "CriticalInjuryResetHealth", 50, "Health above which that player can trigger a future critical-injury morale event again.");
			ZeroScrapExpeditionPenalty = ((BaseUnityPlugin)this).Config.Bind<float>("Events", "ZeroScrapExpeditionPenalty", 10f, "Morale lost when an expedition ends with survivors but no newly recovered scrap brought aboard.");
			LeftBehindPenalty = ((BaseUnityPlugin)this).Config.Bind<float>("Events", "LeftBehindPenalty", 15f, "Morale lost per living crew member left outside when the ship departs.");
			MonsterKillReward = ((BaseUnityPlugin)this).Config.Bind<float>("Events", "MonsterKillReward", 10f, "Morale gained when a player directly kills an enemy, awarded once per enemy instance.");
			PlayerCreditRewardsEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("V2 - LGU Player Credits", "Enabled", true, "Award LGU Player Credits from Crew Morale gameplay events.");
			MonsterKillPlayerCredits = ((BaseUnityPlugin)this).Config.Bind<int>("V2 - LGU Player Credits", "MonsterKillCredits", 5, "Legacy flat monster-kill PC value retained for config compatibility; threat tiers are authoritative.");
			MinorThreatMorale = ((BaseUnityPlugin)this).Config.Bind<float>("V2 - Threat Rewards", "MinorMorale", 2f, "Morale for a Minor threat kill.");
			MinorThreatPc = ((BaseUnityPlugin)this).Config.Bind<int>("V2 - Threat Rewards", "MinorPC", 1, "Personal PC for a Minor threat kill.");
			StandardThreatMorale = ((BaseUnityPlugin)this).Config.Bind<float>("V2 - Threat Rewards", "StandardMorale", 5f, "Morale for a Standard/unclassified threat kill.");
			StandardThreatPc = ((BaseUnityPlugin)this).Config.Bind<int>("V2 - Threat Rewards", "StandardPC", 2, "Personal PC for a Standard/unclassified threat kill.");
			DangerousThreatMorale = ((BaseUnityPlugin)this).Config.Bind<float>("V2 - Threat Rewards", "DangerousMorale", 8f, "Morale for a Dangerous threat kill.");
			DangerousThreatPc = ((BaseUnityPlugin)this).Config.Bind<int>("V2 - Threat Rewards", "DangerousPC", 4, "Personal PC for a Dangerous threat kill.");
			ExtremeThreatMorale = ((BaseUnityPlugin)this).Config.Bind<float>("V2 - Threat Rewards", "ExtremeMorale", 12f, "Morale for an Extreme threat kill.");
			ExtremeThreatPc = ((BaseUnityPlugin)this).Config.Bind<int>("V2 - Threat Rewards", "ExtremePC", 6, "Personal PC for an Extreme threat kill.");
			MinorThreatEnemies = ((BaseUnityPlugin)this).Config.Bind<string>("V2 - Threat Rewards", "MinorEnemies", "Hoarding bug,Centipede,Snare flea,Spore lizard,Manticoil,Roaming locust", "Comma-separated Minor enemy names.");
			DangerousThreatEnemies = ((BaseUnityPlugin)this).Config.Bind<string>("V2 - Threat Rewards", "DangerousEnemies", "Flowerman,Bracken,Nutcracker,Butler,Masked,Thumper,Crawler,Baboon hawk", "Comma-separated Dangerous enemy names.");
			ExtremeThreatEnemies = ((BaseUnityPlugin)this).Config.Bind<string>("V2 - Threat Rewards", "ExtremeEnemies", "Old Bird,ForestGiant,Forest Keeper,Earth Leviathan,Jester", "Comma-separated Extreme enemy names. Unlisted enemies are Standard.");
			SuccessfulHaulTier1Credits = ((BaseUnityPlugin)this).Config.Bind<int>("V2 - LGU Player Credits", "SuccessfulHaul100Credits", 1, "PC for personally recovering at least 100 credits of unique scrap.");
			SuccessfulHaulTier2Credits = ((BaseUnityPlugin)this).Config.Bind<int>("V2 - LGU Player Credits", "SuccessfulHaul200Credits", 2, "PC for personally recovering at least 200 credits of unique scrap.");
			SuccessfulHaulTier3Credits = ((BaseUnityPlugin)this).Config.Bind<int>("V2 - LGU Player Credits", "SuccessfulHaul350Credits", 3, "PC for personally recovering at least 350 credits of unique scrap.");
			SuccessfulHaulTier4Credits = ((BaseUnityPlugin)this).Config.Bind<int>("V2 - LGU Player Credits", "SuccessfulHaul500Credits", 5, "PC for personally recovering at least 500 credits of unique scrap.");
			ApparatusRecoveryCredits = ((BaseUnityPlugin)this).Config.Bind<int>("V2 - LGU Player Credits", "ApparatusRecoveryCredits", 3, "PC for personally recovering the apparatus to the ship.");
			FullCrewExtractionCredits = ((BaseUnityPlugin)this).Config.Bind<int>("V2 - LGU Player Credits", "FullCrewExtractionCredits", 2, "PC awarded to each surviving player when the full starting crew extracts alive.");
			AgainstOddsCredits = ((BaseUnityPlugin)this).Config.Bind<int>("V2 - LGU Player Credits", "AgainstTheOddsCredits", 3, "PC for extracting at critical health after taking damage away from the ship.");
			AgainstOddsHealthThreshold = ((BaseUnityPlugin)this).Config.Bind<int>("V2 - LGU Player Credits", "AgainstTheOddsHealthThreshold", 25, "Maximum extraction health for Against the Odds.");
			DangerousMoonTier1Credits = ((BaseUnityPlugin)this).Config.Bind<int>("V2 - LGU Player Credits", "DangerousMoonTier1Credits", 1, "PC for surviving a moderately dangerous moon (risk C/B).");
			DangerousMoonTier2Credits = ((BaseUnityPlugin)this).Config.Bind<int>("V2 - LGU Player Credits", "DangerousMoonTier2Credits", 2, "PC for surviving a high-danger moon (risk A/S).");
			DangerousMoonTier3Credits = ((BaseUnityPlugin)this).Config.Bind<int>("V2 - LGU Player Credits", "DangerousMoonTier3Credits", 3, "PC for surviving an extreme moon (risk S+/X or equivalent).");
			HallucinationsEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("V2 - Hallucinations", "Enabled", true, "Enable client-side psychological hallucination events driven by InsanityMod.");
			HallucinationMinimumInsanity = ((BaseUnityPlugin)this).Config.Bind<float>("V2 - Hallucinations", "MinimumInsanity", 45f, "Insanity below this value cannot trigger custom hallucinations.");
			HallucinationCheckSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("V2 - Hallucinations", "CheckSeconds", 5f, "Minimum seconds between custom hallucination opportunities. Opportunities are driven by InsanityMod insanity updates, not a separate Crew Morale scheduler.");
			HallucinationBaseChance = ((BaseUnityPlugin)this).Config.Bind<float>("V2 - Hallucinations", "BaseChance", 0.12f, "Base chance per InsanityMod-driven opportunity at maximum insanity before morale scaling.");
			HallucinationGlobalCooldown = ((BaseUnityPlugin)this).Config.Bind<float>("V2 - Hallucinations", "GlobalCooldownSeconds", 18f, "Minimum time between custom hallucinations.");
			HallucinationAudioEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("V2 - Hallucinations", "AudioEnabled", true, "Allow local phantom spatial audio events.");
			HallucinationVisualEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("V2 - Hallucinations", "VisualEnabled", true, "Allow local peripheral/doorway shadow events.");
			HallucinationEnvironmentEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("V2 - Hallucinations", "EnvironmentEnabled", true, "Allow local camera/environment disturbance events.");
			HallucinationAudioThreshold = ((BaseUnityPlugin)this).Config.Bind<float>("V2 - Hallucinations", "AudioMinimumInsanity", 45f, "Minimum insanity for phantom audio.");
			HallucinationVisualThreshold = ((BaseUnityPlugin)this).Config.Bind<float>("V2 - Hallucinations", "VisualMinimumInsanity", 58f, "Minimum insanity for shadow/silhouette events.");
			HallucinationEnvironmentThreshold = ((BaseUnityPlugin)this).Config.Bind<float>("V2 - Hallucinations", "EnvironmentMinimumInsanity", 72f, "Minimum insanity for environmental disturbances.");
			HallucinationAudioWeight = ((BaseUnityPlugin)this).Config.Bind<int>("V2 - Hallucinations", "AudioWeight", 2, "Relative selection weight for phantom-audio events when eligible.");
			HallucinationVisualWeight = ((BaseUnityPlugin)this).Config.Bind<int>("V2 - Hallucinations", "VisualWeight", 1, "Relative selection weight for shadow/silhouette events when eligible.");
			HallucinationEnvironmentWeight = ((BaseUnityPlugin)this).Config.Bind<int>("V2 - Hallucinations", "EnvironmentWeight", 1, "Relative selection weight for environmental events when eligible.");
			HallucinationDebugForceType = ((BaseUnityPlugin)this).Config.Bind<int>("V2 - Hallucinations", "DebugForceEventType", 0, "Testing only: 0=random, 1=audio, 2=visual, 3=environment. Use with BaseChance=1 and a short cooldown to verify an event category.");
			MigrateLegacyBalanceValues();
			HighMoraleSanityGainMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Sanity", "HighMoraleSanityGainMultiplier", 0.15f, "Legacy endpoint retained for compatibility. v2.0.5 uses status-driven sanity gain; Peak is x0.15.");
			LowMoraleSanityGainMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Sanity", "LowMoraleSanityGainMultiplier", 3f, "Legacy endpoint retained for compatibility. v2.0.5 uses status-driven sanity gain; Broken is x3.00.");
			HighMoraleRecoveryMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Sanity", "HighMoraleRecoveryMultiplier", 1.6f, "At 100 morale, negative insanity change (recovery) is multiplied by this.");
			LowMoraleRecoveryMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Sanity", "LowMoraleRecoveryMultiplier", 0.65f, "At 0 morale, negative insanity change (recovery) is multiplied by this.");
			TZPRecoveryMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Sanity", "TZPRecoveryMultiplier", 1.765f, "Multiplier applied specifically to InsanityMod TZP recovery. The default changes a standard full effect from roughly 17 to 30 raw insanity points before morale scaling.");
			SanityDebuffsEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("V2.2 - Sanity Debuffs", "Enabled", true, "Add progressive insanity penalties to interaction speed and stamina efficiency. These stack directly with morale and are shown in the existing INT/STAM HUD values.");
			SanityDebuffTier1Threshold = ((BaseUnityPlugin)this).Config.Bind<float>("V2.2 - Sanity Debuffs", "Tier1Threshold", 60f, "Insanity at which the first sanity penalty begins.");
			SanityDebuffTier2Threshold = ((BaseUnityPlugin)this).Config.Bind<float>("V2.2 - Sanity Debuffs", "Tier2Threshold", 75f, "Insanity at which the second sanity penalty begins.");
			SanityDebuffTier3Threshold = ((BaseUnityPlugin)this).Config.Bind<float>("V2.2 - Sanity Debuffs", "Tier3Threshold", 90f, "Insanity at which the critical sanity penalty begins.");
			SanityDebuffTier1Percent = ((BaseUnityPlugin)this).Config.Bind<float>("V2.2 - Sanity Debuffs", "Tier1Percent", -10f, "Interaction and stamina percentage added at Tier 1 insanity.");
			SanityDebuffTier2Percent = ((BaseUnityPlugin)this).Config.Bind<float>("V2.2 - Sanity Debuffs", "Tier2Percent", -25f, "Interaction and stamina percentage added at Tier 2 insanity.");
			SanityDebuffTier3Percent = ((BaseUnityPlugin)this).Config.Bind<float>("V2.2 - Sanity Debuffs", "Tier3Percent", -50f, "Interaction and stamina percentage added at critical insanity. With default Broken morale, the natural combined total is -150%.");
			SanityVisualEffectsEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("V2.2 - Sanity Visuals", "Enabled", true, "Enable short local-only screen/HUD interference pulses driven by real InsanityMod insanity. Disable this for a clean HUD.");
			SanityVisualMinimumInsanity = ((BaseUnityPlugin)this).Config.Bind<float>("V2.2 - Sanity Visuals", "MinimumInsanity", 60f, "Real insanity at which intermittent screen/HUD interference can begin.");
			SanityVisualMinimumInterval = ((BaseUnityPlugin)this).Config.Bind<float>("V2.2 - Sanity Visuals", "MinimumIntervalSeconds", 5f, "Shortest gap between interference pulses at maximum insanity.");
			SanityVisualMaximumInterval = ((BaseUnityPlugin)this).Config.Bind<float>("V2.2 - Sanity Visuals", "MaximumIntervalSeconds", 24f, "Longest gap between interference pulses near the starting threshold.");
			SanityVisualMaximumDuration = ((BaseUnityPlugin)this).Config.Bind<float>("V2.2 - Sanity Visuals", "MaximumDurationSeconds", 0.9f, "Maximum duration of a high-insanity interference pulse.");
			SanityVisualIntensity = ((BaseUnityPlugin)this).Config.Bind<float>("V2.2 - Sanity Visuals", "Intensity", 1f, "Overall screen/HUD interference strength. Use 0 for no visible effect, 0.5 for reduced intensity or 1 for the intended default.");
			SanityVisualHudJitterPixels = ((BaseUnityPlugin)this).Config.Bind<float>("V2.2 - Sanity Visuals", "MoraleHudJitterPixels", 6f, "Maximum brief movement of the Crew Morale HUD during severe interference pulses.");
			SanityVisualDebugForceEffect = ((BaseUnityPlugin)this).Config.Bind<bool>("V2.2 - Sanity Visuals", "DebugForceEffect", false, "Testing only: continuously trigger the strongest interference pulse regardless of current insanity.");
			ConfidentLguBonus = ((BaseUnityPlugin)this).Config.Bind<float>("V2 - LGU", "ConfidentEffectivenessBonus", 0.05f, "Extra effectiveness applied to supported active LGU effects at 70-84 morale.");
			ThrivingLguBonus = ((BaseUnityPlugin)this).Config.Bind<float>("V2 - LGU", "ThrivingEffectivenessBonus", 0.1f, "Extra effectiveness applied to supported active LGU effects at 85-99 morale.");
			PeakLguBonus = ((BaseUnityPlugin)this).Config.Bind<float>("V2 - LGU", "PeakEffectivenessBonus", 0.15f, "Extra effectiveness applied to supported active LGU effects at 100 morale.");
			BrokenInteractionPercent = ((BaseUnityPlugin)this).Config.Bind<float>("V2 - Interaction Speed", "BrokenPercent", -100f, "Interaction contribution at Broken morale. Negative totals use hold-duration scaling, so values below -100 remain meaningful without reaching zero speed.");
			ShakenInteractionPercent = ((BaseUnityPlugin)this).Config.Bind<float>("V2 - Interaction Speed", "ShakenPercent", -50f, "Interaction speed change at Shaken morale.");
			UneasyInteractionPercent = ((BaseUnityPlugin)this).Config.Bind<float>("V2 - Interaction Speed", "UneasyPercent", -25f, "Interaction speed change at Uneasy morale.");
			StableInteractionPercent = ((BaseUnityPlugin)this).Config.Bind<float>("V2 - Interaction Speed", "StablePercent", 0f, "Interaction speed change at Stable morale.");
			ConfidentInteractionPercent = ((BaseUnityPlugin)this).Config.Bind<float>("V2 - Interaction Speed", "ConfidentPercent", 25f, "Interaction speed change at Confident morale.");
			ThrivingInteractionPercent = ((BaseUnityPlugin)this).Config.Bind<float>("V2 - Interaction Speed", "ThrivingPercent", 50f, "Interaction speed change at Thriving morale.");
			PeakInteractionPercent = ((BaseUnityPlugin)this).Config.Bind<float>("V2 - Interaction Speed", "PeakPercent", 100f, "Interaction speed change at Peak morale.");
			BrokenStaminaPercent = ((BaseUnityPlugin)this).Config.Bind<float>("V2 - Stamina Efficiency", "BrokenPercent", -100f, "Stamina efficiency change at Broken morale. Negative values increase drain and slow recovery.");
			ShakenStaminaPercent = ((BaseUnityPlugin)this).Config.Bind<float>("V2 - Stamina Efficiency", "ShakenPercent", -50f, "Stamina efficiency change at Shaken morale.");
			UneasyStaminaPercent = ((BaseUnityPlugin)this).Config.Bind<float>("V2 - Stamina Efficiency", "UneasyPercent", -25f, "Stamina efficiency change at Uneasy morale.");
			StableStaminaPercent = ((BaseUnityPlugin)this).Config.Bind<float>("V2 - Stamina Efficiency", "StablePercent", 0f, "Stamina efficiency change at Stable morale.");
			ConfidentStaminaPercent = ((BaseUnityPlugin)this).Config.Bind<float>("V2 - Stamina Efficiency", "ConfidentPercent", 25f, "Stamina efficiency change at Confident morale.");
			ThrivingStaminaPercent = ((BaseUnityPlugin)this).Config.Bind<float>("V2 - Stamina Efficiency", "ThrivingPercent", 50f, "Stamina efficiency change at Thriving morale.");
			PeakStaminaPercent = ((BaseUnityPlugin)this).Config.Bind<float>("V2 - Stamina Efficiency", "PeakPercent", 100f, "Stamina efficiency change at Peak morale. Positive values reduce drain and improve recovery.");
			MigrateV22EffectDefaults();
			LastStandEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("V2 - Last Stand", "Enabled", true, "At peak morale, prevent one lethal event and consume peak morale.");
			LastStandTriggerMorale = ((BaseUnityPlugin)this).Config.Bind<float>("V2 - Last Stand", "TriggerMorale", 100f, "Morale required for Last Stand to be ready.");
			LastStandAfterMorale = ((BaseUnityPlugin)this).Config.Bind<float>("V2 - Last Stand", "MoraleAfterActivation", 70f, "Morale immediately after Last Stand activates.");
			LastStandProtectionSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("V2 - Last Stand", "ProtectionSeconds", 3f, "Invulnerability grace period after Last Stand activates.");
			LastStandMinimumHealth = ((BaseUnityPlugin)this).Config.Bind<int>("V2 - Last Stand", "MinimumHealthAfterSave", 20, "Minimum health restored when Last Stand prevents death.");
			EventHudSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("V2 - HUD", "EventPopupSeconds", 5f, "How long contextual morale event HUD remains visible outside the ship.");
			DebugV2Logging = ((BaseUnityPlugin)this).Config.Bind<bool>("V2 - Debug", "VerboseLogging", true, "Log LGU hooks, Last Stand triggers/protection, and v2 integration state.");
			ShowHud = ((BaseUnityPlugin)this).Config.Bind<bool>("HUD", "ShowMoraleBar", true, "Show Crew Morale while the local player is in the ship.");
			HudX = ((BaseUnityPlugin)this).Config.Bind<float>("HUD", "X", 0.5f, "Horizontal screen anchor (0 to 1).");
			HudY = ((BaseUnityPlugin)this).Config.Bind<float>("HUD", "Y", 0.76f, "Vertical screen anchor (0 to 1).");
			HudWidth = ((BaseUnityPlugin)this).Config.Bind<float>("HUD", "Width", 260f, "Morale bar width in pixels.");
			HudHeight = ((BaseUnityPlugin)this).Config.Bind<float>("HUD", "Height", 14f, "Morale bar height in pixels.");
			PlayerCreditsOffsetX = ((BaseUnityPlugin)this).Config.Bind<float>("HUD", "PCOffsetX", -180f, "Fine horizontal offset for PC on the far-right side of the Decor/Sanity information row.");
			PlayerCreditsOffsetY = ((BaseUnityPlugin)this).Config.Bind<float>("HUD", "PCOffsetY", 0f, "Fine vertical offset for PC on the Decor/Sanity information row.");
			SanityHudRaisePixels = ((BaseUnityPlugin)this).Config.Bind<float>("HUD", "SanityHudRaisePixels", 360f, "Moves InsanityMod's bottom-right sanity ring upward by this many pixels.");
			UseStatusColors = ((BaseUnityPlugin)this).Config.Bind<bool>("HUD", "UseStatusColors", true, "Color the morale status text and current bar fill by morale tier.");
			ShowTierDividers = ((BaseUnityPlugin)this).Config.Bind<bool>("HUD", "ShowTierDividers", true, "Draw status-threshold dividers on the morale bar at 20, 35, 50, 70 and 85.");
			FakeInsanityHudEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Insanity HUD Fake Values", "Enabled", true, "At high insanity, make InsanityMod's displayed HUD value drift or lie without changing the real insanity value.");
			FakeInsanityStartThreshold = ((BaseUnityPlugin)this).Config.Bind<float>("Insanity HUD Fake Values", "StartThreshold", 60f, "Real insanity where HUD deception begins.");
			FakeInsanityMaxOffset = ((BaseUnityPlugin)this).Config.Bind<float>("Insanity HUD Fake Values", "MaxFakeOffset", 20f, "Maximum displayed-value deviation near 100 insanity.");
			FakeInsanityUpdateInterval = ((BaseUnityPlugin)this).Config.Bind<float>("Insanity HUD Fake Values", "UpdateInterval", 0.35f, "Seconds between fake-value target changes.");
			FakeInsanitySpikeChance = ((BaseUnityPlugin)this).Config.Bind<float>("Insanity HUD Fake Values", "SpikeChance", 15f, "Percent chance per update for a stronger false spike at high insanity.");
			FakeInsanityMaxDuration = ((BaseUnityPlugin)this).Config.Bind<float>("Insanity HUD Fake Values", "MaxFakeDuration", 1.5f, "Maximum time a fake target is held before drifting back.");
			FakeInsanityReturnSpeed = ((BaseUnityPlugin)this).Config.Bind<float>("Insanity HUD Fake Values", "ReturnSpeed", 18f, "Displayed insanity points per second used when drifting back toward the real value.");
			Morale = Clamp(StartingMorale.Value);
			_harmony = new Harmony("ultimatelethal.crewmorale");
			_harmony.PatchAll();
			TryPatchInsanityMod();
			InsanityEventThresholdIntegration.TryPatch(_harmony, ((BaseUnityPlugin)this).Logger);
			LGUIntegration.TryPatch(_harmony, ((BaseUnityPlugin)this).Logger);
			InteractionSpeedIntegration.TryPatch(_harmony, ((BaseUnityPlugin)this).Logger);
			StaminaEfficiencyIntegration.TryPatch(_harmony, ((BaseUnityPlugin)this).Logger);
			MoraleResetIntegration.TryPatch(_harmony, ((BaseUnityPlugin)this).Logger);
			LGUCurrencyIntegration.Initialize(((BaseUnityPlugin)this).Logger);
			EnemyKillIntegration.TryPatch(_harmony, ((BaseUnityPlugin)this).Logger);
			((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("{0} {1} loaded. Starting morale: {2:0}", "Ultimate Lethal Crew Morale", "2.2.0", Morale));
		}

		private void MigrateLegacyBalanceValues()
		{
			if (Mathf.Approximately(DeathPenalty.Value, 12f))
			{
				DeathPenalty.Value = 18f;
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Migrated legacy DeathPenalty 12 -> 18.");
			}
			if (Mathf.Approximately(FullCrewShipRecoveryPerSecond.Value, 0.2f) || Mathf.Approximately(FullCrewShipRecoveryPerSecond.Value, 0.07f) || Mathf.Approximately(FullCrewShipRecoveryPerSecond.Value, 0.03f))
			{
				FullCrewShipRecoveryPerSecond.Value = 0.00833333f;
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Migrated legacy full-crew passive recovery -> +0.5 morale/minute.");
			}
			if (Mathf.Approximately(OrbitRecoveryPerSecond.Value, 0.35f) || Mathf.Approximately(OrbitRecoveryPerSecond.Value, 0.12f) || Mathf.Approximately(OrbitRecoveryPerSecond.Value, 0.05f))
			{
				OrbitRecoveryPerSecond.Value = 0.0166667f;
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Migrated legacy orbit passive recovery -> +1 morale/minute.");
			}
			if (Mathf.Approximately(DeathPenalty.Value, 18f))
			{
				DeathPenalty.Value = 20f;
			}
			if (Mathf.Approximately(FullCrewShipRecoveryPerSecond.Value, 0.00833333f))
			{
				FullCrewShipRecoveryPerSecond.Value = 0.004166667f;
			}
			if (Mathf.Approximately(OrbitRecoveryPerSecond.Value, 0.0166667f))
			{
				OrbitRecoveryPerSecond.Value = 0.004166667f;
			}
			if (EmptyShipDecayPerSecond.Value > 0f)
			{
				EmptyShipDecayPerSecond.Value = 0f;
			}
		}

		private void MigrateV22EffectDefaults()
		{
			if (Mathf.Approximately(BrokenInteractionPercent.Value, -15f) && Mathf.Approximately(ShakenInteractionPercent.Value, -10f) && Mathf.Approximately(UneasyInteractionPercent.Value, -5f) && Mathf.Approximately(StableInteractionPercent.Value, 0f) && Mathf.Approximately(ConfidentInteractionPercent.Value, 5f) && Mathf.Approximately(ThrivingInteractionPercent.Value, 10f) && Mathf.Approximately(PeakInteractionPercent.Value, 15f))
			{
				BrokenInteractionPercent.Value = -100f;
				ShakenInteractionPercent.Value = -50f;
				UneasyInteractionPercent.Value = -25f;
				StableInteractionPercent.Value = 0f;
				ConfidentInteractionPercent.Value = 25f;
				ThrivingInteractionPercent.Value = 50f;
				PeakInteractionPercent.Value = 100f;
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Migrated v2.1 interaction defaults to -100/-50/-25/0/+25/+50/+100.");
			}
			if (Mathf.Approximately(BrokenStaminaPercent.Value, -15f) && Mathf.Approximately(ShakenStaminaPercent.Value, -10f) && Mathf.Approximately(UneasyStaminaPercent.Value, -5f) && Mathf.Approximately(StableStaminaPercent.Value, 0f) && Mathf.Approximately(ConfidentStaminaPercent.Value, 5f) && Mathf.Approximately(ThrivingStaminaPercent.Value, 10f) && Mathf.Approximately(PeakStaminaPercent.Value, 15f))
			{
				BrokenStaminaPercent.Value = -100f;
				ShakenStaminaPercent.Value = -50f;
				UneasyStaminaPercent.Value = -25f;
				StableStaminaPercent.Value = 0f;
				ConfidentStaminaPercent.Value = 25f;
				ThrivingStaminaPercent.Value = 50f;
				PeakStaminaPercent.Value = 100f;
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Migrated v2.1 stamina defaults to -100/-50/-25/0/+25/+50/+100.");
			}
		}

		private void Start()
		{
			_expeditionRoster.Clear();
			_countedDeadPlayers.Clear();
		}

		private void Update()
		{
			if (!(Time.unscaledTime < _nextStateTick))
			{
				float num = 0.25f;
				_nextStateTick = Time.unscaledTime + num;
				AdjustInsanityHudPosition();
				CleanupExpiredLastStandProtection();
				SaveScopedPersistence.Tick();
				if (!((Object)(object)StartOfRound.Instance == (Object)null))
				{
					RefreshDecorationBaseline();
					ApplyWorldState(num);
					UpdateQuotaFailureReset();
					ProcessHudEventQueue();
				}
			}
		}

		private void LateUpdate()
		{
			UpdateInsanityHudDeception();
			UpdateSanityVisualEffects();
		}

		private void UpdateSanityVisualEffects()
		{
			ConfigEntry<bool> sanityVisualEffectsEnabled = SanityVisualEffectsEnabled;
			if (sanityVisualEffectsEnabled == null || !sanityVisualEffectsEnabled.Value)
			{
				_sanityVisualEffectUntil = 0f;
				_nextSanityVisualEffect = 0f;
				return;
			}
			PlayerControllerB val = null;
			try
			{
				val = GameNetworkManager.Instance?.localPlayerController;
			}
			catch
			{
			}
			if ((Object)(object)val == (Object)null || val.isPlayerDead)
			{
				_sanityVisualEffectUntil = 0f;
				return;
			}
			try
			{
				if ((Object)(object)val.quickMenuManager != (Object)null && val.quickMenuManager.isMenuOpen)
				{
					_sanityVisualEffectUntil = 0f;
					return;
				}
			}
			catch
			{
			}
			bool flag = SanityVisualDebugForceEffect?.Value ?? false;
			if (!InsanityManagerBridge.TryReadInsanity(out var value))
			{
				if (!flag)
				{
					return;
				}
				value = 100f;
			}
			float num = Mathf.Clamp(SanityVisualMinimumInsanity?.Value ?? 60f, 0f, 99f);
			if (!flag && value < num)
			{
				_sanityVisualEffectUntil = 0f;
				_nextSanityVisualEffect = 0f;
				return;
			}
			float num2 = (flag ? 1f : Mathf.InverseLerp(num, 100f, value));
			float unscaledTime = Time.unscaledTime;
			if (flag && unscaledTime >= _sanityVisualEffectUntil)
			{
				BeginSanityVisualEffect(unscaledTime, 1f, 0.75f);
			}
			else if (!flag && unscaledTime >= _sanityVisualEffectUntil && unscaledTime >= _nextSanityVisualEffect)
			{
				float num3 = Mathf.Max(1f, SanityVisualMinimumInterval?.Value ?? 5f);
				float num4 = Mathf.Lerp(Mathf.Max(num3, SanityVisualMaximumInterval?.Value ?? 24f), num3, num2) * RandomRange(0.82f, 1.18f);
				float num5 = Mathf.Max(0.15f, SanityVisualMaximumDuration?.Value ?? 0.9f);
				float num6 = Mathf.Lerp(0.2f, num5, num2) * RandomRange(0.82f, 1.08f);
				BeginSanityVisualEffect(unscaledTime, Mathf.Lerp(0.28f, 1f, num2), num6);
				_nextSanityVisualEffect = unscaledTime + num6 + num4;
			}
		}

		private void BeginSanityVisualEffect(float now, float severity, float duration)
		{
			_sanityVisualEffectStarted = now;
			_sanityVisualEffectUntil = now + Mathf.Max(0.12f, duration);
			_sanityVisualSeverity = Mathf.Clamp01(severity);
			_sanityVisualSeed = _sanityVisualRandom.Next(1, int.MaxValue);
			ConfigEntry<bool> debugV2Logging = DebugV2Logging;
			if (debugV2Logging != null && debugV2Logging.Value)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)$"SANITY VISUAL: strength {_sanityVisualSeverity:0.00} | duration {_sanityVisualEffectUntil - now:0.00}s");
			}
		}

		private float RandomRange(float minimum, float maximum)
		{
			return Mathf.Lerp(minimum, maximum, (float)_sanityVisualRandom.NextDouble());
		}

		private float CurrentSanityVisualStrength()
		{
			float unscaledTime = Time.unscaledTime;
			if (unscaledTime >= _sanityVisualEffectUntil)
			{
				return 0f;
			}
			float num = Mathf.Max(0.01f, _sanityVisualEffectUntil - _sanityVisualEffectStarted);
			float num2 = Mathf.Sin(Mathf.Clamp01((unscaledTime - _sanityVisualEffectStarted) / num) * MathF.PI);
			float num3 = Mathf.Max(0f, SanityVisualIntensity?.Value ?? 1f);
			return Mathf.Clamp(_sanityVisualSeverity * num2 * num3, 0f, 1.5f);
		}

		private float SanityVisualNoise(int index)
		{
			float num = Mathf.Sin((float)(_sanityVisualSeed + index * 92821) * 0.0001234f) * 43758.547f;
			return num - Mathf.Floor(num);
		}

		private void DrawSanityScreenEffect()
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: 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_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			//IL_0132: Unknown result type (might be due to invalid IL or missing references)
			//IL_020a: Unknown result type (might be due to invalid IL or missing references)
			//IL_021f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0245: Unknown result type (might be due to invalid IL or missing references)
			//IL_0272: Unknown result type (might be due to invalid IL or missing references)
			//IL_0298: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0395: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a5: Unknown result type (might be due to invalid IL or missing references)
			float num = CurrentSanityVisualStrength();
			if (num <= 0.001f)
			{
				return;
			}
			EnsureGui();
			Color color = GUI.color;
			int depth = GUI.depth;
			GUI.depth = -1000;
			float num2 = Screen.width;
			float num3 = Screen.height;
			float num4 = Mathf.Lerp(18f, 82f, Mathf.Clamp01(num));
			GUI.color = new Color(0.04f, 0.2f, 0.22f, 0.035f * num);
			GUI.DrawTexture(new Rect(0f, 0f, num2, num3), (Texture)(object)_pixel);
			GUI.color = new Color(0f, 0f, 0f, 0.16f * num);
			GUI.DrawTexture(new Rect(0f, 0f, num2, num4), (Texture)(object)_pixel);
			GUI.DrawTexture(new Rect(0f, num3 - num4, num2, num4), (Texture)(object)_pixel);
			GUI.DrawTexture(new Rect(0f, num4, num4 * 0.65f, num3 - num4 * 2f), (Texture)(object)_pixel);
			GUI.DrawTexture(new Rect(num2 - num4 * 0.65f, num4, num4 * 0.65f, num3 - num4 * 2f), (Texture)(object)_pixel);
			int num5 = 3 + Mathf.RoundToInt(7f * Mathf.Clamp01(num));
			for (int i = 0; i < num5; i++)
			{
				float num6 = SanityVisualNoise(i) * num3;
				float num7 = Mathf.Lerp(1f, 10f, SanityVisualNoise(i + 31)) * Mathf.Lerp(0.5f, 1f, num);
				float num8 = SanityVisualNoise(i + 63) * num2 * 0.18f;
				float num9 = Mathf.Max(20f, num2 - num8 * 2f);
				float num10 = Mathf.Lerp(2f, 18f, SanityVisualNoise(i + 97)) * num;
				GUI.color = new Color(0f, 0f, 0f, (0.08f + SanityVisualNoise(i + 121) * 0.1f) * num);
				GUI.DrawTexture(new Rect(num8 + num10, num6, num9, num7), (Texture)(object)_pixel);
				GUI.color = new Color(0.15f, 0.95f, 1f, 0.065f * num);
				GUI.DrawTexture(new Rect(num8 - num10, num6 - 1f, num9 * 0.72f, 1f + num7 * 0.35f), (Texture)(object)_pixel);
				GUI.color = new Color(1f, 0.12f, 0.28f, 0.052f * num);
				GUI.DrawTexture(new Rect(num8 + num10 * 1.4f, num6 + num7, num9 * 0.58f, 1f + num7 * 0.25f), (Texture)(object)_pixel);
			}
			if (num >= 0.55f)
			{
				int num11 = 2 + Mathf.RoundToInt(4f * Mathf.Clamp01(num));
				for (int j = 0; j < num11; j++)
				{
					float num12 = Mathf.Lerp(45f, num2 * 0.3f, SanityVisualNoise(j + 211));
					float num13 = Mathf.Lerp(2f, 14f, SanityVisualNoise(j + 243));
					float num14 = SanityVisualNoise(j + 277) * Mathf.Max(1f, num2 - num12);
					float num15 = SanityVisualNoise(j + 309) * num3;
					GUI.color = new Color(0f, 0f, 0f, 0.18f * num);
					GUI.DrawTexture(new Rect(num14, num15, num12, num13), (Texture)(object)_pixel);
				}
			}
			GUI.color = color;
			GUI.depth = depth;
		}

		private void UpdateInsanityHudDeception()
		{
			ConfigEntry<bool> fakeInsanityHudEnabled = FakeInsanityHudEnabled;
			if (fakeInsanityHudEnabled == null || !fakeInsanityHudEnabled.Value || !InsanityManagerBridge.TryReadInsanity(out var value))
			{
				return;
			}
			GameObject val = GameObject.Find("InsanityHud_Root");
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			float num = Mathf.Clamp(FakeInsanityStartThreshold?.Value ?? 60f, 0f, 99f);
			if (value < num)
			{
				_fakeInsanityInitialized = false;
				ApplyInsanityHudPresentation(val, value);
				return;
			}
			float num2 = Mathf.InverseLerp(num, 100f, value);
			if (!_fakeInsanityInitialized)
			{
				_fakeInsanityDisplayed = value;
				_fakeInsanityInitialized = true;
				_nextFakeInsanityUpdate = 0f;
				_fakeInsanityUntil = 0f;
			}
			if (Time.unscaledTime >= _nextFakeInsanityUpdate)
			{
				float num3 = Mathf.Lerp(3f, Mathf.Max(3f, FakeInsanityMaxOffset?.Value ?? 20f), num2);
				float num4 = Random.Range(0f - num3, num3);
				float num5 = Mathf.Clamp01((FakeInsanitySpikeChance?.Value ?? 15f) / 100f) * num2;
				if (value >= 75f && Random.value < num5)
				{
					num4 = ((Random.value < 0.35f) ? (-1f) : 1f) * Random.Range(num3 * 0.65f, num3);
				}
				float num6 = ((value >= 90f) ? 70f : ((value >= 75f) ? 55f : Mathf.Max(0f, num - 8f)));
				_fakeInsanityDisplayed = Mathf.Clamp(value + num4, num6, 100f);
				float num7 = Mathf.Max(0.08f, FakeInsanityUpdateInterval?.Value ?? 0.35f);
				_nextFakeInsanityUpdate = Time.unscaledTime + num7;
				float num8 = Mathf.Max(0.25f, FakeInsanityMaxDuration?.Value ?? 1.5f);
				_fakeInsanityUntil = Time.unscaledTime + Random.Range(0.2f, num8);
			}
			if (Time.unscaledTime >= _fakeInsanityUntil)
			{
				float num9 = Mathf.Max(1f, FakeInsanityReturnSpeed?.Value ?? 18f);
				_fakeInsanityDisplayed = Mathf.MoveTowards(_fakeInsanityDisplayed, value, num9 * Time.unscaledDeltaTime);
			}
			ApplyInsanityHudPresentation(val, _fakeInsanityDisplayed);
		}

		private void ApplyInsanityHudPresentation(GameObject root, float displayed)
		{
			bool flag = false;
			try
			{
				Component[] componentsInChildren = root.GetComponentsInChildren<Component>(true);
				foreach (Component val in componentsInChildren)
				{
					if ((Object)(object)val == (Object)null)
					{
						continue;
					}
					Type type = ((object)val).GetType();
					PropertyInfo property = type.GetProperty("text", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					if (property != null && property.CanRead && property.CanWrite && property.PropertyType == typeof(string))
					{
						string text = property.GetValue(val) as string;
						if (LooksLikeNumericHudValue(text))
						{
							string text2 = text.Trim();
							bool flag2 = text2.EndsWith("%", StringComparison.Ordinal);
							bool flag3 = (flag2 ? text2.Substring(0, text2.Length - 1).Trim() : text2).Contains(".");
							string value = displayed.ToString(flag3 ? "0.0" : "0") + (flag2 ? "%" : "");
							property.SetValue(val, value);
							flag = true;
						}
					}
					PropertyInfo property2 = type.GetProperty("fillAmount", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					if (property2 != null && property2.CanWrite && property2.PropertyType == typeof(float))
					{
						string text3 = ((((Object)val).name != null) ? ((Object)val).name.ToLowerInvariant() : "");
						if (text3.Contains("fill") || text3.Contains("progress"))
						{
							property2.SetValue(val, Mathf.Clamp01(displayed / 100f));
							flag = true;
						}
					}
				}
			}
			catch (Exception ex)
			{
				ConfigEntry<bool> debugV2Logging = DebugV2Logging;
				if (debugV2Logging != null && debugV2Logging.Value)
				{
					((BaseUnityPlugin)this).Logger.LogDebug((object)("INSANITY HUD deception note: " + ex.Message));
				}
			}
			if (flag && !_insanityHudPresentationLocated)
			{
				_insanityHudPresentationLocated = true;
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Insanity HUD deception located a presentation value/fill. Real InsanityMod insanity remains untouched.");
			}
		}

		private static bool LooksLikeNumericHudValue(string value)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				return false;
			}
			string text = value.Trim();
			if (text.EndsWith("%", StringComparison.Ordinal))
			{
				text = text.Substring(0, text.Length - 1).Trim();
			}
			if (text.Length == 0 || text.Length > 7)
			{
				return false;
			}
			bool flag = false;
			string text2 = text;
			foreach (char c in text2)
			{
				if (char.IsDigit(c))
				{
					flag = true;
				}
				else if (c != '.' && c != '-' && c != '+')
				{
					return false;
				}
			}
			if (!flag)
			{
				return false;
			}
			if (float.TryParse(text, out var result) && result >= -1f)
			{
				return result <= 101f;
			}
			return false;
		}

		private static void AdjustInsanityHudPosition()
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: 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_006f: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = GameObject.Find("InsanityHud_Root");
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			RectTransform component = val.GetComponent<RectTransform>();
			if (!((Object)(object)component == (Object)null))
			{
				float num = 130f + Mathf.Max(0f, SanityHudRaisePixels?.Value ?? 360f);
				Vector2 anchoredPosition = component.anchoredPosition;
				if (Mathf.Abs(anchoredPosition.y - num) > 0.1f)
				{
					component.anchoredPosition = new Vector2(anchoredPosition.x, num);
				}
			}
		}

		private void ApplyWorldState(float dt)
		{
			StartOfRound instance = StartOfRound.Instance;
			bool flag = (Object)(object)instance != (Object)null && instance.shipHasLanded;
			if (flag && !_wasLanded)
			{
				CaptureExpeditionRoster();
				_expeditionStartingCrewCount = _expeditionRoster.Count;
				_someoneLeftShipThisExpedition = false;
				_expeditionHadDeath = false;
				_reunionAwardedThisExpedition = false;
				_recoveredScrapThisExpedition = false;
				_creditedLocalScrap.Clear();
				_localRecoveredScrapValue = 0;
				_localTookFieldDamage = false;
				_apparatusRecoveredByLocal = false;
				_criticalInjuryActive.Clear();
				CaptureShipScrapAtLanding();
			}
			if (!flag && _wasLanded)
			{
				HandleExpeditionDeparture();
			}
			int num = DetectNewExpeditionDeaths();
			if (num > 0)
			{
				_expeditionHadDeath = true;
				float num2 = DeathPenalty.Value * (float)num;
				if (ChangeMorale(0f - num2, "crew death"))
				{
					PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController;
					if ((Object)(object)val != (Object)null && IsPlayerInShip(val))
					{
						ShowEvent("CREW MEMBER LOST", $"-{num2:0.#} MORALE", -1f, revealOutsideShip: false, 10);
					}
					else
					{
						QueueEvent("CREW MEMBER LOST", $"-{num2:0.#} MORALE", revealOutsideShip: false, shipOnly: true, 10);
					}
				}
			}
			UpdateCriticalInjuries();
			if (flag)
			{
				UpdateRecoveredScrap();
				UpdateLocalPersonalRecoveryTracking();
			}
			int num3 = CountLivingExpeditionPlayers();
			int num4 = CountLivingExpeditionPlayersInShip();
			bool flag2 = num3 > 0 && num4 == num3;
			if (flag && num3 > 0 && num4 < num3)
			{
				_someoneLeftShipThisExpedition = true;
			}
			if (flag && _someoneLeftShipThisExpedition && !_expeditionHadDeath && !_reunionAwardedThisExpedition && _expeditionStartingCrewCount > 1 && num3 == _expeditionStartingCrewCount && flag2)
			{
				ChangeMorale(ReunionReward.Value, "perfect crew reunion");
				ShowEvent("CREW REUNITED", $"+{ReunionReward.Value:0.#} MORALE", -1f, revealOutsideShip: true);
				_reunionAwardedThisExpedition = true;
			}
			UpdateQuotaReward();
			bool num5 = IsInOrbit();
			float num6 = 0f;
			if (flag2)
			{
				num6 += FullCrewShipRecoveryPerSecond.Value;
			}
			if (num5)
			{
				num6 += OrbitRecoveryPerSecond.Value;
			}
			if (num6 > 0f)
			{
				float num7 = Mathf.Max(0f, DecorationBaseline - StartingMorale.Value);
				float num8 = Mathf.Max(0.01f, DecorationBonusCap.Value);
				float num9 = 1f + Mathf.Clamp01(num7 / num8);
				MoveTowardMorale(DecorationBaseline, num6 * num9 * dt);
			}
			_wasFullLivingCrewInShip = flag2;
			_wasLanded = flag;
		}

		private void UpdateCriticalInjuries()
		{
			foreach (PlayerControllerB item in _expeditionRoster)
			{
				if ((Object)(object)item == (Object)null || item.isPlayerDead)
				{
					continue;
				}
				int num = ReadPlayerHealth(item);
				if (num < 0)
				{
					continue;
				}
				if (num <= CriticalInjuryHealthThreshold.Value && !_criticalInjuryActive.Contains(item))
				{
					_criticalInjuryActive.Add(item);
					float morale = Morale;
					bool flag = ChangeMorale(0f - CriticalInjuryPenalty.Value, "critical injury");
					bool revealOutsideShip = false;
					try
					{
						PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController;
						revealOutsideShip = (Object)(object)val != (Object)null && ((Object)(object)val == (Object)(object)item || val.playerClientId == item.playerClientId);
					}
					catch
					{
					}
					if (flag)
					{
						ShowEvent("CRITICAL INJURY", $"-{CriticalInjuryPenalty.Value:0.#} MORALE", -1f, revealOutsideShip);
					}
					ConfigEntry<bool> debugV2Logging = DebugV2Logging;
					if (debugV2Logging != null && debugV2Logging.Value)
					{
						string arg = (flag ? $"morale {morale:0.#} -> {Morale:0.#}" : $"blocked at morale {morale:0.#}");
						((BaseUnityPlugin)this).Logger.LogInfo((object)$"CRITICAL INJURY: player {item.playerClientId} | HP {num} | {arg}");
					}
				}
				else if (num > CriticalInjuryResetHealth.Value && _criticalInjuryActive.Contains(item))
				{
					_criticalInjuryActive.Remove(item);
					ConfigEntry<bool> debugV2Logging2 = DebugV2Logging;
					if (debugV2Logging2 != null && debugV2Logging2.Value)
					{
						((BaseUnityPlugin)this).Logger.LogInfo((object)$"CRITICAL INJURY RESET: player {item.playerClientId} | HP {num}");
					}
				}
			}
		}

		private static int ReadPlayerHealth(PlayerControllerB player)
		{
			try
			{
				FieldInfo fieldInfo = AccessTools.Field(typeof(PlayerControllerB), "health");
				if (fieldInfo != null)
				{
					return Convert.ToInt32(fieldInfo.GetValue(player));
				}
			}
			catch
			{
			}
			return -1;
		}

		private void HandleExpeditionDeparture()
		{
			if (IsCompanyMoon())
			{
				return;
			}
			int num = CountLivingExpeditionPlayers();
			if (num <= 0)
			{
				return;
			}
			AwardPersonalExpeditionCredits(num);
			float num2 = SurvivorReward.Value * (float)num;
			if (num2 > 0f)
			{
				ChangeMorale(num2, "expedition survivors");
				ShowEvent((num == 1) ? "1 SURVIVOR RETURNED" : $"{num} SURVIVORS RETURNED", $"+{num2:0.#} MORALE", -1f, revealOutsideShip: true);
			}
			int num3 = 0;
			foreach (PlayerControllerB item in _expeditionRoster)
			{
				if (!((Object)(object)item == (Object)null) && !item.isPlayerDead && !IsPlayerInShip(item))
				{
					num3++;
				}
			}
			if (num3 > 0)
			{
				float num4 = LeftBehindPenalty.Value * (float)num3;
				if (ChangeMorale(0f - num4, "crew left behind"))
				{
					ShowEvent((num3 == 1) ? "CREW MEMBER LEFT BEHIND" : "CREW LEFT BEHIND", $"-{num4:0.#} MORALE", -1f, revealOutsideShip: true);
				}
			}
			if (_someoneLeftShipThisExpedition && !_recoveredScrapThisExpedition && ChangeMorale(0f - ZeroScrapExpeditionPenalty.Value, "zero-scrap expedition"))
			{
				ShowEvent("EXPEDITION CAME BACK EMPTY", $"-{ZeroScrapExpeditionPenalty.Value:0.#} MORALE", -1f, revealOutsideShip: true);
			}
		}

		private void UpdateLocalPersonalRecoveryTracking()
		{
			if (IsCompanyMoon())
			{
				return;
			}
			PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController;
			if ((Object)(object)val == (Object)null || val.isPlayerDead)
			{
				return;
			}
			if (!IsPlayerInShip(val))
			{
				int num = ReadPlayerHealth(val);
				if (num >= 0 && num < 100)
				{
					_localTookFieldDamage = true;
				}
			}
			try
			{
				Type type = AccessTools.TypeByName("GrabbableObject");
				if (type == null)
				{
					return;
				}
				Object[] array = Object.FindObjectsOfType(type);
				foreach (Object val2 in array)
				{
					if (val2 == (Object)null)
					{
						continue;
					}
					FieldInfo fieldInfo = AccessTools.Field(((object)val2).GetType(), "isInShipRoom");
					if (fieldInfo == null || !Convert.ToBoolean(fieldInfo.GetValue(val2)))
					{
						continue;
					}
					int instanceID = val2.GetInstanceID();
					if (_shipScrapAtLanding.Contains(instanceID) || _creditedLocalScrap.Contains(instanceID))
					{
						continue;
					}
					object? obj = AccessTools.Field(((object)val2).GetType(), "playerHeldBy")?.GetValue(val2) ?? AccessTools.Field(((object)val2).GetType(), "heldByPlayerOnServer")?.GetValue(val2);
					PlayerControllerB val3 = (PlayerControllerB)((obj is PlayerControllerB) ? obj : null);
					if (val3 != null && (Object)(object)val3 != (Object)(object)val)
					{
						continue;
					}
					object obj2 = AccessTools.Field(((object)val2).GetType(), "itemProperties")?.GetValue(val2);
					if (obj2 != null && Convert.ToBoolean(AccessTools.Field(obj2.GetType(), "isScrap")?.GetValue(obj2) ?? ((object)false)))
					{
						int num2 = 0;
						FieldInfo fieldInfo2 = AccessTools.Field(((object)val2).GetType(), "scrapValue");
						if (fieldInfo2 != null)
						{
							num2 = Math.Max(0, Convert.ToInt32(fieldInfo2.GetValue(val2)));
						}
						_creditedLocalScrap.Add(instanceID);
						_localRecoveredScrapValue += num2;
						_recoveredScrapThisExpedition = true;
						if ((AccessTools.Field(obj2.GetType(), "itemName")?.GetValue(obj2)?.ToString() ?? "").IndexOf("apparatus", StringComparison.OrdinalIgnoreCase) >= 0)
						{
							_apparatusRecoveredByLocal = true;
						}
					}
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogDebug((object)("Personal recovery tracking compatibility note: " + ex.Message));
			}
		}

		private int SuccessfulHaulCreditsForValue(int value)
		{
			if (value >= 500)
			{
				return SuccessfulHaulTier4Credits.Value;
			}
			if (value >= 350)
			{
				return SuccessfulHaulTier3Credits.Value;
			}
			if (value >= 200)
			{
				return SuccessfulHaulTier2Credits.Value;
			}
			if (value >= 100)
			{
				return SuccessfulHaulTier1Credits.Value;
			}
			return 0;
		}

		private void AwardPersonalExpeditionCredits(int living)
		{
			ConfigEntry<bool> playerCreditRewardsEnabled = PlayerCreditRewardsEnabled;
			if (playerCreditRewardsEnabled == null || !playerCreditRewardsEnabled.Value || IsCompanyMoon())
			{
				return;
			}
			PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController;
			if (!((Object)(object)val == (Object)null) && !val.isPlayerDead && _expeditionRoster.Contains(val))
			{
				int num = 0;
				List<string> list = new List<string>();
				int num2 = SuccessfulHaulCreditsForValue(_localRecoveredScrapValue);
				if (num2 > 0)
				{
					num += num2;
					list.Add($"HAUL +{num2}");
				}
				int num3 = DangerousMoonCredits();
				if (num3 > 0)
				{
					num += num3;
					list.Add($"DANGEROUS MOON +{num3}");
				}
				if (_apparatusRecoveredByLocal && ApparatusRecoveryCredits.Value > 0)
				{
					num += ApparatusRecoveryCredits.Value;
					list.Add($"APPARATUS +{ApparatusRecoveryCredits.Value}");
				}
				if (living == _expeditionStartingCrewCount && FullCrewExtractionCredits.Value > 0)
				{
					num += FullCrewExtractionCredits.Value;
					list.Add($"FULL CREW +{FullCrewExtractionCredits.Value}");
				}
				int num4 = ReadPlayerHealth(val);
				if (_localTookFieldDamage && num4 >= 0 && num4 <= AgainstOddsHealthThreshold.Value && AgainstOddsCredits.Value > 0)
				{
					num += AgainstOddsCredits.Value;
					list.Add($"AGAINST ODDS +{AgainstOddsCredits.Value}");
				}
				if (num > 0 && LGUCurrencyIntegration.TryAwardLocal(num, "expedition performance"))
				{
					ShowEvent("EXPEDITION PERFORMANCE", string.Join("   •   ", list) + $"   = +{num} PC", -1f, revealOutsideShip: true, 70);
				}
			}
		}

		private int DangerousMoonCredits()
		{
			if (IsCompanyMoon())
			{
				return 0;
			}
			try
			{
				object obj = StartOfRound.Instance?.currentLevel;
				string text = (AccessTools.Field(obj?.GetType(), "riskLevel")?.GetValue(obj)?.ToString() ?? AccessTools.Property(obj?.GetType(), "riskLevel")?.GetValue(obj)?.ToString() ?? "").Trim().ToUpperInvariant();
				if (text.StartsWith("X") || text.StartsWith("S+") || text.Contains("S++"))
				{
					return DangerousMoonTier3Credits.Value;
				}
				if (text.StartsWith("S") || text.StartsWith("A"))
				{
					return DangerousMoonTier2Credits.Value;
				}
				if (text.StartsWith("B") || text.StartsWith("C"))
				{
					return DangerousMoonTier1Credits.Value;
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogDebug((object)("Dangerous moon compatibility note: " + ex.Message));
			}
			return 0;
		}

		internal void TryTriggerAddedHallucination()
		{
			ConfigEntry<bool> hallucinationsEnabled = HallucinationsEnabled;
			if (hallucinationsEnabled == null || !hallucinationsEnabled.Value)
			{
				return;
			}
			PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController;
			if ((Object)(object)val == (Object)null || val.isPlayerDead || (Object)(object)StartOfRound.Instance == (Object)null || !StartOfRound.Instance.shipHasLanded || IsCompanyMoon())
			{
				return;
			}
			if ((Object)(object)_hallucinationVisual != (Object)null && Time.unscaledTime >= _hallucinationVisualUntil)
			{
				Object.Destroy((Object)(object)_hallucinationVisual);
				_hallucinationVisual = null;
			}
			RestoreHallucinationLightsIfNeeded();
			if (Time.unscaledTime < _nextHallucinationCheck || Time.unscaledTime < _hallucinationCooldownUntil)
			{
				return;
			}
			_nextHallucinationCheck = Time.unscaledTime + Mathf.Max(1f, HallucinationCheckSeconds.Value);
			if (!InsanityManagerBridge.TryReadInsanity(out var value) || value < HallucinationMinimumInsanity.Value)
			{
				return;
			}
			float num = Mathf.InverseLerp(HallucinationMinimumInsanity.Value, 100f, value);
			float num2 = InsanityIntegration.EventFrequencyMultiplier();
			float num3 = Mathf.Clamp01(HallucinationBaseChance.Value * num * num2);
			float value2 = Random.value;
			ConfigEntry<bool> debugV2Logging = DebugV2Logging;
			if (debugV2Logging != null && debugV2Logging.Value)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)$"HALLUCINATION ROLL: insanity {value:0.#} | morale {Morale:0.#} | freq x{num2:0.00} | chance {num3 * 100f:0.#}% | roll {value2 * 100f:0.#}%");
			}
			if (!(value2 > num3))
			{
				TriggerHallucination(val, value);
				_hallucinationCooldownUntil = Time.unscaledTime + Mathf.Max(3f, HallucinationGlobalCooldown.Value);
				ConfigEntry<bool> debugV2Logging2 = DebugV2Logging;
				if (debugV2Logging2 != null && debugV2Logging2.Value)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)$"HALLUCINATION TRIGGERED: insanity {value:0.#} | morale {Morale:0.#}");
				}
			}
		}

		private void TriggerHallucination(PlayerControllerB local, float insanity)
		{
			List<int> list = new List<int>();
			int num = Mathf.Clamp(HallucinationDebugForceType?.Value ?? 0, 0, 3);
			if (num == 1 && HallucinationAudioEnabled.Value && insanity >= HallucinationAudioThreshold.Value)
			{
				list.Add(0);
			}
			else if (num == 2 && HallucinationVisualEnabled.Value && insanity >= HallucinationVisualThreshold.Value)
			{
				list.Add(1);
			}
			else if (num == 3 && HallucinationEnvironmentEnabled.Value && insanity >= HallucinationEnvironmentThreshold.Value)
			{
				list.Add(2);
			}
			else
			{
				if (HallucinationAudioEnabled.Value && insanity >= HallucinationAudioThreshold.Value)
				{
					for (int i = 0; i < Mathf.Max(0, HallucinationAudioWeight.Value); i++)
					{
						list.Add(0);
					}
				}
				if (HallucinationVisualEnabled.Value && insanity >= HallucinationVisualThreshold.Value)
				{
					for (int j = 0; j < Mathf.Max(0, HallucinationVisualWeight.Value); j++)
					{
						list.Add(1);
					}
				}
				if (HallucinationEnvironmentEnabled.Value && insanity >= HallucinationEnvironmentThreshold.Value)
				{
					for (int k = 0; k < Mathf.Max(0, HallucinationEnvironmentWeight.Value); k++)
					{
						list.Add(2);
					}
				}
			}
			if (list.Count != 0)
			{
				switch (list[Random.Range(0, list.Count)])
				{
				case 0:
					TriggerPhantomAudio(local, insanity);
					break;
				case 1:
					TriggerShadowFigure(local, insanity);
					break;
				default:
					TriggerCameraDisturbance(local, insanity);
					break;
				}
			}
		}

		private void TriggerPhantomAudio(PlayerControllerB local, float insanity)
		{
			//IL_01f2: 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_020b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0212: Unknown result type (might be due to invalid IL or missing references)
			//IL_021d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0231: Unknown result type (might be due to invalid IL or missing references)
			//IL_0236: Unknown result type (might be due to invalid IL or missing references)
			//IL_023b: Unknown result type (might be due to invalid IL or missing references)
			//IL_023c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0241: 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_024f: Expected O, but got Unknown
			//IL_0256: Unknown result type (might be due to invalid IL or missing references)
			Camera gameplayCamera = local.gameplayCamera;
			if ((Object)(object)gameplayCamera == (Object)null)
			{
				return;
			}
			Type type = AccessTools.TypeByName("UnityEngine.AudioClip");
			Type type2 = AccessTools.TypeByName("UnityEngine.AudioSource");
			if (type == null || type2 == null)
			{
				TriggerShadowFigure(local, insanity);
				return;
			}
			if (_hallucinationClips.Count == 0)
			{
				try
				{
					Object[] array = Resources.FindObjectsOfTypeAll(type);
					foreach (Object val in array)
					{
						if (!(val == (Object)null) && !string.IsNullOrEmpty(val.name))
						{
							string text = val.name.ToLowerInvariant();
							if (text.Contains("footstep") || text.Contains("step") || text.Contains("run") || text.Contains("door") || text.Contains("metal") || text.Contains("creak") || text.Contains("bang") || text.Contains("vent") || text.Contains("radio") || text.Contains("static") || text.Contains("walkie") || text.Contains("growl") || text.Contains("roar") || text.Contains("breath") || text.Contains("scream") || text.Contains("snarl"))
							{
								_hallucinationClips.Add(val);
							}
							if (_hallucinationClips.Count >= 48)
							{
								break;
							}
						}
					}
				}
				catch (Exception ex)
				{
					((BaseUnityPlugin)this).Logger.LogDebug((object)("Hallucination audio discovery note: " + ex.Message));
				}
			}
			if (_hallucinationClips.Count == 0)
			{
				TriggerShadowFigure(local, insanity);
				return;
			}
			Vector3 val2 = ((Component)gameplayCamera).transform.right * Random.Range(-8f, 8f);
			Vector3 position = ((Component)gameplayCamera).transform.position + ((Component)gameplayCamera).transform.forward * Random.Range(-3f, 10f) + val2;
			GameObject val3 = new GameObject("UltimateLethal_PhantomAudio");
			val3.transform.position = position;
			Component obj = val3.AddComponent(type2);
			AccessTools.Property(type2, "spatialBlend")?.SetValue(obj, 1f);
			AccessTools.Property(type2, "minDistance")?.SetValue(obj, 1.5f);
			AccessTools.Property(type2, "maxDistance")?.SetValue(obj, 24f);
			AccessTools.Property(type2, "volume")?.SetValue(obj, Mathf.Lerp(0.28f, 0.62f, Mathf.InverseLerp(45f, 100f, insanity)));
			List<Object> list = _hallucinationClips;
			if (insanity >= 72f)
			{
				List<Object> list2 = _hallucinationClips.Where(delegate(Object c)
				{
					string text2 = ((c != (Object)null) ? c.name.ToLowerInvariant() : "");
					return text2.Contains("footstep") || text2.Contains("step") || text2.Contains("run") || text2.Contains("door") || text2.Contains("vent") || text2.Contains("radio") || text2.Contains("static") || text2.Contains("walkie") || text2.Contains("growl") || text2.Contains("roar") || text2.Contains("breath") || text2.Contains("scream") || text2.Contains("snarl");
				}).ToList();
				if (list2.Count > 0)
				{
					list = list2;
				}
			}
			Object val4 = list[Random.Range(0, list.Count)];
			MethodInfo methodInfo = AccessTools.Method(type2, "PlayOneShot", new Type[1] { type }, (Type[])null);
			if (methodInfo == null)
			{
				Object.Destroy((Object)(object)val3);
				TriggerShadowFigure(local, insanity);
				return;
			}
			methodInfo.Invoke(obj, new object[1] { val4 });
			float num = 2f;
			try
			{
				object obj2 = AccessTools.Property(type, "length")?.GetValue(val4);
				if (obj2 != null)
				{
					num = Convert.ToSingle(obj2);
				}
			}
			catch
			{
			}
			Object.Destroy((Object)(object)val3, Mathf.Max(2f, num + 0.25f));
			((BaseUnityPlugin)this).Logger.LogDebug((object)$"Hallucination: phantom audio '{val4.name}' at insanity {insanity:0.#}.");
		}

		private void TriggerShadowFigure(PlayerControllerB local, float insanity)
		{
			//IL_016c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0188: Unknown result type (might be due to invalid IL or missing references)
			//IL_018f: Expected O, but got Unknown
			//IL_0196: Unknown result type (might be due to invalid IL or missing references)
			//IL_01aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_01af: 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_01b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c9: 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_022c: Unknown result type (might be due to invalid IL or missing references)
			//IL_028a: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0313: Unknown result type (might be due to invalid IL or missing references)
			//IL_033b: Unknown result type (might be due to invalid IL or missing references)
			//IL_035a: Unknown result type (might be due to invalid IL or missing references)
			//IL_03bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_03e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0402: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: 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_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f0: 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_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_043c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0443: Expected O, but got Unknown
			//IL_0459: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: 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_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_014c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_012c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			Camera gameplayCamera = local.gameplayCamera;
			if ((Object)(object)gameplayCamera == (Object)null)
			{
				return;
			}
			if ((Object)(object)_hallucinationVisual != (Object)null)
			{
				Object.Destroy((Object)(object)_hallucinationVisual);
				_hallucinationVisual = null;
			}
			float num = Mathf.InverseLerp(58f, 100f, insanity);
			bool flag = Random.value < Mathf.Lerp(0.45f, 0.75f, num);
			float num2 = ((Random.value < 0.5f) ? (-1f) : 1f);
			float num3 = Random.Range(Mathf.Lerp(3.4f, 2.1f, num), Mathf.Lerp(5.2f, 3.8f, num));
			float num4 = (flag ? Random.Range(-0.65f, 0.65f) : (num2 * Random.Range(1.2f, 2.7f)));
			Vector3 val = ((Component)gameplayCamera).transform.position + ((Component)gameplayCamera).transform.forward * num3 + ((Component)gameplayCamera).transform.right * num4;
			try
			{
				RaycastHit val2 = default(RaycastHit);
				if (Physics.Raycast(val + Vector3.up * 3f, Vector3.down, ref val2, 8f))
				{
					val = ((RaycastHit)(ref val2)).point + Vector3.up * 1.28f;
				}
				else
				{
					val.y = ((Component)local).transform.position.y + 1.28f;
				}
			}
			catch
			{
				val.y = ((Component)local).transform.position.y + 1.28f;
			}
			GameObject val3 = new GameObject("UltimateLethal_LocalHallucination");
			val3.transform.position = val;
			Transform transform = val3.transform;
			Vector3 val4 = Vector3.ProjectOnPlane(((Component)gameplayCamera).transform.position - val, Vector3.up);
			transform.rotation = Quaternion.LookRotation(((Vector3)(ref val4)).normalized, Vector3.up);
			float num5 = Mathf.Lerp(1.05f, 1.38f, num);
			GameObject obj2 = GameObject.CreatePrimitive((PrimitiveType)1);
			((Object)obj2).name = "HallucinationBody";
			obj2.transform.SetParent(val3.transform, false);
			obj2.transform.localScale = new Vector3(0.62f * num5, 1.05f * num5, 0.4f * num5);
			Collider component = obj2.GetComponent<Collider>();
			if ((Object)(object)component != (Object)null)
			{
				Object.Destroy((Object)(object)component);
			}
			GameObject obj3 = GameObject.CreatePrimitive((PrimitiveType)0);
			((Object)obj3).name = "HallucinationHead";
			obj3.transform.SetParent(val3.transform, false);
			obj3.transform.localPosition = new Vector3(0f, 1.38f * num5, 0f);
			obj3.transform.localScale = new Vector3(0.58f * num5, 0.62f * num5, 0.52f * num5);
			Collider component2 = obj3.GetComponent<Collider>();
			if ((Object)(object)component2 != (Object)null)
			{
				Object.Destroy((Object)(object)component2);
			}
			GameObject obj4 = GameObject.CreatePrimitive((PrimitiveType)1);
			((Object)obj4).name = "HallucinationArmL";
			obj4.transform.SetParent(val3.transform, false);
			obj4.transform.localPosition = new Vector3(-0.46f * num5, 0.25f * num5, 0f);
			obj4.transform.localScale = new Vector3(0.18f * num5, 0.86f * num5, 0.18f * num5);
			obj4.transform.localRotation = Quaternion.Euler(0f, 0f, -8f);
			Collider component3 = obj4.GetComponent<Collider>();
			if ((Object)(object)component3 != (Object)null)
			{
				Object.Destroy((Object)(object)component3);
			}
			GameObject obj5 = GameObject.CreatePrimitive((PrimitiveType)1);
			((Object)obj5).name = "HallucinationArmR";
			obj5.transform.SetParent(val3.transform, false);
			obj5.transform.localPosition = new Vector3(0.46f * num5, 0.25f * num5, 0f);
			obj5.transform.localScale = new Vector3(0.18f * num5, 0.86f * num5, 0.18f * num5);
			obj5.transform.localRotation = Quaternion.Euler(0f, 0f, 8f);
			Collider component4 = obj5.GetComponent<Collider>();
			if ((Object)(object)component4 != (Object)null)
			{
				Object.Destroy((Object)(object)component4);
			}
			Shader val5 = Shader.Find("Unlit/Color");
			if ((Object)(object)val5 != (Object)null)
			{
				Material val6 = new Material(val5);
				val6.color = new Color(0.001f, 0.001f, 0.001f, 1f);
				Renderer[] componentsInChildren = val3.GetComponentsInChildren<Renderer>(true);
				foreach (Renderer val7 in componentsInChildren)
				{
					if ((Object)(object)val7 != (Object)null)
					{
						val7.material = val6;
					}
				}
			}
			_hallucinationVisual = val3;
			_hallucinationVisualUntil = Time.unscaledTime + Mathf.Lerp(0.55f, 1.15f, num);
			((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("HALLUCINATION VISUAL: obvious figure | insanity {0:0.#} | distance {1:0.0}m | {2}", insanity, num3, flag ? "center" : "peripheral"));
		}

		private void TriggerCameraDisturbance(PlayerControllerB local, float insanity)
		{
			Camera cam = local.gameplayCamera;
			if ((Object)(object)cam == (Object)null)
			{
				return;
			}
			RestoreHallucinationLightsNow();
			try
			{
				List<Light> list = (from l in Object.FindObjectsOfType<Light>()
					where (Object)(object)l != (Object)null && ((Behaviour)l).enabled && l.intensity > 0.01f && Vector3.Distance(((Component)l).transform.position, ((Component)cam).transform.position) <= 24f
					orderby Vector3.Distance(((Component)l).transform.position, ((Component)cam).transform.position)
					select l).Take(5).ToList();
				foreach (Light item in list)
				{
					_hallucinationLightIntensity[item] = item.intensity;
					item.intensity *= Random.Range(0.02f, 0.18f);
				}
				if (list.Count == 0)
				{
					TriggerShadowFigure(local, insanity);
					return;
				}
				_hallucinationLightsUntil = Time.unscaledTime + Mathf.Lerp(0.16f, 0.42f, Mathf.InverseLerp(72f, 100f, insanity));
				((BaseUnityPlugin)this).Logger.LogDebug((object)$"Hallucination: local light flicker ({list.Count} lights) at insanity {insanity:0.#}.");
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogDebug((object)("Hallucination environment fallback: " + ex.Message));
				TriggerShadowFigure(local, insanity);
			}
		}

		private void RestoreHallucinationLightsIfNeeded()
		{
			if (_hallucinationLightIntensity.Count != 0 && !(Time.unscaledTime < _hallucinationLightsUntil))
			{
				RestoreHallucinationLightsNow();
			}
		}

		private void RestoreHallucinationLightsNow()
		{
			if (_hallucinationLightIntensity.Count == 0)
			{
				return;
			}
			foreach (KeyValuePair<Light, float> item in _hallucinationLightIntensity.ToList())
			{
				if ((Object)(object)item.Key != (Object)null)
				{
					try
					{
						item.Key.intensity = item.Value;
					}
					catch
					{
					}
				}
			}
			_hallucinationLightIntensity.Clear();
			_hallucinationLightsUntil = 0f;
		}

		private static bool IsCompanyMoon()
		{
			try
			{
				object obj = StartOfRound.Instance?.currentLevel;
				if (obj == null)
				{
					return false;
				}
				string text = AccessTools.Field(obj.GetType(), "PlanetName")?.GetValue(obj)?.ToString() ?? AccessTools.Property(obj.GetType(), "PlanetName")?.GetValue(obj)?.ToString() ?? "";
				string text2 = AccessTools.Field(obj.GetType(), "sceneName")?.GetValue(obj)?.ToString() ?? "";
				return text.IndexOf("Gordion", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Company", StringComparison.OrdinalIgnoreCase) >= 0 || text2.IndexOf("Company", StringComparison.OrdinalIgnoreCase) >= 0;
			}
			catch
			{
				return false;
			}
		}

		private void CaptureShipScrapAtLanding()
		{
			_shipScrapAtLanding.Clear();
			foreach (int currentShipScrapInstanceId in GetCurrentShipScrapInstanceIds())
			{
				_shipScrapAtLanding.Add(currentShipScrapInstanceId);
			}
		}

		private void UpdateRecoveredScrap()
		{
			if (_recoveredScrapThisExpedition)
			{
				return;
			}
			foreach (int currentShipScrapInstanceId in GetCurrentShipScrapInstanceIds())
			{
				if (!_shipScrapAtLanding.Contains(currentShipScrapInstanceId))
				{
					_recoveredScrapThisExpedition = true;
					break;
				}
			}
		}

		private static IEnumerable<int> GetCurrentShipScrapInstanceIds()
		{
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			List<int> list = new List<int>();
			try
			{
				Type type = AccessTools.TypeByName("GrabbableObject");
				if (type == null)
				{
					return list;
				}
				Object[] array = Object.FindObjectsOfType(type);
				foreach (Object val in array)
				{
					if (val == (Object)null)
					{
						continue;
					}
					object obj = AccessTools.Field(((object)val).GetType(), "itemProperties")?.GetValue(val);
					if (obj == null)
					{
						continue;
					}
					FieldInfo fieldInfo = AccessTools.Field(obj.GetType(), "isScrap");
					if (fieldInfo == null || !Convert.ToBoolean(fieldInfo.GetValue(obj)))
					{
						continue;
					}
					bool flag = false;
					FieldInfo fieldInfo2 = AccessTools.Field(((object)val).GetType(), "isInShipRoom");
					if (fieldInfo2 != null)
					{
						flag = Convert.ToBoolean(fieldInfo2.GetValue(val));
					}
					else
					{
						Component val2 = (Component)(object)((val is Component) ? val : null);
						if (val2 != null)
						{
							int num;
							if ((Object)(object)StartOfRound.Instance != (Object)null && (Object)(object)StartOfRound.Instance.shipInnerRoomBounds != (Object)null)
							{
								Bounds bounds = StartOfRound.Instance.shipInnerRoomBounds.bounds;
								num = (((Bounds)(ref bounds)).Contains(val2.transform.position) ? 1 : 0);
							}
							else
							{
								num = 0;
							}
							flag = (byte)num != 0;
						}
					}
					if (flag)
					{
						list.Add(val.GetInstanceID());
					}
				}
			}
			catch (Exception ex)
			{
				CrewMoralePlugin instance = Instance;
				if (instance != null)
				{
					((BaseUnityPlugin)instance).Logger.LogDebug((object)("Ship scrap scan compatibility note: " + ex.Message));
				}
			}
			return list;
		}

		private static string GetEnemyRewardName(object enemy)
		{
			try
			{
				Type type = enemy.GetType();
				object obj = AccessTools.Field(type, "enemyType")?.GetValue(enemy) ?? AccessTools.Property(type, "enemyType")?.GetValue(enemy);
				if (obj != null)
				{
					string[] array = new string[2] { "enemyName", "name" };
					foreach (string text in array)
					{
						string text2 = AccessTools.Field(obj.GetType(), text)?.GetValue(obj)?.ToString();
						if (!string.IsNullOrWhiteSpace(text2))
						{
							return text2;
						}
						text2 = AccessTools.Property(obj.GetType(), text)?.GetValue(obj)?.ToString();
						if (!string.IsNullOrWhiteSpace(text2))
						{
							return text2;
						}
					}
				}
				Component val = (Component)((enemy is Component) ? enemy : null);
				if (val != null && (Object)(object)val.gameObject != (Object)null)
				{
					return ((Object)val.gameObject).name;
				}
				return type.Name;
			}
			catch
			{
				return enemy?.GetType().Name ?? "Unknown";
			}
		}

		private static bool ThreatListContains(string csv, string name)
		{
			if (string.IsNullOrWhiteSpace(csv) || string.IsNullOrWhiteSpace(name))
			{
				return false;
			}
			string text = name.Replace("(Clone)", "").Replace("Enemy", "").Trim();
			string[] array = csv.Split(',');
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].Trim();
				if (text2.Length > 0 && (text.Equals(text2, StringComparison.OrdinalIgnoreCase) || text.IndexOf(text2, StringComparison.OrdinalIgnoreCase) >= 0 || text2.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0))
				{
					return true;
				}
			}
			return false;
		}

		private static ThreatTier ClassifyThreat(object enemy, out string name)
		{
			name = GetEnemyRewardName(enemy);
			if (ThreatListContains(ExtremeThreatEnemies?.Value, name))
			{
				return ThreatTier.Extreme;
			}
			if (ThreatListContains(DangerousThreatEnemies?.Value, name))
			{
				return ThreatTier.Dangerous;
			}
			if (ThreatListContains(MinorThreatEnemies?.Value, name))
			{
				return ThreatTier.Minor;
			}
			return ThreatTier.Standard;
		}

		internal static void AwardMonsterKill(object enemyInstance, PlayerControllerB killer)
		{
			if ((Object)(object)Instance == (Object)null || enemyInstance == null || (Object)(object)killer == (Object)null)
			{
				return;
			}
			int instanceId;
			try
			{
				Object val = (Object)((enemyInstance is Object) ? enemyInstance : null);
				instanceId = ((val != null) ? val.GetInstanceID() : enemyInstance.GetHashCode());
			}
			catch
			{
				return;
			}
			if (!EnemyKillIntegration.TryMarkRewarded(instanceId))
			{
				return;
			}
			string name;
			ThreatTier threatTier = ClassifyThreat(enemyInstance, out name);
			float num = threatTier switch
			{
				ThreatTier.Extreme => ExtremeThreatMorale.Value, 
				ThreatTier.Dangerous => DangerousThreatMorale.Value, 
				ThreatTier.Minor => MinorThreatMorale.Value, 
				_ => StandardThreatMorale.Value, 
			};
			int num2 = threatTier switch
			{
				ThreatTier.Extreme => ExtremeThreatPc.Value, 
				ThreatTier.Dangerous => DangerousThreatPc.Value, 
				ThreatTier.Minor => MinorThreatPc.Value, 
				_ => StandardThreatPc.Value, 
			};
			bool flag = num > 0f && ChangeMorale(num, $"{threatTier} threat {name} killed by player {killer.playerClientId}");
			ConfigEntry<bool> playerCreditRewardsEnabled = PlayerCreditRewardsEnabled;
			bool flag2 = playerCreditRewardsEnabled != null && playerCreditRewardsEnabled.Value && num2 > 0 && LGUCurrencyIntegration.TryAwardToLocalPlayer(killer, num2, $"{threatTier} threat kill ({name})");
			if (flag || flag2)
			{
				string arg = threatTier.ToString().ToUpperInvariant() + " THREAT";
				string detail = ((flag && flag2) ? $"{arg}   •   +{num:0.#} MORALE   •   +{num2} PC" : (flag ? $"{arg}   •   +{num:0.#} MORALE" : $"{arg}   •   +{num2} PC"));
				Instance.ShowEvent("THREAT ELIMINATED", detail, -1f, revealOutsideShip: true, 80);
			}
			ConfigEntry<bool> debugV2Logging = DebugV2Logging;
			if (debugV2Logging != null && debugV2Logging.Value)
			{
				ManualLogSource pluginLogger = PluginLogger;
				if (pluginLogger != null)
				{
					pluginLogger.LogInfo((object)$"THREAT REWARD: {name} -> {threatTier} | morale +{num:0.#} | PC +{num2}");
				}
			}
		}

		private void UpdateQuotaReward()
		{
			try
			{
				TimeOfDay val = Object.FindObjectOfType<TimeOfDay>();
				if ((Object)(object)val == (Object)null)
				{
					return;
				}
				FieldInfo fieldInfo = AccessTools.Field(typeof(TimeOfDay), "quotaFulfilled");
				FieldInfo fieldInfo2 = AccessTools.Field(typeof(TimeOfDay), "profitQuota");
				if (fieldInfo == null || fieldInfo2 == null)
				{
					return;
				}
				int num = Convert.ToInt32(fieldInfo.GetValue(val));
				int num2 = Mathf.Max(1, Convert.ToInt32(fieldInfo2.GetValue(val)));
				bool flag = num >= num2;
				if (!_quotaStateInitialized)
				{
					_quotaStateInitialized = true;
					_quotaWasFulfilled = flag;
					return;
				}
				if (flag && !_quotaWasFulfilled)
				{
					ChangeMorale(QuotaFulfilledReward.Value, "quota fulfilled");
					ShowEvent("QUOTA FULFILLED", $"+{QuotaFulfilledReward.Value:0.#} MORALE", -1f, revealOutsideShip: true);
				}
				_quotaWasFulfilled = flag;
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogDebug((object)("Quota morale check failed: " + ex.Message));
			}
		}

		private void ShowEvent(string title, string detail, float seconds = -1f, bool revealOutsideShip = false, int priority = 50)
		{
			float num = ((seconds > 0f) ? seconds : (EventHudSeconds?.Value ?? 5f));
			float unscaledTime = Time.unscaledTime;
			if (unscaledTime < _eventUntil)
			{
				if (priority <= _eventPriority)
				{
					QueueEvent(title, detail, revealOutsideShip, shipOnly: false, priority, num);
					return;
				}
				float seconds2 = Mathf.Max(0.5f, _eventUntil - unscaledTime);
				QueueEvent(_eventTitle, _eventDetail, _eventRevealOutsideShip, shipOnly: false, _eventPriority, seconds2);
			}
			_eventTitle = title ?? "";
			_eventDetail = detail ?? "";
			_eventRevealOutsideShip = revealOutsideShip;
			_eventPriority = priority;
			_eventUntil = unscaledTime + num;
		}

		private void QueueEvent(string title, string detail, bool revealOutsideShip = false, bool shipOnly = false, int priority = 20, float seconds = -1f)
		{
			_eventQueue.Add(new HudEvent
			{
				Title = (title ?? ""),
				Detail = (detail ?? ""),
				Duration = ((seconds > 0f) ? seconds : (EventHudSeconds?.Value ?? 5f)),
				RevealOutsideShip = revealOutsideShip,
				ShipOnly = shipOnly,
				Priority = priority
			});
		}

		private void ProcessHudEventQueue()
		{
			if (Time.unscaledTime < _eventUntil || _eventQueue.Count == 0)
			{
				return;
			}
			PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController;
			bool flag = (Object)(object)val != (Object)null && IsPlayerInShip(val);
			int num = -1;
			int num2 = int.MinValue;
			for (int i = 0; i < _eventQueue.Count; i++)
			{
				HudEvent hudEvent = _eventQueue[i];
				if (hudEvent != null && (!hudEvent.ShipOnly || flag) && hudEvent.Priority > num2)
				{
					num = i;
					num2 = hudEvent.Priority;
				}
			}
			if (num >= 0)
			{
				HudEvent hudEvent2 = _eventQueue[num];
				_eventQueue.RemoveAt(num);
				_eventTitle = hudEvent2.Title;
				_eventDetail = hudEvent2.Detail;
				_eventRevealOutsideShip = hudEvent2.RevealOutsideShip;
				_eventPriority = hudEvent2.Priority;
				_eventUntil = Time.unscaledTime + hudEvent2.Duration;
			}
		}

		private void UpdateQuotaFailureReset()
		{
			bool flag = false;
			try
			{
				object instance = StartOfRound.Instance;
				if (instance != null)
				{
					string[] array = new string[3] { "firingPlayersCutsceneRunning", "firingPlayersCutscene", "playersBeingFired" };
					foreach (string text in array)
					{
						FieldInfo fieldInfo = AccessTools.Field(instance.GetType(), text);
						if (fieldInfo != null && fieldInfo.FieldType == typeof(bool) && Convert.ToBoolean(fieldInfo.GetValue(instance)))
						{
							flag = true;
							break;
						}
						PropertyInfo propertyInfo = AccessTools.Property(instance.GetType(), text);
						if (propertyInfo != null && propertyInfo.PropertyType == typeof(bool) && Convert.ToBoolean(propertyInfo.GetValue(instance)))
						{
							flag = true;
							break;
						}
					}
				}
			}
			catch
			{
			}
			if (flag && !_wasQuotaFailureSequence)
			{
				ResetMoraleToStarting("quota failure");
			}
			_wasQuotaFailureSequence = flag;
		}

		internal static void ResetMoraleToStarting(string reason)
		{
			float morale = Morale;
			Morale = Clamp(StartingMorale?.Value ?? 55f);
			SaveScopedPersistence.MarkMoraleDirty(immediate: true);
			if ((Object)(object)Instance != (Object)null)
			{
				((BaseUnityPlugin)Instance).Logger.LogInfo((object)$"MORALE RESET: {morale:0.#} -> {Morale:0.#} ({reason})");
			}
		}

		internal static void RestoreMoraleFromSave(float value)
		{
			Morale = Clamp(value);
		}

		internal static float CurrentLguBonus()
		{
			if (Morale >= 99.999f)
			{
				return Mathf.Max(0f, PeakLguBonus?.Value ?? 0.15f);
			}
			if (Morale >= 85f)
			{
				return Mathf.Max(0f, ThrivingLguBonus?.Value ?? 0.1f);
			}
			if (Morale >= 70f)
			{
				return Mathf.Max(0f, ConfidentLguBonus?.Value ?? 0.05f);
			}
			return 0f;
		}

		internal static bool IsLastStandProtected(PlayerControllerB player)
		{
			if ((Object)(object)Instance == (Object)null || (Object)(object)player == (Object)null)
			{
				return false;
			}
			if (!Instance._lastStandProtectedUntil.TryGetValue(player, out var value))
			{
				return false;
			}
			return Time.unscaledTime < value;
		}

		internal static float LastStandProtectionRemaining(PlayerControllerB player)
		{
			if ((Object)(object)Instance == (Object)null || (Object)(object)player == (Object)null)
			{
				return 0f;
			}
			if (!Instance._lastStandProtectedUntil.TryGetValue(player, out var value))
			{
				return 0f;
			}
			return Mathf.Max(0f, value - Time.unscaledTime);
		}

		private void CleanupExpiredLastStandProtection()
		{
			if (_lastStandProtectedUntil.Count == 0)
			{
				return;
			}
			float now = Time.unscaledTime;
			foreach (PlayerControllerB item in (from kv in _lastStandProtectedUntil
				where (Object)(object)kv.Key == (Object)null || now >= kv.Value
				select kv.Key).ToList())
			{
				if ((Object)(object)item != (Object)null && DebugV2Logging.Value)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)"Last Stand protection expired.");
				}
				_lastStandProtectedUntil.Remove(item);
			}
		}

		internal static bool TryTriggerLastStand(PlayerControllerB player)
		{
			if ((Object)(object)Instance == (Object)null || (Object)(object)player == (Object)null || !LastStandEnabled.Value)
			{
				return false;
			}
			if (IsLastStandProtected(player))
			{
				return true;
			}
			if (Morale + 0.001f < LastStandTriggerMorale.Value)
			{
				return false;
			}
			float morale = Morale;
			ChangeMorale(LastStandAfterMorale.Value - Morale, "Last Stand", allowPeakBreak: true);
			float num = Mathf.Max(0.25f, LastStandProtectionSeconds.Value);
			Instance._lastStandProtectedUntil[player] = Time.unscaledTime + num;
			try
			{
				FieldInfo fieldInfo = AccessTools.Field(typeof(PlayerControllerB), "health");
				if (fieldInfo != null)
				{
					int num2 = Convert.ToInt32(fieldInfo.GetValue(player));
					fieldInfo.SetValue(player, Mathf.Max(num2, LastStandMinimumHealth.Value));
				}
				FieldInfo fieldInfo2 = AccessTools.Field(typeof(PlayerControllerB), "criticallyInjured");
				if (fieldInfo2 != null && fieldInfo2.FieldType == typeof(bool))
				{
					fieldInfo2.SetValue(player, false);
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)Instance).Logger.LogDebug((object)("Last Stand health recovery compatibility note: " + ex.Message));
			}
			Instance.ShowEvent("★ LAST STAND ACTIVATED ★", $"CREW SAVED  •  MORALE {morale:0} → {Morale:0}  •  PROTECTED {num:0.0}s", -1f, revealOutsideShip: true, 100);
			if (DebugV2Logging.Value)
			{
				((BaseUnityPlugin)Instance).Logger.LogWarning((object)$"LAST STAND: prevented death; morale {morale:0.0}->{Morale:0.0}; protection {num:0.0}s.");
			}
			return true;
		}

		internal static bool ChangeMorale(float amount, string reason = null, bool allowPeakBreak = false)
		{
			float morale = Morale;
			float num = MaximumMorale?.Value ?? 100f;
			if (amount < 0f && morale >= num - 0.001f && !allowPeakBreak)
			{
				if ((Object)(object)Instance != (Object)null && reason != null)
				{
					((BaseUnityPlugin)Instance).Logger.LogDebug((object)$"Peak morale blocked loss of {0f - amount:0.0} ({reason})");
				}
				return false;
			}
			Morale = Clamp(Morale + amount);
			bool flag = Mathf.Abs(Morale - morale) >= 0.01f;
			if (flag)
			{
				SaveScopedPersistence.MarkMoraleDirty();
			}
			if ((Object)(object)Instance != (Object)null && flag && reason != null)
			{
				((BaseUnityPlugin)Instance).Logger.LogDebug((object)$"Morale {morale:0.0} -> {Morale:0.0} ({reason})");
			}
			return flag;
		}

		private static void MoveTowardMorale(float target, float maxDelta)
		{
			target = Clamp(target);
			if (!(target <= Morale))
			{
				float morale = Morale;
				Morale = Mathf.MoveTowards(Morale, target, Mathf.Max(0f, maxDelta));
				if (Mathf.Abs(Morale - morale) >= 0.01f)
				{
					SaveScopedPersistence.MarkMoraleDirty();
				}
			}
		}

		private static float Clamp(float value)
		{
			return Mathf.Clamp(value, MinimumMorale?.Value ?? 0f, MaximumMorale?.Value ?? 100f);
		}

		private void RefreshDecorationBaseline()
		{
			try
			{
				StartOfRound instance = StartOfRound.Instance;
				if (instance?.unlockablesList?.unlockables == null)
				{
					return;
				}
				int num = 0;
				float num2 = 0f;
				foreach (UnlockableItem unlockable in instance.unlockablesList.unlockables)
				{
					if (unlockable != null && unlockable.unlockableType == 1 && !unlockable.alreadyUnlocked && unlockable.hasBeenUnlockedByPlayer && !unlockable.inStorage)
					{
						int num3 = 0;
						if ((Object)(object)unlockable.shopSelectionNode != (Object)null)
						{
							num3 = Mathf.Max(0, unlockable.shopSelectionNode.itemCost);
						}
						float num4 = Mathf.Max(1f, DecorationPriceScaleCredits.Value);
						float num5 = Mathf.Max(0.01f, DecorationPriceExponent.Value);
						float num6 = Mathf.Pow(Mathf.Max(0f, (float)num3) / num4, num5);
						num6 = Mathf.Clamp(num6, 0f, DecorationMaxMoraleEach.Value);
						num2 += num6;
						num++;
					}
				}
				DecorationCount = num;
				float num7 = Mathf.Min(DecorationBonusCap.Value, num2);
				DecorationBaseline = Clamp(StartingMorale.Value + num7);
				if (_previousDecorationBonus >= 0f && Mathf.Abs(num7 - _previousDecorationBonus) >= 0.1f)
				{
					float num8 = num7 - _previousDecorationBonus;
					if (num8 > 0f)
					{
						ShowEvent("SHIP DECOR IMPROVED", $"+{num8:0.##} MORALE  •  DECOR {num7:0.##}/{DecorationBonusCap.Value:0}");
					}
					else
					{
						ShowEvent("DECORATION REMOVED", $"{num8:0.##} MORALE  •  DECOR {num7:0.##}/{DecorationBonusCap.Value:0}");
					}
				}
				_previousDecorationBonus = num7;
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogDebug((object)("Decoration scan failed: " + ex.Message));
			}
		}

		private void CaptureExpeditionRoster()
		{
			_expeditionRoster.Clear();
			_countedDeadPlayers.Clear();
			PlayerControllerB[] array = StartOfRound.Instance?.allPlayerScripts;
			if (array == null)
			{
				return;
			}
			PlayerControllerB[] array2 = array;
			foreach (PlayerControllerB val in array2)
			{
				if (!((Object)(object)val == (Object)null) && val.isPlayerControlled && !val.isPlayerDead)
				{
					_expeditionRoster.Add(val);
				}
			}
			((BaseUnit