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.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("FistAttackPlugin")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("FistAttackPlugin")] [assembly: AssemblyTitle("FistAttackPlugin")] [assembly: AssemblyVersion("1.0.0.0")] namespace FistAttackPlugin; internal static class FistWeaponFilter { internal static bool Matches(ItemData weapon) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Invalid comparison between Unknown and I4 if (weapon?.m_shared == null) { return false; } if ((int)weapon.m_shared.m_skillType == 11) { return true; } GameObject dropPrefab = weapon.m_dropPrefab; string text = ((dropPrefab != null) ? ((Object)dropPrefab).name : null) ?? string.Empty; return text.IndexOf("Fist", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Claw", StringComparison.OrdinalIgnoreCase) >= 0; } } internal static class FistBaselineKick { private static Attack _cached; internal static Attack Get(Humanoid humanoid) { if (_cached != null) { return _cached; } Attack val = humanoid?.m_unarmedWeapon?.m_itemData?.m_shared?.m_secondaryAttack; if (val == null) { Plugin.Log.LogWarning((object)"[FistChain] Humanoid.m_unarmedWeapon에서 발차기(2차 공격)를 찾지 못했습니다. 3타 체인이 비활성화됩니다."); return null; } _cached = val; return _cached; } } internal static class FistChainSystem { private sealed class ChainState { public int Level; } private static readonly ConditionalWeakTable _states = new ConditionalWeakTable(); private static readonly HashSet _pendingPrimary = new HashSet(); private const float ChainMaxTime = 0.2f; internal static void OnPrimaryStartAttempt(Humanoid humanoid) { //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Expected O, but got Unknown if (!Plugin.FcEnable.Value) { return; } ItemData currentWeapon = humanoid.GetCurrentWeapon(); if (!FistWeaponFilter.Matches(currentWeapon) || FistBaselineKick.Get(humanoid) == null) { return; } if (Plugin.FcMinSkillLevel.Value > 0f) { Player val = (Player)(object)((humanoid is Player) ? humanoid : null); if (val != null && ((Character)val).GetSkillLevel((SkillType)11) < Plugin.FcMinSkillLevel.Value) { return; } } _pendingPrimary.Add((Character)humanoid); } internal static void ClearPendingIfLeftover(Character ch) { _pendingPrimary.Remove(ch); } internal static bool TryConsumePending(Character ch) { return _pendingPrimary.Remove(ch); } internal static int GetLevel(Character ch, float timeSinceLastAttack) { ChainState value = _states.GetValue(ch, (Character _) => new ChainState()); if (timeSinceLastAttack > 0.2f) { value.Level = 0; } return value.Level; } internal static void SetLevel(Character ch, int level) { ChainState value = _states.GetValue(ch, (Character _) => new ChainState()); value.Level = level; } } [HarmonyPatch(typeof(Humanoid), "StartAttack")] internal static class HumanoidStartAttack_FistChain_Patch { private static void Prefix(Humanoid __instance, bool secondaryAttack) { if (!secondaryAttack) { FistChainSystem.OnPrimaryStartAttempt(__instance); } } private static void Postfix(Humanoid __instance) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown FistChainSystem.ClearPendingIfLeftover((Character)__instance); } } [HarmonyPatch(typeof(Attack), "Start")] internal static class AttackStart_FistChain_Patch { private static void Prefix(Attack __instance, Humanoid character, ItemData weapon, float timeSinceLastAttack) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Expected O, but got Unknown //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_01ab: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Unknown result type (might be due to invalid IL or missing references) Character ch = (Character)character; if (!FistChainSystem.TryConsumePending(ch) || !FistWeaponFilter.Matches(weapon)) { return; } Attack val = FistBaselineKick.Get(character); if (val != null) { int level = FistChainSystem.GetLevel(ch, timeSinceLastAttack); if (level < 2) { FistChainSystem.SetLevel(ch, level + 1); return; } __instance.m_attackAnimation = val.m_attackAnimation; __instance.m_attackChainLevels = 0; __instance.m_attackRandomAnimations = 0; __instance.m_attackType = val.m_attackType; __instance.m_attackRange = val.m_attackRange; __instance.m_attackHeight = val.m_attackHeight; __instance.m_attackOffset = val.m_attackOffset; __instance.m_attackAngle = val.m_attackAngle; __instance.m_attackRayWidth = val.m_attackRayWidth; __instance.m_maxYAngle = val.m_maxYAngle; __instance.m_forceMultiplier = val.m_forceMultiplier; __instance.m_damageMultiplier = val.m_damageMultiplier * Mathf.Max(0f, Plugin.FcKickDamageMult.Value); __instance.m_staggerMultiplier = val.m_staggerMultiplier; __instance.m_attackStamina = val.m_attackStamina; __instance.m_attackEitr = val.m_attackEitr; __instance.m_attackHealth = val.m_attackHealth; __instance.m_attackHealthPercentage = val.m_attackHealthPercentage; __instance.m_attackHealthReturnHit = val.m_attackHealthReturnHit; __instance.m_attackHealthLowBlockUse = val.m_attackHealthLowBlockUse; __instance.m_attackStartNoise = val.m_attackStartNoise; __instance.m_attackHitNoise = val.m_attackHitNoise; __instance.m_hitTerrain = val.m_hitTerrain; __instance.m_hitFriendly = val.m_hitFriendly; __instance.m_hitThroughWalls = val.m_hitThroughWalls; __instance.m_multiHit = val.m_multiHit; __instance.m_lowerDamagePerHit = val.m_lowerDamagePerHit; __instance.m_hitPointtype = val.m_hitPointtype; __instance.m_recoilPushback = val.m_recoilPushback; FistChainSystem.SetLevel(ch, 0); } } } [BepInPlugin("com.fistattack.plugin", "FistAttackMod", "1.0.0")] [BepInProcess("valheim.exe")] public class Plugin : BaseUnityPlugin { public const string GUID = "com.fistattack.plugin"; public const string NAME = "FistAttackMod"; public const string VERSION = "1.0.0"; private readonly Harmony _harmony = new Harmony("com.fistattack.plugin"); internal static ManualLogSource Log; internal static ConfigEntry FcEnable; internal static ConfigEntry FcKickDamageMult; internal static ConfigEntry FcMinSkillLevel; private void Awake() { Log = ((BaseUnityPlugin)this).Logger; FcEnable = ((BaseUnityPlugin)this).Config.Bind("FistChain", "Enable", true, "Fist(주먹/클로/Warfare 등) 계열 무기 — 좌클릭 3연타: 펀치 + 펀치 + 킥(바닐라 Fists 발차기 고정 사용). 무기 고유의 2차 공격(우클릭 특수기)은 그대로 유지되며 3타 체인과 무관함."); FcKickDamageMult = ((BaseUnityPlugin)this).Config.Bind("FistChain", "KickDamageMult", 2f, "3타(킥) 데미지 배율 — 바닐라 Fists 발차기 데미지에 곱함 (1.0 = 원래 발차기 데미지 그대로)"); FcMinSkillLevel = ((BaseUnityPlugin)this).Config.Bind("FistChain", "MinSkillLevel", 0f, "스킬 발동 최소 레벨 — Unarmed (0~100, 0 = 제한 없음)"); _harmony.PatchAll(); Log.LogInfo((object)"FistAttackMod 1.0.0 로드 완료"); } private void OnDestroy() { _harmony.UnpatchSelf(); } }