Decompiled source of SkillLoss v1.0.0

plugins/TXC.SkillLoss.dll

Decompiled 2 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8.1", FrameworkDisplayName = ".NET Framework 4.8.1")]
[assembly: AssemblyCompany("TXC.SkillLoss")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("TXC.SkillLoss")]
[assembly: AssemblyTitle("TXC.SkillLoss")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace TXC.SkillLoss
{
	[BepInPlugin("txc.skillloss", "TXC Skill Loss", "1.0.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	[SynchronizationMode(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "txc.skillloss";

		public const string PluginName = "TXC Skill Loss";

		public const string PluginVersion = "1.0.0";

		internal const float VanillaNormalLoss = 5f;

		private const string ModdedSection = "Modded Skills";

		private const string ModdedDefaultKey = "Default";

		private static readonly Regex ModdedKeyPattern = new Regex("\\((-?\\d+)\\)\\s*$");

		private static readonly (SkillType Skill, string Category, string Key)[] VanillaSkills = new(SkillType, string, string)[24]
		{
			((SkillType)1, "Combat", "Swords"),
			((SkillType)2, "Combat", "Knives"),
			((SkillType)3, "Combat", "Clubs"),
			((SkillType)4, "Combat", "Polearms"),
			((SkillType)5, "Combat", "Spears"),
			((SkillType)6, "Combat", "Blocking"),
			((SkillType)7, "Combat", "Axes"),
			((SkillType)8, "Combat", "Bows"),
			((SkillType)14, "Combat", "Crossbows"),
			((SkillType)9, "Combat", "Elemental Magic"),
			((SkillType)10, "Combat", "Blood Magic"),
			((SkillType)11, "Combat", "Unarmed"),
			((SkillType)12, "Utility", "Pickaxes"),
			((SkillType)13, "Utility", "Woodcutting"),
			((SkillType)104, "Utility", "Fishing"),
			((SkillType)105, "Crafting", "Cooking"),
			((SkillType)107, "Crafting", "Crafting"),
			((SkillType)106, "Crafting", "Farming"),
			((SkillType)100, "Movement", "Jump"),
			((SkillType)102, "Movement", "Run"),
			((SkillType)101, "Movement", "Sneak"),
			((SkillType)103, "Movement", "Swim"),
			((SkillType)110, "Movement", "Ride"),
			((SkillType)108, "Movement", "Dodge")
		};

		internal static ManualLogSource Log;

		internal static ConfigEntry<bool> UseCategories;

		internal static ConfigEntry<bool> ApplyWorldDeathPenalty;

		internal static ConfigEntry<float> ModdedDefault;

		private static readonly Dictionary<string, ConfigEntry<float>> CategoryLoss = new Dictionary<string, ConfigEntry<float>>();

		private static readonly Dictionary<SkillType, (string Category, ConfigEntry<float> Entry)> VanillaLoss = new Dictionary<SkillType, (string, ConfigEntry<float>)>();

		private static readonly Dictionary<int, ConfigEntry<float>> ModdedLoss = new Dictionary<int, ConfigEntry<float>>();

		private static Plugin _instance;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_014e: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d0: Expected O, but got Unknown
			_instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			UseCategories = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "UseCategories", false, AdminOnly("true = vanilla skills use the [Categories] values; false = each skill uses its own value."));
			ApplyWorldDeathPenalty = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ApplyWorldDeathPenalty", false, AdminOnly("true = multiply every loss by the world's 'Death penalty' modifier relative to Normal (Normal x1, Easy x0.5, Hard x1.5, ...); false = the values here are final."));
			foreach (string item5 in VanillaSkills.Select(((SkillType Skill, string Category, string Key) x) => x.Category).Distinct())
			{
				CategoryLoss[item5] = ((BaseUnityPlugin)this).Config.Bind<float>("Categories", item5, 5f, AdminOnly("% of each " + item5.ToLowerInvariant() + " skill lost on death (0-100). Only used when UseCategories = true.", percent: true));
			}
			(SkillType, string, string)[] vanillaSkills = VanillaSkills;
			for (int num = 0; num < vanillaSkills.Length; num++)
			{
				(SkillType, string, string) tuple = vanillaSkills[num];
				SkillType item = tuple.Item1;
				string item2 = tuple.Item2;
				string item3 = tuple.Item3;
				ConfigEntry<float> item4 = ((BaseUnityPlugin)this).Config.Bind<float>(item2 + " Skills", item3, 5f, AdminOnly("% of " + item3 + " lost on death (0-100). Used when UseCategories = false.", percent: true));
				VanillaLoss[item] = (item2, item4);
			}
			ModdedDefault = ((BaseUnityPlugin)this).Config.Bind<float>("Modded Skills", "Default", 5f, AdminOnly("% lost on death (0-100) for skills added by other mods that have no entry of their own. Entries named 'Skill Name (id)' are added here automatically when a player loads a character.", percent: true));
			foreach (string item6 in ReadModdedKeysFromFile())
			{
				BindModded(item6);
			}
			_harmony = new Harmony("txc.skillloss");
			_harmony.PatchAll(typeof(Patches));
			Log.LogInfo((object)string.Format("{0} {1} loaded ({2} modded skill entries).", "TXC Skill Loss", "1.0.0", ModdedLoss.Count));
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		private static ConfigDescription AdminOnly(string description, bool percent = false)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Expected O, but got Unknown
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Expected O, but got Unknown
			return new ConfigDescription(description, (AcceptableValueBase)(object)(percent ? new AcceptableValueRange<float>(0f, 100f) : null), new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true
			} });
		}

		internal static float GetLossPercent(SkillType skill)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Expected I4, but got Unknown
			if (VanillaLoss.TryGetValue(skill, out (string, ConfigEntry<float>) value))
			{
				if (!UseCategories.Value)
				{
					return value.Item2.Value;
				}
				return CategoryLoss[value.Item1].Value;
			}
			if (!ModdedLoss.TryGetValue((int)skill, out var value2))
			{
				return ModdedDefault.Value;
			}
			return value2.Value;
		}

		internal static bool IsVanilla(SkillType skill)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			return VanillaLoss.ContainsKey(skill);
		}

		internal static void DiscoverModdedSkill(SkillType skill)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Expected I4, but got Unknown
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Invalid comparison between Unknown and I4
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			int num = (int)skill;
			if ((int)skill != 0 && (int)skill != 999 && !IsVanilla(skill) && !ModdedLoss.ContainsKey(num))
			{
				ConfigEntry<float> val = BindModded($"{SkillDisplayName(skill)} ({num})");
				if (val != null)
				{
					Log.LogInfo((object)$"Added config entry for modded skill '{((ConfigEntryBase)val).Definition.Key}' ({val.Value}%).");
				}
			}
		}

		private static ConfigEntry<float> BindModded(string key)
		{
			Match match = ModdedKeyPattern.Match(key);
			if (!match.Success || !int.TryParse(match.Groups[1].Value, out var result) || ModdedLoss.ContainsKey(result))
			{
				return null;
			}
			ConfigEntry<float> val = ((BaseUnityPlugin)_instance).Config.Bind<float>("Modded Skills", key, ModdedDefault.Value, AdminOnly($"% of this modded skill (id {result}) lost on death (0-100).", percent: true));
			ModdedLoss[result] = val;
			return val;
		}

		private unsafe static string SkillDisplayName(SkillType skill)
		{
			Localization instance = Localization.instance;
			string text = ((instance != null) ? instance.Localize("$skill_" + ((object)(*(SkillType*)(&skill))/*cast due to .constrained prefix*/).ToString().ToLowerInvariant()) : null) ?? string.Empty;
			if (string.IsNullOrWhiteSpace(text) || text.StartsWith("["))
			{
				text = "Skill";
			}
			text = Regex.Replace(text, "[=\\n\\t\\\\\"'\\[\\]()]", string.Empty).Trim();
			if (text.Length != 0)
			{
				return text;
			}
			return "Skill";
		}

		private IEnumerable<string> ReadModdedKeysFromFile()
		{
			if (!File.Exists(((BaseUnityPlugin)this).Config.ConfigFilePath))
			{
				yield break;
			}
			bool inSection = false;
			string[] array = File.ReadAllLines(((BaseUnityPlugin)this).Config.ConfigFilePath);
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				if (text.StartsWith("[") && text.EndsWith("]"))
				{
					inSection = text.Substring(1, text.Length - 2).Trim() == "Modded Skills";
				}
				else
				{
					if (!inSection || text.Length == 0 || text.StartsWith("#"))
					{
						continue;
					}
					int num = text.IndexOf('=');
					if (num > 0)
					{
						string text2 = text.Substring(0, num).Trim();
						if (text2 != "Default")
						{
							yield return text2;
						}
					}
				}
			}
		}
	}
	internal static class Patches
	{
		private static readonly FieldRef<Skills, Dictionary<SkillType, Skill>> SkillData = AccessTools.FieldRefAccess<Skills, Dictionary<SkillType, Skill>>("m_skillData");

		private static readonly FieldRef<Player, Skills> PlayerSkills = AccessTools.FieldRefAccess<Player, Skills>("m_skills");

		private static bool _loggedWorldMultiplier;

		[HarmonyPatch(typeof(Skills), "OnDeath")]
		[HarmonyPrefix]
		[HarmonyPriority(800)]
		private static bool SkillsOnDeath(Skills __instance, ref Dictionary<SkillType, Skill> __state)
		{
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a8: 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)
			__state = null;
			try
			{
				float num = 1f;
				if (Plugin.ApplyWorldDeathPenalty.Value)
				{
					float num2 = __instance.m_DeathLowerFactor * Game.m_skillReductionRate;
					num = num2 * 100f / 5f;
					if (!_loggedWorldMultiplier)
					{
						_loggedWorldMultiplier = true;
						Plugin.Log.LogInfo((object)$"World death penalty: vanilla loss {num2 * 100f:0.##}% -> multiplier x{num:0.##}.");
					}
				}
				bool flag = false;
				foreach (KeyValuePair<SkillType, Skill> item in SkillData.Invoke(__instance))
				{
					float num3 = Math.Min(1f, Math.Max(0f, Plugin.GetLossPercent(item.Key) / 100f * num));
					if (!(num3 <= 0f))
					{
						Skill value = item.Value;
						value.m_level -= value.m_level * num3;
						value.m_accumulator = 0f;
						flag = true;
					}
				}
				if (flag)
				{
					Player localPlayer = Player.m_localPlayer;
					if (localPlayer != null)
					{
						((Character)localPlayer).Message((MessageType)1, "$msg_skills_lowered", 0, (Sprite)null, false);
					}
				}
				Dictionary<SkillType, Skill> dictionary = SkillData.Invoke(__instance);
				__state = dictionary.Where((KeyValuePair<SkillType, Skill> x) => !Plugin.IsVanilla(x.Key)).ToDictionary((KeyValuePair<SkillType, Skill> x) => x.Key, (KeyValuePair<SkillType, Skill> x) => x.Value);
				foreach (SkillType key in __state.Keys)
				{
					dictionary.Remove(key);
				}
				return false;
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Skill loss failed, falling back to vanilla: {arg}");
				return true;
			}
		}

		[HarmonyPatch(typeof(Skills), "OnDeath")]
		[HarmonyFinalizer]
		private static void SkillsOnDeathFinalizer(Skills __instance, Dictionary<SkillType, Skill> __state)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			if (__state == null)
			{
				return;
			}
			Dictionary<SkillType, Skill> dictionary = SkillData.Invoke(__instance);
			foreach (KeyValuePair<SkillType, Skill> item in __state)
			{
				dictionary[item.Key] = item.Value;
			}
		}

		[HarmonyPatch(typeof(Player), "OnSpawned")]
		[HarmonyPostfix]
		private static void PlayerOnSpawned(Player __instance)
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer)
			{
				return;
			}
			try
			{
				Skills val = PlayerSkills.Invoke(__instance);
				if ((Object)(object)val == (Object)null)
				{
					return;
				}
				HashSet<SkillType> hashSet = new HashSet<SkillType>(SkillData.Invoke(val).Keys);
				foreach (SkillDef skill in val.m_skills)
				{
					hashSet.Add(skill.m_skill);
				}
				foreach (SkillType item in hashSet.Where((SkillType t) => !Plugin.IsVanilla(t)))
				{
					Plugin.DiscoverModdedSkill(item);
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogWarning((object)$"Modded skill discovery failed: {arg}");
			}
		}
	}
}