Decompiled source of CraftingXP v1.8.0

CraftingXP_v1.8.0.dll

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

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("CraftingXP")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.8.0.0")]
[assembly: AssemblyInformationalVersion("1.8.0")]
[assembly: AssemblyProduct("CraftingXP")]
[assembly: AssemblyTitle("CraftingXP")]
[assembly: AssemblyVersion("1.8.0.0")]
namespace CraftingXP;

[BepInPlugin("frost.craftingxp", "CraftingXP", "1.8.0")]
public class CraftingXPPlugin : BaseUnityPlugin
{
	public const string PluginGuid = "frost.craftingxp";

	public const string PluginName = "CraftingXP";

	public const string PluginVersion = "1.8.0";

	internal static ManualLogSource Log;

	internal static ConfigEntry<float> GlobalMultiplier;

	internal static ConfigEntry<float> BuildMultiplier;

	internal static ConfigEntry<float> BonusPerPowerPoint;

	internal static ConfigEntry<float> MaxXP;

	internal static ConfigEntry<int> RemoveGrace;

	internal static ConfigEntry<float> BonusPerBarTier;

	internal static ConfigEntry<float> NonMetalXP;

	internal static ConfigEntry<string> BarTiersConfig;

	internal static ConfigEntry<int> BonusMaxExtra;

	internal static ConfigEntry<float> BonusEscalationChance;

	internal static ConfigEntry<int> CascadeMinBatch;

	internal static ConfigEntry<float> BarBonus2Start;

	internal static ConfigEntry<float> BarBonus2PerLevel;

	internal static ConfigEntry<float> BarBonus2Cap;

	private void Awake()
	{
		//IL_0236: Unknown result type (might be due to invalid IL or missing references)
		Log = ((BaseUnityPlugin)this).Logger;
		GlobalMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("CraftingXP", "GlobalMultiplier", 2f, "Во сколько раз усиливается весь опыт Ремесла (крафт, постройка, ремонт)");
		BuildMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("CraftingXP", "BuildMultiplier", 8f, "Дополнительный множитель опыта ТОЛЬКО за постановку деталей молотом, применяется поверх GlobalMultiplier: ваниль 0.25 × Global 2 × Build 8 = 4 XP за деталь. На крафт и ремонт не действует. Потолок MaxXP действует и здесь — если хочешь больше 5 XP за деталь, подними и MaxXP");
		BonusMaxExtra = ((BaseUnityPlugin)this).Config.Bind<int>("CraftingXP", "BonusMaxExtra", 10, "Каскад бонуса для пакетных рецептов (стрелы, гвозди — партия 20): ванильный шанс прока не меняется, но если прокнуло — размер бонуса бросается каскадом от +1 до BonusMaxExtra, каждая следующая ступень выпадает реже");
		BonusEscalationChance = ((BaseUnityPlugin)this).Config.Bind<float>("CraftingXP", "BonusEscalationChance", 65f, "% усиления на каждую ступень каскада: 65 → +1 ≈ 35%, +2 ≈ 23%, +5 ≈ 6%, +10 ≈ 2%. Больше — жирнее джекпоты");
		CascadeMinBatch = ((BaseUnityPlugin)this).Config.Bind<int>("CraftingXP", "CascadeMinBatch", 10, "Размер партии рецепта, с которой работает каскад (стрелы/гвозди = 20). Рецепты с меньшей партией (бронза = 4) живут по правилу BarBonus2 ниже");
		BarBonus2Start = ((BaseUnityPlugin)this).Config.Bind<float>("CraftingXP", "BarBonus2Start", 10f, "Шанс (%) на бонус +2 слитка (вместо +1) при навыке ровно 50. Ниже 50 — всегда +1");
		BarBonus2PerLevel = ((BaseUnityPlugin)this).Config.Bind<float>("CraftingXP", "BarBonus2PerLevel", 0.3f, "Сколько % прибавляется к шансу +2 за каждый уровень навыка выше 50 (10% на 50-м → 17.5% на 75-м → 25% на 100-м)");
		BarBonus2Cap = ((BaseUnityPlugin)this).Config.Bind<float>("CraftingXP", "BarBonus2Cap", 25f, "Потолок шанса +2 слитка, %");
		BonusPerPowerPoint = ((BaseUnityPlugin)this).Config.Bind<float>("CraftingXP", "BonusPerPowerPoint", 0.02f, "Бонусный опыт за каждую единицу силы крафтимой вещи (урон, броня или блок)");
		MaxXP = ((BaseUnityPlugin)this).Config.Bind<float>("CraftingXP", "MaxXP", 5f, "Максимум опыта за одно действие");
		RemoveGrace = ((BaseUnityPlugin)this).Config.Bind<int>("CraftingXP", "RemoveGrace", 3, "Сколько построек подряд можно снести молотом без потери опыта (долг за снос не больше этого числа прощается; дальше долг = числу снесённого)");
		BonusPerBarTier = ((BaseUnityPlugin)this).Config.Bind<float>("CraftingXP", "BonusPerBarTier", 1f, "Опыт за одну заложенную руду = тир металла × это число (тиры в BarTiers). В ванили плавка не даёт опыта вообще. Потолок MaxXP на плавку не действует — значение ограничено только старшим тиром");
		NonMetalXP = ((BaseUnityPlugin)this).Config.Bind<float>("CraftingXP", "NonMetalXP", 0.3f, "Опыт за закладку неметалла (ячмень в мельницу и т.п.): конверсия есть, тира в BarTiers нет. Печи дают тир x BonusPerBarTier (железо = 3), здесь фиксированные 0.3 — примерно в 10 раз меньше. 0 = выключить");
		BarTiersConfig = ((BaseUnityPlugin)this).Config.Bind<string>("CraftingXP", "BarTiers", "Copper:1,Tin:1,Bronze:2,Iron:3,Silver:4,BlackMetal:5,EitrInfusedIron:6,Flametal:7", "Тиры слитков: ИмяПрефаба:тир, через запятую. Сравнение по полному имени, регистр не важен. Если у тебя другое название слитка эйтр-стали — посмотри его в LogOutput.log (строка 'Слиток без тира') и впиши сюда");
		BarTiers.Parse(BarTiersConfig.Value);
		BarTiersConfig.SettingChanged += delegate
		{
			BarTiers.Parse(BarTiersConfig.Value);
		};
		new Harmony("frost.craftingxp").PatchAll();
		((BaseUnityPlugin)this).Logger.LogInfo((object)"CraftingXP 1.8.0 загружен");
	}
}
internal static class CraftContext
{
	public const float None = -1f;

	public static float Power = -1f;
}
internal static class BuildContext
{
	public static bool Pending;
}
internal static class SmeltContext
{
	public const float None = -1f;

	public static float XP = -1f;
}
internal static class BarTiers
{
	private static readonly List<KeyValuePair<string, int>> _tiers = new List<KeyValuePair<string, int>>();

	private static readonly HashSet<string> _reportedUnknown = new HashSet<string>();

	public static void Parse(string config)
	{
		_tiers.Clear();
		string[] array = config.Split(',');
		for (int i = 0; i < array.Length; i++)
		{
			string[] array2 = array[i].Split(':');
			if (array2.Length == 2 && int.TryParse(array2[1].Trim(), out var result) && array2[0].Trim().Length > 0)
			{
				_tiers.Add(new KeyValuePair<string, int>(array2[0].Trim(), result));
			}
		}
		CraftingXPPlugin.Log.LogInfo((object)("Тиры слитков: " + config));
	}

	public static int GetTier(string prefabName, string sharedName)
	{
		string text = ((prefabName != null) ? prefabName.Replace("(Clone)", "") : "");
		string b = ((sharedName != null && sharedName.StartsWith("$item_")) ? sharedName.Substring(6) : (sharedName ?? ""));
		foreach (KeyValuePair<string, int> tier in _tiers)
		{
			if (string.Equals(tier.Key, text, StringComparison.OrdinalIgnoreCase) || string.Equals(tier.Key, b, StringComparison.OrdinalIgnoreCase))
			{
				return tier.Value;
			}
		}
		ReportUnknown(text);
		return 0;
	}

	private static void ReportUnknown(string prefab)
	{
		if (!string.IsNullOrEmpty(prefab))
		{
			string text = prefab.ToLowerInvariant();
			if ((text.Contains("metal") || text.Contains("eitr") || text.Contains("flame") || text.Contains("copper") || text.Contains("tin") || text.Contains("bronze") || text.Contains("iron") || text.Contains("silver")) && _reportedUnknown.Add(text))
			{
				CraftingXPPlugin.Log.LogInfo((object)("Слиток без тира: " + prefab + " — можно добавить в BarTiers в конфиге"));
			}
		}
	}
}
[HarmonyPatch(typeof(Smelter), "OnAddOre")]
internal static class Smelter_OnAddOre_Patch
{
	private static readonly FieldInfo ConversionField = AccessTools.Field(typeof(Smelter), "m_conversion");

	private static readonly HashSet<string> _skipReported = new HashSet<string>();

	private static void Postfix(Smelter __instance, Humanoid user, ItemData item, bool __result)
	{
		if (!__result)
		{
			return;
		}
		Player val = (Player)(object)((user is Player) ? user : null);
		if ((Object)(object)val == (Object)null || (Object)(object)val != (Object)(object)Player.m_localPlayer)
		{
			return;
		}
		ItemConversion val2 = FindConversion(__instance, user, item);
		if (val2 == null)
		{
			ReportSkip(__instance, item, "нет подходящей конверсии (не металл для этой печи)");
			return;
		}
		ItemDrop to = val2.m_to;
		if ((Object)(object)to == (Object)null || to.m_itemData == null || to.m_itemData.m_shared == null)
		{
			ReportSkip(__instance, item, "у конверсии нет слитка");
			return;
		}
		int tier = BarTiers.GetTier(((Object)((Component)to).gameObject).name, to.m_itemData.m_shared.m_name);
		if (tier <= 0)
		{
			float value = CraftingXPPlugin.NonMetalXP.Value;
			if (value <= 0f)
			{
				ReportSkip(__instance, item, "у слитка нет тира в BarTiers (NonMetalXP = 0)");
				return;
			}
			string arg = ((item != null && (Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : (item?.m_shared?.m_name ?? "?"));
			SmeltContext.XP = value;
			((Character)val).RaiseSkill((SkillType)107, value);
			CraftingXPPlugin.Log.LogInfo((object)$"[CraftingXP] {((Object)((Component)__instance).gameObject).name}: неметалл '{arg}' — +{value:F2} XP");
		}
		else
		{
			float num = (SmeltContext.XP = (float)tier * CraftingXPPlugin.BonusPerBarTier.Value);
			((Character)val).RaiseSkill((SkillType)107, num);
			CraftingXPPlugin.Log.LogInfo((object)string.Format("Руда в печь ({0} → {1}): +{2:F2} XP (тир {3})", (item != null && (Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : "?", ((Object)((Component)to).gameObject).name, num, tier));
		}
	}

	private static void ReportSkip(Smelter smelter, ItemData item, string reason)
	{
		string text = ((item != null && (Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : (item?.m_shared?.m_name ?? "?"));
		string item2 = ((Object)((Component)smelter).gameObject).name + "|" + text;
		if (_skipReported.Add(item2))
		{
			CraftingXPPlugin.Log.LogInfo((object)("[CraftingXP] " + ((Object)((Component)smelter).gameObject).name + ": '" + text + "' — опыт не начислен (" + reason + ")"));
		}
	}

	private static ItemConversion FindConversion(Smelter smelter, Humanoid user, ItemData item)
	{
		if (!(ConversionField?.GetValue(smelter) is IEnumerable<ItemConversion> enumerable))
		{
			return null;
		}
		string text = ((item != null && (Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : null);
		foreach (ItemConversion item2 in enumerable)
		{
			if (item2 == null || (Object)(object)item2.m_from == (Object)null || (Object)(object)item2.m_to == (Object)null)
			{
				continue;
			}
			if (text != null)
			{
				if (((Object)((Component)item2.m_from).gameObject).name == text)
				{
					return item2;
				}
			}
			else if (user.GetInventory() != null && user.GetInventory().GetItem(item2.m_from.m_itemData.m_shared.m_name, -1, false) != null)
			{
				return item2;
			}
		}
		return null;
	}
}
[HarmonyPatch(typeof(Player), "RaiseSkill")]
internal static class Player_RaiseSkill_Patch
{
	private static float _nextLogTime;

	private static void Prefix(Player __instance, SkillType skill, ref float value)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0003: Invalid comparison between Unknown and I4
		if ((int)skill != 107 || value <= 0f)
		{
			return;
		}
		float xP = SmeltContext.XP;
		SmeltContext.XP = -1f;
		if (xP > 0f)
		{
			value = xP;
			return;
		}
		bool pending = BuildContext.Pending;
		BuildContext.Pending = false;
		if (pending)
		{
			value = Mathf.Min(value * CraftingXPPlugin.GlobalMultiplier.Value * CraftingXPPlugin.BuildMultiplier.Value, CraftingXPPlugin.MaxXP.Value);
			if (Time.time >= _nextLogTime)
			{
				_nextLogTime = Time.time + 10f;
				CraftingXPPlugin.Log.LogInfo((object)$"XP за деталь: {value:F2}");
			}
			return;
		}
		float num = ((CraftContext.Power > 0f) ? CraftContext.Power : 0f);
		CraftContext.Power = -1f;
		value = Mathf.Min(value * CraftingXPPlugin.GlobalMultiplier.Value + num * CraftingXPPlugin.BonusPerPowerPoint.Value, CraftingXPPlugin.MaxXP.Value);
		if (Time.time >= _nextLogTime)
		{
			_nextLogTime = Time.time + 10f;
			CraftingXPPlugin.Log.LogInfo((object)$"Crafting XP: {value:F2} (сила вещи: {num:F0})");
		}
	}
}
[HarmonyPatch(typeof(Player), "TryPlacePiece")]
internal static class Player_TryPlacePiece_Patch
{
	private static void Postfix(bool __result)
	{
		if (__result)
		{
			BuildContext.Pending = true;
		}
	}
}
[HarmonyPatch(typeof(InventoryGui), "DoCrafting")]
internal static class InventoryGui_DoCrafting_Patch
{
	private static readonly FieldInfo BonusAmountField = AccessTools.Field(typeof(InventoryGui), "m_craftBonusAmount");

	private static bool _bonusSet;

	private static void Prefix(InventoryGui __instance)
	{
		Recipe val = null;
		try
		{
			object? obj = AccessTools.Field(typeof(InventoryGui), "m_craftRecipe")?.GetValue(__instance);
			val = (Recipe)((obj is Recipe) ? obj : null);
			CraftContext.Power = CraftMath.CraftPower(val?.m_item?.m_itemData?.m_shared);
		}
		catch (Exception ex)
		{
			Debug.LogError((object)("[CraftingXP] Ошибка расчёта силы вещи: " + ex));
		}
		try
		{
			_bonusSet = false;
			if (BonusAmountField == null || (Object)(object)val == (Object)null || (Object)(object)val.m_item == (Object)null)
			{
				return;
			}
			int amount = val.m_amount;
			int num = 1;
			if (amount >= CraftingXPPlugin.CascadeMinBatch.Value)
			{
				int i;
				for (i = 1; i < CraftingXPPlugin.BonusMaxExtra.Value; i++)
				{
					if (!(Random.value < CraftingXPPlugin.BonusEscalationChance.Value / 100f))
					{
						break;
					}
				}
				num = i;
			}
			else
			{
				float num2 = (((Object)(object)Player.m_localPlayer != (Object)null) ? (((Character)Player.m_localPlayer).GetSkillFactor((SkillType)107) * 100f) : 0f);
				if (num2 > 50f)
				{
					float num3 = Mathf.Min(CraftingXPPlugin.BarBonus2Start.Value + (num2 - 50f) * CraftingXPPlugin.BarBonus2PerLevel.Value, CraftingXPPlugin.BarBonus2Cap.Value);
					if (Random.value < num3 / 100f)
					{
						num = 2;
					}
				}
			}
			if (num != 1)
			{
				BonusAmountField.SetValue(__instance, num);
				_bonusSet = true;
				CraftingXPPlugin.Log.LogInfo((object)$"Каскад бонуса: рецепт на {amount} шт, бонус +{num}");
			}
		}
		catch (Exception ex2)
		{
			Debug.LogError((object)("[CraftingXP] Ошибка каскада бонуса: " + ex2));
		}
	}

	private static void Postfix(InventoryGui __instance)
	{
		try
		{
			if (_bonusSet && BonusAmountField != null)
			{
				BonusAmountField.SetValue(__instance, 1);
				_bonusSet = false;
			}
		}
		catch (Exception ex)
		{
			Debug.LogError((object)("[CraftingXP] Ошибка сброса бонуса: " + ex));
		}
	}
}
[HarmonyPatch(typeof(Player), "UpdatePlacement")]
internal static class Player_UpdatePlacement_Patch
{
	private static readonly FieldInfo DebtField = AccessTools.Field(typeof(Player), "m_buildRemoveDebt");

	private static int _debtBefore;

	private static void Prefix(Player __instance)
	{
		BuildContext.Pending = false;
		_debtBefore = ((DebtField != null) ? ((int)DebtField.GetValue(__instance)) : 0);
	}

	private static void Postfix(Player __instance)
	{
		BuildContext.Pending = false;
		if (DebtField == null)
		{
			return;
		}
		int num = (int)DebtField.GetValue(__instance);
		if (num <= _debtBefore)
		{
			if (_debtBefore > 0 && num == 0)
			{
				((Character)__instance).Message((MessageType)2, "Штраф за снос снят — опыт за постройки вернулся", 0, (Sprite)null, false);
				CraftingXPPlugin.Log.LogInfo((object)"Долг за снос полностью выплачен");
			}
		}
		else if (num <= CraftingXPPlugin.RemoveGrace.Value)
		{
			DebtField.SetValue(__instance, 0);
			CraftingXPPlugin.Log.LogInfo((object)$"Снос прощён (льгота {CraftingXPPlugin.RemoveGrace.Value}): долг {num} → 0");
		}
		else
		{
			if (_debtBefore <= CraftingXPPlugin.RemoveGrace.Value)
			{
				((Character)__instance).Message((MessageType)2, $"Штраф за снос: следующие {num} построек без опыта Ремесла", 0, (Sprite)null, false);
			}
			CraftingXPPlugin.Log.LogInfo((object)string.Format("Долг за снос: {0} (следующие {0} построек без XP)", num));
		}
	}
}
internal static class CraftMath
{
	public static float CraftPower(SharedData shared)
	{
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		//IL_000f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		//IL_0016: Unknown result type (might be due to invalid IL or missing references)
		//IL_001d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0024: Unknown result type (might be due to invalid IL or missing references)
		//IL_002b: 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_0039: Unknown result type (might be due to invalid IL or missing references)
		//IL_0040: Unknown result type (might be due to invalid IL or missing references)
		//IL_0047: Unknown result type (might be due to invalid IL or missing references)
		//IL_004e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0055: Unknown result type (might be due to invalid IL or missing references)
		if (shared == null)
		{
			return 0f;
		}
		DamageTypes damages = shared.m_damages;
		return Mathf.Max(damages.m_damage + damages.m_blunt + damages.m_slash + damages.m_pierce + damages.m_fire + damages.m_frost + damages.m_lightning + damages.m_poison + damages.m_spirit + damages.m_chop + damages.m_pickaxe, Mathf.Max(shared.m_armor, shared.m_blockPower));
	}
}