Decompiled source of ValheimWeaponClasses Continued v1.0.2

BepInEx/plugins/ValheimWeaponClasses.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using JetBrains.Annotations;
using Jotunn;
using Jotunn.Configs;
using Jotunn.Entities;
using Jotunn.GUI;
using Jotunn.Managers;
using Microsoft.CodeAnalysis;
using ServerSync;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.Rendering;
using UnityEngine.UI;
using ValheimClassObelisk;
using ValheimClassObelisk.Fx;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("ValheimWeaponClasses")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyDescription("Class Obelisk build piece for Valheim")]
[assembly: AssemblyFileVersion("1.0.2.0")]
[assembly: AssemblyInformationalVersion("1.0.2")]
[assembly: AssemblyProduct("ValheimWeaponClasses")]
[assembly: AssemblyTitle("ValheimClassObelisk")]
[assembly: AssemblyVersion("1.0.2.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;
		}
	}
}
public static class ClassHeroArt
{
	private sealed class Tile
	{
		public string ClassName;

		public Button Button;

		public Image Glow;
	}

	private const float HeroAspect = 0.7442748f;

	private const float TileHeroHeight = 244f;

	private const float NameplateWidth = 244f;

	private const float NameplateHeight = 54.9404f;

	private const float NameplateGap = 4f;

	private const float DetailPlateWidth = 330f;

	private static readonly Color GlowGold = new Color(0.95f, 0.75f, 0.35f, 0.85f);

	private static readonly Color Dimmed = new Color(0.62f, 0.62f, 0.62f, 1f);

	private static readonly List<Tile> Tiles = new List<Tile>();

	private static GameObject _gallery;

	private static Text _caption;

	private static GameObject _detail;

	private static Image _detailHero;

	private static Image _detailGlow;

	private static GameObject _detailPlate;

	private static bool _detailPlateActive;

	private static string _shown;

	public static bool ShowingGallery => (Object)(object)_gallery != (Object)null && _gallery.activeSelf;

	public static string Key(string className)
	{
		return "hero_" + (className ?? string.Empty).Replace(" ", string.Empty).ToLowerInvariant();
	}

	private static Sprite Hero(string className, string suffix, Player player)
	{
		string text = (((Object)(object)player != (Object)null && player.GetPlayerModel() == 1) ? "_f" : "_m");
		return UiStone.Art(Key(className) + text + suffix, mipmaps: true) ?? UiStone.Art(Key(className) + suffix, mipmaps: true);
	}

	public static void CreateGallery(Transform panel, Player player, Func<GameObject, string, float, float, float, float, GameObject> nameplate, Action<string> pick)
	{
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		//IL_001b: Expected O, but got Unknown
		//IL_0043: Unknown result type (might be due to invalid IL or missing references)
		//IL_0059: Unknown result type (might be due to invalid IL or missing references)
		//IL_0065: Unknown result type (might be due to invalid IL or missing references)
		//IL_0071: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f1: 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)
		//IL_011c: Unknown result type (might be due to invalid IL or missing references)
		Tiles.Clear();
		_gallery = new GameObject("HeroGallery");
		_gallery.transform.SetParent(panel, false);
		RectTransform val = _gallery.AddComponent<RectTransform>();
		val.anchorMin = new Vector2(0.083f, 0.15f);
		val.anchorMax = new Vector2(0.917f, 0.835f);
		val.offsetMin = Vector2.zero;
		val.offsetMax = Vector2.zero;
		string[] obeliskOrder = UiStone.ObeliskOrder;
		for (int i = 0; i < obeliskOrder.Length; i++)
		{
			Tiles.Add(CreateTile(_gallery.transform, player, obeliskOrder[i], i % 5, i / 5, nameplate, pick));
		}
		GameObject val2 = GUIManager.Instance.CreateText(string.Empty, panel, new Vector2(0.5f, 0.08f), new Vector2(0.5f, 0.08f), new Vector2(0f, 44f), GUIManager.Instance.AveriaSerif, 17, new Color(0.85f, 0.85f, 0.85f, 1f), true, Color.black, 1100f, 50f, false);
		val2.transform.SetParent(_gallery.transform, true);
		_caption = val2.GetComponent<Text>();
		_caption.alignment = (TextAnchor)4;
		_caption.fontStyle = (FontStyle)2;
		_caption.horizontalOverflow = (HorizontalWrapMode)0;
		((Graphic)_caption).raycastTarget = false;
		Refresh(player);
	}

	private static Tile CreateTile(Transform parent, Player player, string className, int column, int row, Func<GameObject, string, float, float, float, float, GameObject> nameplate, Action<string> pick)
	{
		//IL_0026: Unknown result type (might be due to invalid IL or missing references)
		//IL_002c: Expected O, but got Unknown
		//IL_005a: 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_008c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0098: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b9: Expected O, but got Unknown
		//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
		//IL_010e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0125: Unknown result type (might be due to invalid IL or missing references)
		//IL_013c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0165: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c2: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f6: Unknown result type (might be due to invalid IL or missing references)
		//IL_0200: Expected O, but got Unknown
		//IL_025d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0274: Unknown result type (might be due to invalid IL or missing references)
		//IL_028b: Unknown result type (might be due to invalid IL or missing references)
		GameObject val = new GameObject("Cell_" + className);
		val.transform.SetParent(parent, false);
		RectTransform val2 = val.AddComponent<RectTransform>();
		val2.anchorMin = new Vector2((float)column / 5f, (row == 0) ? 0.5f : 0f);
		val2.anchorMax = new Vector2((float)(column + 1) / 5f, (row == 0) ? 1f : 0.5f);
		val2.offsetMin = Vector2.zero;
		val2.offsetMax = Vector2.zero;
		GameObject val3 = new GameObject("Hero_" + className);
		val3.transform.SetParent(val.transform, false);
		RectTransform val4 = val3.AddComponent<RectTransform>();
		val4.anchorMin = new Vector2(0.5f, 1f);
		val4.anchorMax = new Vector2(0.5f, 1f);
		val4.pivot = new Vector2(0.5f, 1f);
		val4.sizeDelta = new Vector2(145.28244f, 244f);
		val4.anchoredPosition = new Vector2(0f, -58.9404f);
		Image val5 = val3.AddComponent<Image>();
		((Graphic)val5).color = new Color(0f, 0f, 0f, 0.01f);
		Image val6 = Picture(val3.transform, "Glow", Hero(className, "_glow", player), 244f);
		Image targetGraphic = Picture(val3.transform, "Hero", Hero(className, string.Empty, player), 244f);
		((Graphic)val6).color = GlowGold;
		Button val7 = val3.AddComponent<Button>();
		((Selectable)val7).targetGraphic = (Graphic)(object)targetGraphic;
		((Selectable)val7).transition = (Transition)1;
		((UnityEvent)val7.onClick).AddListener((UnityAction)delegate
		{
			pick(className);
		});
		val3.AddComponent<ButtonHoverEffect>();
		GameObject val8 = nameplate(val, className, 0f, 0f, 244f, 54.9404f);
		RectTransform val9 = (((Object)(object)val8 != (Object)null) ? val8.GetComponent<RectTransform>() : null);
		if ((Object)(object)val9 != (Object)null)
		{
			val9.anchorMin = new Vector2(0.5f, 1f);
			val9.anchorMax = new Vector2(0.5f, 1f);
			val9.anchoredPosition = new Vector2(0f, -27.4702f);
		}
		return new Tile
		{
			ClassName = className,
			Button = val7,
			Glow = val6
		};
	}

	public static Transform CreateDetail(Transform panel)
	{
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_0010: Expected O, but got Unknown
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_003a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0046: 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_0062: Unknown result type (might be due to invalid IL or missing references)
		//IL_0068: Expected O, but got Unknown
		//IL_0091: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
		_detail = new GameObject("ClassDetail");
		_detail.transform.SetParent(panel, false);
		RectTransform val = _detail.AddComponent<RectTransform>();
		val.anchorMin = Vector2.zero;
		val.anchorMax = Vector2.one;
		val.offsetMin = Vector2.zero;
		val.offsetMax = Vector2.zero;
		GameObject val2 = new GameObject("DetailHero");
		val2.transform.SetParent(_detail.transform, false);
		RectTransform val3 = val2.AddComponent<RectTransform>();
		val3.anchorMin = new Vector2(0.09f, 0.2f);
		val3.anchorMax = new Vector2(0.37f, 0.8f);
		val3.offsetMin = Vector2.zero;
		val3.offsetMax = Vector2.zero;
		_detailGlow = Layer(val2.transform, "Glow");
		_detailHero = Layer(val2.transform, "Hero");
		((Graphic)_detailGlow).color = GlowGold;
		_detail.SetActive(false);
		_shown = null;
		return _detail.transform;
	}

	public static void Refresh(Player player)
	{
		//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
		//IL_0100: Unknown result type (might be due to invalid IL or missing references)
		//IL_010f: 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_011c: Unknown result type (might be due to invalid IL or missing references)
		//IL_012b: 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_015e: Unknown result type (might be due to invalid IL or missing references)
		PlayerClassData playerClassData = (((Object)(object)player != (Object)null) ? PlayerClassManager.GetPlayerData(player) : null);
		if ((Object)(object)_detail != (Object)null && _detail.activeSelf && _shown != null)
		{
			bool flag = playerClassData?.IsClassActive(_shown) ?? false;
			((Behaviour)_detailGlow).enabled = flag && (Object)(object)_detailGlow.sprite != (Object)null;
			if (flag != _detailPlateActive)
			{
				BuildDetailPlate(player);
			}
		}
		if (!ShowingGallery)
		{
			return;
		}
		foreach (Tile tile in Tiles)
		{
			bool flag2 = playerClassData?.IsClassActive(tile.ClassName) ?? false;
			((Behaviour)tile.Glow).enabled = flag2 && (Object)(object)tile.Glow.sprite != (Object)null;
			ColorBlock colors = ((Selectable)tile.Button).colors;
			((ColorBlock)(ref colors)).normalColor = (flag2 ? Color.white : Dimmed);
			((ColorBlock)(ref colors)).highlightedColor = Color.white;
			((ColorBlock)(ref colors)).selectedColor = ((ColorBlock)(ref colors)).normalColor;
			((ColorBlock)(ref colors)).pressedColor = new Color(0.85f, 0.85f, 0.85f, 1f);
			((Selectable)tile.Button).colors = colors;
		}
		if ((Object)(object)_caption != (Object)null)
		{
			_caption.text = "Your active classes glow gold. Pick a hero to read about the class, activate it or open its Perk Upgrades. " + ((playerClassData != null && playerClassData.CanSelectSecondClass()) ? "Up to 2 classes can be active." : "Reach level 50 to unlock a second active class.");
		}
	}

	public static void ShowClass(string className)
	{
		if ((Object)(object)_gallery != (Object)null)
		{
			_gallery.SetActive(false);
		}
		if (!((Object)(object)_detail == (Object)null))
		{
			_detail.SetActive(true);
			if (!(className == _shown))
			{
				_shown = className;
				_detailHero.sprite = Hero(className, string.Empty, Player.m_localPlayer);
				_detailGlow.sprite = Hero(className, "_glow", Player.m_localPlayer);
				((Behaviour)_detailHero).enabled = (Object)(object)_detailHero.sprite != (Object)null;
				BuildDetailPlate(Player.m_localPlayer);
				Refresh(Player.m_localPlayer);
			}
		}
	}

	private static void BuildDetailPlate(Player player)
	{
		//IL_0080: Unknown result type (might be due to invalid IL or missing references)
		//IL_0096: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)_detailPlate != (Object)null)
		{
			Object.Destroy((Object)(object)_detailPlate);
		}
		_detailPlateActive = (((Object)(object)player != (Object)null) ? PlayerClassManager.GetPlayerData(player) : null)?.IsClassActive(_shown) ?? false;
		_detailPlate = UiStone.CreateNameplate(_detail, _shown, player, 0f, 0f, 330f);
		RectTransform component = _detailPlate.GetComponent<RectTransform>();
		component.anchorMin = new Vector2(0.23f, 0.835f);
		component.anchorMax = new Vector2(0.23f, 0.835f);
		component.anchoredPosition = Vector2.zero;
		((Behaviour)_detailPlate.GetComponent<Button>()).enabled = false;
	}

	public static void ShowGallery(Player player)
	{
		if ((Object)(object)_detail != (Object)null)
		{
			_detail.SetActive(false);
		}
		if ((Object)(object)_gallery != (Object)null)
		{
			_gallery.SetActive(true);
		}
		_shown = null;
		Refresh(player);
	}

	public static void Forget()
	{
		Tiles.Clear();
		_gallery = null;
		_caption = null;
		_detail = null;
		_detailHero = null;
		_detailGlow = null;
		_detailPlate = null;
		_shown = null;
	}

	private static Image Layer(Transform parent, string name)
	{
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		//IL_0008: Expected O, but got Unknown
		//IL_001e: Unknown result type (might be due to invalid IL or missing references)
		//IL_002a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0036: Unknown result type (might be due to invalid IL or missing references)
		//IL_0042: Unknown result type (might be due to invalid IL or missing references)
		GameObject val = new GameObject(name);
		val.transform.SetParent(parent, false);
		RectTransform val2 = val.AddComponent<RectTransform>();
		val2.anchorMin = Vector2.zero;
		val2.anchorMax = Vector2.one;
		val2.offsetMin = Vector2.zero;
		val2.offsetMax = Vector2.zero;
		Image val3 = val.AddComponent<Image>();
		val3.preserveAspect = true;
		((Graphic)val3).raycastTarget = false;
		return val3;
	}

	private static Image Picture(Transform parent, string name, Sprite art, float height)
	{
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		//IL_0008: Expected O, but got Unknown
		//IL_0028: Unknown result type (might be due to invalid IL or missing references)
		//IL_003e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0054: Unknown result type (might be due to invalid IL or missing references)
		//IL_0068: Unknown result type (might be due to invalid IL or missing references)
		//IL_0074: Unknown result type (might be due to invalid IL or missing references)
		GameObject val = new GameObject(name);
		val.transform.SetParent(parent, false);
		RectTransform val2 = val.AddComponent<RectTransform>();
		val2.anchorMin = new Vector2(0.5f, 1f);
		val2.anchorMax = new Vector2(0.5f, 1f);
		val2.pivot = new Vector2(0.5f, 1f);
		val2.sizeDelta = new Vector2(height * 0.7442748f, height);
		val2.anchoredPosition = Vector2.zero;
		Image val3 = val.AddComponent<Image>();
		val3.sprite = art;
		val3.preserveAspect = true;
		((Graphic)val3).raycastTarget = false;
		((Behaviour)val3).enabled = (Object)(object)val3.sprite != (Object)null;
		return val3;
	}
}
public enum StarRule
{
	Descending,
	Discount,
	Linear,
	Duration,
	Capped,
	Diminishing,
	Steps
}
public static class ClassStarManager
{
	public sealed class StarPerkDef
	{
		public string PerkId;

		public string ClassName;

		public StarRule Rule;

		public float Cap;

		public int Level;

		public string Group;
	}

	public sealed class StarGroupDef
	{
		public string GroupId;

		public string ClassName;

		public int Level;

		public readonly List<string> Keys = new List<string>();
	}

	public struct RankValue
	{
		public string Column;

		public string Row;

		public string Values;
	}

	private const string ConfigFileName = "andythedemon.valheimweaponclasses_continued.stars.cfg";

	private const int DefaultPointsPerCycle = 1;

	private const int DefaultMaxRank = 3;

	private static readonly Dictionary<string, StarPerkDef> Perks = new Dictionary<string, StarPerkDef>(StringComparer.OrdinalIgnoreCase);

	private static ConfigFile _config;

	private static ConfigEntry<bool> _enabled;

	private static ConfigEntry<int> _pointsPerCycle;

	private static ConfigEntry<int> _maxRank;

	private static bool _seedDone;

	private static readonly Dictionary<string, StarGroupDef> Groups = new Dictionary<string, StarGroupDef>(StringComparer.OrdinalIgnoreCase);

	private static readonly Dictionary<string, string> GroupNames = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

	private static readonly Dictionary<string, string> LabelOverrides = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
	{
		{ "IMPALING_MOMENTUM_BONUS", "Distant damage" },
		{ "IMPALING_MOMENTUM_THROW_DISTANCE", "Range" },
		{ "COMBAT_RHYTHM_DURATION", "Slinger duration" },
		{ "QUICK_CRANK_DURATION", "Crank duration" },
		{ "COLD_STEEL_SHATTER_BONUS", "Shatter damage" },
		{ "COLD_STEEL_SLOW_PER_STACK", "Slow per stack" },
		{ "DARK_EFFICIENCY_EITR_MULT", "Eitr cost" },
		{ "ARCANE_EFFICIENCY_EITR_MULT", "Eitr cost" },
		{ "SPEAR_MASTERY_VELOCITY", "Velocity" },
		{ "SPEAR_STORM_PER_STACK", "Per stack" },
		{ "SPEAR_STORM_DURATION", "Time" },
		{ "STORMPOINT_LIGHTNING_PERCENT", "Lightning" },
		{ "REACH_ADVANTAGE_DAMAGE_BONUS", "Damage" },
		{ "MAX_POISON_STACKS", "Stacks" },
		{ "COLD_SNAP_1H_LIGHT", "1H light splash" },
		{ "COLD_SNAP_1H_HEAVY", "1H heavy splash" },
		{ "COLD_SNAP_2H_LIGHT", "2H light splash" },
		{ "COLD_SNAP_2H_HEAVY", "2H heavy splash" },
		{ "COLD_SNAP_RADIUS", "Splash radius" },
		{ "RIPOSTE_1H_DAMAGE", "1H damage" },
		{ "RIPOSTE_2H_DAMAGE", "2H damage" },
		{ "RIPOSTE_2H_STAGGER", "2H stagger" },
		{ "VERDANT_AURA_HEAL_PER_SEC", "Heal / s" },
		{ "IMMOLATION_AURA_DAMAGE", "Aura damage" },
		{ "SANGUINE_RECLAMATION_HEAL_CAP_PER_SEC", "Heal cap" },
		{ "BLOOD_PACT_DURATION", "Time" },
		{ "ARCHMAGE_AURA_DURATION", "Time" }
	};

	private static readonly HashSet<string> FractionKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "COLD_STEEL_SHATTER_BONUS", "WOODSMANS_CARRY_WEIGHT_REDUCTION", "COLOSSUS_WEIGHT_REDUCTION" };

	private static readonly HashSet<string> PercentPointKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

	private static readonly Dictionary<string, float> IntegerKeyDefaults = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase)
	{
		{ "MAX_POISON_STACKS", 1f },
		{ "SPEAR_STORM_MAX_STACKS", 5f }
	};

	private static readonly string[] LabelSuffixes = new string[19]
	{
		"_DAMAGE_BONUS", "_WEIGHT_REDUCTION", "_STAMINA_REDUCTION", "_DAMAGE_REDUCTION", "_REDUCTION", "_PER_STACK", "_PERCENT", "_DURATION", "_CHANCE", "_DISTANCE",
		"_RADIUS", "_RANGE", "_BONUS", "_VELOCITY", "_STACKS", "_SPEED", "_TTL", "_WINDOW", "_HEAL_PER_SEC"
	};

	private static readonly Dictionary<string, KeyValuePair<string, string>> GridPlace = new Dictionary<string, KeyValuePair<string, string>>(StringComparer.OrdinalIgnoreCase)
	{
		{
			"PRACTICED_AIM_DAMAGE_BONUS",
			new KeyValuePair<string, string>("Damage", "")
		},
		{
			"MAGIC_SHOT_REFUND_CHANCE",
			new KeyValuePair<string, string>("Ammo saved", "")
		},
		{
			"COMBAT_RHYTHM_DURATION",
			new KeyValuePair<string, string>("Arrow Slinger (bow)", "")
		},
		{
			"QUICK_CRANK_DURATION",
			new KeyValuePair<string, string>("Quick Crank (crossbow)", "")
		},
		{
			"STORM_FLETCHING_LIGHTNING_PERCENT",
			new KeyValuePair<string, string>("Target damage taken", "")
		},
		{
			"DEADEYE_DAMAGE_BONUS",
			new KeyValuePair<string, string>("Damage", "")
		},
		{
			"DEADEYE_RANGE_BONUS",
			new KeyValuePair<string, string>("Long-range damage", "")
		},
		{
			"COMBAT_RHYTHM_REDUCTION",
			new KeyValuePair<string, string>("Faster draw / reload", "")
		},
		{
			"CUTTHROAT_DAMAGE_BONUS",
			new KeyValuePair<string, string>("Damage", "")
		},
		{
			"SILENT_HANDS_STAMINA_REDUCTION",
			new KeyValuePair<string, string>("Stamina saved", "")
		},
		{
			"SILENT_HANDS_CROUCH_SPEED_MULT",
			new KeyValuePair<string, string>("Crouch speed", "")
		},
		{
			"ASSASSINATION_DAMAGE_BONUS",
			new KeyValuePair<string, string>("Stealth hit damage", "")
		},
		{
			"ASSASSINATION_SPEED_DURATION",
			new KeyValuePair<string, string>("Speed buff", "")
		},
		{
			"MAX_POISON_STACKS",
			new KeyValuePair<string, string>("Poison stacks", "")
		},
		{
			"TWIST_KNIFE_DAMAGE_BONUS",
			new KeyValuePair<string, string>("Damage vs poisoned", "")
		},
		{
			"TWIST_KNIFE_DAMAGE_REDUCTION",
			new KeyValuePair<string, string>("Damage taken reduced", "")
		},
		{
			"BARE_KNUCKLE_DAMAGE_BONUS",
			new KeyValuePair<string, string>("Damage", "")
		},
		{
			"LIGHT_ON_FEET_STAMINA_REDUCTION",
			new KeyValuePair<string, string>("Stamina saved", "")
		},
		{
			"ONE_TWO_COMBO_DAMAGE_BONUS",
			new KeyValuePair<string, string>("3rd hit damage", "")
		},
		{
			"ONE_TWO_COMBO_STAMINA_RESTORE",
			new KeyValuePair<string, string>("Stamina restored", "")
		},
		{
			"IRON_FIST_SPIRIT_PERCENT",
			new KeyValuePair<string, string>("Spirit damage", "")
		},
		{
			"RAGE_DURATION",
			new KeyValuePair<string, string>("Duration", "")
		},
		{
			"RAGE_DAMAGE_BONUS",
			new KeyValuePair<string, string>("Damage", "")
		},
		{
			"RAGE_ATTACK_SPEED",
			new KeyValuePair<string, string>("Attack speed", "")
		},
		{
			"SHIELD_WALL_BLOCK_POWER",
			new KeyValuePair<string, string>("Block power", "")
		},
		{
			"SHIELD_WALL_STAMINA_REDUCTION",
			new KeyValuePair<string, string>("Block stamina saved", "")
		},
		{
			"PERFECT_GUARD_STAGGER_BONUS",
			new KeyValuePair<string, string>("Next hit stagger", "")
		},
		{
			"PERFECT_GUARD_STAMINA_RESTORE",
			new KeyValuePair<string, string>("Stamina restored", "")
		},
		{
			"THORNS_PIERCE_PERCENT",
			new KeyValuePair<string, string>("Reflected damage", "")
		},
		{
			"REVERB_SHOCKWAVE_DAMAGE",
			new KeyValuePair<string, string>("Shockwave damage", "")
		},
		{
			"REVERB_RANGE",
			new KeyValuePair<string, string>("Range", "")
		},
		{
			"BONEBREAKER_DAMAGE_BONUS",
			new KeyValuePair<string, string>("Damage", "")
		},
		{
			"COLOSSUS_WEIGHT_REDUCTION",
			new KeyValuePair<string, string>("Weight reduced", "")
		},
		{
			"COLD_SNAP_1H_LIGHT",
			new KeyValuePair<string, string>("One-handed splash", "Light")
		},
		{
			"COLD_SNAP_1H_HEAVY",
			new KeyValuePair<string, string>("One-handed splash", "Heavy")
		},
		{
			"COLD_SNAP_2H_LIGHT",
			new KeyValuePair<string, string>("Two-handed splash", "Light")
		},
		{
			"COLD_SNAP_2H_HEAVY",
			new KeyValuePair<string, string>("Two-handed splash", "Heavy")
		},
		{
			"COLD_SNAP_RADIUS",
			new KeyValuePair<string, string>("Splash radius", "")
		},
		{
			"COLD_STEEL_SHATTER_BONUS",
			new KeyValuePair<string, string>("Shatter damage", "")
		},
		{
			"COLD_STEEL_SLOW_PER_STACK",
			new KeyValuePair<string, string>("Slow per stack", "")
		},
		{
			"EARTHSHAKER_DAMAGE_BONUS",
			new KeyValuePair<string, string>("Damage", "")
		},
		{
			"EARTHSHAKER_DURATION",
			new KeyValuePair<string, string>("Speed buff", "")
		},
		{
			"CHOPPERS_TRAINING_DAMAGE_BONUS",
			new KeyValuePair<string, string>("Damage", "")
		},
		{
			"WOODSMANS_CARRY_WEIGHT_REDUCTION",
			new KeyValuePair<string, string>("Weight reduced", "")
		},
		{
			"RENDING_RHYTHM_PER_STACK",
			new KeyValuePair<string, string>("Damage per stack", "")
		},
		{
			"RENDING_RHYTHM_DURATION",
			new KeyValuePair<string, string>("Stack time", "")
		},
		{
			"HEMORRHAGE_PERCENT",
			new KeyValuePair<string, string>("Bleed per stack", "")
		},
		{
			"HEMORRHAGE_DURATION",
			new KeyValuePair<string, string>("Bleed time", "")
		},
		{
			"EXECUTE_DAMAGE_BONUS",
			new KeyValuePair<string, string>("Damage", "")
		},
		{
			"EXECUTE_LOW_HEALTH_BONUS",
			new KeyValuePair<string, string>("Below 40% health", "")
		},
		{
			"REACH_ADVANTAGE_DAMAGE_BONUS",
			new KeyValuePair<string, string>("Damage", "")
		},
		{
			"SPEAR_STORM_PER_STACK",
			new KeyValuePair<string, string>("Per stack", "")
		},
		{
			"SPEAR_STORM_DURATION",
			new KeyValuePair<string, string>("Stack time", "")
		},
		{
			"STORMPOINT_LIGHTNING_PERCENT",
			new KeyValuePair<string, string>("Target damage taken", "")
		},
		{
			"IMPALING_MOMENTUM_BONUS",
			new KeyValuePair<string, string>("Distant damage", "")
		},
		{
			"IMPALING_MOMENTUM_THROW_DISTANCE",
			new KeyValuePair<string, string>("Counts beyond", "")
		},
		{
			"SPEAR_MASTERY_VELOCITY",
			new KeyValuePair<string, string>("Throw speed", "")
		},
		{
			"BLADE_TRAINING_DAMAGE_BONUS",
			new KeyValuePair<string, string>("Damage", "")
		},
		{
			"DUELIST_STAMINA_REDUCTION",
			new KeyValuePair<string, string>("Stamina saved", "")
		},
		{
			"RIPOSTE_DURATION",
			new KeyValuePair<string, string>("Window", "")
		},
		{
			"RIPOSTE_1H_DAMAGE",
			new KeyValuePair<string, string>("One-handed", "Damage")
		},
		{
			"RIPOSTE_2H_DAMAGE",
			new KeyValuePair<string, string>("Two-handed special", "Damage")
		},
		{
			"RIPOSTE_2H_STAGGER",
			new KeyValuePair<string, string>("Two-handed special", "Stagger")
		},
		{
			"SEARING_EDGE_FIRE_PERCENT",
			new KeyValuePair<string, string>("Fire damage", "")
		},
		{
			"DANCING_STEEL_ATTACK_SPEED",
			new KeyValuePair<string, string>("Attack speed", "")
		},
		{
			"DANCING_STEEL_PARRY_DAMAGE",
			new KeyValuePair<string, string>("After parry", "Damage")
		},
		{
			"DANCING_STEEL_PARRY_DURATION",
			new KeyValuePair<string, string>("After parry", "Time")
		},
		{
			"FORBIDDEN_KNOWLEDGE_DAMAGE_BONUS",
			new KeyValuePair<string, string>("Damage", "")
		},
		{
			"BLOOD_FEAST_FOOD_HEALTH_BONUS",
			new KeyValuePair<string, string>("Food health", "")
		},
		{
			"BLOOD_FEAST_REGEN_BONUS",
			new KeyValuePair<string, string>("Health regen", "")
		},
		{
			"BLOOD_PACT_BONUS",
			new KeyValuePair<string, string>("Damage", "")
		},
		{
			"BLOOD_PACT_DURATION",
			new KeyValuePair<string, string>("Duration", "")
		},
		{
			"DARK_EFFICIENCY_EITR_MULT",
			new KeyValuePair<string, string>("Eitr cost", "")
		},
		{
			"SANGUINE_RECLAMATION_DURATION",
			new KeyValuePair<string, string>("Duration", "")
		},
		{
			"SANGUINE_RECLAMATION_LIFESTEAL_PERCENT",
			new KeyValuePair<string, string>("Lifesteal", "")
		},
		{
			"SANGUINE_RECLAMATION_HEAL_CAP_PER_SEC",
			new KeyValuePair<string, string>("Heal cap / s", "")
		},
		{
			"EITR_WEAVE_DAMAGE_BONUS",
			new KeyValuePair<string, string>("Damage", "")
		},
		{
			"ARCANE_NOURISHMENT_FOOD_EITR_BONUS",
			new KeyValuePair<string, string>("Food eitr", "")
		},
		{
			"ARCANE_SURGE_DURATION",
			new KeyValuePair<string, string>("Duration", "")
		},
		{
			"ARCANE_SURGE_REGEN_BONUS",
			new KeyValuePair<string, string>("Eitr regen", "")
		},
		{
			"ARCANE_EFFICIENCY_EITR_MULT",
			new KeyValuePair<string, string>("Eitr cost", "")
		},
		{
			"ARCHMAGE_AURA_DURATION",
			new KeyValuePair<string, string>("Duration", "")
		},
		{
			"FROST_ARMOR_BONUS",
			new KeyValuePair<string, string>("Frost Armor", "")
		},
		{
			"IMMOLATION_AURA_DAMAGE",
			new KeyValuePair<string, string>("Immolation Aura", "Damage")
		},
		{
			"IMMOLATION_AURA_RADIUS",
			new KeyValuePair<string, string>("Immolation Aura", "Radius")
		},
		{
			"STORMSTRIDE_SPEED_BONUS",
			new KeyValuePair<string, string>("Stormstrider", "Speed")
		},
		{
			"STORMSTRIDE_JUMP_BONUS",
			new KeyValuePair<string, string>("Stormstrider", "Jump")
		},
		{
			"VERDANT_AURA_HEAL_PER_SEC",
			new KeyValuePair<string, string>("Verdant Aura", "Heal / s")
		},
		{
			"VERDANT_AURA_RADIUS",
			new KeyValuePair<string, string>("Verdant Aura", "Radius")
		}
	};

	private static readonly string[] GridColumnOrder = new string[11]
	{
		"Duration", "Window", "Damage", "Frost Armor", "Immolation Aura", "Stormstrider", "Verdant Aura", "One-handed splash", "Two-handed splash", "One-handed",
		"Two-handed special"
	};

	public static readonly Dictionary<string, string> ArtOverrides = new Dictionary<string, string> { { "perk_arcane_surge_128", "buff_arcane_surge_128" } };

	public const int ResetCostXp = 1000;

	private static readonly Dictionary<long, Dictionary<string, int>> _serverRanks = new Dictionary<long, Dictionary<string, int>>();

	public static int PointsPerCycle => (_pointsPerCycle == null) ? 1 : _pointsPerCycle.Value;

	public static int MaxRank => (_maxRank != null) ? _maxRank.Value : 3;

	public static bool Enabled => _enabled == null || _enabled.Value;

	public static void Initialize()
	{
		//IL_0021: Unknown result type (might be due to invalid IL or missing references)
		//IL_002b: Expected O, but got Unknown
		try
		{
			if (!_seedDone)
			{
				_config = new ConfigFile(Path.Combine(Paths.ConfigPath, "andythedemon.valheimweaponclasses_continued.stars.cfg"), true);
				_enabled = _config.Bind<bool>("Stars", "Enabled", true, "Enable class star levels. When off, every perk value is exactly its configured base.");
				_pointsPerCycle = _config.Bind<int>("Stars", "PointsPerBar", 1, "Star points granted each time a class fills 1-50 again after reaching 50.");
				_maxRank = _config.Bind<int>("Stars", "MaxRank", 3, "Maximum rank a single perk can reach (1 = baseline rank, so 3 = base, x2, x3).");
				SynchronizationManager.Instance.RegisterCustomConfig(_config);
				StarRegistryGenerated.Apply();
				_seedDone = true;
				int num = Perks.Values.Count((StarPerkDef p) => p.Rule == StarRule.Duration);
				int num2 = Perks.Values.Count((StarPerkDef p) => p.Rule == StarRule.Capped);
				Logger.LogInfo((object)("[Stars] star levels enabled: " + Perks.Count + " rankable perk(s) (" + num + " duration, " + num2 + " capped), " + PointsPerCycle + " point(s) per cycle, max rank " + MaxRank + "."));
			}
		}
		catch (Exception ex)
		{
			Logger.LogError((object)("[Stars] initialize failed: " + ex.Message));
		}
	}

	public static void Register(string perkId, string groupId, string className, StarRule rule, float cap, int level)
	{
		if (!string.IsNullOrEmpty(perkId) && !string.IsNullOrEmpty(className))
		{
			Perks[perkId] = new StarPerkDef
			{
				PerkId = perkId,
				ClassName = className,
				Rule = rule,
				Cap = cap,
				Level = level,
				Group = groupId
			};
			if (!Groups.TryGetValue(groupId, out var value))
			{
				value = new StarGroupDef
				{
					GroupId = groupId,
					ClassName = className,
					Level = level
				};
				Groups[groupId] = value;
			}
			if (level > 0 && (value.Level <= 0 || level < value.Level))
			{
				value.Level = level;
			}
			value.Keys.Add(perkId);
		}
	}

	public static void SetPerkName(string groupId, string displayName, int level)
	{
		if (!string.IsNullOrEmpty(groupId) && !string.IsNullOrEmpty(displayName))
		{
			GroupNames[groupId] = displayName;
			if (!Groups.TryGetValue(groupId, out var value))
			{
				string className = (groupId.Contains("|") ? groupId.Substring(0, groupId.IndexOf('|')) : groupId);
				value = new StarGroupDef
				{
					GroupId = groupId,
					ClassName = className,
					Level = level
				};
				Groups[groupId] = value;
			}
			if (value.Level <= 0)
			{
				value.Level = level;
			}
		}
	}

	public static string GetGroupName(string groupId)
	{
		if (groupId != null && GroupNames.TryGetValue(groupId, out var value))
		{
			return value;
		}
		return groupId;
	}

	public static bool HasRankableValues(string groupId)
	{
		StarGroupDef value;
		return groupId != null && Groups.TryGetValue(groupId, out value) && value.Keys.Count > 0;
	}

	public static IEnumerable<string> RankableGroupsFor(string className)
	{
		return from g in Groups.Values
			where string.Equals(g.ClassName, className, StringComparison.OrdinalIgnoreCase)
			select g.GroupId into k
			orderby k
			select k;
	}

	public static int GetGroupLevel(string groupId)
	{
		StarGroupDef value;
		return (groupId != null && Groups.TryGetValue(groupId, out value)) ? value.Level : 0;
	}

	public static int GetGroupRank(Player player, string className, string groupId)
	{
		if ((Object)(object)player == (Object)null)
		{
			return 0;
		}
		PlayerClassData playerData = PlayerClassManager.GetPlayerData(player);
		if (playerData == null)
		{
			return 0;
		}
		int starRank = playerData.GetStarRank(className, groupId);
		if (starRank > 0)
		{
			return starRank;
		}
		if (IsGroupUnlocked(player, className, groupId))
		{
			playerData.SetStarRank(className, groupId, 1);
			return 1;
		}
		int num = 0;
		if (Groups.TryGetValue(groupId, out var value))
		{
			foreach (string key in value.Keys)
			{
				num = Math.Max(num, playerData.GetStarRank(className, key));
			}
		}
		if (num > 0)
		{
			playerData.SetStarRank(className, groupId, num);
		}
		return num;
	}

	public static bool IsGroupUnlocked(Player player, string className, string groupId)
	{
		int groupLevel = GetGroupLevel(groupId);
		if (groupLevel <= 0)
		{
			return true;
		}
		if ((Object)(object)player == (Object)null)
		{
			return false;
		}
		PlayerClassData playerData = PlayerClassManager.GetPlayerData(player);
		return playerData != null && playerData.GetClassLevel(className) >= groupLevel;
	}

	public static int GetPerkLevel(string perkId)
	{
		StarPerkDef value;
		return (perkId != null && Perks.TryGetValue(perkId, out value)) ? value.Level : 0;
	}

	public static bool IsPerkUnlocked(Player player, string className, string perkId)
	{
		int perkLevel = GetPerkLevel(perkId);
		if (perkLevel <= 0)
		{
			return true;
		}
		if ((Object)(object)player == (Object)null)
		{
			return false;
		}
		PlayerClassData playerData = PlayerClassManager.GetPlayerData(player);
		return playerData != null && playerData.GetClassLevel(className) >= perkLevel;
	}

	public static bool IsRankable(string perkId)
	{
		return perkId != null && Perks.ContainsKey(perkId);
	}

	public static IEnumerable<string> RankablePerksFor(string className)
	{
		return from p in Perks.Values
			where string.Equals(p.ClassName, className, StringComparison.OrdinalIgnoreCase)
			select p.PerkId;
	}

	public static int MultiplierForRank(int rank)
	{
		if (rank <= 1)
		{
			return 1;
		}
		return Math.Min(rank, Math.Max(1, MaxRank));
	}

	private static float DiminishingFactor(int rank)
	{
		return rank switch
		{
			2 => 1.35f, 
			3 => 1.7f, 
			_ => 1f, 
		};
	}

	private static float DurationFactor(int rank)
	{
		return rank switch
		{
			2 => 1.5f, 
			3 => 2f, 
			_ => 1f, 
		};
	}

	public static string DebugScaled(string perkId, float baseValue)
	{
		float num = Scale(perkId, baseValue);
		StarPerkDef value;
		string text = (Perks.TryGetValue(perkId, out value) ? value.Rule.ToString() : "unregistered");
		return perkId + " rule=" + text + " base=" + baseValue.ToString("0.###") + " scaled=" + num.ToString("0.###") + " factor=" + (num / baseValue).ToString("0.###");
	}

	public static float ScaleAtRank(string perkId, float baseValue, int rank)
	{
		if (!Enabled || string.IsNullOrEmpty(perkId))
		{
			return baseValue;
		}
		if (!Perks.TryGetValue(perkId, out var value))
		{
			return baseValue;
		}
		rank = Math.Max(1, Math.Min(rank, MaxRank));
		if (rank <= 1)
		{
			return baseValue;
		}
		float num;
		if (value.Rule == StarRule.Discount)
		{
			num = Mathf.Max(0f, 1f - (1f - baseValue) * (float)rank);
		}
		else if (value.Rule == StarRule.Descending)
		{
			num = baseValue * ((float)(MaxRank - rank + 1) / (float)MaxRank);
		}
		else if (value.Rule == StarRule.Duration)
		{
			num = baseValue * DurationFactor(rank);
		}
		else if (value.Rule == StarRule.Diminishing)
		{
			num = baseValue * DiminishingFactor(rank);
		}
		else if (value.Rule == StarRule.Steps)
		{
			int num2 = Math.Max(1, MaxRank - 1);
			float num3 = ((value.Cap > 0f) ? value.Cap : baseValue);
			num = baseValue + (num3 - baseValue) * (((float)rank - 1f) / (float)num2);
		}
		else
		{
			num = baseValue * (float)MultiplierForRank(rank);
		}
		if (value.Cap > 0f && value.Rule != StarRule.Steps)
		{
			num = Math.Min(num, value.Cap);
		}
		return num;
	}

	public static List<RankValue> DescribeRankList(string group)
	{
		List<RankValue> list = new List<RankValue>();
		foreach (StarPerkDef item in Perks.Values.Where((StarPerkDef p) => string.Equals(p.Group, group, StringComparison.OrdinalIgnoreCase)).OrderBy<StarPerkDef, string>((StarPerkDef p) => p.PerkId, StringComparer.Ordinal))
		{
			float num = PerkConfig.RawFloat(item.PerkId, 0f);
			if (Math.Abs(num) < 1E-05f)
			{
				if (!IntegerKeyDefaults.TryGetValue(item.PerkId, out var value))
				{
					continue;
				}
				num = value;
			}
			string unit = UnitFor(item.PerkId, num);
			string values = Number(ScaleAtRank(item.PerkId, num, 1), unit) + " > " + Number(ScaleAtRank(item.PerkId, num, 2), unit) + " > " + Number(ScaleAtRank(item.PerkId, num, 3), unit);
			if (!GridPlace.TryGetValue(item.PerkId, out var value2))
			{
				value2 = new KeyValuePair<string, string>(LabelFor(item.PerkId), "");
			}
			list.Add(new RankValue
			{
				Column = value2.Key,
				Row = value2.Value,
				Values = values
			});
		}
		return list.OrderBy(delegate(RankValue v)
		{
			int num2 = Array.IndexOf(GridColumnOrder, v.Column);
			return (num2 < 0) ? 100 : num2;
		}).ToList();
	}

	public static string DescribeRanks(string group)
	{
		List<string> list = new List<string>();
		foreach (StarPerkDef item in Perks.Values.Where((StarPerkDef p) => string.Equals(p.Group, group, StringComparison.OrdinalIgnoreCase)).OrderBy<StarPerkDef, string>((StarPerkDef p) => p.PerkId, StringComparer.Ordinal))
		{
			float num = PerkConfig.RawFloat(item.PerkId, 0f);
			if (Math.Abs(num) < 1E-05f)
			{
				if (!IntegerKeyDefaults.TryGetValue(item.PerkId, out var value))
				{
					continue;
				}
				num = value;
			}
			string unit = UnitFor(item.PerkId, num);
			string text = Number(ScaleAtRank(item.PerkId, num, 1), unit);
			string text2 = Number(ScaleAtRank(item.PerkId, num, 2), unit);
			string text3 = Number(ScaleAtRank(item.PerkId, num, 3), unit);
			list.Add(LabelFor(item.PerkId) + " " + text + " > " + text2 + " > " + text3);
		}
		return string.Join("    ", list.ToArray());
	}

	private static string UnitFor(string perkId, float baseValue)
	{
		if (PercentPointKeys.Contains(perkId))
		{
			return "pt";
		}
		if (perkId.EndsWith("_DURATION") || perkId.EndsWith("_TTL") || perkId.EndsWith("_WINDOW"))
		{
			return "s";
		}
		if (perkId.EndsWith("_DISTANCE") || perkId.EndsWith("_RADIUS") || perkId.EndsWith("_RANGE"))
		{
			return "m";
		}
		if (perkId.EndsWith("_STACKS"))
		{
			return string.Empty;
		}
		if (baseValue >= 1f && (perkId.EndsWith("_VELOCITY") || perkId.EndsWith("_MULT") || perkId.EndsWith("_SPEED")))
		{
			return "+";
		}
		if (FractionKeys.Contains(perkId))
		{
			return "%";
		}
		return (baseValue > 0f && baseValue < 1f) ? "%" : string.Empty;
	}

	private static string LabelFor(string perkId)
	{
		if (LabelOverrides.TryGetValue(perkId, out var value))
		{
			return value;
		}
		string text = perkId;
		string[] labelSuffixes = LabelSuffixes;
		foreach (string text2 in labelSuffixes)
		{
			if (text.EndsWith(text2, StringComparison.OrdinalIgnoreCase))
			{
				text = text.Substring(0, text.Length - text2.Length);
				break;
			}
		}
		string[] array = text.Split(new char[1] { '_' }, StringSplitOptions.RemoveEmptyEntries);
		StringBuilder stringBuilder = new StringBuilder();
		string[] array2 = array;
		foreach (string text3 in array2)
		{
			if (stringBuilder.Length > 0)
			{
				stringBuilder.Append(' ');
			}
			stringBuilder.Append(char.ToUpperInvariant(text3[0]));
			stringBuilder.Append(text3.Substring(1).ToLowerInvariant());
		}
		return (stringBuilder.Length == 0) ? perkId : stringBuilder.ToString();
	}

	private static string Number(float value, string unit)
	{
		object obj;
		switch (unit)
		{
		case "%":
			return Mathf.RoundToInt(value * 100f) + "%";
		case "pt":
			return Mathf.RoundToInt(value) + "%";
		case "+":
			return "+" + Mathf.RoundToInt((value - 1f) * 100f) + "%";
		default:
			obj = string.Empty;
			break;
		case "m":
			obj = "m";
			break;
		case "s":
			obj = "s";
			break;
		}
		string text = (string)obj;
		if (Math.Abs(value - Mathf.Round(value)) < 0.05f)
		{
			return Mathf.RoundToInt(value) + text;
		}
		return (Mathf.Round(value * 10f) / 10f).ToString("0.#") + text;
	}

	public static string IconNameForGroup(string group)
	{
		string groupName = GetGroupName(group);
		if (string.IsNullOrEmpty(groupName))
		{
			return null;
		}
		StringBuilder stringBuilder = new StringBuilder();
		string text = groupName;
		foreach (char c in text)
		{
			stringBuilder.Append(char.IsLetterOrDigit(c) ? char.ToLowerInvariant(c) : '_');
		}
		string text2 = Regex.Replace(stringBuilder.ToString(), "_+", "_").Trim(new char[1] { '_' });
		string text3 = "perk_" + text2 + "_128";
		string value;
		return ArtOverrides.TryGetValue(text3, out value) ? value : text3;
	}

	public static float Scale(string perkId, float baseValue)
	{
		if (!Enabled || string.IsNullOrEmpty(perkId))
		{
			return baseValue;
		}
		if (!Perks.TryGetValue(perkId, out var value))
		{
			return baseValue;
		}
		int localRank = GetLocalRank(value.ClassName, value.Group);
		if (localRank <= 1)
		{
			return baseValue;
		}
		float num;
		if (value.Rule == StarRule.Discount)
		{
			num = Mathf.Max(0f, 1f - (1f - baseValue) * (float)localRank);
		}
		else if (value.Rule == StarRule.Descending)
		{
			num = baseValue * ((float)(MaxRank - localRank + 1) / (float)MaxRank);
		}
		else if (value.Rule == StarRule.Duration)
		{
			num = baseValue * DurationFactor(Math.Min(localRank, MaxRank));
		}
		else if (value.Rule == StarRule.Diminishing)
		{
			num = baseValue * DiminishingFactor(localRank);
		}
		else if (value.Rule == StarRule.Steps)
		{
			int num2 = Math.Max(1, MaxRank - 1);
			float num3 = ((value.Cap > 0f) ? value.Cap : baseValue);
			num = baseValue + (num3 - baseValue) * (((float)Math.Min(localRank, MaxRank) - 1f) / (float)num2);
		}
		else
		{
			num = baseValue * (float)MultiplierForRank(localRank);
		}
		if (value.Rule == StarRule.Capped && value.Cap > 0f)
		{
			num = Math.Min(num, value.Cap);
		}
		return num;
	}

	public static int ScaleInt(string perkId, int baseValue)
	{
		if (!Enabled || string.IsNullOrEmpty(perkId))
		{
			return baseValue;
		}
		if (!Perks.TryGetValue(perkId, out var value))
		{
			return baseValue;
		}
		int localRank = GetLocalRank(value.ClassName, value.Group);
		if (localRank <= 1)
		{
			return baseValue;
		}
		int num = baseValue * MultiplierForRank(localRank);
		if (value.Rule == StarRule.Capped && value.Cap > 0f)
		{
			num = Math.Min(num, (int)value.Cap);
		}
		return num;
	}

	private static int GetLocalRank(string className, string groupId)
	{
		try
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return 0;
			}
			PlayerClassData playerData = PlayerClassManager.GetPlayerData(localPlayer);
			if (playerData == null)
			{
				return 0;
			}
			return GetGroupRank(localPlayer, className, groupId);
		}
		catch (Exception)
		{
			return 0;
		}
	}

	public static int GetSpentPoints(PlayerClassData data, string className, out int spent)
	{
		spent = 0;
		if (data == null)
		{
			return 0;
		}
		foreach (string item in RankableGroupsFor(className))
		{
			int starRank = data.GetStarRank(className, item);
			spent += Math.Max(0, starRank - 1);
		}
		return spent;
	}

	public static bool RefundIfOverspent(PlayerClassData data, string className)
	{
		if (data == null)
		{
			return false;
		}
		GetSpentPoints(data, className, out var spent);
		if (spent <= data.GetStarCycles(className) * PointsPerCycle)
		{
			return false;
		}
		foreach (string item in RankableGroupsFor(className))
		{
			if (data.GetStarRank(className, item) > 1)
			{
				data.SetStarRank(className, item, 1);
			}
		}
		return true;
	}

	public static int GetAvailablePoints(PlayerClassData data, string className)
	{
		if (data == null)
		{
			return 0;
		}
		GetSpentPoints(data, className, out var spent);
		int num = data.GetStarCycles(className) * PointsPerCycle;
		return Math.Max(0, num - spent);
	}

	public static int GetAvailablePoints(Player player, string className)
	{
		if ((Object)(object)player == (Object)null)
		{
			return 0;
		}
		return GetAvailablePoints(PlayerClassManager.GetPlayerData(player), className);
	}

	public static int GetRank(Player player, string className, string perkId)
	{
		if ((Object)(object)player == (Object)null)
		{
			return 0;
		}
		return PlayerClassManager.GetPlayerData(player)?.GetStarRank(className, perkId) ?? 0;
	}

	public static IEnumerable<string> RankableClasses()
	{
		return from n in Perks.Values.Select((StarPerkDef p) => p.ClassName).Distinct()
			orderby n
			select n;
	}

	public static bool TrySetRank(Player player, string className, string perkId, int rank, out string message)
	{
		message = null;
		if ((Object)(object)player == (Object)null)
		{
			message = "no player";
			return false;
		}
		if (perkId == null || !Groups.TryGetValue(perkId, out var value))
		{
			message = "'" + perkId + "' is not rankable (not in the registry)";
			return false;
		}
		if (value.Keys.Count == 0)
		{
			message = "'" + GetGroupName(perkId) + "' has no numeric value to rank";
			return false;
		}
		if (!string.Equals(value.ClassName, className, StringComparison.OrdinalIgnoreCase))
		{
			message = "'" + perkId + "' belongs to " + value.ClassName + ", not " + className;
			return false;
		}
		if (rank < 0 || rank > MaxRank)
		{
			message = "rank out of range 0.." + MaxRank;
			return false;
		}
		PlayerClassData playerData = PlayerClassManager.GetPlayerData(player);
		if (playerData == null)
		{
			message = "no class data";
			return false;
		}
		if (!IsGroupUnlocked(player, className, perkId))
		{
			message = "'" + perkId + "' is still undiscovered - it unlocks at class level " + value.Level;
			return false;
		}
		int groupRank = GetGroupRank(player, className, perkId);
		int num = Math.Max(0, rank - 1) - Math.Max(0, groupRank - 1);
		if (num > 0 && num > GetAvailablePoints(playerData, className))
		{
			message = "not enough points: need " + num + ", have " + GetAvailablePoints(playerData, className);
			return false;
		}
		playerData.SetStarRank(className, perkId, rank);
		message = className + "." + perkId + ": " + groupRank + " -> " + rank + " (" + GetAvailablePoints(playerData, className) + " point(s) left)";
		Logger.LogInfo((object)("[Stars] " + message));
		return true;
	}

	public static float GetSpareXp(Player player, string className)
	{
		if ((Object)(object)player == (Object)null)
		{
			return 0f;
		}
		PlayerClassData playerData = PlayerClassManager.GetPlayerData(player);
		if (playerData == null)
		{
			return 0f;
		}
		int classLevel = playerData.GetClassLevel(className);
		float totalXPForLevel = XPCurveHelper.GetTotalXPForLevel(Math.Min(classLevel, 50));
		return Math.Max(0f, playerData.GetClassXP(className) - totalXPForLevel);
	}

	public static bool TryResetClass(Player player, string className, out string message)
	{
		message = null;
		if ((Object)(object)player == (Object)null)
		{
			message = "no player";
			return false;
		}
		PlayerClassData playerData = PlayerClassManager.GetPlayerData(player);
		if (playerData == null)
		{
			message = "no class data";
			return false;
		}
		float spareXp = GetSpareXp(player, className);
		if (spareXp < 1000f)
		{
			message = "reset costs " + 1000 + " XP and only " + (int)spareXp + " is spare";
			return false;
		}
		int num = 0;
		foreach (string item in RankableGroupsFor(className))
		{
			if (GetGroupRank(player, className, item) > 1)
			{
				playerData.SetStarRank(className, item, 1);
				num++;
			}
		}
		if (num == 0)
		{
			message = "nothing to reset";
			return false;
		}
		playerData.classXP[className] = Math.Max(0f, playerData.GetClassXP(className) - 1000f);
		ReportToServer();
		message = "reset " + num + " perk(s) for " + 1000 + " XP (" + (int)GetSpareXp(player, className) + " XP spare, " + GetAvailablePoints(playerData, className) + " point(s) back)";
		Logger.LogInfo((object)("[Stars] " + className + ": " + message));
		return true;
	}

	public static void OnCycleCompleted(string className, int totalCycles)
	{
		try
		{
			Logger.LogInfo((object)("[Stars] " + className + " completed cycle " + totalCycles + " — " + totalCycles * PointsPerCycle + " point(s) earned in total."));
		}
		catch (Exception)
		{
		}
	}

	public static void AcceptRankReport(long sender, long playerId, string payload)
	{
		try
		{
			Dictionary<string, int> serverStore = GetServerStore(playerId);
			int num = 0;
			int num2 = 0;
			if (!string.IsNullOrEmpty(payload))
			{
				string[] array = payload.Split(new char[1] { ';' }, StringSplitOptions.RemoveEmptyEntries);
				string[] array2 = array;
				foreach (string text in array2)
				{
					string[] array3 = text.Split(new char[1] { '|' });
					if (array3.Length != 3)
					{
						num2++;
						continue;
					}
					string text2 = array3[0];
					string text3 = array3[1];
					if (!int.TryParse(array3[2], out var result))
					{
						num2++;
						continue;
					}
					if (!Perks.TryGetValue(text3, out var value) || !string.Equals(value.ClassName, text2, StringComparison.OrdinalIgnoreCase))
					{
						num2++;
						continue;
					}
					int num3 = Math.Max(0, Math.Min(MaxRank, result));
					if (num3 != result)
					{
						num2++;
					}
					serverStore[text2 + "|" + text3] = num3;
					num++;
				}
			}
			Logger.LogInfo((object)("[Stars] rank report from " + sender + " for player " + playerId + ": " + num + " accepted, " + num2 + " clamped/rejected, " + serverStore.Count + " rank(s) on record."));
		}
		catch (Exception ex)
		{
			Logger.LogError((object)("[Stars] rank report failed: " + ex.Message));
		}
	}

	private static Dictionary<string, int> GetServerStore(long playerId)
	{
		if (!_serverRanks.TryGetValue(playerId, out var value))
		{
			value = new Dictionary<string, int>();
			_serverRanks[playerId] = value;
		}
		return value;
	}

	public static string ReportToServer()
	{
		try
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || ZRoutedRpc.instance == null)
			{
				return "no player or no RPC";
			}
			PlayerClassData playerData = PlayerClassManager.GetPlayerData(localPlayer);
			if (playerData == null)
			{
				return "no class data";
			}
			List<string> list = new List<string>();
			foreach (string item in RankableClasses())
			{
				foreach (string item2 in RankableGroupsFor(item))
				{
					int groupRank = GetGroupRank(localPlayer, item, item2);
					if (groupRank > 0)
					{
						list.Add(item + "|" + item2 + "|" + groupRank);
					}
				}
			}
			string text = string.Join(";", list);
			ZRoutedRpc.instance.InvokeRoutedRPC(0L, "CO_SetStarRanks", new object[2]
			{
				localPlayer.GetPlayerID(),
				text
			});
			return "reported " + list.Count + " rank(s)";
		}
		catch (Exception ex)
		{
			return "report failed: " + ex.Message;
		}
	}

	public static string DebugServerRanks()
	{
		Player localPlayer = Player.m_localPlayer;
		if ((Object)(object)localPlayer == (Object)null)
		{
			return "no player";
		}
		Dictionary<string, int> serverStore = GetServerStore(localPlayer.GetPlayerID());
		List<string> list = new List<string>();
		foreach (KeyValuePair<string, int> item in serverStore)
		{
			list.Add(item.Key + "=" + item.Value);
		}
		list.Sort();
		return "server store (" + serverStore.Count + "): " + string.Join("; ", list);
	}

	public static string DebugActivateClass(string className)
	{
		Player localPlayer = Player.m_localPlayer;
		if ((Object)(object)localPlayer == (Object)null)
		{
			return "no player";
		}
		PlayerClassData playerData = PlayerClassManager.GetPlayerData(localPlayer);
		object obj = PlayerClassManager.ActivateClass(localPlayer, className);
		return className + " activation=" + obj?.ToString() + ", active=" + string.Join(",", playerData.activeClasses);
	}

	public static string DebugDeactivateClass(string className)
	{
		Player localPlayer = Player.m_localPlayer;
		if ((Object)(object)localPlayer == (Object)null)
		{
			return "no player";
		}
		PlayerClassData playerData = PlayerClassManager.GetPlayerData(localPlayer);
		PlayerClassManager.DeactivateClass(localPlayer, className);
		return className + " deactivated, active=" + string.Join(",", playerData.activeClasses);
	}

	public static string DebugSetRank(string className, string perkId, int rank)
	{
		string message;
		return (TrySetRank(Player.m_localPlayer, className, perkId, rank, out message) ? "ok: " : "refused: ") + message;
	}

	public static string DebugGrantCycles(string className, int cycles)
	{
		Player localPlayer = Player.m_localPlayer;
		if ((Object)(object)localPlayer == (Object)null)
		{
			return "no player";
		}
		PlayerClassData playerData = PlayerClassManager.GetPlayerData(localPlayer);
		playerData.SetStarCycles(className, Math.Max(0, cycles));
		return className + " cycles = " + playerData.GetStarCycles(className) + ", points available = " + GetAvailablePoints(playerData, className);
	}

	public static string DebugSetLevel(string className, int level)
	{
		Player localPlayer = Player.m_localPlayer;
		if ((Object)(object)localPlayer == (Object)null)
		{
			return "no player";
		}
		PlayerClassData playerData = PlayerClassManager.GetPlayerData(localPlayer);
		int num = Math.Max(0, Math.Min(50, level));
		playerData.classLevels[className] = num;
		float totalXPForLevel = XPCurveHelper.GetTotalXPForLevel(num);
		playerData.classXP[className] = ((totalXPForLevel > 0f) ? (totalXPForLevel - 1f) : 0f);
		return className + " level = " + num + ", xp = " + playerData.classXP[className];
	}

	public static string DebugAddXP(string className, float amount)
	{
		Player localPlayer = Player.m_localPlayer;
		if ((Object)(object)localPlayer == (Object)null)
		{
			return "no player";
		}
		PlayerClassData playerData = PlayerClassManager.GetPlayerData(localPlayer);
		playerData.AddClassXP(className, amount);
		return className + " level = " + playerData.GetClassLevel(className) + ", xp = " + playerData.GetClassXP(className) + ", cycles = " + playerData.GetStarCycles(className) + ", points = " + GetAvailablePoints(playerData, className);
	}

	public static string DebugRoundTrip()
	{
		Player localPlayer = Player.m_localPlayer;
		if ((Object)(object)localPlayer == (Object)null)
		{
			return "no player";
		}
		PlayerClassData playerData = PlayerClassManager.GetPlayerData(localPlayer);
		playerData.PrepareForSerialization();
		string text = JsonUtility.ToJson((object)playerData, false);
		bool flag = text.Contains("_starKeys") && text.Contains("_starRanks");
		PlayerClassData playerClassData = JsonUtility.FromJson<PlayerClassData>(text);
		playerClassData.RestoreFromSerialization();
		return "payload has stars: " + flag + " | round trip rank(Wizard/EITR_WEAVE) = " + playerClassData.GetStarRank("Wizard", "EITR_WEAVE_DAMAGE_BONUS") + ", cycles(Wizard) = " + playerClassData.GetStarCycles("Wizard") + " | json bytes = " + text.Length;
	}

	public static string DebugState()
	{
		Player localPlayer = Player.m_localPlayer;
		if ((Object)(object)localPlayer == (Object)null)
		{
			return "no player";
		}
		PlayerClassData playerData = PlayerClassManager.GetPlayerData(localPlayer);
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.Append("{\"enabled\":").Append(Enabled ? "true" : "false");
		stringBuilder.Append(",\"rankablePerks\":").Append(Perks.Count);
		stringBuilder.Append(",\"pointsPerCycle\":").Append(PointsPerCycle);
		stringBuilder.Append(",\"maxRank\":").Append(MaxRank);
		stringBuilder.Append(",\"classes\":{");
		bool flag = true;
		foreach (string item in RankableClasses())
		{
			if (!flag)
			{
				stringBuilder.Append(',');
			}
			flag = false;
			stringBuilder.Append('"').Append(item).Append("\":{\"cycles\":")
				.Append(playerData.GetStarCycles(item));
			stringBuilder.Append(",\"points\":").Append(GetAvailablePoints(playerData, item));
			stringBuilder.Append(",\"ranks\":{");
			bool flag2 = true;
			foreach (string item2 in RankableGroupsFor(item))
			{
				if (!flag2)
				{
					stringBuilder.Append(',');
				}
				flag2 = false;
				stringBuilder.Append('"').Append(item2).Append("\":")
					.Append(GetGroupRank(localPlayer, item, item2));
			}
			stringBuilder.Append("}}");
		}
		stringBuilder.Append("}}");
		return stringBuilder.ToString();
	}
}
public static class PerkIcons
{
	private static readonly Dictionary<string, string> Map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
	{
		{ "SE_ArrowSlinger", "perk_arrow_slinger_128" },
		{ "SE_QuickCrank", "buff_quick_crank_128" },
		{ "SE_AssassinationSpeed", "perk_assassination_128" },
		{ "SE_AssassinPoison", "perk_venom_coating_128" },
		{ "SE_RiposteReady", "perk_riposte_training_128" },
		{ "SE_Rage", "perk_rage_128" },
		{ "SE_ArcaneSurge", "buff_arcane_surge_128" },
		{ "SE_BloodPact", "perk_blood_pact_128" },
		{ "SE_Bloodwell", "perk_blood_feast_128" },
		{ "SE_BloodFeast", "perk_blood_feast_128" },
		{ "SE_SanguineReclamation", "perk_sanguine_reclamation_128" },
		{ "SE_ElementalAffinity", "perk_eitr_weave_128" },
		{ "SE_ReverbCharged", "perk_reverb_128" },
		{ "SE_ReverbBuildup", "perk_reverb_128" },
		{ "SE_ReverbCooldown", "perk_reverb_128" },
		{ "SE_PerfectGuard", "perk_perfect_guard_128" },
		{ "SE_ThunderingBlows", "perk_thundering_blows_128" },
		{ "SE_Earthshaker", "perk_earthshaker_128" }
	};

	public static Sprite Get(string effectName)
	{
		if (string.IsNullOrEmpty(effectName))
		{
			return null;
		}
		string value;
		return Map.TryGetValue(effectName, out value) ? UiStone.PerkIcon(value) : null;
	}

	public static string DebugAll()
	{
		List<string> list = new List<string>();
		foreach (KeyValuePair<string, string> item in Map)
		{
			Sprite val = UiStone.PerkIcon(item.Value);
			list.Add(item.Key + "->" + item.Value + (((Object)(object)val != (Object)null) ? " ok" : " MISSING"));
		}
		list.Sort();
		return string.Join(" | ", list.ToArray());
	}
}
public class PerkMist : MonoBehaviour
{
	private const float ClearDelay = 0.4f;

	private const float ClearTime = 1.6f;

	private static readonly Color FarTint = new Color(0.59f, 0.64f, 0.73f, 0.3f);

	private static readonly Color NearTint = new Color(0.77f, 0.81f, 0.89f, 0.4f);

	private RawImage _far;

	private RawImage _near;

	private float _phase;

	private float _clock;

	private bool _clearing;

	public static PerkMist Add(Transform row, bool clear)
	{
		//IL_0041: Unknown result type (might be due to invalid IL or missing references)
		//IL_0047: Expected O, but got Unknown
		//IL_005d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0069: Unknown result type (might be due to invalid IL or missing references)
		//IL_007f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0095: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ef: 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_0135: Unknown result type (might be due to invalid IL or missing references)
		Sprite val = UiStone.Art("ui_mist_fog", mipmaps: false);
		if ((Object)(object)row == (Object)null || (Object)(object)val == (Object)null)
		{
			return null;
		}
		((Texture)val.texture).wrapMode = (TextureWrapMode)0;
		GameObject val2 = new GameObject("perk-mist");
		val2.transform.SetParent(row, false);
		RectTransform val3 = val2.AddComponent<RectTransform>();
		val3.anchorMin = Vector2.zero;
		val3.anchorMax = Vector2.one;
		val3.offsetMin = new Vector2(4f, 0f);
		val3.offsetMax = new Vector2(-8f, 0f);
		PerkMist perkMist = val2.AddComponent<PerkMist>();
		perkMist._phase = Random.value * 10f;
		perkMist._far = Layer(val2.transform, (Texture)(object)val.texture, FarTint, new Rect(Mathf.Repeat(perkMist._phase * 0.37f, 1f), 0.3f, 0.7f, 1f));
		perkMist._near = Layer(val2.transform, (Texture)(object)val.texture, NearTint, new Rect(Mathf.Repeat(perkMist._phase * 0.21f, 1f), 0f, 1.2f, 1f));
		perkMist._clearing = clear;
		perkMist.Tick(0f);
		return perkMist;
	}

	private static RawImage Layer(Transform parent, Texture texture, Color tint, Rect uv)
	{
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_000c: Expected O, but got Unknown
		//IL_0022: Unknown result type (might be due to invalid IL or missing references)
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_003a: 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_0061: Unknown result type (might be due to invalid IL or missing references)
		//IL_0069: Unknown result type (might be due to invalid IL or missing references)
		GameObject val = new GameObject("fog");
		val.transform.SetParent(parent, false);
		RectTransform val2 = val.AddComponent<RectTransform>();
		val2.anchorMin = Vector2.zero;
		val2.anchorMax = Vector2.one;
		val2.offsetMin = Vector2.zero;
		val2.offsetMax = Vector2.zero;
		RawImage val3 = (RawImage)(object)val.AddComponent<SoftEdgeRawImage>();
		val3.texture = texture;
		((Graphic)val3).color = tint;
		val3.uvRect = uv;
		((Graphic)val3).raycastTarget = false;
		return val3;
	}

	private void Update()
	{
		Tick(Time.unscaledDeltaTime);
	}

	private void Tick(float dt)
	{
		//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00df: Unknown result type (might be due to invalid IL or missing references)
		_clock += dt;
		float num = 1f;
		float num2 = 1f;
		if (_clearing)
		{
			float num3 = Mathf.Clamp01((_clock - 0.4f) / 1.6f);
			if (num3 >= 1f)
			{
				Object.Destroy((Object)(object)((Component)this).gameObject);
				return;
			}
			num = 1f - num3 * num3 * (3f - 2f * num3);
			num2 = 1f + 5f * num3;
		}
		float num4 = 0.88f + 0.12f * Mathf.Sin((_clock + _phase) * 0.8f);
		Drift(_far, -0.012f * num2 * dt, FarTint, num * num4);
		Drift(_near, 0.022f * num2 * dt, NearTint, num * (2f - num4));
	}

	private static void Drift(RawImage layer, float du, Color tint, float fade)
	{
		//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_0032: 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)
		if (!((Object)(object)layer == (Object)null))
		{
			Rect uvRect = layer.uvRect;
			((Rect)(ref uvRect)).x = Mathf.Repeat(((Rect)(ref uvRect)).x + du, 1f);
			layer.uvRect = uvRect;
			tint.a *= fade;
			((Graphic)layer).color = tint;
		}
	}
}
public class SoftEdgeRawImage : RawImage
{
	public Vector2 Fade = new Vector2(40f, 16f);

	private static readonly float[] Xs = new float[4];

	private static readonly float[] Ys = new float[4];

	protected override void OnPopulateMesh(VertexHelper vh)
	{
		//IL_0009: Unknown result type (might be due to invalid IL or missing references)
		//IL_000e: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f9: 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_0103: Unknown result type (might be due to invalid IL or missing references)
		//IL_0105: Unknown result type (might be due to invalid IL or missing references)
		//IL_0107: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a2: 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_01ab: 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_01b3: Unknown result type (might be due to invalid IL or missing references)
		vh.Clear();
		Rect pixelAdjustedRect = ((Graphic)this).GetPixelAdjustedRect();
		if (((Rect)(ref pixelAdjustedRect)).width <= 0f || ((Rect)(ref pixelAdjustedRect)).height <= 0f)
		{
			return;
		}
		float num = Mathf.Min(Fade.x, ((Rect)(ref pixelAdjustedRect)).width * 0.5f);
		float num2 = Mathf.Min(Fade.y, ((Rect)(ref pixelAdjustedRect)).height * 0.5f);
		Xs[0] = ((Rect)(ref pixelAdjustedRect)).xMin;
		Xs[1] = ((Rect)(ref pixelAdjustedRect)).xMin + num;
		Xs[2] = ((Rect)(ref pixelAdjustedRect)).xMax - num;
		Xs[3] = ((Rect)(ref pixelAdjustedRect)).xMax;
		Ys[0] = ((Rect)(ref pixelAdjustedRect)).yMin;
		Ys[1] = ((Rect)(ref pixelAdjustedRect)).yMin + num2;
		Ys[2] = ((Rect)(ref pixelAdjustedRect)).yMax - num2;
		Ys[3] = ((Rect)(ref pixelAdjustedRect)).yMax;
		Rect uvRect = ((RawImage)this).uvRect;
		Color32 val = Color32.op_Implicit(((Graphic)this).color);
		Color32 val2 = val;
		val2.a = 0;
		Vector2 val3 = default(Vector2);
		for (int i = 0; i < 4; i++)
		{
			for (int j = 0; j < 4; j++)
			{
				bool flag = j == 0 || j == 3 || i == 0 || i == 3;
				((Vector2)(ref val3))..ctor(((Rect)(ref uvRect)).x + ((Rect)(ref uvRect)).width * (Xs[j] - ((Rect)(ref pixelAdjustedRect)).xMin) / ((Rect)(ref pixelAdjustedRect)).width, ((Rect)(ref uvRect)).y + ((Rect)(ref uvRect)).height * (Ys[i] - ((Rect)(ref pixelAdjustedRect)).yMin) / ((Rect)(ref pixelAdjustedRect)).height);
				vh.AddVert(new Vector3(Xs[j], Ys[i]), flag ? val2 : val, Vector4.op_Implicit(val3));
			}
		}
		for (int k = 0; k < 3; k++)
		{
			for (int l = 0; l < 3; l++)
			{
				int num3 = k * 4 + l;
				vh.AddTriangle(num3, num3 + 4, num3 + 5);
				vh.AddTriangle(num3, num3 + 5, num3 + 1);
			}
		}
	}
}
[HarmonyPatch]
public static class ActiveClassesTextsDialogPatch
{
	[HarmonyPatch(typeof(TextsDialog), "UpdateTextsList")]
	[HarmonyPostfix]
	public static void TextsDialog_UpdateTextsList_Postfix(List<TextInfo> ___m_texts)
	{
		//IL_0028: Unknown result type (might be due to invalid IL or missing references)
		//IL_0032: Expected O, but got Unknown
		try
		{
			if (!((Object)(object)Player.m_localPlayer == (Object)null))
			{
				string text = PlayerClassManager.BuildActiveClassesCompendiumText(Player.m_localPlayer);
				___m_texts.Insert(0, new TextInfo("Active Classes", text));
			}
		}
		catch (Exception arg)
		{
			Logger.LogError((object)$"Error adding Active Classes compendium entry: {arg}");
		}
	}
}
public static class ArcherPerkManager
{
	public const float MAGIC_SHOT_REFUND_CHANCE = 0.15f;

	public const float PRACTICED_AIM_DAMAGE_BONUS = 0.07f;

	public const float DEADEYE_DAMAGE_BONUS = 0.15f;

	private const string Intro = "Expert marksmen with disciplined bow technique and practical crossbow mastery.";

	private const string Outro = "Best for players who want ranged weapons to feel steady early, distinct by weapon type mid-game, and deadly at mastery.";

	public static readonly List<PerkInfo> Perks = new List<PerkInfo>
	{
		new PerkInfo
		{
			RequiredLevel = 10,
			Name = "Practiced Aim",
			Description = "+7% bow and crossbow damage."
		},
		new PerkInfo
		{
			RequiredLevel = 20,
			Name = "Magic Shot",
			Description = "15% chance to not consume arrows or bolts."
		},
		new PerkInfo
		{
			RequiredLevel = 30,
			Name = "Arrow Slinger",
			Description = "Bow hits grant Arrow Slinger for 6s, reducing draw time by 25%. Crossbow hits grant Quick Crank for 10s, reducing reload time by 25%."
		},
		new PerkInfo
		{
			RequiredLevel = 40,
			Name = "Storm Fletching",
			Description = "Bow and crossbow hits Shock the target for 5s: it takes 12% more damage from all sources. Each hit refreshes the shock and builds up to 3 stacks of visible charge."
		},
		new PerkInfo
		{
			RequiredLevel = 50,
			Name = "Deadeye",
			Description = "+15% bow and crossbow damage. Hits beyond 25m gain an additional +10% damage."
		}
	};

	public const float COMBAT_RHYTHM_DURATION = 6f;

	public const float COMBAT_RHYTHM_REDUCTION = 0.25f;

	private static readonly Dictionary<Player, float> bowBuffExpire = new Dictionary<Player, float>();

	private static readonly Dictionary<Player, float> crossbowBuffExpire = new Dictionary<Player, float>();

	public const float STORM_FLETCHING_LIGHTNING_PERCENT = 0.12f;

	public const float DEADEYE_RANGE_BONUS = 0.1f;

	public const float DEADEYE_RANGE_THRESHOLD = 25f;

	public static bool HasArcherPerk(Player player, int requiredLevel)
	{
		if ((Object)(object)player == (Object)null)
		{
			return false;
		}
		PlayerClassData playerData = PlayerClassManager.GetPlayerData(player);
		if (playerData == null || !playerData.IsClassActive(PlayerClass.Archer))
		{
			return false;
		}
		return playerData.GetClassLevel(PlayerClass.Archer) >= requiredLevel;
	}

	public static string GetClassDescription(Player player)
	{
		int currentLevel = PlayerClassManager.GetPlayerData(player)?.GetClassLevel(PlayerClass.Archer) ?? 0;
		return PerkDescriptionBuilder.Build("Expert marksmen with disciplined bow technique and practical crossbow mastery.", Perks, "Best for players who want ranged weapons to feel steady early, distinct by weapon type mid-game, and deadly at mastery.", currentLevel);
	}

	public static void TriggerArrowSlinger(Player player)
	{
		if (!HasArrowSlingerActive(player))
		{
			ArcherFx.Slinger(player, crossbow: false);
		}
		bowBuffExpire[player] = Time.time + PerkConfig.COMBAT_RHYTHM_DURATION_v;
		ApplyCombatRhythmStatusEffect(player, "SE_ArrowSlinger", "Arrow Slinger", "Draw speed increased by 25%", PerkConfig.COMBAT_RHYTHM_DURATION_v);
		((Character)player).Message((MessageType)1, $"Arrow Slinger: 25% faster draw for {PerkConfig.COMBAT_RHYTHM_DURATION_v:F0}s!", 0, (Sprite)null, false);
	}

	public static void TriggerQuickCrank(Player player)
	{
		if (!HasQuickCrankActive(player))
		{
			ArcherFx.Slinger(player, crossbow: true);
		}
		crossbowBuffExpire[player] = Time.time + PerkConfig.QUICK_CRANK_DURATION_v;
		ApplyCombatRhythmStatusEffect(player, "SE_QuickCrank", "Quick Crank", "Reload speed increased by 25%", PerkConfig.QUICK_CRANK_DURATION_v);
		((Character)player).Message((MessageType)1, $"Quick Crank: 25% faster reload for {PerkConfig.QUICK_CRANK_DURATION_v:F0}s!", 0, (Sprite)null, false);
	}

	private static void ApplyCombatRhythmStatusEffect(Player player, string effectName, string displayName, string tooltip, float seconds)
	{
		//IL_0077: Unknown result type (might be due to invalid IL or missing references)
		//IL_0089: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			SEMan sEMan = ((Character)player).GetSEMan();
			if (sEMan != null)
			{
				sEMan.RemoveStatusEffect(StringExtensionMethods.GetStableHashCode(effectName), true);
				SE_Stats val = ScriptableObject.CreateInstance<SE_Stats>();
				((Object)val).name = effectName;
				((StatusEffect)val).m_name = displayName;
				((StatusEffect)val).m_tooltip = tooltip;
				ItemData currentWeapon = ((Humanoid)player).GetCurrentWeapon();
				((StatusEffect)val).m_icon = PerkIcons.Get(effectName) ?? ((currentWeapon != null) ? currentWeapon.GetIcon() : null);
				((StatusEffect)val).m_ttl = seconds;
				((StatusEffect)val).m_startMessage = "";
				((StatusEffect)val).m_startMessageType = (MessageType)2;
				((StatusEffect)val).m_stopMessage = "";
				((StatusEffect)val).m_stopMessageType = (MessageType)2;
				sEMan.AddStatusEffect((StatusEffect)(object)val, true, 0, 0f, (short)(-1));
			}
		}
		catch (Exception ex)
		{
			Logger.LogError((object)("Error adding Combat Rhythm status effect (" + effectName + "): " + ex.Message));
		}
	}

	public static bool HasArrowSlingerActive(Player player)
	{
		if ((Object)(object)player != (Object)null && bowBuffExpire.TryGetValue(player, out var value))
		{
			return Time.time < value;
		}
		return false;
	}

	public static bool HasQuickCrankActive(Player player)
	{
		if ((Object)(object)player != (Object)null && crossbowBuffExpire.TryGetValue(player, out var value))
		{
			return Time.time < value;
		}
		return false;
	}

	public static void UpdateBuffs()
	{
		float currentTime = Time.time;
		foreach (Player item in bowBuffExpire.Where(delegate(KeyValuePair<Player, float> kvp)
		{
			KeyValuePair<Player, float> keyValuePair = kvp;
			int num2;
			if (!((Object)(object)keyValuePair.Key == (Object)null))
			{
				float num = currentTime;
				keyValuePair = kvp;
				num2 = ((num >= keyValuePair.Value) ? 1 : 0);
			}
			else
			{
				num2 = 1;
			}
			return (byte)num2 != 0;
		}).Select(delegate(KeyValuePair<Player, float> kvp)
		{
			KeyValuePair<Player, float> keyValuePair = kvp;
			return keyValuePair.Key;
		}).ToList())
		{
			bowBuffExpire.Remove(item);
		}
		foreach (Player item2 in crossbowBuffExpire.Where(delegate(KeyValuePair<Player, float> kvp)
		{
			KeyValuePair<Player, float> keyValuePair = kvp;
			int num2;
			if (!((Object)(object)keyValuePair.Key == (Object)null))
			{
				float num = currentTime;
				keyValuePair = kvp;
				num2 = ((num >= keyValuePair.Value) ? 1 : 0);
			}
			else
			{
				num2 = 1;
			}
			return (byte)num2 != 0;
		}).Select(delegate(KeyValuePair<Player, float> kvp)
		{
			KeyValuePair<Player, float> keyValuePair = kvp;
			return keyValuePair.Key;
		}).ToList())
		{
			crossbowBuffExpire.Remove(item2);
		}
	}

	public static void ApplyStormFletchingLightning(ref HitData hit, float weaponDamage)
	{
		ArcherElectrified.TrackHit(ref hit, weaponDamage);
	}
}
[HarmonyPatch]
public static class ArcherPerkPatches
{
	private static float lastCleanupTime;

	[HarmonyPatch(typeof(Character), "Damage")]
	[HarmonyPrefix]
	public static void Character_Damage_Archer_Prefix(Character __instance, ref HitData hit)
	{
		//IL_0004: Unknown result type (might be due to invalid IL or missing references)
		//IL_000a: Invalid comparison between Unknown and I4
		//IL_002a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0030: Expected O, but got Unknown
		//IL_0098: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
		//IL_00db: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			if ((int)hit.m_skill == 0)
			{
				return;
			}
			Character attacker = hit.GetAttacker();
			Player val = (Player)((attacker is Player) ? attacker : null);
			if ((Object)(object)val != (Object)null && !((Object)(object)__instance == (Object)null) && !(__instance is Player) && ClassCombatManager.IsBowWeapon(((Humanoid)val).GetCurrentWeapon()) && ArcherPerkManager.HasArcherPerk(val, 1))
			{
				float totalDamage = hit.GetTotalDamage();
				if (ArcherPerkManager.HasArcherPerk(val, 40))
				{
					ArcherPerkManager.ApplyStormFletchingLightning(ref hit, totalDamage);
					ShockedDebuff.AddStack(__instance, PerkConfig.STORM_FLETCHING_LIGHTNING_PERCENT_v);
					ArcherFx.StormHit(Fxh.Front(hit));
				}
				if (ArcherPerkManager.HasArcherPerk(val, 50) && Vector3.Distance(((Component)val).transform.position, hit.m_point) > PerkConfig.DEADEYE_RANGE_THRESHOLD_v)
				{
					ArcherFx.Deadeye(Fxh.Front(hit));
					float num = 1f + PerkConfig.DEADEYE_RANGE_BONUS_v;
					hit.m_damage.m_damage *= num;
					hit.m_damage.m_blunt *= num;
					hit.m_damage.m_slash *= num;
					hit.m_damage.m_pierce *= num;
					hit.m_damage.m_chop *= num;
					hit.m_damage.m_fire *= num;
					hit.m_damage.m_frost *= num;
					hit.m_damage.m_lightning *= num;
					hit.m_damage.m_poison *= num;
					hit.m_damage.m_spirit *= num;
				}
			}
		}
		catch (Exception ex)
		{
			Logger.LogError((object)("Error in Character_Damage_Archer_Prefix: " + ex.Message));
		}
	}

	[HarmonyPatch(typeof(Character), "Damage")]
	[HarmonyPostfix]
	public static void Character_Damage_Archer_Postfix(Character __instance, HitData hit)
	{
		//IL_0003: Unknown result type (might be due to invalid IL or missing references)
		//IL_0009: Invalid comparison between Unknown and I4
		//IL_0028: Unknown result type (might be due to invalid IL or missing references)
		//IL_002e: Expected O, but got Unknown
		//IL_008b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0092: Invalid comparison between Unknown and I4
		try
		{
			if ((int)hit.m_skill == 0)
			{
				return;
			}
			Character attacker = hit.GetAttacker();
			Player val = (Player)((attacker is Player) ? attacker : null);
			if ((Object)(object)val == (Object)null || (Object)(object)__instance == (Object)null || __instance is Player || hit.GetTotalDamage() <= 0f)
			{
				return;
			}
			ItemData currentWeapon = ((Humanoid)val).GetCurrentWeapon();
			if (ClassCombatManager.IsBowWeapon(currentWeapon) && ArcherPerkManager.HasArcherPerk(val, 30))
			{
				if ((int)currentWeapon.m_shared.m_skillType == 14)
				{
					ArcherPerkManager.TriggerQuickCrank(val);
				}
				else
				{
					ArcherPerkManager.TriggerArrowSlinger(val);
				}
			}
		}
		catch (Exception ex)
		{
			Logger.LogError((object)("Error in Character_Damage_Archer_Postfix: " + ex.Message));
		}
	}

	[HarmonyPatch(typeof(Humanoid), "GetAttackDrawPercentage")]
	[HarmonyPostfix]
	public static void Humanoid_GetAttackDrawPercentage_Archer_Postfix(Humanoid __instance, ref float __result)
	{
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0022: Expected O, but got Unknown
		try
		{
			if (!(__result <= 0f))
			{
				Player val = (Player)((__instance is Player) ? __instance : null);
				if ((Object)(object)val != (Object)null && ArcherPerkManager.HasArrowSlingerActive(val))
				{
					__result = Mathf.Clamp01(__result / Mathf.Max(0.05f, 1f - PerkConfig.COMBAT_RHYTHM_REDUCTION_v));
				}
			}
		}
		catch (Exception ex)
		{
			Logger.LogError((object)("Error in Humanoid_GetAttackDrawPercentage_Archer_Postfix: " + ex.Message));
		}
	}

	[HarmonyPatch(typeof(Player), "UpdateActionQueue")]
	[HarmonyPrefix]
	public static void Player_UpdateActionQueue_Archer_Prefix(Player __instance, ref float dt, List<MinorActionData> ___m_actionQueue)
	{
		//IL_0019: Unknown result type (might be due to invalid IL or missing references)
		//IL_001f: Invalid comparison between Unknown and I4
		try
		{
			if (___m_actionQueue.Count != 0)
			{
				MinorActionData val = ___m_actionQueue[0];
				if ((int)val.m_type == 2 && ArcherPerkManager.HasQuickCrankActive(__instance))
				{
					dt /= Mathf.Max(0.05f, 1f - PerkConfig.COMBAT_RHYTHM_REDUCTION_v);
					_ = val.m_time;
				}
			}
		}
		catch (Exception ex)
		{
			Logger.LogError((object)("Error in Player_UpdateActionQueue_Archer_Prefix: " + ex.Message));
		}
	}

	[HarmonyPatch(typeof(Inventory), "RemoveItem", new Type[]
	{
		typeof(ItemData),
		typeof(int)
	})]
	[HarmonyPrefix]
	public static void Inventory_RemoveItem_MagicShot_PreFix(Inventory __instance, ItemData item, int amount)
	{
		try
		{
			if (item == null || !IsArrowItem(item))
			{
				return;
			}
			Player val = FindPlayerWithInventory(__instance);
			if ((Object)(object)val == (Object)null || !ClassCombatManager.IsBowWeapon(((Humanoid)val).GetCurrentWeapon()) || !ArcherPerkManager.HasArcherPerk(val, 20) || !(Random.Range(0f, 1f) < PerkConfig.Float("MAGIC_SHOT_REFUND_CHANCE", 0.15f)))
			{
				return;
			}
			List<ItemData> list = (from i in __instance.GetAllItems()
				where i.m_shared.m_name == item.m_shared.m_name
				orderby i.m_stack
				select i).ToList();
			if (list.Count > 0 && amount == 1)
			{
				ItemData val2 = list.First();
				int num = val2.m_shared.m_maxStackSize - val2.m_stack;
				if (num > 0)
				{
				}
			}
		}
		catch (Exception ex)
		{
			Logger.LogError((object)("Error in Inventory_RemoveItem_MagicShot_PreFix: " + ex.Message));
		}
	}

	private static bool IsArrowItem(ItemData item)
	{
		//IL_001f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0026: Invalid comparison between Unknown and I4
		if (item?.m_shared == null)
		{
			return false;
		}
		if ((int)item.m_shared.m_itemType != 9 && !item.m_shared.m_name.ToLower().Contains("arrow"))
		{
			return item.m_shared.m_name.ToLower().Contains("bolt");
		}
		return true;
	}

	private static Player FindPlayerWithInventory(Inventory inventory)
	{
		return ((IEnumerable<Player>)Player.GetAllPlayers()).FirstOrDefault((Func<Player, bool>)((Player p) => ((Humanoid)p).GetInventory() == inventory));
	}

	[HarmonyPatch(typeof(Game), "Update")]
	[HarmonyPostfix]
	public static void Game_Update_Archer_Postfix()
	{
		try
		{
			if (Time.time - lastCleanupTime >= 1f)
			{
				lastCleanupTime = Time.time;
				ArcherPerkManager.UpdateBuffs();
			}
		}
		catch (Exception ex)
		{
			Logger.LogError((object)("Error in Game_Update_Archer_Postfix: " + ex.Message));
		}
	}
}
[HarmonyPatch]
public static class AssassinPerkManager
{
	public class PoisonData
	{
		public int stacks;

		public float expireTime;

		public float pendingTotalDamage;

		public bool damagePending;

		public Player source;
	}

	public const float CUTTHROAT_DAMAGE_BONUS = 0.07f;

	public static Dictionary<Character, PoisonData> poisonTracking = new Dictionary<Character, PoisonData>();

	private static Dictionary<Player, float> assassinationBuffs = new Dictionary<Player, float>();

	private static readonly Dictionary<Player, float> _cachedMoveSpeed = new Dictionary<Player, float>();

	public const int MAX_POISON_STACKS = 3;

	public const float ASSASSINATION_SPEED_DURATION = 5f;

	public const float ASSASSINATION_SPEED_BONUS = 1.2f;

	public const float ASSASSINATION_DAMAGE_BONUS = 0.5f;

	public const float SILENT_HANDS_STAMINA_REDUCTION = 0.15f;

	private const float VANILLA_BASE_CROUCH_SPEED = 2f;

	private const float SILENT_HANDS_CROUCH_SPEED_MULT = 1.3f;

	public const float TWIST_KNIFE_DAMAGE_BONUS = 0.15f;

	public const float TWIST_KNIFE_DAMAGE_REDUCTION = 0.1f;

	public static bool DAMAGE_MODS_ON = true;

	private const string Intro = "Stealthy fighters who use knives, positioning, and toxins to end fights quickly.";

	private const string Outro = "Best for players who want the class to build from light knife mastery into stealth burst and poison execution.";

	public static readonly List<PerkInfo> Perks = new List<PerkInfo>
	{
		new PerkInfo
		{
			RequiredLevel = 10,
			Name = "Cutthroat",
			Description = "+7% knife damage."
		},
		new PerkInfo
		{
			RequiredLevel = 20,
			Name = "Silent Hands",
			Description = "Knife attacks and sneaking consume 15% less stamina, and you move 30% faster than normal crouch speed while crouched (still slower than walking)."
		},
		new PerkInfo
		{
			RequiredLevel = 30,
			Name = "Assassination",
			Description = "First knife hit from stealth deals +50% damage. After a stealth hit, knife attack speed is increased by 20% for 5s."
		},
		new PerkInfo
		{
			RequiredLevel = 40,
			Name = "Venom Coating",
			Description = "Knife hits apply poison damage over time; further hits refresh it. Poison damage scales with knife skill."
		},
		new PerkInfo
		{
			RequiredLevel = 50,
			Name = "Twist the Knife",
			Description = "+15% knife damage against poisoned targets. Poisoned enemies deal 10% less damage to you."
		}
	};

	private static Player assassinSneakDiscountFor;

	private static int assassinSneakDiscountFrame = -1;

	public static bool HasAssassinPerk(Player player, int requiredLevel)
	{
		if ((Object)(object)player == (Object)null)
		{
			return false;
		}
		PlayerClassData playerData = PlayerClassManager.GetPlayerData(player);
		if (playerData == null || !playerData.IsClassActive(PlayerClass.Assassin))
		{
			return false;
		}
		return playerData.GetClassLevel(PlayerClass.Assassin) >= requiredLevel;
	}

	public static string GetClassDescription(Player player)
	{
		int currentLevel = PlayerClassManager.GetPlayerData(player)?.GetClassLevel(PlayerClass.Assassin) ?? 0;
		return PerkDescriptionBuilder.Build("Stealthy fighters who use knives, positioning, and toxins to end fights quickly.", Perks, "Best for players who want the class to build from light knife mastery into stealth burst and poison execution.", currentLevel);
	}

	public static string DebugSetCrouch(bool on)
	{
		Player localPlayer = Player.m_localPlayer;
		if ((Object)(object)localPlayer == (Object)null)
		{
			return "no local player";
		}
		((Character)localPlayer).SetCrouch(on);
		return "crouching=" + ((Character)localPlayer).IsCrouching() + " crouchSpeed=" + ((Character)localPlayer).m_crouchSpeed.ToString("0.###");
	}

	public static string DebugStealth()
	{
		Player localPlayer = Player.m_localPlayer;
		if ((Object)(object)localPlayer == (Object)null)
		{
			return "no local player";
		}
		float walkSpeed = ((Character)localPlayer).m_walkSpeed;
		float crouchSpeed = ((Character)localPlayer).m_crouchSpeed;
		float num = PerkConfig.VANILLA_BASE_CROUCH_SPEED_v * PerkConfig.SILENT_HANDS_CROUCH_SPEED_MULT_v;
		float value;
		float num2 = ((_cachedMoveSpeed.TryGetValue(localPlayer, out value) && value > 0.01f) ? value : ((Character)localPlayer).m_speed);
		float num3 = ((num2 > 0.01f) ? (num2 * 0.9f) : num);
		return "walk=" + walkSpeed.ToString("0.###") + " run=" + ((Character)localPlayer).m_runSpeed.ToString("0.###") + " crouch=" + crouchSpeed.ToString("0.###") + " speed=" + ((Character)localPlayer).m_speed.ToString("0.###") + " desired=" + num.ToString("0.###") + " cap90=" + num3.ToString("0.###") + " perkActive=" + HasAssassinPerk(localPlayer, 20) + " crouching=" + ((Character)localPlayer).IsCrouching();
	}

	private static void RefreshSilentHandsCrouchSpeed(Player player)
	{
		if (!((Object)(object)player == (Object)null))
		{
			float value;
			float num = (_cachedMoveSpeed.TryGetValue(player, out value) ? value : ((Character)player).m_speed);
			float value2;
			float num2 = ((_cachedMoveSpeed.TryGetValue(player, out value2) && value2 > 0.01f) ? value2 : ((Character)player).m_speed);
			float num3 = ((num2 > 0.01f) ? (num2 * 0.95f) : 2f);
			if (HasAssassinPerk(player, 20))
			{
				float num4 = PerkConfig.VANILLA_BASE_CROUCH_SPEED_v * PerkConfig.SILENT_HANDS_CROUCH_SPEED_MULT_v;
				((Character)player).m_crouchSpeed = Mathf.Min(num4, num3);
			}
			else
			{
				((Character)player).m_crouchSpeed = 2f;
			}
		}
	}

	private static void ApplyAssassinationSpeedBuff(Player player)
	{
		float value = Time.time + PerkConfig.ASSASSINATION_SPEED_DURATION_v;
		assassinationBuffs[player] = value;
		AnimationSpeedManager.Set((Character)(object)player, "Assassin_Knife_AS", PerkConfig.ASSASSINATION_SPEED_BONUS_v);
		Logger.LogInfo((object)("[Assassin] speed buff x" + PerkConfig.ASSASSINATION_SPEED_BONUS_v.ToString("0.00") + " for " + PerkConfig.ASSASSINATION_SPEED_DURATION_v.ToString("0.#") + "s"));
		AddAssassinationStatusEffect(player);
	}

	private static void AddAssassinationStatusEffect(Player player)
	{
		try
		{
			SEMan sEMan = ((Character)player).GetSEMan();
			if (sEMan != null)
			{
				sEMan.RemoveStatusEffect(StringExtensionMethods.GetStableHashCode("SE_AssassinationSpeed"), true);
				SE_Stats val = ScriptableObject.CreateInstance<SE_Stats>();
				((Object)val).name = "SE_AssassinationSpeed";
				((StatusEffect)val).m_name = "Assassination";
				((StatusEffect)val).m_tooltip = "+" + Mathf.RoundToInt((PerkConfig.ASSASSINATION_SPEED_BONUS_v - 1f) * 100f) + "% knife attack speed";
				ItemData currentWeapon = ((Humanoid)player).GetCurrentWeapon();
				((StatusEffect)val).m_icon = PerkIcons.Get("SE_AssassinationSpeed") ?? ((currentWeapon != null) ? currentWeapon.GetIcon() : null);
				((StatusEffect)val).m_ttl = PerkConfig.ASSASSINATION_SPEED_DURATION_v;
				sEMan.AddStatusEffect((StatusEffect)(object)val, true, 0, 0f, (short)(-1));
			}
		}
		catch (Exception ex)
		{
			Logger.LogError((object)("Error adding assassination status effect: " + ex.Message));
		}
	}

	public static bool IsStealthHit(Player attacker, Character target)
	{
		if ((Object)(object)attacker == (Object)null || (Object)(object)target == (Object)null)
		{
			return false;
		}
		BaseAI baseAI = target.GetBaseAI();
		if ((Object)(object)baseAI == (Object)null)
		{
			return false;
		}
		if (baseAI.IsAlerted())
		{
			return false;
		}
		if (baseAI.HaveTarget())
		{
			return false;
		}
		return true;
	}

	private static float EstimateVanillaPoisonTTL(float totalDamage)
	{
		return 2f + Mathf.Pow(totalDamage * 2f, 0.5f);
	}

	public static void ApplyLv40_VenomCoating(Player player, Character target, HitData hit)
	{
		//IL_0177: Unknown result type (might be due to invalid IL or missing references)
		//IL_0183: 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)
		Logger.LogInfo((object)("[Venom] hit " + target.m_name + " stacks=" + (poisonTracking.ContainsKey(target) ? poisonTracking[target].stacks : 0)));
		if (HasAssassinPerk(player, 40) && !((Object)(object)target == (Object)null) && !target.IsDead())
		{
			float skillLevel = ((Character)player).GetSkillLevel((SkillType)2);
			if (!poisonTracking.TryGetValue(target, out var value) || Time.time >= value.expireTime)
			{
				value = new PoisonData();
				poisonTracking[target] = value;
			}
			int num = Mathf.Clamp(ClassStarManager.GetRank(player, "Assassin", "Assassin|40"), 1, 3);
			int num2 = Mathf.Min(num, PerkConfig.MAX_POISON_STACKS_v);
			if (value.stacks < num2)
			{
				value.stacks++;
			}
			float num3 = Mathf.Max(hit.GetTotalDamage() * PerkConfig.Float("VENOM_COATING_POISON_PERCENT", 0.15f), skillLevel);
			float num4 = num3 * (float)value.stacks;
			float num5 = EstimateVanillaPoisonTTL(num4);
			value.pendingTotalDamage = num4;
			value.damagePending = true;
			value.expireTime = Time.time + num5;
			value.source = player;
			AddPoisonStatusEffect(target, value.stacks, num5);
			StatusVisuals.Venom(target, num5, value.stacks, StringExtensionMethods.GetStableHashCode("SE_AssassinPoison"));
			AssassinFx.VenomHit(Fxh.Front(hit), Fxh.Dir(hit, ((Component)player).transform.forward));
			Logger.LogInfo((object)("[Venom] applied stacks=" + value.stacks + " ttl=" + num5.ToString("0.0") + " total=" + num4));
		}
	}

	private static void AddPoisonStatusEffect(Character target, int stacks, float ttl)
	{
		try
		{
			SEMan sEMan = target.GetSEMan();
			if (sEMan != null)
			{
				sEMan.RemoveStatusEffect(StringExtensionMethods.GetStableHashCode("SE_AssassinPoison"), true);
				SE_Stats val = ScriptableObject.CreateInstance<SE_Stats>();
				((Object)val).name = "SE_AssassinPoison";
				((StatusEffect)val).m_name = $"Venom ({stacks} stacks)";
				((StatusEffect)val).m_tooltip = $"Taking poison damage over time. {stacks}/{PerkConfig.MAX_POISON_STACKS_v} stacks";
				Sprite val2 = UiStone.PerkIcon("perk_venom_" + Mathf.Clamp(stacks, 1, 3) + "_128") ?? GetPoisonIcon();
				if ((Object)(object)val2 != (Object)null)
				{
					((StatusEffect)val).m_icon = val2;
				}
				((StatusEffect)val).m_ttl = ttl;
				sEMan.AddStatusEffect((StatusEffect)(object)val, true, 0, 0f, (short)(-1));
			}
		}
		catch (Exception ex)
		{
			Logger.LogError((object)("Error adding poison status effect: " + ex.Message));
		}
	}

	private static Sprite GetPoisonIcon()
	{
		try
		{
			ObjectDB instance = ObjectDB.instance;
			return (((Object)(object)instance != (Object)null) ? instance.GetStatusEffect(StringExtensionMethods.GetStableHashCode("Poison")) : null)?.m_icon;
		}
		catch
		{
			return null;
		}
	}

	public static void ProcessPoisonExpiry()
	{
		//IL_0082: Unknown result type (might be due to invalid IL or missing references)
		//IL_0089: Expected O, but got Unknown
		//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cd: 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)
		float time = Time.time;
		List<Character> list = new List<Character>();
		foreach (KeyValuePair<Character, PoisonData> item in poisonTracking)
		{
			Character key = item.Key;
			PoisonData value = item.Value;
			if ((Object)(object)key == (Object)null || key.IsDead() || time > value.expireTime)
			{
				list.Add(key);
			}
			else if (value.damagePending)
			{
				value.damagePending = false;
				HitData val = new HitData();
				val.m_damage.m_poison = value.pendingTotalDamage;
				Player source = value.source;
				val.m_attacker = (((Object)(object)source != (Object)null) ? ((Character)source).GetZDOID() : ZDOID.None);
				val.m_point = ((Component)key).transform.position;
				VanillaFxSuppressor.Mark(key, Element.Poison);
				key.Damage(val);
			}
		}
		foreach (Character item2 in list)
		{
			if ((Object)(object)item2 != (Object)null)
			{
				SEMan sEMan = item2.GetSEMan();
				if (sEMan != null)
				{
					sEMan.RemoveStatusEffect(StringExtensionMethods.GetStableHashCode("SE_AssassinPoison"), true);
				}
			}
			poisonTracking.Remove(item2);
		}
	}

	private static bool IsTargetPoisoned(Character target)
	{
		if ((Object)(object)target != (Object)null && poisonTracking.TryGetValue(target, out var value))
		{
			return Time.time < value.expireTime;
		}
		return false;
	}

	public static HitData ModDamage(HitData hit, float mod)
	{
		if (hit == null)
		{
			return hit;
		}
		hit.m_damage.m_damage *= mod;
		hit.m_damage.m_slash *= mod;
		hit.m_damage.m_pierce *= mod;
		hit.m_damage.m_blunt *= mod;
		hit.m_damage.m_fire *= mod;
		hit.m_damage.m_frost *= mod;
		hit.m_damage.m_spirit *= mod;
		hit.m_damage.m_poison *= mod;
		return hit;
	}

	public static void CleanupBuffs()
	{
		float currentTime = Time.time;
		foreach (Player item in assassinationBuffs.Where(delegate(KeyValuePair<Player, float> kvp)
		{
			KeyValuePair<Player, float> keyValuePair = kvp;
			return keyValuePair.Value < currentTime;
		}).Select(delegate(KeyValuePair<Player, float> kvp)
		{
			KeyValuePair<Player, float> keyValuePair = kvp;
			return keyValuePair.Key;
		}).ToList())
		{
			assassinationBuffs.Remove(item);
			if ((Object)(object)item != (Object)null)
			{
				SEMan sEMan = ((Character)item).GetSEMan();
				if (sEMan != null)
				{
					sEMan.RemoveStatusEffect(StringExtensionMethods.GetStableHashCode("SE_AssassinationSpeed"), true);
				}
				AnimationSpeedManager.Clear((Character)(object)item, "Assassin_Knife_AS");
			}
		}
	}

	[HarmonyPatch(typeof(Character), "Damage")]
	[HarmonyPrefix]
	public static void Character_Damage_Assassin_Prefix(Character __instance, ref HitData hit)
	{
		//IL_0033: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: Expected O, but got Unknown
		//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
		//IL_0151: Unknown result type (might be due to invalid IL or missing references)
		//IL_015e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0163: Unknown result type (might be due to invalid IL or missing references)
		//IL_019c: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			if (hit.m_damage.m_poison > 0f)
			{
				return;
			}
			Character attacker = hit.GetAttacker();
			Player val = (Player)((attacker is Player) ? attacker : null);
			if ((Object)(object)val == (Object)null || (Object)(object)__instance == (Object)null || (object)__instance == Player.m_localPlayer)
			{
				Character attacker2 = hit.GetAttacker();
				PlayerClassData playerData = PlayerClassManager.GetPlayerData(Player.m_localPlayer);
				if (playerData != null && playerData.IsClassActive(PlayerClass.Assassin) && HasAssassinPerk(Player.m_localPlayer, 50) && IsTargetPoisoned(attacker2))
				{
					hit = ModDamage(hit, 1f - PerkConfig.TWIST_KNIFE_DAMAGE_REDUCTION_v);
				}
			}
			else
			{
				if ((int)hit.m_skill == 0 || !ClassCombatManager.IsKnifeWeapon(((Humanoid)val).GetCurrentWeapon()))
				{
					return;
				}
				PlayerClassData playerData2 = PlayerClassManager.GetPlayerData(val);
				if (playerData2 != null && playerData2.IsClassActive(PlayerClass.Assassin))
				{
					float num = 0f;
					if (HasAssassinPerk(val, 30) && (IsStealthHit(val, __instance) || (((Character)val).IsSneaking() && (Object)(object)__instance.GetBaseAI() == (Object)null)))
					{
						num += PerkConfig.ASSASSINATION_DAMAGE_BONUS_v;
						ApplyAssassinationSpeedBuff(val);
						AssassinFx.StealthStrike(Fxh.Front(hit), Fxh.Dir(hit, ((Component)val).transform.forward));
						AssassinFx.ShadowStep(val);
					}
					if (HasAssassinPerk(val, 50) && IsTargetPoisoned(__instance))
					{
						num += PerkConfig.TWIST_KNIFE_DAMAGE_BONUS_v;
						AssassinFx.Twist(Fxh.Front(hit));
					}
					if (num > 0f && DAMAGE_MODS_ON)
					{
						hit = ModDamage(hit, num + 1f);
					}
				}
			}
		}
		catch (Exception ex)
		{
			Logger.LogError((object)("Error in Character_Damage_Assassin_Prefix: " + ex.Message));
		}
	}

	[HarmonyPatch(typeof(Character), "Damage")]
	[HarmonyPostfix]
	public static void Character_Damage_Assassin_Postfix(Character __instance, HitData hit)
	{
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		//IL_0037: Expected O, but got Unknown
		try
		{
			if (hit.m_damage.m_poison > 0f)
			{
				return;
			}
			Character attacker = hit.GetAttacker();
			Player val = (Player)((attacker is Player) ? attacker : null);
			if ((Object)(object)val != (Object)null && !((Object)(object)__instance == (Object)null) && !(__instance is Player) && !(hit.GetTotalDamage() <= 0f) && ClassCombatManager.IsKnifeWeapon(((Humanoid)val).GetCurrentWeapon()))
			{
				PlayerClassData playerData = PlayerClassManager.GetPlayerData(val);
				if (playerData != null && playerData.IsClassActive(PlayerClass.Assassin) && (hit.m_damage.m_blunt != 0f || hit.m_damage.m_slash != 0f || hit.m_damage.m_pierce != 0f))
				{
					ApplyLv40_VenomCoating(val, __instance, hit);
				}
			}
		}
		catch (Exception ex)
		{
			Logger.LogError((object)("Error in Character_Damage_Assassin_Postfix: " + ex.Message));
		}
	}

	[HarmonyPatch(typeof(Player), "UseStamina")]
	[HarmonyPrefix]
	public static void Player_UseStamina_Assassin_Prefix(Player __instance, ref float v)
	{
		try
		{
			if ((Object)(object)assassinSneakDiscountFor != (Object)null)
			{
				bool flag = (Object)(object)assassinSneakDiscountFor == (Object)(object)__instance && assassinSneakDiscountFrame == Time.frameCount;
				assassinSneakDiscountFor = null;
				if (flag)
				{
					v *= 1f - PerkConfig.SILENT_HANDS_STAMINA_REDUCTION_v;
				}
			}
		}
		catch (Exception ex)
		{
			Logger.LogError((object)("Error in Player_UseStamina_Assassin_Prefix: " + ex.Message));
		}
	}

	[HarmonyPatch(typeof(Player), "OnSneaking")]
	[HarmonyPrefix]
	public static void Player_OnSneaking_Assassin_Prefix(Player __instance)
	{
		try
		{
			if (HasAssassinPerk(__instance, 20) && !((Character)__instance).IsBlocking())
			{
				assassinSneakDiscountFor = __instance;
				assassinSneakDiscountFrame = Time.frameCount;
			}
		}
		catch (Exception ex)
		{
			Logger.LogError((object)("Error in Player_OnSneaking_Assassin_Prefix: " + ex.Message));
		}
	}

	[HarmonyPatch(typeof(Attack), "GetAttackStamina")]
	[HarmonyPostfix]
	public static void Attack_GetAttackStamina_Assassin_Postfix(Attack __instance, ref float __result)
	{
		try
		{
			Character value = Traverse.Create((object)__instance).Field("m_character").GetValue<Character>();
			Player val = (Player)(object)((value is Player) ? value : null);
			if ((Object)(object)val != (Object)null && __result > 0f && HasAssassinPerk(val, 20) && ClassCombatManager.IsKnifeWeapon(Traverse.Create((object)__instance).Field("m_weapon").GetValue<ItemData>()))
			{
				__result *= 1f - PerkConfig.SILENT_HANDS_STAMINA_REDUCTION_v