Decompiled source of Tamed Creature Balance v1.0.1

BepInEx/plugins/TamedCreatureBalance/TamedCreatureBalance.dll

Decompiled a week ago
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;

[assembly: AssemblyFileVersion("1.0.1.0")]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("1.0.1.0")]
namespace Fai.TamedCreatureBalance;

[BepInPlugin("fai.tamedcreaturebalance", "Tamed Creature Balance", "1.0.1")]
[BepInIncompatibility("fai.tamedwolfbalance")]
public sealed class Plugin : BaseUnityPlugin
{
	internal struct HealthRecord
	{
		internal float Normal;

		internal float Maximum;

		internal bool Current;

		internal bool NeedsRepair;
	}

	[HarmonyPatch(typeof(Tameable), "Tame", new Type[] { })]
	internal static class TamePatch
	{
		[HarmonyPriority(0)]
		internal static void Postfix(Tameable __instance)
		{
			Character component = ((Component)__instance).GetComponent<Character>();
			if (IsTamedCreature(component))
			{
				Apply(component);
			}
		}
	}

	internal struct LevelSnapshot
	{
		internal float Normal;

		internal int Level;
	}

	[HarmonyPatch(typeof(Character), "SetLevel", new Type[] { typeof(int) })]
	internal static class LevelPatch
	{
		[HarmonyPriority(800)]
		internal static void Prefix(Character __instance, out LevelSnapshot __state)
		{
			__state = default(LevelSnapshot);
			if (IsTamedCreature(__instance) && __instance.IsOwner() && !__instance.IsDead() && ReadSaved(Data(__instance), out var record))
			{
				__state.Normal = record.Normal;
				__state.Level = __instance.GetLevel();
			}
		}

		[HarmonyPriority(0)]
		internal static void Postfix(Character __instance, LevelSnapshot __state)
		{
			if (__state.Level < 1 || !IsTamedCreature(__instance) || !__instance.IsOwner() || __instance.IsDead())
			{
				return;
			}
			int level = __instance.GetLevel();
			if (level < 1 || level == __state.Level)
			{
				return;
			}
			ZDO val = Data(__instance);
			if (val != null)
			{
				float num = __state.Normal * ((float)level / (float)__state.Level);
				if (Plan(__instance.GetMaxHealth(), __instance.GetHealth(), num, out var targetMaximum, out var targetHealth))
				{
					Save(__instance, val, num, targetMaximum, targetHealth);
				}
			}
		}
	}

	[HarmonyPatch(typeof(Character), "SetMaxHealth", new Type[] { typeof(float) })]
	internal static class WriteMaxPatch
	{
		[HarmonyPriority(0)]
		internal static void Prefix(Character __instance, ref float __0)
		{
			if (Target(__instance, out var maximum))
			{
				__0 = maximum;
			}
		}
	}

	[HarmonyPatch(typeof(Character), "GetMaxHealth", new Type[] { })]
	internal static class ReadMaxPatch
	{
		[HarmonyPriority(0)]
		internal static void Postfix(Character __instance, ref float __result)
		{
			if (Target(__instance, out var maximum))
			{
				__result = maximum;
			}
		}
	}

	[HarmonyPatch(typeof(Attack), "ModifyDamage", new Type[]
	{
		typeof(HitData),
		typeof(float)
	})]
	internal static class DamagePatch
	{
		private static readonly ConditionalWeakTable<HitData, object> adjusted = new ConditionalWeakTable<HitData, object>();

		private static readonly object adjustedMarker = new object();

		[HarmonyPriority(0)]
		internal static void Postfix(Humanoid ___m_character, HitData __0)
		{
			if (__0 != null && IsTamedCreature((Character)(object)___m_character) && !adjusted.TryGetValue(__0, out var _))
			{
				adjusted.Add(__0, adjustedMarker);
				((DamageTypes)(ref __0.m_damage)).Modify(0.35f);
			}
		}
	}

	[HarmonyPatch(typeof(BaseAI), "UpdateRegeneration", new Type[] { typeof(float) })]
	internal static class RegenerationPatch
	{
		internal static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			List<CodeInstruction> list = new List<CodeInstruction>(instructions);
			MethodInfo method = typeof(Character).GetMethod("Heal", new Type[2]
			{
				typeof(float),
				typeof(bool)
			});
			MethodInfo method2 = typeof(Plugin).GetMethod("HealRegeneration", BindingFlags.Static | BindingFlags.NonPublic);
			int num = 0;
			foreach (CodeInstruction item in list)
			{
				if ((item.opcode == OpCodes.Callvirt || item.opcode == OpCodes.Call) && object.Equals(item.operand, method))
				{
					item.opcode = OpCodes.Call;
					item.operand = method2;
					num++;
				}
			}
			if (num != 1)
			{
				throw new InvalidOperationException("TCB: unrecognized BaseAI.UpdateRegeneration layout (Heal calls: " + num + ").");
			}
			return list;
		}
	}

	public const string Id = "fai.tamedcreaturebalance";

	public const string Version = "1.0.1";

	internal const float HealthFactor = 3f;

	internal const float DamageFactor = 0.35f;

	internal const float RegenerationFactor = 0.01f;

	internal const string Marker = "fai_tcb_v100";

	internal const string BaseKey = "fai_tcb_basehp_v100";

	internal const string MaxKey = "fai_tcb_maxhp_v100";

	internal const string SourceMarker = "fai_twb_alltamed_v170";

	internal const string SourceBaseKey = "fai_twb_alltamed_basehp_v170";

	internal const string SourceMaxKey = "fai_twb_alltamed_maxhp_v170";

	internal const string PreviousMarker = "fai_twb_alltamed_v160";

	internal const string PreviousBaseKey = "fai_twb_alltamed_basehp_v160";

	internal const string PreviousMaxKey = "fai_twb_alltamed_maxhp_v160";

	internal const string LegacyMarker = "fai_twb_newtame_v150";

	internal const string LegacyMaxKey = "fai_twb_newtame_maxhp_v150";

	internal static ManualLogSource Log;

	private Harmony harmony;

	private bool ready;

	private float scanTimer;

	private void Awake()
	{
		//IL_003a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0044: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		((BaseUnityPlugin)this).Logger.LogInfo((object)("TCB 1.0.1 LOADING: " + typeof(Plugin).Assembly.Location));
		harmony = new Harmony("fai.tamedcreaturebalance");
		try
		{
			harmony.PatchAll(typeof(Plugin).Assembly);
			ready = true;
			((BaseUnityPlugin)this).Logger.LogInfo((object)"TCB 1.0.1 READY | all tamed creatures | HP x3 | damage x0.35 | fed regeneration 1% per tick");
		}
		catch (Exception ex)
		{
			harmony.UnpatchSelf();
			((BaseUnityPlugin)this).Logger.LogError((object)("TCB 1.0.1 PATCH FAILED; no balance patches active: " + ex));
		}
	}

	private void Update()
	{
		if (!ready)
		{
			return;
		}
		scanTimer -= Time.unscaledDeltaTime;
		if (!(scanTimer > 0f))
		{
			scanTimer = 1f;
			List<Character> allCharacters = Character.GetAllCharacters();
			for (int i = 0; i < allCharacters.Count; i++)
			{
				Apply(allCharacters[i]);
			}
		}
	}

	private void OnDestroy()
	{
		ready = false;
		if (harmony != null)
		{
			harmony.UnpatchSelf();
		}
		Log = null;
	}

	internal static bool Positive(float value)
	{
		return value > 0f && !float.IsNaN(value) && !float.IsInfinity(value);
	}

	internal static bool IsTamedCreature(Character c)
	{
		return (Object)(object)c != (Object)null && !c.IsPlayer() && c.IsTamed();
	}

	internal static ZDO Data(Character c)
	{
		if ((Object)(object)c == (Object)null)
		{
			return null;
		}
		ZNetView val = ((!((Object)(object)c.m_nViewOverride != (Object)null)) ? ((Component)c).GetComponent<ZNetView>() : c.m_nViewOverride);
		return (!((Object)(object)val != (Object)null) || !val.IsValid()) ? null : val.GetZDO();
	}

	private static bool ReadRecord(ZDO zdo, string marker, string baseKey, string maxKey, float factor, out HealthRecord record)
	{
		record = default(HealthRecord);
		if (!zdo.GetBool(marker, false))
		{
			return false;
		}
		float num = ((baseKey != null) ? zdo.GetFloat(baseKey, 0f) : 0f);
		float num2 = zdo.GetFloat(maxKey, 0f);
		float num3 = num;
		if (!Positive(num3) || !Positive(num3 * factor))
		{
			num3 = num2 / factor;
		}
		float num4 = num3 * factor;
		if (!Positive(num3) || !Positive(num4))
		{
			return false;
		}
		record = new HealthRecord
		{
			Normal = num3,
			Maximum = num4,
			NeedsRepair = (num != num3 || num2 != num4)
		};
		return true;
	}

	internal static bool ReadSaved(ZDO zdo, out HealthRecord record)
	{
		record = default(HealthRecord);
		if (zdo == null)
		{
			return false;
		}
		if (ReadRecord(zdo, "fai_tcb_v100", "fai_tcb_basehp_v100", "fai_tcb_maxhp_v100", 3f, out record))
		{
			record.Current = true;
			return true;
		}
		return ReadRecord(zdo, "fai_twb_alltamed_v170", "fai_twb_alltamed_basehp_v170", "fai_twb_alltamed_maxhp_v170", 3f, out record) || ReadRecord(zdo, "fai_twb_alltamed_v160", "fai_twb_alltamed_basehp_v160", "fai_twb_alltamed_maxhp_v160", 5f, out record) || ReadRecord(zdo, "fai_twb_newtame_v150", null, "fai_twb_newtame_maxhp_v150", 3f, out record);
	}

	internal static bool Target(Character c, out float maximum)
	{
		maximum = 0f;
		if (!IsTamedCreature(c) || !ReadSaved(Data(c), out var record))
		{
			return false;
		}
		maximum = record.Maximum;
		return true;
	}

	internal static bool Plan(float currentMaximum, float currentHealth, float normalMaximum, out float targetMaximum, out float targetHealth)
	{
		targetMaximum = (targetHealth = 0f);
		if (!Positive(currentMaximum) || !Positive(currentHealth) || !Positive(normalMaximum))
		{
			return false;
		}
		float num = normalMaximum * 3f;
		if (!Positive(num))
		{
			return false;
		}
		targetMaximum = num;
		targetHealth = Math.Min(1f, currentHealth / currentMaximum) * num;
		return Positive(targetHealth);
	}

	private static void Save(Character c, ZDO zdo, float normal, float maximum, float health)
	{
		zdo.Set("fai_tcb_basehp_v100", normal);
		zdo.Set("fai_tcb_maxhp_v100", maximum);
		zdo.Set("fai_tcb_v100", true);
		c.SetMaxHealth(maximum);
		c.SetHealth(health);
	}

	internal static void Apply(Character c)
	{
		//IL_0155: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)c == (Object)null || c.IsPlayer() || !c.IsOwner() || c.IsDead())
		{
			return;
		}
		ZDO val = Data(c);
		if (val == null)
		{
			return;
		}
		HealthRecord record;
		bool flag = ReadSaved(val, out record);
		if (!c.IsTamed())
		{
			if (flag)
			{
				Restore(c, val, record.Normal);
			}
		}
		else
		{
			if ((flag && record.Current && !record.NeedsRepair) || (!flag && (val.GetBool("fai_tcb_v100", false) || val.GetBool("fai_twb_alltamed_v170", false) || val.GetBool("fai_twb_alltamed_v160", false) || val.GetBool("fai_twb_newtame_v150", false))))
			{
				return;
			}
			float maxHealth = c.GetMaxHealth();
			float health = c.GetHealth();
			float num = ((!flag) ? maxHealth : record.Normal);
			if (Plan(maxHealth, health, num, out var targetMaximum, out var targetHealth))
			{
				Save(c, val, num, targetMaximum, targetHealth);
				if (Log != null)
				{
					Log.LogInfo((object)string.Concat("TCB ", (!flag) ? "APPLIED" : "UPDATED", " | ZDO=", c.GetZDOID(), " | normalMax=", num.ToString("0.##"), " | HP=", c.GetHealth().ToString("0.##"), "/", targetMaximum.ToString("0.##"), " | damage=x0.35"));
				}
			}
		}
	}

	private static void Restore(Character c, ZDO zdo, float normal)
	{
		float maxHealth = c.GetMaxHealth();
		float health = c.GetHealth();
		if (Positive(maxHealth) && Positive(health) && Positive(normal))
		{
			float num = Math.Min(1f, health / maxHealth) * normal;
			if (Positive(num))
			{
				zdo.Set("fai_tcb_v100", false);
				zdo.Set("fai_twb_alltamed_v170", false);
				zdo.Set("fai_twb_alltamed_v160", false);
				zdo.Set("fai_twb_newtame_v150", false);
				c.SetMaxHealth(normal);
				c.SetHealth(num);
			}
		}
	}

	internal static void HealRegeneration(Character c, float amount, bool showText)
	{
		if ((Object)(object)c == (Object)null)
		{
			return;
		}
		if (IsTamedCreature(c))
		{
			Tameable component = ((Component)c).GetComponent<Tameable>();
			if ((Object)(object)component != (Object)null)
			{
				if (component.IsHungry() || !c.IsOwner() || c.IsDead() || !Positive(c.GetHealth()))
				{
					return;
				}
				float maxHealth = c.GetMaxHealth();
				if (!Positive(maxHealth))
				{
					return;
				}
				amount = maxHealth * 0.01f;
			}
		}
		c.Heal(amount, showText);
	}
}