
【FixCraftingButtonPatch.cs】
﻿using HarmonyLib;
using System.Collections.Generic;
using TaleWorlds.CampaignSystem;
using TaleWorlds.CampaignSystem.CampaignBehaviors;
using TaleWorlds.CampaignSystem.Party;
using TaleWorlds.CampaignSystem.ViewModelCollection.WeaponCrafting;
using TaleWorlds.Core;
using TaleWorlds.Library;
using System;
using System.Collections.Generic;

namespace SmeltingOverhaul
{   
    [HarmonyPatch(typeof(CraftingVM), "ExecuteMainAction")]
    class FixExecuteMainActionPatch
    {     // =========================
          // 工具方法：判断是否可用金属补足零件
          // =========================
        private static bool CanCraftWithMetals(CraftingVM __instance, SmeltingMechanicBehavior behavior)
        {
            if (__instance.WeaponDesign == null) return false;

            // 遍历每个零件类别，只看已选中的零件
            Dictionary<CraftingMaterials, int> totalMetalCosts = new Dictionary<CraftingMaterials, int>();

            foreach (var pieceListVM in __instance.WeaponDesign.PieceLists)
            {
                var pieceVM = pieceListVM.SelectedPiece;
                if (pieceVM == null) continue;

                var piece = pieceVM.CraftingPiece;
                if (piece == null) continue;

                string pieceId = piece.CraftingPiece.StringId;

                if (pieceId!=null && behavior.GetPartCount(pieceId) > 0) continue; // 库存足够

                // 累加金属需求
                foreach (var mat in piece.CraftingPiece.MaterialsUsed)
                {
                    if (totalMetalCosts.ContainsKey(mat.Item1))
                        totalMetalCosts[mat.Item1] += mat.Item2;
                    else
                        totalMetalCosts[mat.Item1] = mat.Item2;
                }
            }

            // 检查玩家库存
            foreach (var kv in totalMetalCosts)
            {
                var materialItem = Campaign.Current.Models.SmithingModel.GetCraftingMaterialItem(kv.Key);
                int playerStock = MobileParty.MainParty.ItemRoster.GetItemNumber(materialItem);
                if (playerStock < kv.Value)
                    return false;
            }

            return true;
        }
        // =========================
        // Patch 2: 修复 HaveMaterialsNeeded，让 ExecuteMainAction 可用
        // =========================
        [HarmonyPatch("HaveMaterialsNeeded")]
        [HarmonyPostfix]
        public static void HaveMaterialsNeededPostfix(CraftingVM __instance, ref bool __result)
        {
            if (__result) return; // 原版材料充足
            var behavior = Campaign.Current?.GetCampaignBehavior<SmeltingMechanicBehavior>();
            if (behavior == null) return;

            if (CanCraftWithMetals(__instance, behavior))
            {
                __result = true; // 材料不足，但金属可补足
            }
        }
        static bool Prefix(CraftingVM __instance)
        {
            if (__instance.IsInSmeltingMode || __instance.IsInRefinementMode)
                return true; // 让原版处理熔炼/精炼

            if (__instance.IsMainActionEnabled) // 你的按钮已经亮
            {
                // 绕过原版 HaveMaterialsNeeded() 检查
                return true;
            }

            // 如果按钮没亮，也不用锻造
            return false;
        }
    }
    [HarmonyPatch(typeof(CraftingVM), "RefreshEnableMainAction")]
    public class FixCraftingButtonPatch
    {
        private static bool HaveEnergy(CraftingVM instance)
        {
            var currentCraftingHero = (CraftingAvailableHeroItemVM)AccessTools.Field(typeof(CraftingVM), "_currentCraftingHero").GetValue(instance);
            if (currentCraftingHero?.Hero == null) return true;

            var craftingBehavior = (ICraftingCampaignBehavior)AccessTools.Field(typeof(CraftingVM), "_craftingBehavior").GetValue(instance);
            return craftingBehavior.GetHeroCraftingStamina(currentCraftingHero.Hero) > 10;
        }
        /// <summary>
        /// 修复锻造界面按钮的可用性问题：
        /// 1. 当自由制作模式下零件不足时，原版错误禁用按钮（因未考虑金属补充）
        /// 2. 本补丁在原版检查后，用我们的混合资源逻辑重新判定
        /// 
        /// 【设计验证】
        /// - 仅在锻造模式（非熔炼/精炼）生效
        /// - 保留原版零件解锁检查
        /// - 精确计算零件不足时的金属需求
        /// - 100%匹配 `PrefixSpendMaterials` 的混合消耗逻辑
        /// </summary>
        static void Postfix(CraftingVM __instance)
        {
            // 1️⃣ 如果按钮已亮，说明原版已通过检查（材料/体力充足）
            if (__instance.IsMainActionEnabled) return;

            // 2️⃣ 排除体力不足/熔炼/精炼模式（保持原版行为）
            if (!HaveEnergy(__instance) || __instance.IsInSmeltingMode || __instance.IsInRefinementMode) return;

            // 3️⃣ 保留原版零件解锁检查（未解锁零件不能制作）
            if (__instance.WeaponDesign == null || !__instance.WeaponDesign.HaveUnlockedAllSelectedPieces()) return;

            var behavior = Campaign.Current?.GetCampaignBehavior<SmeltingMechanicBehavior>();
            if (behavior == null) return;

            var designVM = __instance.WeaponDesign;

            // 🔍 用字典存储总金属需求 (CraftingMaterials -> 数量)
            Dictionary<CraftingMaterials, int> totalMetalCosts = new Dictionary<CraftingMaterials, int>();

            // 遍历每个零件类别，只看已选中的零件
            foreach (var pieceListVM in designVM.PieceLists)
            {
                var pieceVM = pieceListVM.SelectedPiece; // 当前选中零件
                if (pieceVM == null) continue;

                var piece = pieceVM.CraftingPiece;
                if (piece == null) continue;

                string pieceId = piece.CraftingPiece.StringId;

                // 零件库存足够 -> 跳过金属
                if (pieceId!=null && behavior.GetPartCount(pieceId) > 0) continue;

                // 零件库存不足 -> 累加金属消耗
                foreach (var (material, amount) in piece.CraftingPiece.MaterialsUsed)
                {
                    if (!totalMetalCosts.ContainsKey(material))
                        totalMetalCosts[material] = 0;

                    totalMetalCosts[material] += amount;
                }
            }

            // 4️⃣ 检查玩家库存是否满足总金属需求
            bool canCraft = true;
            foreach (var kvp in totalMetalCosts)
            {
                var materialItem = Campaign.Current.Models.SmithingModel.GetCraftingMaterialItem(kvp.Key);
                int playerStock = MobileParty.MainParty.ItemRoster.GetItemNumber(materialItem);
                if (playerStock < kvp.Value)
                {
                    canCraft = false;
                    break; // 任意一种金属不足即失败
                }
            }

            // 5️⃣ 如果混合资源检查通过，强制启用按钮
            if (canCraft)
            {
                __instance.IsMainActionEnabled = true;
                __instance.MainActionHint = new TaleWorlds.Core.ViewModelCollection.Information.BasicTooltipViewModel();
                InformationManager.DisplayMessage(
                    new InformationMessage("锻造按钮已启用（零件不足部分用金属补充）", Color.FromUint(0xFF00FF00)));
            }
        }
    }
}
========================================

【ModConfig.cs】
﻿using System;

namespace SmeltingOverhaul
{
    /// <summary>
    /// 全局配置参数
    /// </summary>
    public static class ModConfig
    {
        public static float BaseProgressUnit = 1.0f;
        public static float CuriousSmelterMultiplier = 2.0f;
        public static float DifficultyFactor = 1.0f;
        public static float PartRecycleRate { get; set; } = 0.5f; // 默认50%回收

        public static float GetModifierWeight(float priceMultiplier)
        {
            if (priceMultiplier >= 10.0f) return 5.0f;
            if (priceMultiplier >= 3.0f) return 3.0f;
            if (priceMultiplier >= 1.2f) return 1.5f;
            if (priceMultiplier >= 0.8f) return 1.0f;
            if (priceMultiplier >= 0.5f) return 0.5f;
            return 0.2f;
        }
    }
}
========================================

【SmeltingCraftingPatches.cs】
﻿using HarmonyLib;
using System.Reflection;
using TaleWorlds.CampaignSystem;
using TaleWorlds.CampaignSystem.CampaignBehaviors;
using TaleWorlds.CampaignSystem.CharacterDevelopment;
using TaleWorlds.CampaignSystem.CraftingSystem;
using TaleWorlds.CampaignSystem.GameState;
using TaleWorlds.CampaignSystem.Party;
using TaleWorlds.CampaignSystem.ViewModelCollection.WeaponCrafting;
using TaleWorlds.Core;
using TaleWorlds.Library;
using TaleWorlds.MountAndBlade;
using TaleWorlds.CampaignSystem.ViewModelCollection;
using TaleWorlds.CampaignSystem.GameState;
using HarmonyLib;

namespace SmeltingOverhaul
{
    [HarmonyPatch(typeof(CraftingCampaignBehavior))]
    public static class CraftingAndSmeltingPatches
    {
        // =============================
        // 当前制作模式记录
        // =============================
        private static bool _currentIsFreeMode = true;

        [HarmonyPatch("CreateCraftedWeaponInternal")]
        [HarmonyPrefix]
        static void PrefixCreateCraftedWeaponInternal(bool isFreeMode)
        {
            _currentIsFreeMode = isFreeMode;
        }

        // =============================
        // 屏蔽自由制作随机解锁
        // =============================
        [HarmonyPatch("AddResearchPoints")]
        [HarmonyPrefix]
        static bool BlockAddResearchPoints(
            CraftingCampaignBehavior __instance,
            CraftingTemplate craftingTemplate,
            int researchPoints)
        {
            // 自由制作屏蔽
            if (_currentIsFreeMode)
                return false;

            // 订单制作允许
            return true;
        }

        // =======================================================
        // 【修复版】混合消耗逻辑 (Hybrid Consumption)
        // =======================================================
        /// <summary>
        /// 替代原版的 SpendMaterials 方法。
        /// 实现逻辑：
        /// 1. 遍历所有配件。
        /// 2. 优先消耗“零件库存”。
        /// 3. 库存不足的配件，自动回退为消耗“金属材料”。
        /// </summary>
        [HarmonyPatch("SpendMaterials")]
        [HarmonyPrefix]
        static bool PrefixSpendMaterials(WeaponDesign weaponDesign)
        {
            var behavior = Campaign.Current?.GetCampaignBehavior<SmeltingMechanicBehavior>();
            if (behavior == null || weaponDesign?.UsedPieces == null)
                return true; // 安全回退原版

            // 统计金属消耗（9种材料）
            int[] totalMetalCosts = new int[9];

            // 逐零件处理
            foreach (var element in weaponDesign.UsedPieces)
            {
                var piece = element?.CraftingPiece;
                if (piece.BladeData == null || piece.StringId == null)
                    continue;

                // 尝试消耗零件库存
                bool consumed = behavior.ConsumePart(piece.StringId, 1);

                if (!consumed)
                {
                    // 库存不足 → 仅该零件走金属
                    var pieceCosts = piece.MaterialsUsed;

                    if (pieceCosts != null)
                    {

                        if (pieceCosts != null)
                        {
                            foreach (var cost in pieceCosts)
                            {
                                CraftingMaterials material = cost.Item1;
                                int amount = cost.Item2;

                                totalMetalCosts[(int)material] += amount;
                            }
                        }
                    }
                }
            }

            // 统一扣金属
            var partyItemRoster = MobileParty.MainParty.ItemRoster;
            var smithingModel = Campaign.Current.Models.SmithingModel;

            for (int i = 0; i < 9; i++)
            {
                if (totalMetalCosts[i] > 0)
                {
                    var materialEnum = (CraftingMaterials)i;
                    var materialItem = smithingModel.GetCraftingMaterialItem(materialEnum);

                    if (materialItem != null)
                    {
                        partyItemRoster.AddToCounts(materialItem, -totalMetalCosts[i]);
                    }
                }
            }

            // 完全跳过原版
            return false;
        }


        // =============================
        // 反射缓存（避免频繁调用）
        // =============================
        private static readonly MethodInfo _isOpenedMethod =
            AccessTools.Method(typeof(CraftingCampaignBehavior), "IsOpened");

        private static readonly MethodInfo _openPartMethod =
            AccessTools.Method(typeof(CraftingCampaignBehavior), "OpenPart");

        // =============================
        // 完全接管 DoSmelting
        // =============================
        [HarmonyPatch("DoSmelting")]
        [HarmonyPrefix]
        static bool PrefixDoSmelting(
            CraftingCampaignBehavior __instance,
            Hero currentCraftingHero,
            EquipmentElement equipmentElement)
        {
            if (currentCraftingHero == null || equipmentElement.Item == null)
                return false;

            var item = equipmentElement.Item;
            var design = item.WeaponDesign;

            if (design?.UsedPieces == null || design.Template == null)
                return false;

            var behavior = Campaign.Current.GetCampaignBehavior<SmeltingMechanicBehavior>();
            if (behavior == null)
                return false;

            var itemRoster = MobileParty.MainParty.ItemRoster;

            // 1️⃣ 扣装备
            itemRoster.AddToCounts(equipmentElement, -1);

            // 2️⃣ 给经验
            currentCraftingHero.AddSkillXp(
                DefaultSkills.Crafting,
                (float)Campaign.Current.Models.SmithingModel.GetSkillXpForSmelting(item));

            // 3️⃣ 扣体力
            int energyCost = Campaign.Current.Models.SmithingModel
                .GetEnergyCostForSmelting(item, currentCraftingHero);

            __instance.SetHeroCraftingStamina(
                currentCraftingHero,
                __instance.GetHeroCraftingStamina(currentCraftingHero) - energyCost);

            // 4️⃣ 逆向工程
            float perkBonus = currentCraftingHero.GetPerkValue(DefaultPerks.Crafting.CuriousSmelter)
                ? ModConfig.CuriousSmelterMultiplier
                : 1f;

            float modifierWeight = ModConfig.GetModifierWeight(
                equipmentElement.ItemModifier?.PriceMultiplier ?? 1f);

            float earnedProgress =
                ModConfig.BaseProgressUnit * perkBonus * modifierWeight;

            foreach (var usedPart in design.UsedPieces)
            {
                var piece = usedPart?.CraftingPiece;
                if (piece?.StringId == null)
                    continue;

                // 回收零件
                behavior.AddPart(piece.StringId, 1);

                bool isOpened = false;

                try
                {
                    if (_isOpenedMethod != null)
                    {
                        isOpened = (bool)_isOpenedMethod.Invoke(
                            __instance,
                            new object[] { piece, design.Template });
                    }
                }
                catch
                {
                    continue;
                }

                if (!isOpened)
                {
                    float required = piece.PieceTier * ModConfig.DifficultyFactor;
                    float current = behavior.AddProgress(piece.StringId, earnedProgress);

                    if (current >= required)
                    {
                        try
                        {
                            _openPartMethod?.Invoke(
                                __instance,
                                new object[] { piece, design.Template, false });
                        }
                        catch { }

                        InformationManager.DisplayMessage(
                            new InformationMessage(
                                $"【逆向解析完成】已掌握：{piece.Name}",
                                Color.FromUint(0xFF00FF00)));
                    }
                    else
                    {
                        int percent = (int)((current / required) * 100);

                        InformationManager.DisplayMessage(
                            new InformationMessage(
                                $"解析中：{piece.Name} [回收+1] 进度:{percent}%",
                                Color.FromUint(0xFFFFFF00)));
                    }
                }
                else
                {
                    InformationManager.DisplayMessage(
                        new InformationMessage(
                            $"配件回收：{piece.Name} [库存:{behavior.GetPartCount(piece.StringId)}]",
                            Color.FromUint(0xFFDAA520)));
                }
            }

            // 保留原版事件
            CampaignEventDispatcher.Instance
                .OnEquipmentSmeltedByHero(currentCraftingHero, equipmentElement);

            return false;
        }
    }
    
}
========================================

【SmeltingMechanicBehavior.cs】
﻿using System.Collections.Generic;
using TaleWorlds.CampaignSystem;
using TaleWorlds.Library;

namespace SmeltingOverhaul
{
    /// <summary>
    /// 熔炼机制的存档数据与运行数据
    /// </summary>
    public class SmeltingMechanicBehavior : CampaignBehaviorBase
    {
        private Dictionary<string, float> _unlockProgress = new Dictionary<string, float>();
        private Dictionary<string, int> _partInventory = new Dictionary<string, int>();

        public float AddProgress(string pieceId, float amount)
        {
            if (!_unlockProgress.ContainsKey(pieceId))
                _unlockProgress[pieceId] = 0f;

            _unlockProgress[pieceId] += amount;
            return _unlockProgress[pieceId];
        }

        public void AddPart(string pieceId, int count)
        {
            if (!_partInventory.ContainsKey(pieceId))
                _partInventory[pieceId] = 0;

            _partInventory[pieceId] += count;
        }

        public bool ConsumePart(string pieceId, int count)
        {
            if (GetPartCount(pieceId) < count)
                return false;

            _partInventory[pieceId] -= count;
            return true;
        }

        public int GetPartCount(string pieceId)
        {
            return _partInventory.TryGetValue(pieceId, out int v) ? v : 0;
        }

        public override void RegisterEvents()
        {
            InformationManager.DisplayMessage(new InformationMessage(    "SmeltingMechanicBehavior loaded?"));
        }

        public override void SyncData(IDataStore dataStore)
        {
            dataStore.SyncData("_unlockProgress", ref _unlockProgress);
            dataStore.SyncData("_partInventory", ref _partInventory);
        }
    }
}
========================================

【SubModule.cs】
﻿using Bannerlord.UIExtenderEx;
using HarmonyLib;
using System;
using System.Reflection;
using TaleWorlds.CampaignSystem;
using TaleWorlds.Core;
using TaleWorlds.GauntletUI.BaseTypes;
using TaleWorlds.Library;
using TaleWorlds.MountAndBlade;

namespace SmeltingOverhaul
{
    public class SubModule : MBSubModuleBase
    {

        protected override void OnSubModuleLoad()
        {
            base.OnSubModuleLoad();


            // Harmony 补丁
            try
            {
                var harmony = new Harmony("smelting_overhaul");
                harmony.PatchAll();
                InformationManager.DisplayMessage(new InformationMessage("[SmeltingOverhaul] Harmony PatchAll completed."));
            }
            catch (Exception ex)
            {
                InformationManager.DisplayMessage(new InformationMessage("[SmeltingOverhaul] Harmony PatchAll failed: " + ex.Message));
            }

        }

        protected override void OnGameStart(Game game, IGameStarter starterObject)
        {
            base.OnGameStart(game, starterObject);

            if (game.GameType is Campaign)
            {
                if (starterObject is CampaignGameStarter campaignStarter)
                {
                    campaignStarter.AddBehavior(new SmeltingMechanicBehavior());
                    campaignStarter.AddBehavior(new WorldSmithingStaminaBehavior());
                }
            }
        }

        protected override void OnSubModuleUnloaded()
        {
            base.OnSubModuleUnloaded();
        }

        protected override void OnBeforeInitialModuleScreenSetAsRoot()
        {
            base.OnBeforeInitialModuleScreenSetAsRoot();
        }
    }
}
========================================

【WorldSmithingStaminaBehavior.cs】
﻿using TaleWorlds.CampaignSystem;
using TaleWorlds.CampaignSystem.CampaignBehaviors;
using TaleWorlds.CampaignSystem.Party;
using TaleWorlds.Core;

namespace SmeltingOverhaul
{
    public class WorldSmithingStaminaBehavior : CampaignBehaviorBase
    {
        private const int StaminaPerHour = 2; // 野外每小时恢复量

        public override void RegisterEvents()
        {
            CampaignEvents.HourlyTickEvent.AddNonSerializedListener(this, OnHourlyTick);
        }

        public override void SyncData(IDataStore dataStore)
        {
        }

        private void OnHourlyTick()
        {
            if (MobileParty.MainParty == null)
                return;

            // ✅ 只在野外恢复
            if (MobileParty.MainParty.CurrentSettlement != null)
                return;

            var craftingBehavior = Campaign.Current.GetCampaignBehavior<CraftingCampaignBehavior>();
            if (craftingBehavior == null)
                return;

            foreach (var troop in MobileParty.MainParty.MemberRoster.GetTroopRoster())
            {
                if (!troop.Character.IsHero || troop.Character.HeroObject == null)
                    continue;

                Hero hero = troop.Character.HeroObject;

                int current = craftingBehavior.GetHeroCraftingStamina(hero);
                int max = craftingBehavior.GetMaxHeroCraftingStamina(hero);

                if (current >= max)
                    continue;

                int newValue = current + StaminaPerHour;

                if (newValue > max)
                    newValue = max;

                craftingBehavior.SetHeroCraftingStamina(hero, newValue);
            }
        }
    }
}
========================================
