Decompiled source of SetUpSkills v0.1.0

plugins/SetUpSkills.dll

Decompiled 2 weeks ago
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;

[assembly: AssemblyProduct("Set Up Skills")]
[assembly: AssemblyTitle("Set Up Skills")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("0.1.0.0")]
namespace SetUpSkills;

[BepInPlugin("neocor.SetUpSkills", "Set Up Skills", "0.1.0")]
public class BepInExPlugin : BaseUnityPlugin
{
	public const string pluginGuid = "neocor.SetUpSkills";

	public const string pluginName = "Set Up Skills";

	public const string pluginVersion = "0.1.0";

	private static ManualLogSource logger;

	public static void Log(string str)
	{
		if (logger != null)
		{
			logger.LogInfo((object)str);
		}
	}

	public static void Warn(string str)
	{
		if (logger != null)
		{
			logger.LogWarning((object)str);
		}
	}

	public static void Dbgl(string str)
	{
		if (logger != null && Cfg.IsDebug != null && Cfg.IsDebug.Value)
		{
			logger.LogInfo((object)str);
		}
	}

	public void Awake()
	{
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0025: Expected O, but got Unknown
		logger = ((BaseUnityPlugin)this).Logger;
		Cfg.Bind(((BaseUnityPlugin)this).Config);
		Patches.PatchAll(new Harmony("neocor.SetUpSkills"));
		ConfigSync.StartWatching(((BaseUnityPlugin)this).Config);
		Log("Ready. " + Tuning.Knobs.Count + " tunable values in " + ((BaseUnityPlugin)this).Config.ConfigFilePath);
	}

	public void Update()
	{
		ConfigSync.PumpReload();
	}
}
public static class Cfg
{
	public static readonly SkillType[] AllSkills = (SkillType[])(object)new SkillType[24]
	{
		(SkillType)1,
		(SkillType)2,
		(SkillType)3,
		(SkillType)4,
		(SkillType)5,
		(SkillType)6,
		(SkillType)7,
		(SkillType)8,
		(SkillType)9,
		(SkillType)10,
		(SkillType)11,
		(SkillType)12,
		(SkillType)13,
		(SkillType)14,
		(SkillType)100,
		(SkillType)101,
		(SkillType)102,
		(SkillType)103,
		(SkillType)104,
		(SkillType)105,
		(SkillType)106,
		(SkillType)107,
		(SkillType)108,
		(SkillType)110
	};

	public static ConfigEntry<bool> Enabled;

	public static ConfigEntry<bool> IsDebug;

	public static ConfigEntry<bool> ServerIsAuthority;

	public static Knob BowDrawMult0;

	public static Knob BowDrawMult100;

	public static Knob BowDrawSeconds0;

	public static Knob BowDrawSeconds100;

	public static Knob ReloadMult0;

	public static Knob ReloadMult100;

	public static Knob DrawStaminaReduction;

	public static Knob DrawEitrReduction;

	public static Knob AttackEitrReduction;

	public static Knob DamageMin0;

	public static Knob DamageMax100;

	public static Knob DamageRandomRange;

	public static Knob BlockPowerBonus;

	public static Knob PlaceDurabilityMult;

	public static Knob XpCurveExponent;

	public static Knob XpCurveScale;

	public static Knob DeathLossFactor;

	private static readonly Dictionary<SkillType, Knob[]> curves = new Dictionary<SkillType, Knob[]>();

	private static readonly Dictionary<SkillType, Knob> xpGain = new Dictionary<SkillType, Knob>();

	public static Knob[] CurveFor(SkillType t)
	{
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		if (curves.TryGetValue(t, out var value))
		{
			return value;
		}
		return null;
	}

	public static Knob XpGainFor(SkillType t)
	{
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		if (xpGain.TryGetValue(t, out var value))
		{
			return value;
		}
		return null;
	}

	public static void Bind(ConfigFile cfg)
	{
		//IL_03ea: Unknown result type (might be due to invalid IL or missing references)
		//IL_03eb: Unknown result type (might be due to invalid IL or missing references)
		//IL_03fc: Unknown result type (might be due to invalid IL or missing references)
		//IL_04c2: Unknown result type (might be due to invalid IL or missing references)
		Enabled = cfg.Bind<bool>("General", "Enabled", true, "Master switch for the whole mod.\nfalse = every setting below is ignored and the game behaves exactly as it does\nwithout the mod. Useful for checking whether the mod is responsible for something\nwithout having to uninstall it. Takes effect on the next config reload.");
		IsDebug = cfg.Bind<bool>("General", "IsDebug", false, "Writes extra lines to BepInEx/LogOutput.log.\nLogs the computed draw duration each time you start drawing a bow, which patches\nwere installed, and what a server pushed to you. Turn on while tuning, off after.");
		ServerIsAuthority = cfg.Bind<bool>("ServerSync", "ServerIsAuthority", true, "Only read when this copy of the mod is running on a server.\ntrue  = the server sends this entire file to every client that has the mod as it\n        connects, so everyone plays on the same balance. The client keeps the\n        values in memory only; its own file on disk is never written to, and its\n        own settings come back as soon as it disconnects.\nfalse = the server sends nothing and every player uses their own file.\nClients without the mod can always join; they just keep vanilla behaviour.");
		BowDrawMult0 = Tuning.Bind(cfg, "Bows", "DrawTimeMultAtSkill0", 1f, "How long a full draw takes at Bows 0, as a multiple of the base draw time of the\nweapon. Every bow in the game has a base of 2.5 seconds, so 1.0 = 2.50 s.\nHigher = slower at low skill, which makes levelling the skill feel more rewarding.\nVanilla 1.0. Example: 1.2 gives 3.00 s at skill 0.");
		BowDrawMult100 = Tuning.Bind(cfg, "Bows", "DrawTimeMultAtSkill100", 0.2f, "The same multiplier at Bows 100, i.e. how fast a maxed archer can be.\nVanilla 0.2, which on a 2.5 s bow means 0.50 s.\nHigher = the skill saves you less time. Example: 0.4 gives 1.00 s at skill 100.\nThe game interpolates linearly between the two: Lerp(base * this0, base * this100,\nskillFactor). This one value is what makes archery feel fast or slow at end game.");
		BowDrawSeconds0 = Tuning.Bind(cfg, "Bows", "DrawTimeSecondsAtSkill0", 0f, "Draw time at Bows 0 in seconds, as an absolute number, ignoring the base of the\nweapon entirely. Use this if you would rather think in seconds than in multipliers,\nor if a mod adds bows with a different base and you want them all equal.\n0 = disabled. Both this and DrawTimeSecondsAtSkill100 must be above 0 for either\nto apply; otherwise the two Mult settings are used.");
		BowDrawSeconds100 = Tuning.Bind(cfg, "Bows", "DrawTimeSecondsAtSkill100", 0f, "Draw time at Bows 100 in seconds, absolute. 0 = disabled.\nSetting this pair to 3.0 and 1.0 is the same balance as DrawTimeMult 1.2 / 0.4 for\na stock 2.5 s bow, but it stays 3.0 to 1.0 no matter what bow is equipped.");
		ReloadMult0 = Tuning.Bind(cfg, "Crossbows", "ReloadTimeMultAtSkill0", 1f, "How long reloading takes at Crossbows 0, as a multiple of the reload time of the\nweapon. Vanilla 1.0 = the value on the crossbow itself, unchanged.\nHigher = slower reload for a beginner.");
		ReloadMult100 = Tuning.Bind(cfg, "Crossbows", "ReloadTimeMultAtSkill100", 0.5f, "The same multiplier at Crossbows 100. Vanilla 0.5, so a maxed user reloads in half\nthe time printed on the item. Higher = the skill helps less.\nVanilla formula: Lerp(reload * this0, reload * this100, skillFactor).");
		DrawStaminaReduction = Tuning.Bind(cfg, "Combat", "DrawStaminaReductionAtSkill100", 0.33f, "How much of the stamina drained while holding a bow or staff drawn is removed at\nskill 100, as a fraction. Vanilla 0.33, so a maxed archer pays 67% of the drain.\n1.0 = holding a full draw is free at skill 100. 0.0 = the skill never helps here.\nVanilla formula: drain - drain * this * skillFactor.");
		DrawEitrReduction = Tuning.Bind(cfg, "Combat", "DrawEitrReductionAtSkill100", 0.33f, "The same for eitr drained while charging a magic staff. Vanilla 0.33.\nAffects Elemental Magic and Blood Magic weapons that charge before firing.");
		Tuning.Bind(cfg, "Combat", "AttackStaminaReductionAtSkill100", 0.33f, "How much of the stamina cost of a swing or shot is removed at skill 100, as a\nfraction. Vanilla 0.33, so a maxed fighter pays 67% of the cost printed on the item.\nThis applies to every weapon skill. Raising it is the single biggest lever on how\nlong a late-game fight can last before you run dry.");
		AttackEitrReduction = Tuning.Bind(cfg, "Combat", "AttackEitrReductionAtSkill100", 0.33f, "The same for the eitr cost of casting. Vanilla 0.33.");
		Tuning.Bind(cfg, "Combat", "AttackHealthReductionAtSkill100", 0.33f, "The same for attacks that cost health instead of stamina, which in the base game\nmeans Blood Magic. Vanilla 0.33.");
		DamageMin0 = Tuning.Bind(cfg, "Damage", "MinFactorAtSkill0", 0.4f, "Damage multiplier at skill 0, before the random spread is applied.\nVanilla 0.4, so a beginner deals 40% of the damage printed on the weapon.\nRaise it to make early combat less punishing; lower it to widen the gap between\na novice and a master. This applies to every weapon skill at once.");
		DamageMax100 = Tuning.Bind(cfg, "Damage", "MaxFactorAtSkill100", 1f, "Damage multiplier at skill 100, before the random spread.\nVanilla 1.0, meaning the number on the weapon tooltip is what a maxed player deals.\nAbove 1.0 pushes late-game damage past the printed value, which also inflates the\ntooltip range the game shows you.\nVanilla formula: Lerp(MinFactorAtSkill0, MaxFactorAtSkill100, skillFactor).");
		DamageRandomRange = Tuning.Bind(cfg, "Damage", "RandomRange", 0.15f, "Half-width of the random roll applied on top of the multiplier above, so the real\nmultiplier lands anywhere in [mid - this, mid + this], clamped to 0..1 range checks.\nVanilla 0.15. At skill 50 the midpoint is 0.7, so vanilla rolls 0.55 to 0.85.\n0 = every hit of the same kind deals exactly the same damage. Higher = swingier.");
		BlockPowerBonus = Tuning.Bind(cfg, "Blocking", "BlockPowerBonusAtSkill100", 0.5f, "Extra block power at Blocking 100, as a fraction of the base value of the shield.\nVanilla 0.5, so a maxed player blocks with 150% of the number on the shield.\nHigher = shields scale harder with skill. 0 = the shield is always its base value.\nVanilla formula: base + base * skillFactor * this.");
		Tuning.Bind(cfg, "Run", "StaminaMultAtSkill0", 1f, "Stamina drained while running, as a multiple of the base drain, at Run 0.\nVanilla 1.0. Higher = running is more expensive before you have trained it.");
		Tuning.Bind(cfg, "Run", "StaminaMultAtSkill100", 0.5f, "The same multiplier at Run 100. Vanilla 0.5, so a maxed runner pays half.\nLower = long-distance travel becomes far cheaper at high skill.\nVanilla formula: drain * Lerp(this0, this100, skillFactor).");
		Tuning.Bind(cfg, "Run", "SpeedBonusAtSkill100", 0.25f, "Extra running speed at Run 100, as a fraction. Vanilla 0.25, meaning plus 25%.\nThis stacks multiplicatively with the movement modifier of your gear.\nVanilla formula: (1 + skillFactor * this) * (1 + equipmentModifier * 1.5).");
		Tuning.Bind(cfg, "Sneak", "StaminaMultAtSkill0", 1f, "Stamina drained while crouched, as a multiple of the base drain, at Sneak 0.\nVanilla 1.0.");
		Tuning.Bind(cfg, "Sneak", "StaminaMultAtSkill100", 0.25f, "The same multiplier at Sneak 100. Vanilla 0.25, so a maxed sneak costs a quarter.\nLower = you can stay crouched almost indefinitely at high skill.");
		Tuning.Bind(cfg, "Sneak", "StaminaExponent", 0.5f, "Shape of the sneak stamina curve, and the only skill in the game with one.\nThe skill factor is raised to this power before being interpolated.\nVanilla 0.5, a square root, which front-loads the benefit: the first 25 levels give\nhalf of the total saving. 1.0 makes it a straight line. Above 1.0 back-loads it, so\nalmost all of the benefit arrives in the last levels.");
		Tuning.Bind(cfg, "Sneak", "StealthBaseAtSkill100", 0.2f, "How visible you are at Sneak 100 standing in complete darkness, where 0 is unseen\nand 1 is fully visible. Vanilla 0.2. Lower = stealthier.\nThe skill 0 end of this curve is fixed by the game at 0.5 and is not configurable,\nso this sets how much of a difference training Sneak makes at all.\nVanilla formula: Lerp(0.5 + light * 0.5, this + light * LightWeight, skillFactor).");
		Tuning.Bind(cfg, "Sneak", "StealthLightWeightAtSkill100", 0.4f, "How much ambient light adds to your visibility at Sneak 100, on top of the base\nabove. Vanilla 0.4, so a maxed sneak in full daylight sits at 0.2 + 0.4 = 0.6.\nLower = daylight matters less and high-skill stealth works in the open.");
		Tuning.Bind(cfg, "Dodge", "StaminaMultAtSkill0", 1f, "Stamina cost of a dodge roll, as a multiple of the base cost, at Dodge 0.\nVanilla 1.0.");
		Tuning.Bind(cfg, "Dodge", "StaminaMultAtSkill100", 0.5f, "The same multiplier at Dodge 100. Vanilla 0.5, so a maxed player rolls for half.\nLower = more consecutive rolls in a fight at high skill.");
		Tuning.Bind(cfg, "Jump", "ForceBonusAtSkill100", 0.4f, "Extra jump force at Jump 100, as a fraction. Vanilla 0.4, meaning plus 40%.\nApplied to both the upward and the forward part of a jump, so raising it makes\nhigh-skill players jump both higher and further. Note that the stamina cost of a\njump is a flat value in the game and is not affected by skill at all.");
		Tuning.Bind(cfg, "Building", "StaminaReductionAtSkill100", 0.5f, "How much of the stamina cost of placing a piece is removed at the relevant crafting\nskill 100, as a fraction. Vanilla 0.5, so a maxed builder pays half.\n1.0 = building costs no stamina at skill 100.\nVanilla formula: cost - cost * this * skillFactor.");
		PlaceDurabilityMult = Tuning.Bind(cfg, "Building", "PlaceDurabilityReductionMultiplier", 1f, "Scales how much durability skill saves your hammer, hoe or cultivator when placing.\nUnlike the other settings here the game stores this per tool, so this is a\nmultiplier on whatever the tool itself declares rather than a fixed number.\nVanilla 1.0 = leave the value of the tool alone. 2.0 = skill saves twice as much\ndurability. 0 = skill never saves durability.");
		XpCurveExponent = Tuning.Bind(cfg, "Experience", "CurveExponent", 1.5f, "How steeply the XP needed per level grows. Vanilla 1.5.\nFull vanilla formula: Pow(floor(level + 1), CurveExponent) * CurveScale + 0.5.\nLower = the late levels come much faster; 1.0 makes every level cost the same\namount more than the last instead of accelerating. Higher = a harsher grind.\nThis is a much stronger lever than GainMultiplier, so change it in small steps.");
		XpCurveScale = Tuning.Bind(cfg, "Experience", "CurveScale", 0.5f, "Flat multiplier on the XP needed per level. Vanilla 0.5.\nHalving it roughly doubles overall levelling speed at every level equally, which\nmakes it a safer knob than CurveExponent if you just want faster progress.");
		DeathLossFactor = Tuning.Bind(cfg, "Experience", "DeathLossFactor", -1f, "Fraction of every skill level lost when you die, before the world modifier is\napplied on top. The game ships with 0.05, so a death costs 5% of each skill.\n-1 = do not touch it, keep whatever the game uses. 0 = deaths cost no skill.\n0.2 = a harsh run where dying really hurts.");
		SkillType[] allSkills = AllSkills;
		foreach (SkillType val in allSkills)
		{
			string text = ((object)val).ToString();
			curves[val] = new Knob[3]
			{
				Tuning.Bind(cfg, "Curve", text + ".Level0Factor", 0f, "What " + text + " is worth at level 0, on a 0 to 1 scale.\nVanilla 0 = the skill does nothing for you until you train it.\nRaising it gives an untrained character part of the benefit up front, which\nflattens the whole skill. This feeds every effect of " + text + " at once."),
				Tuning.Bind(cfg, "Curve", text + ".Level100Factor", 1f, "What " + text + " is worth at level 100, on a 0 to 1 scale.\nVanilla 1 = the full benefit the game was designed around.\nLower it to cap how far the skill can carry you: 0.75 on Bows, for example,\nleaves a maxed archer at three quarters of the usual draw speed bonus, the\nusual damage bonus and the usual stamina saving, all together."),
				Tuning.Bind(cfg, "Curve", text + ".Exponent", 1f, "Shape of the " + text + " curve between the two values above.\n1 = a straight line, which is what the game does.\nBelow 1 front-loads it, so the first levels give most of the benefit and\nthe grind to 100 matters less.\nAbove 1 back-loads it, so the skill stays weak until it is nearly maxed.")
			};
			xpGain[val] = Tuning.Bind(cfg, "Experience", text + ".GainMultiplier", 1f, "How fast " + text + " gains experience. Vanilla 1.\n2 = levels twice as fast, 0.5 = half as fast, 0 = the skill never rises.\nMultiplies with the skill gain rate world modifier rather than replacing it.");
		}
	}
}
public static class ConfigSync
{
	public const string RpcName = "SetUpSkills_Config";

	private const int Format = 1;

	private static bool registered;

	private static FileSystemWatcher watcher;

	private static volatile bool reloadRequested;

	private static ConfigFile config;

	public static void ZNet_Awake_Postfix()
	{
		if (!registered)
		{
			ZRoutedRpc instance = ZRoutedRpc.instance;
			if (instance == null)
			{
				BepInExPlugin.Warn("ZRoutedRpc was not ready in ZNet.Awake; config sync is off.");
				return;
			}
			instance.Register<ZPackage>("SetUpSkills_Config", (Action<long, ZPackage>)OnReceive);
			registered = true;
			BepInExPlugin.Dbgl("Registered the config sync RPC.");
		}
	}

	public static void ZNet_RPC_PeerInfo_Postfix(ZNet __instance, ZRpc rpc)
	{
		if (!((Object)(object)__instance == (Object)null) && __instance.IsServer() && Cfg.ServerIsAuthority.Value)
		{
			ZNetPeer val = FindPeer(__instance, rpc);
			if (val == null)
			{
				BepInExPlugin.Dbgl("A peer sent its info but is not in the peer list yet; not syncing.");
				return;
			}
			SendTo(val.m_uid);
			BepInExPlugin.Log("Sent the skill config to " + (string.IsNullOrEmpty(val.m_playerName) ? val.m_uid.ToString() : val.m_playerName) + ".");
		}
	}

	private static ZNetPeer FindPeer(ZNet net, ZRpc rpc)
	{
		List<ZNetPeer> peers = net.GetPeers();
		if (peers == null)
		{
			return null;
		}
		for (int i = 0; i < peers.Count; i++)
		{
			if (peers[i] != null && peers[i].m_rpc == rpc)
			{
				return peers[i];
			}
		}
		return null;
	}

	private static void SendTo(long peerId)
	{
		ZRoutedRpc instance = ZRoutedRpc.instance;
		if (instance != null)
		{
			instance.InvokeRoutedRPC(peerId, "SetUpSkills_Config", new object[1] { BuildPackage() });
		}
	}

	public static void PushToAll()
	{
		ZNet instance = ZNet.instance;
		if ((Object)(object)instance == (Object)null || !instance.IsServer() || !Cfg.ServerIsAuthority.Value)
		{
			return;
		}
		List<ZNetPeer> peers = instance.GetPeers();
		if (peers == null)
		{
			return;
		}
		int num = 0;
		for (int i = 0; i < peers.Count; i++)
		{
			if (peers[i] != null)
			{
				SendTo(peers[i].m_uid);
				num++;
			}
		}
		if (num > 0)
		{
			BepInExPlugin.Log("Pushed the reloaded skill config to " + num + " player(s).");
		}
	}

	private static ZPackage BuildPackage()
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Expected O, but got Unknown
		ZPackage val = new ZPackage();
		val.Write(1);
		val.Write(Tuning.Knobs.Count + 1);
		val.Write("General.Enabled");
		val.Write(Cfg.Enabled.Value ? 1f : 0f);
		foreach (KeyValuePair<string, Knob> knob in Tuning.Knobs)
		{
			val.Write(knob.Key);
			val.Write(knob.Value.Entry.Value);
		}
		return val;
	}

	private static void OnReceive(long sender, ZPackage pkg)
	{
		ZNet instance = ZNet.instance;
		if (((Object)(object)instance != (Object)null && instance.IsServer()) || pkg == null)
		{
			return;
		}
		try
		{
			int num = pkg.ReadInt();
			if (num != 1)
			{
				BepInExPlugin.Warn("The server sent config format " + num + ", this build speaks " + 1 + ". Keeping local settings.");
				return;
			}
			int num2 = pkg.ReadInt();
			Dictionary<string, float> dictionary = new Dictionary<string, float>(num2);
			for (int i = 0; i < num2; i++)
			{
				string key = pkg.ReadString();
				float value = pkg.ReadSingle();
				dictionary[key] = value;
			}
			int num3 = Tuning.ApplySynced(dictionary);
			BepInExPlugin.Log("Applied " + num3 + " skill settings from the server" + ((num3 < num2) ? (" (" + (num2 - num3) + " unknown to this version)") : "") + ".");
		}
		catch (Exception ex)
		{
			BepInExPlugin.Warn("Could not read the config the server sent: " + ex.Message);
		}
	}

	public static void ZNet_Shutdown_Postfix()
	{
		if (Tuning.SyncedActive)
		{
			Tuning.ClearSynced();
			BepInExPlugin.Log("Disconnected; local skill settings are back in effect.");
		}
	}

	public static void StartWatching(ConfigFile cfg)
	{
		config = cfg;
		try
		{
			string directoryName = Path.GetDirectoryName(cfg.ConfigFilePath);
			string fileName = Path.GetFileName(cfg.ConfigFilePath);
			watcher = new FileSystemWatcher(directoryName, fileName);
			watcher.Changed += OnConfigFileEvent;
			watcher.Created += OnConfigFileEvent;
			watcher.Renamed += OnConfigFileEvent;
			watcher.IncludeSubdirectories = false;
			watcher.EnableRaisingEvents = true;
		}
		catch (Exception ex)
		{
			BepInExPlugin.Warn("Could not watch the config file for changes: " + ex.Message);
		}
	}

	private static void OnConfigFileEvent(object sender, FileSystemEventArgs e)
	{
		reloadRequested = true;
	}

	public static void PumpReload()
	{
		if (!reloadRequested)
		{
			return;
		}
		reloadRequested = false;
		try
		{
			config.Reload();
			BepInExPlugin.Log("Reloaded the config file.");
			PushToAll();
		}
		catch (Exception ex)
		{
			BepInExPlugin.Warn("Could not reload the config file: " + ex.Message);
		}
	}
}
public static class ILTools
{
	private const float Epsilon = 0.0001f;

	private static readonly MethodInfo ByKey = AccessTools.Method(typeof(Tuning), "ByKey", new Type[1] { typeof(string) }, (Type[])null);

	private static bool IsConst(CodeInstruction ins, float value)
	{
		if (ins.opcode != OpCodes.Ldc_R4 || !(ins.operand is float))
		{
			return false;
		}
		float num = (float)ins.operand;
		float num2 = num - value;
		if (num2 < 0f)
		{
			num2 = 0f - num2;
		}
		return num2 < 0.0001f;
	}

	private static void Swap(List<CodeInstruction> list, int i, string key)
	{
		//IL_000e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0014: Expected O, but got Unknown
		//IL_004c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0056: Expected O, but got Unknown
		CodeInstruction val = list[i];
		CodeInstruction val2 = new CodeInstruction(OpCodes.Ldstr, (object)key);
		val2.labels.AddRange(val.labels);
		val2.blocks.AddRange(val.blocks);
		list[i] = val2;
		list.Insert(i + 1, new CodeInstruction(OpCodes.Call, (object)ByKey));
	}

	public static List<CodeInstruction> ReplaceConst(IEnumerable<CodeInstruction> input, string where, float value, string key)
	{
		List<CodeInstruction> list = new List<CodeInstruction>(input);
		int num = 0;
		for (int i = 0; i < list.Count; i++)
		{
			if (IsConst(list[i], value))
			{
				Swap(list, i, key);
				i++;
				num++;
			}
		}
		Report(where, key, num);
		return list;
	}

	public static List<CodeInstruction> ReplaceLerpPair(IEnumerable<CodeInstruction> input, string where, float a, float b, string keyA, string keyB)
	{
		List<CodeInstruction> list = new List<CodeInstruction>(input);
		int num = 0;
		for (int i = 0; i < list.Count - 1; i++)
		{
			if (IsConst(list[i], a) && IsConst(list[i + 1], b))
			{
				Swap(list, i + 1, keyB);
				Swap(list, i, keyA);
				i += 3;
				num++;
			}
		}
		Report(where, keyA + " / " + keyB, num);
		return list;
	}

	private static void Report(string where, string key, int found)
	{
		if (found == 0)
		{
			BepInExPlugin.Warn("Transpiler on " + where + " found no constant to replace for " + key + ". That effect stays at vanilla values; the game was probably updated.");
		}
		else if (found > 1)
		{
			BepInExPlugin.Warn("Transpiler on " + where + " replaced " + found + " constants for " + key + ", expected 1. Check the result in game.");
		}
		else
		{
			BepInExPlugin.Dbgl("Transpiler on " + where + " patched " + key + ".");
		}
	}
}
public static class Patches
{
	private const int Prefix = 0;

	private const int Postfix = 1;

	private const int Transpiler = 2;

	private static readonly FieldInfo AttackDrawTimeField = AccessTools.Field(typeof(Humanoid), "m_attackDrawTime");

	public static void Skills_GetSkillFactor_Postfix(SkillType skillType, ref float __result)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		__result = Tuning.ApplyCurve(skillType, __result);
	}

	public static bool Humanoid_GetAttackDrawPercentage_Prefix(Humanoid __instance, ref float __result)
	{
		//IL_0064: Unknown result type (might be due to invalid IL or missing references)
		//IL_010d: Unknown result type (might be due to invalid IL or missing references)
		if (!Tuning.IsEnabled() || AttackDrawTimeField == null)
		{
			return true;
		}
		ItemData currentWeapon = __instance.GetCurrentWeapon();
		if (currentWeapon == null || !currentWeapon.m_shared.m_attack.m_bowDraw)
		{
			__result = 0f;
			return false;
		}
		float num = (float)AttackDrawTimeField.GetValue(__instance);
		if (num <= 0f)
		{
			__result = 0f;
			return false;
		}
		float skillFactor = ((Character)__instance).GetSkillFactor(currentWeapon.m_shared.m_skillType);
		float drawDurationMin = currentWeapon.m_shared.m_attack.m_drawDurationMin;
		float num2 = Cfg.BowDrawSeconds0.Value;
		float num3 = Cfg.BowDrawSeconds100.Value;
		if (num2 <= 0f || num3 <= 0f)
		{
			num2 = drawDurationMin * Cfg.BowDrawMult0.Value;
			num3 = drawDurationMin * Cfg.BowDrawMult100.Value;
		}
		float num4 = Mathf.Lerp(num2, num3, skillFactor);
		if (Cfg.IsDebug.Value && num <= Time.fixedDeltaTime * 1.5f)
		{
			BepInExPlugin.Dbgl(string.Concat("Draw start: skill=", currentWeapon.m_shared.m_skillType, " factor=", skillFactor.ToString("0.###"), " base=", drawDurationMin.ToString("0.###"), "s duration=", num4.ToString("0.###"), "s"));
		}
		if (num4 <= 0f)
		{
			__result = 1f;
		}
		else
		{
			__result = Mathf.Clamp01(num / num4);
		}
		return false;
	}

	public static bool ItemData_GetWeaponLoadingTime_Prefix(ItemData __instance, ref float __result)
	{
		//IL_003c: Unknown result type (might be due to invalid IL or missing references)
		if (!Tuning.IsEnabled())
		{
			return true;
		}
		if (!__instance.m_shared.m_attack.m_requiresReload || (Object)(object)Player.m_localPlayer == (Object)null)
		{
			__result = 1f;
			return false;
		}
		float skillFactor = ((Character)Player.m_localPlayer).GetSkillFactor(__instance.m_shared.m_skillType);
		float reloadTime = __instance.m_shared.m_attack.m_reloadTime;
		__result = Mathf.Lerp(reloadTime * Cfg.ReloadMult0.Value, reloadTime * Cfg.ReloadMult100.Value, skillFactor);
		return false;
	}

	public static bool ItemData_GetDrawStaminaDrain_Prefix(ItemData __instance, ref float __result)
	{
		//IL_0048: Unknown result type (might be due to invalid IL or missing references)
		if (!Tuning.IsEnabled())
		{
			return true;
		}
		float drawStaminaDrain = __instance.m_shared.m_attack.m_drawStaminaDrain;
		if (drawStaminaDrain <= 0f)
		{
			__result = 0f;
			return false;
		}
		if ((Object)(object)Player.m_localPlayer == (Object)null)
		{
			__result = drawStaminaDrain;
			return false;
		}
		float skillFactor = ((Character)Player.m_localPlayer).GetSkillFactor(__instance.m_shared.m_skillType);
		__result = drawStaminaDrain - drawStaminaDrain * Cfg.DrawStaminaReduction.Value * skillFactor;
		return false;
	}

	public static bool ItemData_GetDrawEitrDrain_Prefix(ItemData __instance, ref float __result)
	{
		//IL_0048: Unknown result type (might be due to invalid IL or missing references)
		if (!Tuning.IsEnabled())
		{
			return true;
		}
		float drawEitrDrain = __instance.m_shared.m_attack.m_drawEitrDrain;
		if (drawEitrDrain <= 0f)
		{
			__result = 0f;
			return false;
		}
		if ((Object)(object)Player.m_localPlayer == (Object)null)
		{
			__result = drawEitrDrain;
			return false;
		}
		float skillFactor = ((Character)Player.m_localPlayer).GetSkillFactor(__instance.m_shared.m_skillType);
		__result = drawEitrDrain - drawEitrDrain * Cfg.DrawEitrReduction.Value * skillFactor;
		return false;
	}

	public static bool Attack_GetAttackEitr_Prefix(Character character, ItemData weapon, ref float __result)
	{
		//IL_0032: Unknown result type (might be due to invalid IL or missing references)
		if (!Tuning.IsEnabled())
		{
			return true;
		}
		float attackEitr = weapon.m_shared.m_attack.m_attackEitr;
		if (attackEitr <= 0f)
		{
			__result = 0f;
			return false;
		}
		float skillFactor = character.GetSkillFactor(weapon.m_shared.m_skillType);
		__result = attackEitr - attackEitr * Cfg.AttackEitrReduction.Value * skillFactor;
		return false;
	}

	private static void DamageRange(Skills instance, SkillType skillType, out float min, out float max)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		float skillFactor = instance.GetSkillFactor(skillType);
		float num = Mathf.Lerp(Cfg.DamageMin0.Value, Cfg.DamageMax100.Value, skillFactor);
		float value = Cfg.DamageRandomRange.Value;
		min = Mathf.Clamp01(num - value);
		max = Mathf.Clamp01(num + value);
	}

	public static bool Skills_GetRandomSkillFactor_Prefix(Skills __instance, SkillType skillType, ref float __result)
	{
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		if (!Tuning.IsEnabled())
		{
			return true;
		}
		DamageRange(__instance, skillType, out var min, out var max);
		__result = Mathf.Lerp(min, max, Random.value);
		return false;
	}

	public static bool Skills_GetRandomSkillRange_Prefix(Skills __instance, ref float min, ref float max, SkillType skillType)
	{
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		if (!Tuning.IsEnabled())
		{
			return true;
		}
		DamageRange(__instance, skillType, out min, out max);
		return false;
	}

	public static bool ItemData_GetBlockPower_Prefix(ItemData __instance, int __0, float __1, ref float __result)
	{
		if (!Tuning.IsEnabled())
		{
			return true;
		}
		float baseBlockPower = __instance.GetBaseBlockPower(__0);
		__result = baseBlockPower + baseBlockPower * __1 * Cfg.BlockPowerBonus.Value;
		return false;
	}

	public static bool Player_GetPlaceDurability_Prefix(Player __instance, ItemData __0, ref float __result)
	{
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0020: Unknown result type (might be due to invalid IL or missing references)
		//IL_0021: Unknown result type (might be due to invalid IL or missing references)
		//IL_0025: Unknown result type (might be due to invalid IL or missing references)
		if (!Tuning.IsEnabled())
		{
			return true;
		}
		float num = __0.m_shared.m_useDurabilityDrain;
		SkillType placementDurabilitySkill = __0.m_shared.m_placementDurabilitySkill;
		if ((int)placementDurabilitySkill != 0)
		{
			float skillFactor = ((Character)__instance).GetSkillFactor(placementDurabilitySkill);
			float num2 = __0.m_shared.m_placementDurabilityMax * Cfg.PlaceDurabilityMult.Value;
			num -= num * num2 * skillFactor;
		}
		__result = num;
		return false;
	}

	public static void Skills_RaiseSkill_Prefix(SkillType skillType, ref float factor)
	{
		//IL_0008: Unknown result type (might be due to invalid IL or missing references)
		if (Tuning.IsEnabled())
		{
			Knob knob = Cfg.XpGainFor(skillType);
			if (knob != null)
			{
				factor *= knob.Value;
			}
		}
	}

	public static bool Skill_GetNextLevelRequirement_Prefix(Skill __instance, ref float __result)
	{
		if (!Tuning.IsEnabled())
		{
			return true;
		}
		float value = Cfg.XpCurveExponent.Value;
		float value2 = Cfg.XpCurveScale.Value;
		__result = Mathf.Pow(Mathf.Floor(__instance.m_level + 1f), value) * value2 + 0.5f;
		return false;
	}

	public static bool Skills_OnDeath_Prefix(Skills __instance)
	{
		if (!Tuning.IsEnabled())
		{
			return true;
		}
		float value = Cfg.DeathLossFactor.Value;
		if (value < 0f)
		{
			return true;
		}
		__instance.LowerAllSkills(value * Game.m_skillReductionRate);
		return false;
	}

	public static IEnumerable<CodeInstruction> Attack_GetAttackStamina_Transpiler(IEnumerable<CodeInstruction> instructions)
	{
		return ILTools.ReplaceConst(instructions, "Attack.GetAttackStamina", 0.33f, "Combat.AttackStaminaReductionAtSkill100");
	}

	public static IEnumerable<CodeInstruction> Attack_GetAttackHealth_Transpiler(IEnumerable<CodeInstruction> instructions)
	{
		return ILTools.ReplaceConst(instructions, "Attack.GetAttackHealth", 0.33f, "Combat.AttackHealthReductionAtSkill100");
	}

	public static IEnumerable<CodeInstruction> Character_Jump_Transpiler(IEnumerable<CodeInstruction> instructions)
	{
		return ILTools.ReplaceConst(instructions, "Character.Jump", 0.4f, "Jump.ForceBonusAtSkill100");
	}

	public static IEnumerable<CodeInstruction> Player_CheckRun_Transpiler(IEnumerable<CodeInstruction> instructions)
	{
		return ILTools.ReplaceLerpPair(instructions, "Player.CheckRun", 1f, 0.5f, "Run.StaminaMultAtSkill0", "Run.StaminaMultAtSkill100");
	}

	public static IEnumerable<CodeInstruction> Player_GetRunSpeedFactor_Transpiler(IEnumerable<CodeInstruction> instructions)
	{
		return ILTools.ReplaceConst(instructions, "Player.GetRunSpeedFactor", 0.25f, "Run.SpeedBonusAtSkill100");
	}

	public static IEnumerable<CodeInstruction> Player_OnSneaking_Transpiler(IEnumerable<CodeInstruction> instructions)
	{
		List<CodeInstruction> input = ILTools.ReplaceConst(instructions, "Player.OnSneaking", 0.5f, "Sneak.StaminaExponent");
		return ILTools.ReplaceLerpPair(input, "Player.OnSneaking", 1f, 0.25f, "Sneak.StaminaMultAtSkill0", "Sneak.StaminaMultAtSkill100");
	}

	public static IEnumerable<CodeInstruction> Player_UpdateStealth_Transpiler(IEnumerable<CodeInstruction> instructions)
	{
		List<CodeInstruction> input = ILTools.ReplaceConst(instructions, "Player.UpdateStealth", 0.2f, "Sneak.StealthBaseAtSkill100");
		return ILTools.ReplaceConst(input, "Player.UpdateStealth", 0.4f, "Sneak.StealthLightWeightAtSkill100");
	}

	public static IEnumerable<CodeInstruction> Player_GetDodgeStaminaUse_Transpiler(IEnumerable<CodeInstruction> instructions)
	{
		return ILTools.ReplaceLerpPair(instructions, "Player.GetDodgeStaminaUse", 1f, 0.5f, "Dodge.StaminaMultAtSkill0", "Dodge.StaminaMultAtSkill100");
	}

	public static IEnumerable<CodeInstruction> Player_GetBuildStamina_Transpiler(IEnumerable<CodeInstruction> instructions)
	{
		return ILTools.ReplaceConst(instructions, "Player.GetBuildStamina", 0.5f, "Building.StaminaReductionAtSkill100");
	}

	public static void PatchAll(Harmony harmony)
	{
		int ok = 0;
		int failed = 0;
		Type typeFromHandle = typeof(ItemData);
		Type[] args = new Type[0];
		Apply(harmony, ref ok, ref failed, typeof(Skills), "GetSkillFactor", new Type[1] { typeof(SkillType) }, 1, "Skills_GetSkillFactor_Postfix");
		Apply(harmony, ref ok, ref failed, typeof(Humanoid), "GetAttackDrawPercentage", args, 0, "Humanoid_GetAttackDrawPercentage_Prefix");
		Apply(harmony, ref ok, ref failed, typeFromHandle, "GetWeaponLoadingTime", args, 0, "ItemData_GetWeaponLoadingTime_Prefix");
		Apply(harmony, ref ok, ref failed, typeFromHandle, "GetDrawStaminaDrain", args, 0, "ItemData_GetDrawStaminaDrain_Prefix");
		Apply(harmony, ref ok, ref failed, typeFromHandle, "GetDrawEitrDrain", args, 0, "ItemData_GetDrawEitrDrain_Prefix");
		Apply(harmony, ref ok, ref failed, typeof(Attack), "GetAttackEitr", new Type[2]
		{
			typeof(Character),
			typeFromHandle
		}, 0, "Attack_GetAttackEitr_Prefix");
		Apply(harmony, ref ok, ref failed, typeof(Attack), "GetAttackStamina", args, 2, "Attack_GetAttackStamina_Transpiler");
		Apply(harmony, ref ok, ref failed, typeof(Attack), "GetAttackHealth", args, 2, "Attack_GetAttackHealth_Transpiler");
		Apply(harmony, ref ok, ref failed, typeof(Skills), "GetRandomSkillFactor", new Type[1] { typeof(SkillType) }, 0, "Skills_GetRandomSkillFactor_Prefix");
		Apply(harmony, ref ok, ref failed, typeof(Skills), "GetRandomSkillRange", new Type[3]
		{
			typeof(float).MakeByRefType(),
			typeof(float).MakeByRefType(),
			typeof(SkillType)
		}, 0, "Skills_GetRandomSkillRange_Prefix");
		Apply(harmony, ref ok, ref failed, typeFromHandle, "GetBlockPower", new Type[2]
		{
			typeof(int),
			typeof(float)
		}, 0, "ItemData_GetBlockPower_Prefix");
		Apply(harmony, ref ok, ref failed, typeof(Player), "CheckRun", null, 2, "Player_CheckRun_Transpiler");
		Apply(harmony, ref ok, ref failed, typeof(Player), "GetRunSpeedFactor", args, 2, "Player_GetRunSpeedFactor_Transpiler");
		Apply(harmony, ref ok, ref failed, typeof(Player), "OnSneaking", null, 2, "Player_OnSneaking_Transpiler");
		Apply(harmony, ref ok, ref failed, typeof(Player), "UpdateStealth", null, 2, "Player_UpdateStealth_Transpiler");
		Apply(harmony, ref ok, ref failed, typeof(Player), "GetDodgeStaminaUse", args, 2, "Player_GetDodgeStaminaUse_Transpiler");
		Apply(harmony, ref ok, ref failed, typeof(Character), "Jump", null, 2, "Character_Jump_Transpiler");
		Apply(harmony, ref ok, ref failed, typeof(Player), "GetBuildStamina", args, 2, "Player_GetBuildStamina_Transpiler");
		Apply(harmony, ref ok, ref failed, typeof(Player), "GetPlaceDurability", new Type[1] { typeFromHandle }, 0, "Player_GetPlaceDurability_Prefix");
		Apply(harmony, ref ok, ref failed, typeof(Skills), "RaiseSkill", new Type[2]
		{
			typeof(SkillType),
			typeof(float)
		}, 0, "Skills_RaiseSkill_Prefix");
		Apply(harmony, ref ok, ref failed, typeof(Skill), "GetNextLevelRequirement", args, 0, "Skill_GetNextLevelRequirement_Prefix");
		Apply(harmony, ref ok, ref failed, typeof(Skills), "OnDeath", args, 0, "Skills_OnDeath_Prefix");
		ApplyFrom(harmony, ref ok, ref failed, typeof(ZNet), "Awake", null, 1, typeof(ConfigSync), "ZNet_Awake_Postfix");
		ApplyFrom(harmony, ref ok, ref failed, typeof(ZNet), "RPC_PeerInfo", new Type[2]
		{
			typeof(ZRpc),
			typeof(ZPackage)
		}, 1, typeof(ConfigSync), "ZNet_RPC_PeerInfo_Postfix");
		ApplyFrom(harmony, ref ok, ref failed, typeof(ZNet), "Shutdown", new Type[1] { typeof(bool) }, 1, typeof(ConfigSync), "ZNet_Shutdown_Postfix");
		BepInExPlugin.Log("Installed " + ok + " patches" + ((failed > 0) ? (", " + failed + " failed (see warnings above)") : "."));
	}

	private static void Apply(Harmony harmony, ref int ok, ref int failed, Type type, string methodName, Type[] args, int kind, string patchName)
	{
		ApplyFrom(harmony, ref ok, ref failed, type, methodName, args, kind, typeof(Patches), patchName);
	}

	private static void ApplyFrom(Harmony harmony, ref int ok, ref int failed, Type type, string methodName, Type[] args, int kind, Type patchHost, string patchName)
	{
		//IL_0073: Unknown result type (might be due to invalid IL or missing references)
		//IL_0079: Expected O, but got Unknown
		try
		{
			MethodBase methodBase = ((args == null) ? AccessTools.Method(type, methodName, (Type[])null, (Type[])null) : AccessTools.Method(type, methodName, args, (Type[])null));
			if (methodBase == null)
			{
				failed++;
				BepInExPlugin.Warn("Could not find " + type.Name + "." + methodName + "; that effect stays at vanilla values.");
			}
			else
			{
				HarmonyMethod val = new HarmonyMethod(AccessTools.Method(patchHost, patchName, (Type[])null, (Type[])null));
				harmony.Patch(methodBase, (kind == 0) ? val : null, (kind == 1) ? val : null, (kind == 2) ? val : null, (HarmonyMethod)null, (HarmonyMethod)null);
				ok++;
			}
		}
		catch (Exception ex)
		{
			failed++;
			BepInExPlugin.Warn("Failed to patch " + type.Name + "." + methodName + ": " + ex.Message);
		}
	}
}
public class Knob
{
	public readonly string Key;

	public readonly ConfigEntry<float> Entry;

	public float Value => Tuning.Resolve(this);

	public Knob(string key, ConfigEntry<float> entry)
	{
		Key = key;
		Entry = entry;
	}
}
public static class Tuning
{
	public const string EnabledKey = "General.Enabled";

	private static readonly Dictionary<string, Knob> knobs = new Dictionary<string, Knob>();

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

	private static bool syncedActive;

	private static bool syncedEnabledSet;

	private static bool syncedEnabled;

	public static bool SyncedActive => syncedActive;

	public static Dictionary<string, Knob> Knobs => knobs;

	public static Knob Bind(ConfigFile cfg, string section, string key, float def, string desc)
	{
		ConfigEntry<float> entry = cfg.Bind<float>(section, key, def, desc);
		Knob knob = new Knob(section + "." + key, entry);
		knobs[knob.Key] = knob;
		return knob;
	}

	public static float Resolve(Knob knob)
	{
		if (!IsEnabled())
		{
			return (float)((ConfigEntryBase)knob.Entry).DefaultValue;
		}
		if (syncedActive && synced.TryGetValue(knob.Key, out var value))
		{
			return value;
		}
		return knob.Entry.Value;
	}

	public static float ByKey(string key)
	{
		if (!knobs.TryGetValue(key, out var value))
		{
			return 0f;
		}
		if (!IsEnabled())
		{
			return (float)((ConfigEntryBase)value.Entry).DefaultValue;
		}
		if (syncedActive && synced.TryGetValue(key, out var value2))
		{
			return value2;
		}
		return value.Entry.Value;
	}

	public static bool IsEnabled()
	{
		if (syncedActive && syncedEnabledSet)
		{
			return syncedEnabled;
		}
		if (Cfg.Enabled != null)
		{
			return Cfg.Enabled.Value;
		}
		return false;
	}

	public static int ApplySynced(Dictionary<string, float> values)
	{
		synced.Clear();
		syncedEnabledSet = false;
		syncedEnabled = true;
		int num = 0;
		foreach (KeyValuePair<string, float> value in values)
		{
			if (value.Key == "General.Enabled")
			{
				syncedEnabled = value.Value > 0.5f;
				syncedEnabledSet = true;
				num++;
			}
			else if (knobs.ContainsKey(value.Key))
			{
				synced[value.Key] = value.Value;
				num++;
			}
		}
		syncedActive = true;
		return num;
	}

	public static void ClearSynced()
	{
		synced.Clear();
		syncedActive = false;
		syncedEnabledSet = false;
	}

	public static float ApplyCurve(SkillType skillType, float factor)
	{
		//IL_0009: Unknown result type (might be due to invalid IL or missing references)
		if (!IsEnabled())
		{
			return factor;
		}
		Knob[] array = Cfg.CurveFor(skillType);
		if (array == null)
		{
			return factor;
		}
		float value = array[0].Value;
		float value2 = array[1].Value;
		float value3 = array[2].Value;
		if (value == 0f && value2 == 1f && value3 == 1f)
		{
			return factor;
		}
		float num = Mathf.Clamp01(factor);
		if (value3 != 1f && value3 > 0f)
		{
			num = Mathf.Pow(num, value3);
		}
		return Mathf.Lerp(value, value2, num);
	}
}