Decompiled source of Valheim Stealth Opportunity v1.0.0

Valheim_StealthOpportunity.dll

Decompiled 2 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("0.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 Valheim_StealthOpportunity
{
	internal sealed class SneakRate
	{
		public sealed class Effect
		{
			public string Name;

			public float Bonus;
		}

		private static readonly MethodInfo GetSkillMethod = AccessTools.Method(typeof(Skills), "GetSkill", (Type[])null, (Type[])null);

		public float Step;

		public float RangeFactor;

		public readonly List<Effect> Effects = new List<Effect>();

		public float EffectFactor;

		public float WorldRate;

		public float GainPerTick;

		public float Level;

		public float Accumulator;

		public float Requirement;

		public bool Maxed;

		public float SkillFactor;

		public float StaminaSkillFactor;

		public float StaminaEquipment;

		public float StaminaEffects;

		public float DrainPerSecond;

		public float MaxStamina;

		public float SecondsToLevel
		{
			get
			{
				if (!(GainPerTick > 0f))
				{
					return float.PositiveInfinity;
				}
				return (Requirement - Accumulator) / GainPerTick;
			}
		}

		public float SecondsPerBar
		{
			get
			{
				if (!(DrainPerSecond > 0f))
				{
					return float.PositiveInfinity;
				}
				return MaxStamina / DrainPerSecond;
			}
		}

		public static SneakRate Read(Player player, bool fullRate)
		{
			SneakRate sneakRate = new SneakRate();
			Skills skills = ((Character)player).GetSkills();
			sneakRate.Step = 1f;
			object? obj = GetSkillMethod?.Invoke(skills, new object[1] { (object)(SkillType)101 });
			Skill val = (Skill)((obj is Skill) ? obj : null);
			if (val != null)
			{
				sneakRate.Step = val.m_info.m_increseStep;
				sneakRate.Level = val.m_level;
				sneakRate.Accumulator = val.m_accumulator;
			}
			else
			{
				sneakRate.Level = skills.GetSkillLevel((SkillType)101);
			}
			sneakRate.Maxed = sneakRate.Level >= 100f;
			sneakRate.Requirement = Mathf.Pow(Mathf.Floor(sneakRate.Level + 1f), 1.5f) * 0.5f + 0.5f;
			sneakRate.RangeFactor = (fullRate ? 1f : 0.1f);
			sneakRate.EffectFactor = 1f;
			foreach (StatusEffect statusEffect in ((Character)player).GetSEMan().GetStatusEffects())
			{
				float num = 0f;
				statusEffect.ModifyRaiseSkill((SkillType)101, ref num);
				if (!Mathf.Approximately(num, 0f))
				{
					sneakRate.Effects.Add(new Effect
					{
						Name = Localize(statusEffect.m_name),
						Bonus = num
					});
					sneakRate.EffectFactor += num;
				}
			}
			sneakRate.WorldRate = Game.m_skillGainRate;
			sneakRate.GainPerTick = (sneakRate.Maxed ? 0f : (sneakRate.Step * sneakRate.RangeFactor * sneakRate.EffectFactor * sneakRate.WorldRate));
			sneakRate.SkillFactor = skills.GetSkillFactor((SkillType)101);
			sneakRate.StaminaSkillFactor = Mathf.Lerp(1f, 0.25f, Mathf.Pow(sneakRate.SkillFactor, 0.5f));
			float num2 = player.m_sneakStaminaDrain * sneakRate.StaminaSkillFactor;
			sneakRate.StaminaEquipment = ((Character)player).GetEquipmentSneakStaminaModifier();
			num2 += num2 * sneakRate.StaminaEquipment;
			float num3 = num2;
			((Character)player).GetSEMan().ModifySneakStaminaUsage(num2, ref num2, true);
			sneakRate.StaminaEffects = ((num3 > 0f) ? (num2 / num3 - 1f) : 0f);
			sneakRate.DrainPerSecond = num2;
			sneakRate.MaxStamina = ((Character)player).GetMaxStamina();
			return sneakRate;
		}

		private static string Localize(string token)
		{
			if (Localization.instance == null)
			{
				return token;
			}
			return Localization.instance.Localize(token);
		}
	}
	internal static class SneakTrainer
	{
		private enum Phase
		{
			Circling,
			Resting
		}

		private static readonly FieldRef<Character, Vector3> MoveDir = AccessTools.FieldRefAccess<Character, Vector3>("m_moveDir");

		private static readonly FieldRef<Player, bool> CrouchToggled = AccessTools.FieldRefAccess<Player, bool>("m_crouchToggled");

		private static Phase s_phase;

		private static Vector3 s_center;

		private static float s_turn = 1f;

		public static bool Active { get; private set; }

		public static void Toggle()
		{
			if (Active)
			{
				Stop("key");
			}
			else
			{
				Start();
			}
		}

		private static void Start()
		{
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || CannotTrain(localPlayer))
			{
				if (localPlayer != null)
				{
					((Character)localPlayer).Message((MessageType)1, StealthWords.Get("train_cant"), 0, (Sprite)null, false);
				}
				return;
			}
			Active = true;
			localPlayer.m_autoRun = false;
			s_center = ((Component)localPlayer).transform.position;
			s_turn = ((Random.value < 0.5f) ? 1f : (-1f));
			s_phase = ((!(localPlayer.GetStamina() > ((Character)localPlayer).GetMaxStamina() * StealthConfig.StopAtStamina.Value)) ? Phase.Resting : Phase.Circling);
			bool fullRate = StealthSurvey.Take(localPlayer, checkPaths: false).FullRate;
			((Character)localPlayer).Message((MessageType)1, StealthWords.Get(fullRate ? "train_on" : "train_on_low"), 0, (Sprite)null, false);
			StealthPlugin.Log.LogInfo((object)$"Train Sneaking on, full rate: {fullRate}.");
		}

		public static void Stop(string reason)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			if (Active)
			{
				Active = false;
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer != (Object)null)
				{
					MoveDir.Invoke((Character)(object)localPlayer) = Vector3.zero;
					((Character)localPlayer).Message((MessageType)1, StealthWords.Get("train_off"), 0, (Sprite)null, false);
				}
				StealthPlugin.Log.LogInfo((object)("Train Sneaking off: " + reason + "."));
			}
		}

		private static bool CannotTrain(Player player)
		{
			if (!((Character)player).IsDead() && !((Character)player).IsSwimming() && !((Character)player).IsTeleporting() && !((Character)player).InPlaceMode() && !((Character)player).IsAttached() && !((Character)player).InBed() && !((Character)player).IsRiding())
			{
				return ((Character)player).IsFlying();
			}
			return true;
		}

		public static bool IsUserInput(Vector3 movedir, bool attack, bool attackHold, bool secondaryAttack, bool secondaryAttackHold, bool block, bool blockHold, bool jump, bool crouch, bool run, bool autoRun, bool dodge)
		{
			return ((Vector3)(ref movedir)).sqrMagnitude > 0.0001f || attack || attackHold || secondaryAttack || secondaryAttackHold || block || blockHold || jump || crouch || run || autoRun || dodge;
		}

		public static void Drive(Player player)
		{
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			if (CannotTrain(player))
			{
				Stop("cannot sneak here any more");
				return;
			}
			float stamina = player.GetStamina();
			float maxStamina = ((Character)player).GetMaxStamina();
			if (s_phase == Phase.Circling && stamina <= maxStamina * StealthConfig.StopAtStamina.Value)
			{
				s_phase = Phase.Resting;
			}
			else if (s_phase == Phase.Resting && stamina >= maxStamina * StealthConfig.ResumeAtStamina.Value - 0.01f)
			{
				s_phase = Phase.Circling;
			}
			if (!CrouchToggled.Invoke(player) && ((Character)player).HaveStamina(0f))
			{
				CrouchToggled.Invoke(player) = true;
			}
			MoveDir.Invoke((Character)(object)player) = ((s_phase == Phase.Circling) ? CircleDirection(player) : Vector3.zero);
		}

		private static Vector3 CircleDirection(Player player)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = ((Component)player).transform.position - s_center;
			val.y = 0f;
			float magnitude = ((Vector3)(ref val)).magnitude;
			Vector3 val2 = ((magnitude > 0.01f) ? (val / magnitude) : ((Component)player).transform.right);
			Vector3 val3 = Vector3.Cross(Vector3.up, val2) * s_turn;
			float value = StealthConfig.CircleRadius.Value;
			float num = Mathf.Clamp((magnitude - value) / value, -1f, 1f);
			Vector3 val4 = val3 - val2 * num * 1.5f;
			return ((Vector3)(ref val4)).normalized;
		}
	}
	[HarmonyPatch(typeof(Player), "SetControls")]
	internal static class PlayerSetControlsPatch
	{
		private static void Prefix(Player __instance, Vector3 movedir, bool attack, bool attackHold, bool secondaryAttack, bool secondaryAttackHold, bool block, bool blockHold, bool jump, bool crouch, bool run, bool autoRun, bool dodge)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			if (SneakTrainer.Active && (Object)(object)__instance == (Object)(object)Player.m_localPlayer && SneakTrainer.IsUserInput(movedir, attack, attackHold, secondaryAttack, secondaryAttackHold, block, blockHold, jump, crouch, run, autoRun, dodge))
			{
				SneakTrainer.Stop("player input");
			}
		}

		private static void Postfix(Player __instance)
		{
			if (SneakTrainer.Active && (Object)(object)__instance == (Object)(object)Player.m_localPlayer)
			{
				SneakTrainer.Drive(__instance);
			}
		}
	}
	[HarmonyPatch(typeof(Humanoid), "UseItem")]
	internal static class HumanoidUseItemPatch
	{
		private static void Prefix(Humanoid __instance)
		{
			if (SneakTrainer.Active && (Object)(object)__instance == (Object)(object)Player.m_localPlayer)
			{
				SneakTrainer.Stop("item used");
			}
		}
	}
	[HarmonyPatch(typeof(Player), "StartEmote")]
	internal static class PlayerStartEmotePatch
	{
		private static void Prefix(Player __instance)
		{
			if (SneakTrainer.Active && (Object)(object)__instance == (Object)(object)Player.m_localPlayer)
			{
				SneakTrainer.Stop("emote");
			}
		}
	}
	[HarmonyPatch(typeof(Game), "OnDestroy")]
	internal static class GameOnDestroyPatch
	{
		private static void Postfix()
		{
			SneakTrainer.Stop("world closed");
		}
	}
	public static class StealthConfig
	{
		public static ConfigEntry<bool> Enabled;

		public static ConfigEntry<string> PageTitle;

		public static ConfigEntry<bool> ShowHudStatus;

		public static ConfigEntry<string> HudLabel;

		public static ConfigEntry<KeyboardShortcut> TrainKey;

		public static ConfigEntry<float> CircleRadius;

		public static ConfigEntry<float> StopAtStamina;

		public static ConfigEntry<float> ResumeAtStamina;

		public static ConfigEntry<Color> TitleColor;

		public static ConfigEntry<Color> TextColor;

		public static ConfigEntry<Color> GoodColor;

		public static ConfigEntry<Color> MutedColor;

		public static ConfigEntry<bool> LogReport;

		public static void Bind(ConfigFile config)
		{
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Expected O, but got Unknown
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Expected O, but got Unknown
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_0140: Expected O, but got Unknown
			//IL_0164: Unknown result type (might be due to invalid IL or missing references)
			//IL_0197: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fd: Unknown result type (might be due to invalid IL or missing references)
			Enabled = config.Bind<bool>("General", "Enabled", true, "Master switch for the Compendium page. Train Sneaking keeps working when it is off.");
			PageTitle = config.Bind<string>("General", "PageTitle", "Stealth Opportunity", "Name of the Compendium topic. It is only listed while sneaking here would train at full rate and no nearby enemy can walk to you.");
			ShowHudStatus = config.Bind<bool>("HUD", "ShowStatus", true, "Show an icon with the Sneak skill picture among the status effects at the top right of the screen (Cold, Rested, Shelter...) while there is a Stealth Opportunity. Checked once a second.");
			HudLabel = config.Bind<string>("HUD", "Label", "Stealth Opp.", "Text under the icon.");
			TrainKey = config.Bind<KeyboardShortcut>("Train Sneaking", "ToggleKey", new KeyboardShortcut((KeyCode)290, Array.Empty<KeyCode>()), "Starts or stops Train Sneaking. Any action of yours (moving, attacking, blocking, jumping, crouching, using or interacting, emotes, hotbar) stops it too; switching windows does not.");
			CircleRadius = config.Bind<float>("Train Sneaking", "CircleRadius", 1.2f, new ConfigDescription("Radius in metres of the circle walked around the spot where Train Sneaking started.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 5f), Array.Empty<object>()));
			StopAtStamina = config.Bind<float>("Train Sneaking", "StopAtStamina", 0.08f, new ConfigDescription("Stop circling when stamina falls to this fraction of the maximum. The game itself stands you up at zero, so keep it above 0.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.02f, 0.9f), Array.Empty<object>()));
			ResumeAtStamina = config.Bind<float>("Train Sneaking", "ResumeAtStamina", 1f, new ConfigDescription("Start circling again once stamina is back to this fraction of the maximum.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 1f), Array.Empty<object>()));
			TitleColor = config.Bind<Color>("Colors", "TitleColor", new Color(1f, 0.78f, 0.35f, 1f), "Headings of the page.");
			TextColor = config.Bind<Color>("Colors", "TextColor", new Color(0.85f, 0.85f, 0.85f, 1f), "Ordinary lines.");
			GoodColor = config.Bind<Color>("Colors", "GoodColor", new Color(0.45f, 0.85f, 0.4f, 1f), "Factors that help, and the resulting rate.");
			MutedColor = config.Bind<Color>("Colors", "MutedColor", new Color(0.6f, 0.6f, 0.6f, 1f), "Explanations and neutral factors.");
			LogReport = config.Bind<bool>("Advanced", "LogReport", false, "Write the page to the BepInEx log each time the Compendium is opened, with the reason when it is not listed.");
		}
	}
	internal static class StealthHud
	{
		private static StatusEffect s_effect;

		private static bool s_wasShown;

		public static bool Opportunity;

		public static void AddTo(List<StatusEffect> effects)
		{
			if (!Opportunity || !StealthConfig.ShowHudStatus.Value)
			{
				s_wasShown = false;
				return;
			}
			StatusEffect effect = GetEffect();
			if (!((Object)(object)effect == (Object)null))
			{
				effect.m_name = StealthConfig.HudLabel.Value;
				if (!s_wasShown)
				{
					effect.m_isNew = true;
					s_wasShown = true;
				}
				effects.Add(effect);
			}
		}

		private static StatusEffect GetEffect()
		{
			if ((Object)(object)s_effect != (Object)null && (Object)(object)s_effect.m_icon != (Object)null)
			{
				return s_effect;
			}
			Sprite val = SneakIcon();
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			if ((Object)(object)s_effect == (Object)null)
			{
				s_effect = ScriptableObject.CreateInstance<StatusEffect>();
				((Object)s_effect).name = "SE_StealthOpportunity";
				Object.DontDestroyOnLoad((Object)(object)s_effect);
			}
			s_effect.m_icon = val;
			s_effect.m_ttl = 0f;
			return s_effect;
		}

		private static Sprite SneakIcon()
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Invalid comparison between Unknown and I4
			Skills val = (((Object)(object)Player.m_localPlayer != (Object)null) ? ((Character)Player.m_localPlayer).GetSkills() : null);
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			foreach (SkillDef skill in val.m_skills)
			{
				if ((int)skill.m_skill == 101)
				{
					return skill.m_icon;
				}
			}
			return null;
		}
	}
	[HarmonyPatch(typeof(Hud), "UpdateStatusEffects")]
	internal static class HudUpdateStatusEffectsPatch
	{
		private static void Prefix(List<StatusEffect> statusEffects)
		{
			StealthHud.AddTo(statusEffects);
		}
	}
	[BepInPlugin("Tchernobill.Valheim_StealthOpportunity", "Stealth Opportunity", "1.0.0")]
	public class StealthPlugin : BaseUnityPlugin
	{
		public const string GUID = "Tchernobill.Valheim_StealthOpportunity";

		public const string NAME = "Stealth Opportunity";

		public const string VERSION = "1.0.0";

		private const float SurveyInterval = 1f;

		private float m_surveyTimer;

		public static ManualLogSource Log { get; private set; }

		private void Awake()
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			Log = ((BaseUnityPlugin)this).Logger;
			StealthConfig.Bind(((BaseUnityPlugin)this).Config);
			new Harmony("Tchernobill.Valheim_StealthOpportunity").PatchAll();
			Log.LogInfo((object)"Stealth Opportunity 1.0.0 ready.");
		}

		private void Update()
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				StealthHud.Opportunity = false;
				return;
			}
			if (!TypingSomewhere())
			{
				KeyboardShortcut value = StealthConfig.TrainKey.Value;
				if (((KeyboardShortcut)(ref value)).IsDown())
				{
					SneakTrainer.Toggle();
				}
				else if (SneakTrainer.Active && OtherActionPressed())
				{
					SneakTrainer.Stop("player action");
				}
			}
			m_surveyTimer -= Time.deltaTime;
			if (m_surveyTimer <= 0f)
			{
				m_surveyTimer = 1f;
				StealthHud.Opportunity = (StealthConfig.Enabled.Value || StealthConfig.ShowHudStatus.Value) && StealthSurvey.Take(localPlayer, checkPaths: true).Opportunity;
			}
		}

		private static bool OtherActionPressed()
		{
			if (!ZInput.GetButtonDown("Use") && !ZInput.GetButtonDown("JoyUse") && !ZInput.GetButtonDown("Hide"))
			{
				return ZInput.GetButtonDown("JoyHide");
			}
			return true;
		}

		private static bool TypingSomewhere()
		{
			if ((!((Object)(object)Chat.instance != (Object)null) || !Chat.instance.HasFocus()) && !Console.IsVisible() && !TextInput.IsVisible() && !Minimap.InTextInput())
			{
				return Menu.IsVisible();
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(TextsDialog), "UpdateTextsList")]
	internal static class TextsDialogUpdateTextsListPatch
	{
		private static readonly FieldInfo TextsField = AccessTools.Field(typeof(TextsDialog), "m_texts");

		private static void Postfix(TextsDialog __instance)
		{
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Expected O, but got Unknown
			if (StealthConfig.Enabled.Value && TextsField?.GetValue(__instance) is List<TextInfo> list)
			{
				string whyNot;
				string text = StealthReport.Build(out whyNot);
				if (StealthConfig.LogReport.Value)
				{
					StealthPlugin.Log.LogInfo((object)(text ?? ("No Stealth Opportunity: " + whyNot + ".")));
				}
				if (text != null)
				{
					list.Insert(Mathf.Min(1, list.Count), new TextInfo(StealthConfig.PageTitle.Value, text));
				}
			}
		}
	}
	internal static class StealthReport
	{
		public static string Build(out string whyNot)
		{
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_011a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0173: Unknown result type (might be due to invalid IL or missing references)
			//IL_029b: Unknown result type (might be due to invalid IL or missing references)
			//IL_030c: Unknown result type (might be due to invalid IL or missing references)
			//IL_02dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0387: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0442: Unknown result type (might be due to invalid IL or missing references)
			//IL_0456: Unknown result type (might be due to invalid IL or missing references)
			//IL_045b: Unknown result type (might be due to invalid IL or missing references)
			whyNot = null;
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				whyNot = "no local player";
				return null;
			}
			StealthSurvey stealthSurvey = StealthSurvey.Take(localPlayer, checkPaths: true);
			if (!stealthSurvey.Opportunity)
			{
				whyNot = ((stealthSurvey.InRange.Count == 0) ? "no enemy in stealth range" : (stealthSurvey.AnyAlerted ? "an enemy in range is alerted" : (stealthSurvey.PathfindingMissing ? "pathfinding not ready" : "an enemy in range has a path to you")));
				return null;
			}
			SneakRate sneakRate = SneakRate.Read(localPlayer, fullRate: true);
			Color value = StealthConfig.TextColor.Value;
			Color value2 = StealthConfig.MutedColor.Value;
			Color value3 = StealthConfig.GoodColor.Value;
			StringBuilder stringBuilder = new StringBuilder(1024);
			stringBuilder.Append(Colored(value, StealthWords.Get("intro"))).Append("\n\n");
			Title(stringBuilder, "watchers");
			foreach (Watcher item in stealthSurvey.InRange)
			{
				stringBuilder.Append(Colored(value, $"  {Localize(item.Character.m_name)}  {item.Distance:0} m"));
				stringBuilder.Append(Colored(value2, "  " + StealthWords.Get("nopath"))).Append('\n');
			}
			stringBuilder.Append('\n');
			Title(stringBuilder, "rate");
			stringBuilder.Append(Colored(value2, StealthWords.Get("tick"))).Append('\n');
			Factor(stringBuilder, "step", sneakRate.Step, null);
			Factor(stringBuilder, "range", sneakRate.RangeFactor, StealthWords.Get("range_note"));
			if (sneakRate.Effects.Count == 0)
			{
				Factor(stringBuilder, "effects", sneakRate.EffectFactor, StealthWords.Get("effects_none"));
			}
			else
			{
				StringBuilder stringBuilder2 = new StringBuilder();
				foreach (SneakRate.Effect effect in sneakRate.Effects)
				{
					if (stringBuilder2.Length > 0)
					{
						stringBuilder2.Append(", ");
					}
					stringBuilder2.Append(effect.Name).Append(' ').Append(Percent(effect.Bonus));
				}
				Factor(stringBuilder, "effects", sneakRate.EffectFactor, stringBuilder2.ToString());
			}
			Factor(stringBuilder, "world", sneakRate.WorldRate, StealthWords.Get("world_note"));
			stringBuilder.Append(Colored(value3, "  = " + StealthWords.Get("gain") + " : " + Number(sneakRate.GainPerTick))).Append('\n');
			if (sneakRate.Maxed)
			{
				stringBuilder.Append(Colored(value2, StealthWords.Get("maxed"))).Append('\n');
			}
			else
			{
				int num = Mathf.FloorToInt(sneakRate.Level);
				stringBuilder.Append(Colored(value, StealthWords.Format("level", num, num + 1, Number(sneakRate.Accumulator), Number(sneakRate.Requirement), Duration(sneakRate.SecondsToLevel)))).Append('\n');
			}
			stringBuilder.Append('\n');
			Title(stringBuilder, "stamina");
			stringBuilder.Append(Colored(value, StealthWords.Format("drain", Number(sneakRate.DrainPerSecond), Number(localPlayer.m_sneakStaminaDrain), Number(sneakRate.StaminaSkillFactor), Percent(sneakRate.StaminaEquipment), Percent(sneakRate.StaminaEffects)))).Append('\n');
			stringBuilder.Append(Colored(value, StealthWords.Format("bar", Mathf.RoundToInt(sneakRate.MaxStamina), Duration(sneakRate.SecondsPerBar)))).Append('\n');
			stringBuilder.Append('\n');
			stringBuilder.Append(Colored(value2, StealthWords.Format("train", ((object)StealthConfig.TrainKey.Value/*cast due to .constrained prefix*/).ToString())));
			return stringBuilder.ToString();
		}

		private static void Title(StringBuilder page, string key)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			page.Append(Colored(StealthConfig.TitleColor.Value, StealthWords.Get(key))).Append('\n');
		}

		private static void Factor(StringBuilder page, string key, float value, string note)
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			Color color = (Mathf.Approximately(value, 1f) ? StealthConfig.TextColor.Value : ((value > 1f) ? StealthConfig.GoodColor.Value : StealthConfig.MutedColor.Value));
			page.Append(Colored(color, "  x" + Number(value) + "  " + StealthWords.Get(key)));
			if (!string.IsNullOrEmpty(note))
			{
				page.Append(Colored(StealthConfig.MutedColor.Value, "  (" + note + ")"));
			}
			page.Append('\n');
		}

		private static string Number(float value)
		{
			return value.ToString((value >= 10f) ? "0.#" : "0.##", CultureInfo.InvariantCulture);
		}

		private static string Percent(float fraction)
		{
			int num = Mathf.RoundToInt(fraction * 100f);
			return ((num >= 0) ? "+" : "") + num + "%";
		}

		private static string Duration(float seconds)
		{
			if (float.IsInfinity(seconds) || float.IsNaN(seconds))
			{
				return "-";
			}
			int num = Mathf.CeilToInt(seconds);
			if (num >= 3600)
			{
				return $"{num / 3600}h{num % 3600 / 60:00}";
			}
			if (num < 60)
			{
				return num + " s";
			}
			return $"{num / 60}:{num % 60:00}";
		}

		private static string Colored(Color color, string text)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			return "<color=#" + ColorUtility.ToHtmlStringRGB(color) + ">" + text + "</color>";
		}

		private static string Localize(string token)
		{
			if (Localization.instance == null)
			{
				return token;
			}
			return Localization.instance.Localize(token);
		}
	}
	internal sealed class Watcher
	{
		public Character Character;

		public float Distance;

		public bool Alerted;

		public bool Flying;

		public bool Reachable;
	}
	internal sealed class StealthSurvey
	{
		public readonly List<Watcher> InRange = new List<Watcher>();

		public bool AnyAlerted;

		public bool AnyReachable;

		public bool PathfindingMissing;

		private const float MinStealthRange = 10f;

		public bool FullRate
		{
			get
			{
				if (InRange.Count > 0)
				{
					return !AnyAlerted;
				}
				return false;
			}
		}

		public bool Opportunity
		{
			get
			{
				if (FullRate && !AnyReachable)
				{
					return !PathfindingMissing;
				}
				return false;
			}
		}

		public static StealthSurvey Take(Player player, bool checkPaths)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			StealthSurvey stealthSurvey = new StealthSurvey();
			Vector3 position = ((Component)player).transform.position;
			foreach (BaseAI baseAIInstance in BaseAI.BaseAIInstances)
			{
				if ((Object)(object)baseAIInstance == (Object)null)
				{
					continue;
				}
				Character component = ((Component)baseAIInstance).GetComponent<Character>();
				if ((Object)(object)component == (Object)null || !BaseAI.IsEnemy((Character)(object)player, component))
				{
					continue;
				}
				float num = Vector3.Distance(position, ((Component)baseAIInstance).transform.position);
				if (!(num < baseAIInstance.m_viewRange) && !(num < 10f))
				{
					continue;
				}
				Watcher watcher = new Watcher
				{
					Character = component,
					Distance = num,
					Alerted = baseAIInstance.IsAlerted(),
					Flying = component.IsFlying()
				};
				if (checkPaths)
				{
					if (watcher.Flying)
					{
						watcher.Reachable = true;
					}
					else if ((Object)(object)Pathfinding.instance == (Object)null)
					{
						stealthSurvey.PathfindingMissing = true;
					}
					else
					{
						watcher.Reachable = Pathfinding.instance.HavePath(((Component)baseAIInstance).transform.position, position, baseAIInstance.m_pathAgentType);
					}
				}
				stealthSurvey.AnyAlerted |= watcher.Alerted;
				stealthSurvey.AnyReachable |= watcher.Reachable;
				stealthSurvey.InRange.Add(watcher);
			}
			stealthSurvey.InRange.Sort((Watcher a, Watcher b) => a.Distance.CompareTo(b.Distance));
			return stealthSurvey;
		}
	}
	internal static class StealthWords
	{
		private static readonly Dictionary<string, string> English = new Dictionary<string, string>
		{
			{ "intro", "Enemies are close, none is alerted and none can walk to you: sneaking here trains at full rate, in safety." },
			{ "watchers", "Enemies in stealth range" },
			{ "nopath", "no path to you" },
			{ "rate", "Sneak experience rate" },
			{ "tick", "One gain per second spent moving while crouched (the timer only runs while you sneak, standing still pauses it)." },
			{ "step", "Sneak skill step" },
			{ "range", "Enemy in stealth range" },
			{ "range_note", "x0.1 with no enemy near, or with one alerted" },
			{ "effects", "Status effects" },
			{ "effects_none", "none of yours changes Sneak gain" },
			{ "world", "World skill gain rate" },
			{ "world_note", "world modifier" },
			{ "gain", "Gain per second of sneaking" },
			{ "level", "Level {0} to {1}: {2} / {3}, about {4} of sneaking" },
			{ "maxed", "Sneak is at 100, it cannot rise any more." },
			{ "stamina", "Stamina while sneaking" },
			{ "drain", "{0}/s: base {1}, skill x{2}, equipment {3}, effects {4}" },
			{ "bar", "A full bar ({0}) lasts {1} of sneaking; no regeneration while you sneak." },
			{ "train", "Train Sneaking: press {0} to walk small circles here until stamina runs low, rest, and start again." },
			{ "train_on", "Train Sneaking: on" },
			{ "train_on_low", "Train Sneaking: on (no enemy in stealth range: x0.1)" },
			{ "train_off", "Train Sneaking: off" },
			{ "train_cant", "Train Sneaking: not possible now" }
		};

		private static readonly Dictionary<string, string> French = new Dictionary<string, string>
		{
			{ "intro", "Des ennemis sont proches, aucun n'est en alerte et aucun ne peut marcher jusqu'a vous : se faufiler ici entraine a plein rythme, sans risque." },
			{ "watchers", "Ennemis a portee de discretion" },
			{ "nopath", "aucun chemin jusqu'a vous" },
			{ "rate", "Rythme de l'experience de Discretion" },
			{ "tick", "Un gain par seconde passee a marcher accroupi (le minuteur ne tourne que pendant la marche accroupie, s'arreter le met en pause)." },
			{ "step", "Pas de la competence Discretion" },
			{ "range", "Ennemi a portee de discretion" },
			{ "range_note", "x0,1 sans ennemi proche, ou si l'un d'eux est en alerte" },
			{ "effects", "Effets en cours" },
			{ "effects_none", "aucun des votres ne change le gain de Discretion" },
			{ "world", "Taux de gain du monde" },
			{ "world_note", "modificateur du monde" },
			{ "gain", "Gain par seconde de marche accroupie" },
			{ "level", "Niveau {0} vers {1} : {2} / {3}, environ {4} de marche accroupie" },
			{ "maxed", "Discretion est a 100, elle ne peut plus monter." },
			{ "stamina", "Endurance en marche accroupie" },
			{ "drain", "{0}/s : base {1}, competence x{2}, equipement {3}, effets {4}" },
			{ "bar", "Une barre pleine ({0}) dure {1} de marche accroupie ; pas de regeneration pendant ce temps." },
			{ "train", "Train Sneaking : {0} fait tourner le personnage en petits cercles ici jusqu'a presque plus d'endurance, puis il attend qu'elle remonte et recommence." },
			{ "train_on", "Train Sneaking : actif" },
			{ "train_on_low", "Train Sneaking : actif (aucun ennemi a portee : x0,1)" },
			{ "train_off", "Train Sneaking : arrete" },
			{ "train_cant", "Train Sneaking : impossible ici" }
		};

		private static bool IsFrench()
		{
			if (Localization.instance != null)
			{
				return Localization.instance.GetSelectedLanguage() == "French";
			}
			return false;
		}

		public static string Get(string key)
		{
			if (!(IsFrench() ? French : English).TryGetValue(key, out var value))
			{
				return key;
			}
			return value;
		}

		public static string Format(string key, params object[] args)
		{
			return string.Format(Get(key), args);
		}
	}
}