Decompiled source of AdaptiveCreatureScaling v1.3.1

AdaptiveCreatureScaling.dll

Decompiled a day ago
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using HarmonyLib;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Adaptive Creature Scaling")]
[assembly: AssemblyDescription("Scales Valheim creatures against upgraded player equipment.")]
[assembly: AssemblyCompany("Adaptive Creature Scaling contributors")]
[assembly: AssemblyProduct("Adaptive Creature Scaling")]
[assembly: AssemblyCopyright("Copyright © 2026 Adaptive Creature Scaling contributors")]
[assembly: AssemblyFileVersion("1.3.1.0")]
[assembly: AssemblyVersion("1.3.1.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 AdaptiveCreatureScaling
{
	[BepInPlugin("com.forgepotential.adaptivecreaturescaling", "Adaptive Creature Scaling", "1.3.1")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	public sealed class AdaptiveCreatureScalingPlugin : BaseUnityPlugin
	{
		[HarmonyPatch(typeof(Character), "RPC_Damage")]
		private static class CreatureDamageScalingPatch
		{
			private static void Prefix(Character __instance, HitData hit)
			{
				if (!((Object)(object)__instance == (Object)null) && __instance.IsPlayer() && hit != null && IsScalableCreature(hit.GetAttacker()))
				{
					float damageMultiplier = GetDamageMultiplier(GetMaximumQuality());
					if (damageMultiplier > 1f)
					{
						((DamageTypes)(ref hit.m_damage)).Modify(damageMultiplier);
					}
				}
			}
		}

		public const string PluginGuid = "com.forgepotential.adaptivecreaturescaling";

		public const string PluginName = "Adaptive Creature Scaling";

		public const string PluginVersion = "1.3.1";

		private const string PotentialDraughtPluginGuid = "com.forgepotential.potentialdraught";

		private const string PotentialDraughtPluginType = "PotentialDraught.PotentialDraughtPlugin, PotentialDraught";

		private const int DefaultReferenceQuality = 25;

		private const string CreatureBaseHealthZdoKey = "ACS_BaseMaxHealth";

		private const string CreatureScaledQualityZdoKey = "ACS_ScaledQuality";

		private const string BossStarRollCompletedZdoKey = "ACS_BossStarRollCompleted";

		private const string BossStarLevelZdoKey = "ACS_BossStarLevel";

		private const float UpdateInterval = 2f;

		private static readonly FieldInfo CharacterNetViewField = AccessTools.Field(typeof(Character), "m_nview");

		private static ConfigEntry<int> _baselineQuality;

		private static ConfigEntry<int> _maximumQuality;

		private static ConfigEntry<bool> _autoDetectUpgradeCap;

		private static ConfigEntry<int> _referenceQuality;

		private static ConfigEntry<float> _maximumHealthMultiplier;

		private static ConfigEntry<float> _maximumDamageMultiplier;

		private static ConfigEntry<bool> _scaleBosses;

		private static ConfigEntry<bool> _enableBossStars;

		private static ConfigEntry<float> _oneStarBossChance;

		private static ConfigEntry<float> _twoStarBossChance;

		private Harmony _harmony;

		private float _updateTimer;

		private int _lastReportedMaximumQuality = int.MinValue;

		private void Awake()
		{
			//IL_0158: Unknown result type (might be due to invalid IL or missing references)
			//IL_0162: Expected O, but got Unknown
			_baselineQuality = ((BaseUnityPlugin)this).Config.Bind<int>("Scaling", "BaselineItemQuality", 4, "The highest item quality that uses unmodified vanilla creature balance.");
			_maximumQuality = ((BaseUnityPlugin)this).Config.Bind<int>("Scaling", "MaximumItemQuality", 25, "Fallback upgrade ceiling used when automatic Potential Draught cap detection is disabled or unavailable.");
			_autoDetectUpgradeCap = ((BaseUnityPlugin)this).Config.Bind<bool>("Scaling", "AutoDetectUpgradeCap", true, "Automatically use Potential Draught's compiled Forge upgrade cap when that mod is installed.");
			_referenceQuality = ((BaseUnityPlugin)this).Config.Bind<int>("Scaling", "ReferenceItemQuality", 25, "The item quality at which MaximumHealthMultiplier and MaximumDamageMultiplier are reached. Higher detected caps extrapolate the same curve.");
			_maximumHealthMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Scaling", "MaximumHealthMultiplier", 3f, "Creature maximum-health multiplier when nearby equipment reaches MaximumItemQuality.");
			_maximumDamageMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Scaling", "MaximumDamageMultiplier", 2f, "Creature damage multiplier against a player whose equipment reaches MaximumItemQuality.");
			_scaleBosses = ((BaseUnityPlugin)this).Config.Bind<bool>("Scaling", "ScaleBosses", true, "Whether bosses use adaptive health and damage scaling.");
			_enableBossStars = ((BaseUnityPlugin)this).Config.Bind<bool>("Boss Stars", "Enabled", true, "Whether naturally spawned unstarred bosses receive a one-time star roll.");
			_oneStarBossChance = ((BaseUnityPlugin)this).Config.Bind<float>("Boss Stars", "OneStarChancePercent", 10f, "Chance that a naturally spawned unstarred boss becomes a one-star boss.");
			_twoStarBossChance = ((BaseUnityPlugin)this).Config.Bind<float>("Boss Stars", "TwoStarChancePercent", 2f, "Chance that a naturally spawned unstarred boss becomes a two-star boss. This roll takes priority over one star.");
			_harmony = new Harmony("com.forgepotential.adaptivecreaturescaling");
			_harmony.PatchAll(Assembly.GetExecutingAssembly());
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Adaptive Creature Scaling 1.3.1 loaded. All hostile creatures use the " + $"quality-{GetMaximumQuality()} difficulty tier ({GetMaximumQualitySource()})."));
		}

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

		private void Update()
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Invalid comparison between Unknown and I4
			_updateTimer += Time.unscaledDeltaTime;
			if (_updateTimer < 2f)
			{
				return;
			}
			_updateTimer = 0f;
			if (!((Object)(object)ZNet.instance == (Object)null) && (int)ZNet.GetConnectionStatus() == 2)
			{
				ScaleOwnedCreatures();
				int maximumQuality = GetMaximumQuality();
				if (maximumQuality != _lastReportedMaximumQuality)
				{
					_lastReportedMaximumQuality = maximumQuality;
					((BaseUnityPlugin)this).Logger.LogInfo((object)($"Creature scaling target is now quality {maximumQuality} " + "(" + GetMaximumQualitySource() + "): " + $"{GetHealthMultiplier(maximumQuality):0.###}x health, " + $"{GetDamageMultiplier(maximumQuality):0.###}x damage."));
				}
			}
		}

		private static ZNetView GetNetView(Character character)
		{
			if (!((Object)(object)character == (Object)null))
			{
				object? obj = CharacterNetViewField?.GetValue(character);
				return (ZNetView)((obj is ZNetView) ? obj : null);
			}
			return null;
		}

		private static int GetMaximumQuality()
		{
			int num = Mathf.Max(1, _baselineQuality?.Value ?? 4);
			ConfigEntry<bool> autoDetectUpgradeCap = _autoDetectUpgradeCap;
			if ((autoDetectUpgradeCap == null || autoDetectUpgradeCap.Value) && TryGetPotentialDraughtMaximumQuality(out var quality))
			{
				return Mathf.Max(num + 1, quality);
			}
			return Mathf.Max(num + 1, _maximumQuality?.Value ?? 25);
		}

		private static bool TryGetPotentialDraughtMaximumQuality(out int quality)
		{
			quality = 0;
			Type type = Type.GetType("PotentialDraught.PotentialDraughtPlugin, PotentialDraught", throwOnError: false);
			if (type == null && Chainloader.PluginInfos.TryGetValue("com.forgepotential.potentialdraught", out var value))
			{
				type = ((object)value.Instance)?.GetType();
			}
			PropertyInfo propertyInfo = type?.GetProperty("MaximumItemQuality", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			object obj = null;
			if (propertyInfo != null)
			{
				try
				{
					obj = propertyInfo.GetValue(null, null);
				}
				catch
				{
				}
			}
			FieldInfo fieldInfo = type?.GetField("DefaultForgeMaxQuality", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			if (obj == null && fieldInfo == null)
			{
				return false;
			}
			if (obj == null)
			{
				obj = (fieldInfo.IsLiteral ? fieldInfo.GetRawConstantValue() : fieldInfo.GetValue(null));
			}
			if (!(obj is int num) || num < 1)
			{
				return false;
			}
			quality = num;
			return true;
		}

		private static string GetMaximumQualitySource()
		{
			ConfigEntry<bool> autoDetectUpgradeCap = _autoDetectUpgradeCap;
			if ((autoDetectUpgradeCap != null && !autoDetectUpgradeCap.Value) || !TryGetPotentialDraughtMaximumQuality(out var _))
			{
				return "MaximumItemQuality fallback";
			}
			return "Potential Draught auto-detection";
		}

		private static float GetProgress(int quality)
		{
			int num = Mathf.Max(1, _baselineQuality?.Value ?? 4);
			int num2 = Mathf.Max(num + 1, _referenceQuality?.Value ?? 25);
			return Mathf.Max(0f, (float)(quality - num) / (float)(num2 - num));
		}

		private static float GetHealthMultiplier(int quality)
		{
			return Mathf.Lerp(1f, Mathf.Max(1f, _maximumHealthMultiplier?.Value ?? 3f), GetProgress(quality));
		}

		private static float GetDamageMultiplier(int quality)
		{
			return Mathf.Lerp(1f, Mathf.Max(1f, _maximumDamageMultiplier?.Value ?? 2f), GetProgress(quality));
		}

		private static bool IsMonsterCreature(Character character)
		{
			if ((Object)(object)character != (Object)null && !character.IsPlayer() && !character.IsTamed())
			{
				return (Object)(object)character.GetBaseAI() != (Object)null;
			}
			return false;
		}

		private static bool IsScalableCreature(Character character)
		{
			if (IsMonsterCreature(character))
			{
				if (character.IsBoss())
				{
					return _scaleBosses?.Value ?? true;
				}
				return true;
			}
			return false;
		}

		private static void RollBossStars(Character character, ZDO zdo)
		{
			if ((Object)(object)character == (Object)null || zdo == null || !character.IsBoss())
			{
				return;
			}
			ConfigEntry<bool> enableBossStars = _enableBossStars;
			if ((enableBossStars != null && !enableBossStars.Value) || zdo.GetBool("ACS_BossStarRollCompleted", false))
			{
				return;
			}
			int num = Mathf.Max(1, character.GetLevel());
			if (num > 1)
			{
				zdo.Set("ACS_BossStarLevel", num);
				zdo.Set("ACS_BossStarRollCompleted", true);
				return;
			}
			float num2 = Mathf.Clamp(_twoStarBossChance?.Value ?? 2f, 0f, 100f);
			float num3 = Mathf.Clamp(_oneStarBossChance?.Value ?? 10f, 0f, 100f - num2);
			float num4 = Random.Range(0f, 100f);
			int num5 = ((num4 < num2) ? 3 : ((!(num4 < num2 + num3)) ? 1 : 2));
			zdo.Set("ACS_BossStarLevel", num5);
			zdo.Set("ACS_BossStarRollCompleted", true);
			if (num5 > 1)
			{
				character.SetLevel(num5);
				zdo.Set("ACS_BaseMaxHealth", character.GetMaxHealth());
			}
		}

		private static void ScaleOwnedCreatures()
		{
			foreach (Character allCharacter in Character.GetAllCharacters())
			{
				if (!IsMonsterCreature(allCharacter) || allCharacter.GetHealth() <= 0f)
				{
					continue;
				}
				ZNetView netView = GetNetView(allCharacter);
				if ((Object)(object)netView == (Object)null || !netView.IsValid() || !netView.IsOwner())
				{
					continue;
				}
				ZDO zDO = netView.GetZDO();
				if (zDO == null)
				{
					continue;
				}
				RollBossStars(allCharacter, zDO);
				if (IsScalableCreature(allCharacter))
				{
					float num = zDO.GetFloat("ACS_BaseMaxHealth", 0f);
					if (num <= 0f)
					{
						num = Mathf.Max(1f, allCharacter.GetMaxHealth());
						zDO.Set("ACS_BaseMaxHealth", num);
					}
					int maximumQuality = GetMaximumQuality();
					int num2 = zDO.GetInt("ACS_ScaledQuality", int.MinValue);
					float num3 = num * GetHealthMultiplier(maximumQuality);
					float num4 = Mathf.Max(1f, allCharacter.GetMaxHealth());
					if (num2 != maximumQuality || !(Mathf.Abs(num4 - num3) < 0.05f))
					{
						float num5 = Mathf.Clamp01(allCharacter.GetHealth() / num4);
						allCharacter.SetMaxHealth(num3);
						allCharacter.SetHealth(Mathf.Max(1f, num3 * num5));
						zDO.Set("ACS_ScaledQuality", maximumQuality);
					}
				}
			}
		}
	}
}