Decompiled source of MonsterFallDamage v1.1.0

plugins/MonsterFallDamagePlugin/MonsterFallDamagePlugin.dll

Decompiled 2 weeks ago
using System;
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.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("MonsterFallDamagePlugin")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("MonsterFallDamagePlugin")]
[assembly: AssemblyTitle("MonsterFallDamagePlugin")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace MonsterFallDamagePlugin;

[BepInPlugin("com.mods.monsterfallDamage", "MonsterFallDamage", "1.1.0")]
[BepInProcess("valheim.exe")]
public class Plugin : BaseUnityPlugin
{
	public const string GUID = "com.mods.monsterfallDamage";

	public const string NAME = "MonsterFallDamage";

	public const string VERSION = "1.1.0";

	internal static ManualLogSource Log;

	private readonly Harmony _harmony = new Harmony("com.mods.monsterfallDamage");

	internal static ConfigEntry<bool> ApplyToPlayer;

	internal static ConfigEntry<float> MinFallHeight;

	internal static ConfigEntry<float> DamageDistance;

	internal static ConfigEntry<float> DamagePerUnit;

	internal static ConfigEntry<float> MaxDamage;

	internal static ConfigEntry<bool> IgnoreJumpAscent;

	internal static ConfigEntry<bool> IgnoreFlyingLandings;

	internal static ConfigEntry<float> FlyingLandingGraceSeconds;

	internal static ConfigEntry<bool> EnableDebugLog;

	internal static ConfigEntry<string> ExemptPrefabPrefixes;

	private void Awake()
	{
		//IL_005a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0064: Expected O, but got Unknown
		//IL_0097: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a1: Expected O, but got Unknown
		//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00de: Expected O, but got Unknown
		//IL_0111: Unknown result type (might be due to invalid IL or missing references)
		//IL_011b: Expected O, but got Unknown
		//IL_018e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0198: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		ApplyToPlayer = ((BaseUnityPlugin)this).Config.Bind<bool>("Fall Damage", "Apply To Player", true, "If true, the same fall damage formula is applied to the player (overrides Valheim default).");
		MinFallHeight = ((BaseUnityPlugin)this).Config.Bind<float>("Fall Damage", "Min Fall Height (m)", 4f, new ConfigDescription("Falls below this height deal no damage. (Player default: 4)", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 20f), Array.Empty<object>()));
		DamageDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Fall Damage", "Damage Interval (m)", 16f, new ConfigDescription("Damage is added for every X meters fallen above the minimum height. (Player default: 16)", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 100f), Array.Empty<object>()));
		DamagePerUnit = ((BaseUnityPlugin)this).Config.Bind<float>("Fall Damage", "Damage Per Interval", 100f, new ConfigDescription("Damage dealt per interval distance. (Player default: 100)", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>()));
		MaxDamage = ((BaseUnityPlugin)this).Config.Bind<float>("Fall Damage", "Max Damage (0 for unlimited)", 100f, new ConfigDescription("Maximum fall damage cap. Set to 0 for no limit. (Player default: 100)", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 99999f), Array.Empty<object>()));
		IgnoreJumpAscent = ((BaseUnityPlugin)this).Config.Bind<bool>("Fall Exemption", "IgnoreJumpAscent", true, "Height gained by a creature's own jump is not counted toward fall damage (hopping on flat ground deals no damage; jumping off a cliff only counts the drop below the take-off point).");
		IgnoreFlyingLandings = ((BaseUnityPlugin)this).Config.Bind<bool>("Fall Exemption", "IgnoreFlyingLandings", true, "While flying, the altitude baseline is reset every frame, and no fall damage is applied for a short grace period after landing.");
		FlyingLandingGraceSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Fall Exemption", "FlyingLandingGraceSeconds", 3f, new ConfigDescription("Grace period (seconds) after a flying creature lands during which fall damage is not applied.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), Array.Empty<object>()));
		EnableDebugLog = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "EnableDebugLog", false, "Logs verification details on each landing (prefab name, heights, exemption reason, etc). Can be left off once verified.");
		ExemptPrefabPrefixes = ((BaseUnityPlugin)this).Config.Bind<string>("Fall Exemption", "ExemptPrefabPrefixes", "Blob", "Creatures whose prefab name (after stripping \"(Clone)\", case-insensitive) starts with one of these comma-separated prefixes are fully immune to fall damage (other damage is unaffected). The default \"Blob\" covers all slime variants (Blob/BlobElite/BlobAspect/BlobLava/BlobTar/ BlobMork/BlobMorkMini/BlobFrost, etc).");
		_harmony.PatchAll();
		((BaseUnityPlugin)this).Logger.LogInfo((object)"MonsterFallDamage 1.1.0 loaded.");
	}

	private void OnDestroy()
	{
		_harmony.UnpatchSelf();
	}
}
internal static class FallExemptionState
{
	internal sealed class State
	{
		public float? JumpStartY;

		public float JumpRecordedAt = -1f;

		public float FlyingGraceUntil = -1f;

		public bool WasAirborne;

		public bool? IsFallDamageExempt;
	}

	private static readonly ConditionalWeakTable<Character, State> _table = new ConditionalWeakTable<Character, State>();

	public static State Get(Character c)
	{
		return _table.GetOrCreateValue(c);
	}
}
[HarmonyPatch(typeof(Character), "Jump")]
public static class Patch_RecordJumpStart
{
	public static void Postfix(Character __instance)
	{
		//IL_0021: Unknown result type (might be due to invalid IL or missing references)
		if (Plugin.IgnoreJumpAscent.Value)
		{
			FallExemptionState.State state = FallExemptionState.Get(__instance);
			float y = ((Component)__instance).transform.position.y;
			state.JumpStartY = y;
			state.JumpRecordedAt = Time.time;
			if (Plugin.EnableDebugLog.Value)
			{
				Plugin.Log.LogInfo((object)$"[MonsterFallDamage:점프기록] id={((Object)__instance).GetInstanceID()} {((Object)((Component)__instance).gameObject).name} | 기록Y={y:F2}");
			}
		}
	}
}
[HarmonyPatch(typeof(Character), "UpdateFlying")]
public static class Patch_FlyingBaselineReset
{
	private static readonly FieldInfo f_maxAirAltitude = AccessTools.Field(typeof(Character), "m_maxAirAltitude");

	public static void Postfix(Character __instance)
	{
		//IL_0020: Unknown result type (might be due to invalid IL or missing references)
		if (Plugin.IgnoreFlyingLandings.Value)
		{
			f_maxAirAltitude.SetValue(__instance, ((Component)__instance).transform.position.y);
		}
	}
}
[HarmonyPatch(typeof(Character), "Land")]
public static class Patch_FlyingLandGrace
{
	public static void Postfix(Character __instance)
	{
		if (Plugin.IgnoreFlyingLandings.Value)
		{
			FallExemptionState.State state = FallExemptionState.Get(__instance);
			state.FlyingGraceUntil = Time.time + Plugin.FlyingLandingGraceSeconds.Value;
		}
	}
}
[HarmonyPatch(typeof(Character), "UpdateGroundContact")]
public static class Patch_MonsterFallDamage
{
	private const float JumpRecordExpirySeconds = 0.5f;

	private static readonly FieldInfo f_groundContact = AccessTools.Field(typeof(Character), "m_groundContact");

	private static readonly FieldInfo f_maxAirAltitude = AccessTools.Field(typeof(Character), "m_maxAirAltitude");

	private static readonly FieldInfo f_groundContactPoint = AccessTools.Field(typeof(Character), "m_groundContactPoint");

	private static readonly FieldInfo f_groundContactNormal = AccessTools.Field(typeof(Character), "m_groundContactNormal");

	public static void Prefix(Character __instance)
	{
		//IL_0128: Unknown result type (might be due to invalid IL or missing references)
		//IL_03bb: Unknown result type (might be due to invalid IL or missing references)
		//IL_03c0: Unknown result type (might be due to invalid IL or missing references)
		//IL_03cd: Unknown result type (might be due to invalid IL or missing references)
		//IL_03d2: Unknown result type (might be due to invalid IL or missing references)
		//IL_03d4: Unknown result type (might be due to invalid IL or missing references)
		//IL_03db: Expected O, but got Unknown
		//IL_03eb: Unknown result type (might be due to invalid IL or missing references)
		//IL_03ed: Unknown result type (might be due to invalid IL or missing references)
		//IL_03f4: Unknown result type (might be due to invalid IL or missing references)
		//IL_03f6: Unknown result type (might be due to invalid IL or missing references)
		//IL_03fe: Unknown result type (might be due to invalid IL or missing references)
		if (__instance.IsDead())
		{
			return;
		}
		bool flag = __instance.IsPlayer();
		if (flag && !Plugin.ApplyToPlayer.Value)
		{
			return;
		}
		FallExemptionState.State state = FallExemptionState.Get(__instance);
		if (!(bool)f_groundContact.GetValue(__instance))
		{
			state.WasAirborne = true;
			return;
		}
		bool wasAirborne = state.WasAirborne;
		state.WasAirborne = false;
		if (!wasAirborne)
		{
			if (state.JumpStartY.HasValue && Time.time - state.JumpRecordedAt > 0.5f)
			{
				if (Plugin.EnableDebugLog.Value)
				{
					Plugin.Log.LogInfo((object)($"[MonsterFallDamage:점프기록만료] id={((Object)__instance).GetInstanceID()} {((Object)((Component)__instance).gameObject).name} | " + $"기록Y={state.JumpStartY.Value:F2} (공중 상태 진입 없이 {0.5f:F1}초 경과)"));
				}
				state.JumpStartY = null;
			}
			return;
		}
		float y = ((Component)__instance).transform.position.y;
		if (!state.IsFallDamageExempt.HasValue)
		{
			state.IsFallDamageExempt = IsExemptPrefab(((Object)((Component)__instance).gameObject).name);
		}
		if (state.IsFallDamageExempt.Value)
		{
			float num = (float)f_maxAirAltitude.GetValue(__instance);
			float num2 = num;
			if (Plugin.IgnoreJumpAscent.Value && state.JumpStartY.HasValue)
			{
				num2 = Mathf.Min(num2, state.JumpStartY.Value);
			}
			float fallHeight = Mathf.Max(0f, num2 - y);
			state.JumpStartY = null;
			Log(__instance, num, num2, y, fallHeight, 0f, "슬라임 면제");
			return;
		}
		if (Plugin.IgnoreFlyingLandings.Value && Time.time < state.FlyingGraceUntil)
		{
			if (flag)
			{
				f_maxAirAltitude.SetValue(__instance, y);
			}
			state.JumpStartY = null;
			Log(__instance, 0f, null, y, 0f, 0f, "비행 착지 유예");
			return;
		}
		float num3 = (float)f_maxAirAltitude.GetValue(__instance);
		float num4 = num3;
		if (Plugin.IgnoreJumpAscent.Value && state.JumpStartY.HasValue)
		{
			num4 = Mathf.Min(num4, state.JumpStartY.Value);
		}
		float num5 = Mathf.Max(0f, num4 - y);
		state.JumpStartY = null;
		float value = Plugin.MinFallHeight.Value;
		if (num5 <= value)
		{
			Log(__instance, num3, num4, y, num5, 0f, "최소 낙하 높이 미만");
			return;
		}
		float num6 = (num5 - value) / Plugin.DamageDistance.Value * Plugin.DamagePerUnit.Value;
		float value2 = Plugin.MaxDamage.Value;
		if (value2 > 0f)
		{
			num6 = Mathf.Min(num6, value2);
		}
		if (num6 <= 0f)
		{
			Log(__instance, num3, num4, y, num5, 0f, "계산 데미지 0 이하");
			return;
		}
		if (flag)
		{
			f_maxAirAltitude.SetValue(__instance, y);
		}
		Vector3 point = (Vector3)f_groundContactPoint.GetValue(__instance);
		Vector3 dir = (Vector3)f_groundContactNormal.GetValue(__instance);
		HitData val = new HitData();
		val.m_damage.m_damage = num6;
		val.m_point = point;
		val.m_dir = dir;
		val.m_hitType = (HitType)3;
		__instance.Damage(val);
		Log(__instance, num3, num4, y, num5, num6, null);
	}

	private static bool IsExemptPrefab(string gameObjectName)
	{
		string text = (gameObjectName.EndsWith("(Clone)") ? gameObjectName.Substring(0, gameObjectName.Length - "(Clone)".Length) : gameObjectName);
		string[] array = Plugin.ExemptPrefabPrefixes.Value.Split(new char[1] { ',' });
		string[] array2 = array;
		foreach (string text2 in array2)
		{
			string text3 = text2.Trim();
			if (text3.Length != 0 && text.StartsWith(text3, StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
		}
		return false;
	}

	private static void Log(Character c, float maxAlt, float? baseline, float currentY, float fallHeight, float damage, string exemptReason)
	{
		if (Plugin.EnableDebugLog.Value)
		{
			string name = ((Object)((Component)c).gameObject).name;
			string arg = (baseline.HasValue ? baseline.Value.ToString("F2") : "-");
			Plugin.Log.LogInfo((object)($"[MonsterFallDamage:착지] id={((Object)c).GetInstanceID()} {name} | 최고높이={maxAlt:F2} " + $"기준점(점프반영)={arg} 착지높이={currentY:F2} 낙하높이={fallHeight:F2} " + string.Format("비행={0} 적용데미지={1:F1} 제외사유={2}", c.IsFlying(), damage, exemptReason ?? "없음")));
		}
	}
}