using HarmonyLib;
using KFCommonUtilityLib.Scripts.StaticManagers;
using KFCommonUtilityLib.Scripts.Utilities;
using System;
using System.Collections.Generic;
using UniLinq;
using System.Reflection;
using System.Reflection.Emit;
using System.Xml.Linq;
using UnityEngine;
using KFCommonUtilityLib.Scripts.NetPackages;
using KFCommonUtilityLib;

[HarmonyPatch]
public static class CommonUtilityPatch
{
    //fix reloading issue and onSelfRangedBurstShot timing
    public static void FakeReload(EntityAlive holdingEntity, ItemActionRanged.ItemActionDataRanged _actionData)
    {
        if (!holdingEntity)
            return;
        _actionData.isReloading = true;
        _actionData.isWeaponReloading = true;
        holdingEntity.MinEventContext.ItemActionData = _actionData;
        holdingEntity.FireEvent(MinEventTypes.onReloadStart, true);
        _actionData.isReloading = false;
        _actionData.isWeaponReloading = false;
        _actionData.isReloadCancelled = false;
        _actionData.isWeaponReloadCancelled = false;
        holdingEntity.FireEvent(MinEventTypes.onReloadStop);

        AnimationAmmoUpdateState.SetAmmoCountForEntity(holdingEntity, holdingEntity.inventory.holdingItemIdx);
    }

    [HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.SwapAmmoType))]
    [HarmonyTranspiler]
    private static IEnumerable<CodeInstruction> Transpiler_SwapAmmoType_ItemActionRanged(IEnumerable<CodeInstruction> instructions)
    {
        var codes = new List<CodeInstruction>(instructions);

        for (int i = 0; i < codes.Count; ++i)
        {
            if (codes[i].opcode == OpCodes.Ret)
            {
                codes.InsertRange(i, new CodeInstruction[]
                {
                    new CodeInstruction(OpCodes.Ldarg_1),
                    new CodeInstruction(OpCodes.Ldloc_0),
                    CodeInstruction.Call(typeof(CommonUtilityPatch), nameof(FakeReload))
                });
                break;
            }
        }

        return codes;
    }

    [HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.ExecuteAction))]
    [HarmonyTranspiler]
    private static IEnumerable<CodeInstruction> Transpiler_ExecuteAction_ItemActionRanged(IEnumerable<CodeInstruction> instructions)
    {
        var codes = new List<CodeInstruction>(instructions);
        var mtd_fire_event = AccessTools.Method(typeof(EntityAlive), nameof(EntityAlive.FireEvent));
        var mtd_get_model_layer = AccessTools.Method(typeof(EntityAlive), nameof(EntityAlive.GetModelLayer));
        var mtd_get_perc_left = AccessTools.PropertyGetter(typeof(ItemValue), nameof(ItemValue.PercentUsesLeft));

        int take = -1, insert = -1;
        for (int i = 0; i < codes.Count; ++i)
        {
            if (codes[i].opcode == OpCodes.Ldc_I4_S && codes[i].OperandIs((int)MinEventTypes.onSelfRangedBurstShotEnd) && codes[i + 2].Calls(mtd_fire_event))
                take = i - 3;
            else if (codes[i].Calls(mtd_get_model_layer))
                insert = i + 2;
        }

        if (take < insert)
        {
            var list = codes.GetRange(take, 6);
            codes.InsertRange(insert, list);
            codes.RemoveRange(take, 6);
        }

        return codes;
    }
    //fix recoil animation does not match weapon RPM
    private static int weaponFireHash = Animator.StringToHash("WeaponFire");
    private static int aimHash = Animator.StringToHash("IsAiming");
    private static HashSet<int> hash_shot_state = new HashSet<int>();
    private static HashSet<int> hash_aimshot_state = new HashSet<int>();

    public static void InitShotStates()
    {
        string[] weapons =
        {
            "fpvAK47",
            "fpvMagnum",
            "fpvRocketLauncher",
            "fpvSawedOffShotgun",
            "fpvBlunderbuss",
            "fpvCrossbow",
            "fpvPistol",
            "fpvHuntingRifle",
            "fpvSMG",
            "fpvSniperRifle",
            "M60",
            "fpvDoubleBarrelShotgun",
            //"fpvJunkTurret",
            "fpvTacticalAssaultRifle",
            "fpvDesertEagle",
            "fpvAutoShotgun",
            "fpvSharpShooterRifle",
            "fpvPipeMachineGun",
            "fpvPipeRifle",
            "fpvPipeRevolver",
            "fpvPipeShotgun",
            "fpvLeverActionRifle",
        };
        foreach (string weapon in weapons)
        {
            hash_shot_state.Add(Animator.StringToHash(weapon + "Fire"));
            hash_aimshot_state.Add(Animator.StringToHash(weapon + "AimFire"));
        }
    }

    [HarmonyPatch(typeof(EntityPlayerLocal), nameof(EntityPlayerLocal.OnFired))]
    [HarmonyPostfix]
    private static void Postfix_OnFired_EntityPlayerLocal(EntityPlayerLocal __instance)
    {
        if (!__instance.bFirstPersonView)
            return;

        ItemActionRanged.ItemActionDataRanged _rangedData;
        if ((_rangedData = __instance.inventory.holdingItemData.actionData[0] as ItemActionRanged.ItemActionDataRanged) == null && (_rangedData = __instance.inventory.holdingItemData.actionData[1] as ItemActionRanged.ItemActionDataRanged) == null)
            return;

        if (_rangedData.invData.model.TryGetComponent<AnimationTargetsAbs>(out var targets) && targets.ItemFpv)
            return;

        var anim = (__instance.emodel.avatarController as AvatarLocalPlayerController).FPSArms.Animator;
        if (anim.IsInTransition(0))
            return;

        var curState = anim.GetCurrentAnimatorStateInfo(0);
        if (curState.length > _rangedData.Delay)
        {
            bool aimState = anim.GetBool(aimHash);
            short shotState = 0;
            if (hash_shot_state.Contains(curState.shortNameHash))
                shotState = 1;
            else if (hash_aimshot_state.Contains(curState.shortNameHash))
                shotState = 2;
            if (shotState == 0 || (shotState == 1 && aimState) || (shotState == 2 && !aimState))
            {
                if (shotState > 0)
                    anim.ResetTrigger(weaponFireHash);
                return;
            }

            //current state, layer 0, offset 0
            anim.PlayInFixedTime(0, 0, 0);
            anim.ResetTrigger(weaponFireHash);
            if (_rangedData.invData.itemValue.Meta == 0)
            {
                __instance.emodel.avatarController.CancelEvent(weaponFireHash);
                Log.Out("Cancel fire event because meta is 0");
            }
        }
    }

    //[HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.ItemActionEffects))]
    //[HarmonyPostfix]
    //private static void Postfix_ItemActionEffects_ItemActionRanged(ItemActionData _actionData, int _firingState)
    //{
    //    if (_firingState == 0 && _actionData.invData.holdingEntity is EntityPlayerLocal && !(_actionData.invData.itemValue.ItemClass.Actions[0] is ItemActionCatapult))
    //    {
    //        _actionData.invData.holdingEntity?.emodel.avatarController.CancelEvent(weaponFireHash);
    //        //Log.Out("Cancel fire event because firing state is 0\n" + StackTraceUtility.ExtractStackTrace());
    //    }
    //}

    //[HarmonyPatch(typeof(GameManager), "gmUpdate")]
    //[HarmonyTranspiler]
    //private static IEnumerable<CodeInstruction> Transpiler_gmUpdate_GameManager(IEnumerable<CodeInstruction> instructions)
    //{
    //    var codes = new List<CodeInstruction>(instructions);
    //    var mtd_unload = AccessTools.Method(typeof(Resources), nameof(Resources.UnloadUnusedAssets));
    //    var fld_duration = AccessTools.Field(typeof(GameManager), "unloadAssetsDuration");

    //    for (int i = 0; i < codes.Count; ++i)
    //    {
    //        if (codes[i].opcode == OpCodes.Call && codes[i].Calls(mtd_unload))
    //        {
    //            for (int j = i; j >= 0; --j)
    //            {
    //                if (codes[j].opcode == OpCodes.Ldfld && codes[j].LoadsField(fld_duration) && codes[j + 1].opcode == OpCodes.Ldc_R4)
    //                    codes[j + 1].operand = (float)codes[j + 1].operand / 2;
    //            }
    //            break;
    //        }
    //    }

    //    return codes;
    //}

    //internal static void ForceUpdateGC()
    //{
    //    if (GameManager.IsDedicatedServer)
    //        return;
    //    if (GameManager.frameCount % 18000 == 0)
    //    {
    //        long rss = GetRSS.GetCurrentRSS();
    //        if (rss / 1024 / 1024 > 6144)
    //        {
    //            Log.Out("Memory usage exceeds threshold, now performing garbage collection...");
    //            GC.Collect();
    //        }
    //    }
    //}

    //altmode workarounds
    //deprecated by action module
    private static void ParseAltRequirements(XElement _node)
    {
        string itemName = _node.GetAttribute("name");
        if (string.IsNullOrEmpty(itemName))
        {
            return;
        }
        ItemClass item = ItemClass.GetItemClass(itemName);
        for (int i = 0; i < item.Actions.Length; i++)
        {
            if (item.Actions[i] is ItemActionAltMode _alt)
                _alt.ParseAltRequirements(_node, i);
        }
    }

    [HarmonyPatch(typeof(ItemClassesFromXml), nameof(ItemClassesFromXml.parseItem))]
    [HarmonyPostfix]
    private static void Postfix_parseItem_ItemClassesFromXml(XElement _node)
    {
        ParseAltRequirements(_node);
    }

    [HarmonyPatch(typeof(ItemClass), nameof(ItemClass.ExecuteAction))]
    [HarmonyPrefix]
    private static bool Prefix_ExecuteAction_ItemClass(ItemClass __instance, int _actionIdx, ItemInventoryData _data, bool _bReleased)
    {
        if (!_bReleased && __instance.Actions[_actionIdx] is ItemActionAltMode _alt)
            _alt.SetAltRequirement(_data.actionData[_actionIdx]);

        return true;
    }

    [HarmonyPatch(typeof(DynamicProperties), nameof(DynamicProperties.Parse))]
    [HarmonyPrefix]
    private static bool Prefix_Parse_DynamicProperties(XElement elementProperty)
    {
        if (elementProperty.Name.LocalName != "property")
            return false;
        return true;
    }

    //MinEventParams workarounds
    [HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.fireShot))]
    [HarmonyTranspiler]
    private static IEnumerable<CodeInstruction> Transpiler_fireShot_ItemActionRanged(IEnumerable<CodeInstruction> instructions)
    {
        var codes = new List<CodeInstruction>(instructions);

        var fld_ranged_tag = AccessTools.Field(typeof(ItemActionAttack), nameof(ItemActionAttack.RangedTag));
        var fld_params = AccessTools.Field(typeof(EntityAlive), nameof(EntityAlive.MinEventContext));

        for (int i = 0; i < codes.Count; i++)
        {
            if (codes[i].LoadsField(fld_ranged_tag))
            {
                if (!codes[i + 3].LoadsField(fld_params))
                {
                    codes.InsertRange(i + 2, new CodeInstruction[]
                    {
                        new CodeInstruction(OpCodes.Ldloc_1),
                        CodeInstruction.LoadField(typeof(EntityAlive), nameof(EntityAlive.MinEventContext)),
                        new CodeInstruction(OpCodes.Dup),
                        new CodeInstruction(OpCodes.Ldloc, 10),
                        CodeInstruction.LoadField(typeof(WorldRayHitInfo), nameof(WorldRayHitInfo.hit)),
                        CodeInstruction.LoadField(typeof(HitInfoDetails), nameof(HitInfoDetails.pos)),
                        CodeInstruction.StoreField(typeof(MinEventParams), nameof(MinEventParams.Position)),
                        new CodeInstruction(OpCodes.Ldloc_1),
                        CodeInstruction.Call(typeof(EntityAlive), nameof(EntityAlive.GetPosition)),
                        CodeInstruction.StoreField(typeof(MinEventParams), nameof(MinEventParams.StartPosition))
                    });
                }
                break;
            }
        }

        return codes;
    }

    [HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.OnHoldingUpdate))]
    [HarmonyTranspiler]
    private static IEnumerable<CodeInstruction> Transpiler_OnHoldingUpdate_ItemActionRanged(IEnumerable<CodeInstruction> instructions)
    {
        var mtd_release = AccessTools.Method(typeof(ItemActionRanged), nameof(ItemActionRanged.triggerReleased));
        var codes = instructions.ToList();

        for (int i = 0; i < codes.Count; i++)
        {
            if (codes[i].Calls(mtd_release))
            {
                codes[i + 1].labels.Clear();
                codes[i + 1].MoveLabelsFrom(codes[i - 20]);
                codes.RemoveRange(i - 20, 21);
                break;
            }
        }

        return codes;
    }

    [HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.triggerReleased))]
    [HarmonyTranspiler]
    private static IEnumerable<CodeInstruction> Transpiler_triggerReleased_ItemActionRanged(IEnumerable<CodeInstruction> instructions)
    {
        var mtd_effect = AccessTools.Method(typeof(IGameManager), nameof(IGameManager.ItemActionEffectsServer));
        var mtd_data = AccessTools.Method(typeof(ItemActionRanged), nameof(ItemActionRanged.getUserData));
        var codes = instructions.ToList();

        for (int i = 0; i < codes.Count; i++)
        {
            if (codes[i].Calls(mtd_effect))
            {
                codes.InsertRange(i, new CodeInstruction[]
                {
                    new CodeInstruction(OpCodes.Ldarg_0),
                    new CodeInstruction(OpCodes.Ldarg_1),
                    new CodeInstruction(OpCodes.Callvirt, mtd_data)
                });
                codes.RemoveAt(i - 1);
                break;
            }
        }

        return codes;
    }

    [HarmonyPatch(typeof(GameManager), nameof(GameManager.StartGame))]
    [HarmonyPrefix]
    private static bool Prefix_StartGame_GameManager()
    {
        CustomEffectEnumManager.InitFinal();
        return true;
    }

    [HarmonyPatch(typeof(PassiveEffect), nameof(PassiveEffect.ParsePassiveEffect))]
    [HarmonyTranspiler]
    private static IEnumerable<CodeInstruction> Transpiler_ParsePassiveEffect_PassiveEffect(IEnumerable<CodeInstruction> instructions)
    {
        var codes = instructions.ToList();
        MethodInfo mtd_enum_parse = AccessTools.Method(typeof(EnumUtils), nameof(EnumUtils.Parse), new[] { typeof(string), typeof(bool) }, new[] { typeof(PassiveEffects) });

        for (int i = 0; i < codes.Count; i++)
        {
            if (codes[i].Calls(mtd_enum_parse))
            {
                codes.Insert(i + 1, CodeInstruction.Call(typeof(CustomEffectEnumManager), nameof(CustomEffectEnumManager.RegisterOrGetEnum), new[] { typeof(string), typeof(bool) }, new[] { typeof(PassiveEffects) }));
                codes.RemoveAt(i);
                break;
            }
        }

        return codes;
    }

    [HarmonyPatch(typeof(MinEventActionBase), nameof(MinEventActionBase.ParseXmlAttribute))]
    [HarmonyTranspiler]
    private static IEnumerable<CodeInstruction> Transpiler_ParseXmlAttribute_MinEventActionBase(IEnumerable<CodeInstruction> instructions)
    {
        var codes = instructions.ToList();
        MethodInfo mtd_enum_parse = AccessTools.Method(typeof(EnumUtils), nameof(EnumUtils.Parse), new[] { typeof(string), typeof(bool) }, new[] { typeof(MinEventTypes) });

        for (int i = 0; i < codes.Count; i++)
        {
            if (codes[i].Calls(mtd_enum_parse))
            {
                codes.Insert(i + 1, CodeInstruction.Call(typeof(CustomEffectEnumManager), nameof(CustomEffectEnumManager.RegisterOrGetEnum), new[] { typeof(string), typeof(bool) }, new[] { typeof(MinEventTypes) }));
                codes.RemoveAt(i);
                break;
            }
        }

        return codes;
    }

    [HarmonyPatch(typeof(ItemActionDynamicMelee), nameof(ItemActionDynamicMelee.OnHoldingUpdate))]
    [HarmonyTranspiler]
    private static IEnumerable<CodeInstruction> Transpiler_OnHoldingUpdate_ItemActionDynamicMelee(IEnumerable<CodeInstruction> instructions)
    {
        var codes = instructions.ToList();
        for (int i = 0; i < codes.Count; i++)
        {
            if (codes[i].Is(OpCodes.Ldc_R4, 0.1f))
            {
                codes.RemoveRange(i, 2);
                break;
            }
        }
        return codes;
    }

    [HarmonyPatch(typeof(ItemActionDynamicMelee), nameof(ItemActionDynamicMelee.canStartAttack))]
    [HarmonyTranspiler]
    private static IEnumerable<CodeInstruction> Transpiler_canStartAttack_ItemActionDynamicMelee(IEnumerable<CodeInstruction> instructions)
    {
        var codes = instructions.ToList();
        for (int i = 0; i < codes.Count; i++)
        {
            if (codes[i].Is(OpCodes.Ldc_R4, 0.1f))
            {
                codes.RemoveRange(i, 2);
                break;
            }
        }
        return codes;
    }

    /// <summary>
    /// projectile direct hit damage percent
    /// removed due to new explosion damage passives
    /// </summary>
    /// <param name="instructions"></param>
    /// <returns></returns>
    //[HarmonyPatch(typeof(ProjectileMoveScript), nameof(ProjectileMoveScript.checkCollision))]
    //[HarmonyTranspiler]
    //private static IEnumerable<CodeInstruction> Transpiler_checkCollision_ProjectileMoveScript(IEnumerable<CodeInstruction> instructions)
    //{
    //    var codes = instructions.ToList();
    //    var fld_strain = AccessTools.Field(typeof(ItemActionLauncher.ItemActionDataLauncher), nameof(ItemActionLauncher.ItemActionDataLauncher.strainPercent));
    //    var mtd_block = AccessTools.Method(typeof(ItemActionAttack), nameof(ItemActionAttack.GetDamageBlock));

    //    for (int i = 0; i < codes.Count; i++)
    //    {
    //        if (codes[i].LoadsField(fld_strain))
    //        {
    //            codes.InsertRange(i + 1, new CodeInstruction[]
    //            {
    //                new CodeInstruction(OpCodes.Ldarg_0),
    //                CodeInstruction.LoadField(typeof(ProjectileMoveScript), nameof(ProjectileMoveScript.itemValueProjectile)),
    //                new CodeInstruction(OpCodes.Ldloc_S, 4),
    //                CodeInstruction.Call(typeof(CommonUtilityPatch), codes[i - 3].Calls(mtd_block) ? nameof(GetProjectileBlockDamagePerc) : nameof(GetProjectileEntityDamagePerc)),
    //                new CodeInstruction(OpCodes.Mul)
    //            });
    //        }
    //    }

    //    return codes;
    //}

    //public static float GetProjectileBlockDamagePerc(ItemValue _itemValue, EntityAlive _holdingEntity)
    //{
    //    return EffectManager.GetValue(CustomEnums.ProjectileImpactDamagePercentBlock, _itemValue, 1, _holdingEntity, null);
    //}

    //public static float GetProjectileEntityDamagePerc(ItemValue _itemValue, EntityAlive _holdingEntity)
    //{
    //    return EffectManager.GetValue(CustomEnums.ProjectileImpactDamagePercentEntity, _itemValue, 1, _holdingEntity, null);
    //}

    /// <summary>
    /// force tpv crosshair
    /// </summary>
    /// <param name="instructions"></param>
    /// <returns></returns>
    [HarmonyPatch(typeof(EntityPlayerLocal), nameof(EntityPlayerLocal.guiDrawCrosshair))]
    [HarmonyTranspiler]
    private static IEnumerable<CodeInstruction> Transpiler_guiDrawCrosshair_EntityPlayerLocal(IEnumerable<CodeInstruction> instructions)
    {
        var codes = instructions.ToList();

        FieldInfo fld_debug = AccessTools.Field(typeof(ItemAction), nameof(ItemAction.ShowDistanceDebugInfo));

        for ( int i = 0; i < codes.Count; i++ )
        {
            if (codes[i].LoadsField(fld_debug))
            {
                var label = codes[i - 1].operand;
                codes.InsertRange(i, new[]
                {
                    new CodeInstruction(OpCodes.Ldarg_0),
                    CodeInstruction.LoadField(typeof(EntityPlayerLocal), nameof(EntityPlayerLocal.bFirstPersonView)),
                    new CodeInstruction(OpCodes.Brfalse_S, label)
                });
                break;
            }
        }

        return codes;
    }

    /// <summary>
    /// correctly apply muzzle flash silence with modifications
    /// </summary>
    /// <param name="_data"></param>
    [HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.OnModificationsChanged))]
    [HarmonyPostfix]
    private static void Postfix_OnModificationsChanged_ItemActionRanged(ItemActionData _data)
    {
        ItemActionRanged.ItemActionDataRanged itemActionDataRanged = _data as ItemActionRanged.ItemActionDataRanged;
        if (itemActionDataRanged.SoundStart.Contains("silenced"))
        {
            itemActionDataRanged.IsFlashSuppressed = true;
        }

        //should fix stuck on switching item?
        itemActionDataRanged.isReloadCancelled = false;
        itemActionDataRanged.isWeaponReloadCancelled = false;
        itemActionDataRanged.isReloading = false;
        itemActionDataRanged.isWeaponReloading = false;
        itemActionDataRanged.isChangingAmmoType = false;
    }

    #region item tags modifier

    /// <summary>
    /// should handle swapping mod
    /// first check if the mod to install can be installed after current mod is removed
    /// then check if any other mod requires or conflicts current mod
    /// </summary>
    /// <param name="instructions"></param>
    /// <param name="generator"></param>
    /// <returns></returns>
    [HarmonyPatch(typeof(XUiC_ItemPartStack), nameof(XUiC_ItemPartStack.CanSwap))]
    [HarmonyTranspiler]
    private static IEnumerable<CodeInstruction> Transpiler_CanSwap_XUiC_ItemPartStack(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
    {
        var codes = instructions.ToList();

        LocalBuilder lbd_tags_if_remove_prev = generator.DeclareLocal(typeof(FastTags<TagGroup.Global>));
        LocalBuilder lbd_tags_if_install_new = generator.DeclareLocal(typeof(FastTags<TagGroup.Global>));
        MethodInfo mtd_get_item_class = AccessTools.PropertyGetter(typeof(ItemValue), nameof(ItemValue.ItemClass));
        MethodInfo mtd_has_any_tags = AccessTools.Method(typeof(ItemClass), nameof(ItemClass.HasAnyTags));
        MethodInfo mtd_test_any_set = AccessTools.Method(typeof(FastTags<TagGroup.Global>), nameof(FastTags<TagGroup.Global>.Test_AnySet));
        FieldInfo fld_mod = AccessTools.Field(typeof(ItemValue), nameof(ItemValue.Modifications));
        FieldInfo fld_installable_tags = AccessTools.Field(typeof(ItemClassModifier), nameof(ItemClassModifier.InstallableTags));

        for (int i = 3; i < codes.Count; i++)
        {
            //get current tags
            if (codes[i].opcode == OpCodes.Stloc_2)
            {
                codes.InsertRange(i + 1, new[]
                {
                    new CodeInstruction(OpCodes.Ldloc_1),
                    new CodeInstruction(OpCodes.Ldarg_0),
                    CodeInstruction.LoadField(typeof(XUiC_ItemPartStack), "itemValue"),
                    CodeInstruction.Call(typeof(LocalItemTagsManager), nameof(LocalItemTagsManager.GetTagsAsIfNotInstalled)),
                    new CodeInstruction(OpCodes.Stloc_S, lbd_tags_if_remove_prev),
                    new CodeInstruction(OpCodes.Ldloc_1),
                    new CodeInstruction(OpCodes.Ldloc_0),
                    CodeInstruction.Call(typeof(LocalItemTagsManager), nameof(LocalItemTagsManager.GetTagsAsIfInstalled)),
                    new CodeInstruction(OpCodes.Stloc_S, lbd_tags_if_install_new)
                });
                i += 10;
                Log.Out("mod 1!!!");
            }
            //replace checking tags
            else if (codes[i].Calls(mtd_has_any_tags) && codes[i - 3].opcode == OpCodes.Ldloc_2)
            {
                if (codes[i - 1].LoadsField(fld_installable_tags) && (codes[i + 1].opcode == OpCodes.Brtrue || codes[i + 1].opcode == OpCodes.Brtrue_S))
                {
                    var lbl_prev = codes[i + 4].ExtractLabels();
                    var lbl_jump = generator.DefineLabel();
                    codes[i + 4].WithLabels(lbl_jump);
                    codes.InsertRange(i + 4, new[]
                    {
                        new CodeInstruction(OpCodes.Ldloc_1).WithLabels(lbl_prev),
                        new CodeInstruction(OpCodes.Ldarg_0),
                        CodeInstruction.LoadField(typeof(XUiC_ItemPartStack), "itemValue"),
                        new CodeInstruction(OpCodes.Ldloc_0),
                        CodeInstruction.Call(typeof(LocalItemTagsManager), nameof(LocalItemTagsManager.CanSwapMod)),
                        new CodeInstruction(OpCodes.Brtrue, lbl_jump),
                        new CodeInstruction(OpCodes.Ldc_I4_0),
                        new CodeInstruction(OpCodes.Ret)
                    });
                }
                codes[i - 3].opcode = OpCodes.Ldloca_S;
                codes[i - 3].operand = lbd_tags_if_remove_prev;
                codes[i].opcode = OpCodes.Call;
                codes[i].operand = mtd_test_any_set;
                Log.Out("mod 2!!!");
            }
        }

        return codes;
    }

    [HarmonyPatch(typeof(XUiC_ItemCosmeticStack), nameof(XUiC_ItemCosmeticStack.CanSwap))]
    [HarmonyTranspiler]
    private static IEnumerable<CodeInstruction> Transpiler_CanSwap_XUiC_ItemCosmeticStack(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
    {
        var codes = instructions.ToList();

        LocalBuilder lbd_tags_if_remove_prev = generator.DeclareLocal(typeof(FastTags<TagGroup.Global>));
        LocalBuilder lbd_tags_if_install_new = generator.DeclareLocal(typeof(FastTags<TagGroup.Global>));
        LocalBuilder lbd_item_being_assembled = generator.DeclareLocal(typeof(ItemValue));
        MethodInfo mtd_get_item_class = AccessTools.PropertyGetter(typeof(ItemValue), nameof(ItemValue.ItemClass));
        MethodInfo mtd_has_any_tags = AccessTools.Method(typeof(ItemClass), nameof(ItemClass.HasAnyTags));
        MethodInfo mtd_test_any_set = AccessTools.Method(typeof(FastTags<TagGroup.Global>), nameof(FastTags<TagGroup.Global>.Test_AnySet));
        MethodInfo mtd_get_xui = AccessTools.PropertyGetter(typeof(XUiController), nameof(XUiController.xui));
        MethodInfo mtd_get_cur_item = AccessTools.PropertyGetter(typeof(XUiM_AssembleItem), nameof(XUiM_AssembleItem.CurrentItem));
        FieldInfo fld_cos = AccessTools.Field(typeof(ItemValue), nameof(ItemValue.CosmeticMods));
        FieldInfo fld_installable_tags = AccessTools.Field(typeof(ItemClassModifier), nameof(ItemClassModifier.InstallableTags));

        for (int i = 3; i < codes.Count; i++)
        {
            //get current tags
            if ((codes[i].opcode == OpCodes.Brtrue || codes[i].opcode == OpCodes.Brtrue_S) && codes[i - 1].opcode == OpCodes.Ldloc_0)
            {
                codes.InsertRange(i + 3, new[]
                {
                    new CodeInstruction(OpCodes.Ldarg_0).MoveLabelsFrom(codes[i + 3]),
                    new CodeInstruction(OpCodes.Call, mtd_get_xui),
                    CodeInstruction.LoadField(typeof(XUi), nameof(XUi.AssembleItem)),
                    new CodeInstruction(OpCodes.Callvirt, mtd_get_cur_item),
                    CodeInstruction.LoadField(typeof(ItemStack), nameof(ItemStack.itemValue)),
                    new CodeInstruction(OpCodes.Stloc_S, lbd_item_being_assembled),
                    new CodeInstruction(OpCodes.Ldloc_S, lbd_item_being_assembled),
                    new CodeInstruction(OpCodes.Ldarg_0),
                    CodeInstruction.LoadField(typeof(XUiC_ItemCosmeticStack), "itemValue"),
                    CodeInstruction.Call(typeof(LocalItemTagsManager), nameof(LocalItemTagsManager.GetTagsAsIfNotInstalled)),
                    new CodeInstruction(OpCodes.Stloc_S, lbd_tags_if_remove_prev),
                    new CodeInstruction(OpCodes.Ldloc_S, lbd_item_being_assembled),
                    new CodeInstruction(OpCodes.Ldloc_0),
                    CodeInstruction.Call(typeof(LocalItemTagsManager), nameof(LocalItemTagsManager.GetTagsAsIfInstalled)),
                    new CodeInstruction(OpCodes.Stloc_S, lbd_tags_if_install_new)
                });
                i += 18;
                Log.Out("cos 1!!!");
            }
            //replace checking tags
            else if (codes[i].Calls(mtd_has_any_tags) && codes[i - 3].Calls(mtd_get_item_class))
            {
                if (codes[i - 1].LoadsField(fld_installable_tags) && (codes[i + 1].opcode == OpCodes.Brtrue || codes[i + 1].opcode == OpCodes.Brtrue_S))
                {
                    var lbl_prev = codes[i + 4].ExtractLabels();
                    var lbl_jump = generator.DefineLabel();
                    codes[i + 4].WithLabels(lbl_jump);
                    codes.InsertRange(i + 4, new[]
                    {
                        new CodeInstruction(OpCodes.Ldloc_S, lbd_item_being_assembled).WithLabels(lbl_prev),
                        new CodeInstruction(OpCodes.Ldarg_0),
                        CodeInstruction.LoadField(typeof(XUiC_ItemPartStack), "itemValue"),
                        new CodeInstruction(OpCodes.Ldloc_0),
                        CodeInstruction.Call(typeof(LocalItemTagsManager), nameof(LocalItemTagsManager.CanSwapMod)),
                        new CodeInstruction(OpCodes.Brtrue, lbl_jump),
                        new CodeInstruction(OpCodes.Ldc_I4_0),
                        new CodeInstruction(OpCodes.Ret)
                    });
                }
                codes[i - 8].MoveLabelsTo(codes[i - 3]);
                codes[i - 3].opcode = OpCodes.Ldloca_S;
                codes[i - 3].operand = lbd_tags_if_remove_prev;
                codes[i].opcode = OpCodes.Call;
                codes[i].operand = mtd_test_any_set;
                codes.RemoveRange(i - 8, 5);
                i -= 5;
                Log.Out("cos 2!!!");
            }
        }

        return codes;
    }

    /// <summary>
    /// check if other mods relies on this one
    /// </summary>
    /// <param name="__result"></param>
    /// <param name="__instance"></param>
    /// <param name="___itemValue"></param>
    [HarmonyPatch(typeof(XUiC_ItemPartStack), "CanRemove")]
    [HarmonyPostfix]
    private static void Postfix_CanRemove_XUiC_ItemPartStack(ref bool __result, XUiC_ItemPartStack __instance)
    {
        if (__result && __instance.xui?.AssembleItem?.CurrentItem?.itemValue is ItemValue itemValue)
        {
            ItemClass itemClass = itemValue.ItemClass;
            FastTags<TagGroup.Global> tagsAfterRemove = LocalItemTagsManager.GetTagsAsIfNotInstalled(itemValue, __instance.itemValue);
            if (tagsAfterRemove.IsEmpty)
            {
                __result = false;
                return;
            }

            foreach (var mod in itemValue.Modifications)
            {
                if (mod.IsEmpty())
                    continue;
                ItemClassModifier modClass = mod.ItemClass as ItemClassModifier;
                if (modClass == null || !tagsAfterRemove.Test_AnySet(modClass.InstallableTags) || tagsAfterRemove.Test_AnySet(modClass.DisallowedTags))
                {
                    __result = false;
                    return;
                }
            }
        }
    }

    [HarmonyPatch(typeof(XUiC_ItemCosmeticStack), "CanRemove")]
    [HarmonyPostfix]
    private static void Postfix_CanRemove_XUiC_ItemCosmeticStack(ref bool __result, XUiC_ItemCosmeticStack __instance)
    {
        if (__result && __instance.xui?.AssembleItem?.CurrentItem?.itemValue is ItemValue itemValue)
        {
            ItemClass itemClass = itemValue.ItemClass;
            FastTags<TagGroup.Global> tagsAfterRemove = LocalItemTagsManager.GetTagsAsIfNotInstalled(itemValue, __instance.itemValue);
            if (tagsAfterRemove.IsEmpty)
            {
                __result = false;
                return;
            }

            foreach (var mod in itemValue.CosmeticMods)
            {
                if (mod.IsEmpty())
                    continue;
                ItemClassModifier modClass = mod.ItemClass as ItemClassModifier;
                if (modClass == null || !tagsAfterRemove.Test_AnySet(modClass.InstallableTags) || tagsAfterRemove.Test_AnySet(modClass.DisallowedTags))
                {
                    __result = false;
                    return;
                }
            }
        }
    }

    /// <summary>
    /// should update the gear icon?
    /// </summary>
    /// <param name="instructions"></param>
    /// <param name="generator"></param>
    /// <returns></returns>
    [HarmonyPatch(typeof(XUiC_ItemStack), nameof(XUiC_ItemStack.updateLockTypeIcon))]
    [HarmonyTranspiler]
    private static IEnumerable<CodeInstruction> Transpiler_updateLockTypeIcon_XUiC_ItemStack(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
    {
        var codes = instructions.ToList();

        LocalBuilder lbd_tags = generator.DeclareLocal(typeof(FastTags<TagGroup.Global>));
        MethodInfo mtd_has_any_tags = AccessTools.Method(typeof(ItemClass), nameof(ItemClass.HasAnyTags));
        MethodInfo mtd_test_any_set = AccessTools.Method(typeof(FastTags<TagGroup.Global>), nameof(FastTags<TagGroup.Global>.Test_AnySet));
        MethodInfo mtd_get_item_class = AccessTools.PropertyGetter(typeof(ItemValue), nameof(ItemValue.ItemClass));
        MethodInfo mtd_get_cur_item = AccessTools.PropertyGetter(typeof(XUiM_AssembleItem), nameof(XUiM_AssembleItem.CurrentItem));
        MethodInfo mtd_get_xui = AccessTools.PropertyGetter(typeof(XUiController), nameof(XUiController.xui));

        for (int i = 3; i < codes.Count; i++)
        {
            //get current tags
            if ((codes[i].opcode == OpCodes.Brfalse_S || codes[i].opcode == OpCodes.Brfalse) && codes[i - 1].Calls(mtd_get_cur_item))
            {
                codes.InsertRange(i + 1, new[]
                {
                    new CodeInstruction(OpCodes.Ldarg_0),
                    new CodeInstruction(OpCodes.Call, mtd_get_xui),
                    CodeInstruction.LoadField(typeof(XUi), nameof(XUi.AssembleItem)),
                    new CodeInstruction(OpCodes.Callvirt, mtd_get_cur_item),
                    CodeInstruction.LoadField(typeof(ItemStack), nameof(ItemStack.itemValue)),
                    CodeInstruction.Call(typeof(LocalItemTagsManager), nameof(LocalItemTagsManager.GetTags)),
                    new CodeInstruction(OpCodes.Stloc_S, lbd_tags)
                });
                i += 7;
            }
            //do not touch check on the modification item
            else if (codes[i].Calls(mtd_has_any_tags) && codes[i - 3].Calls(mtd_get_item_class))
            {
                codes[i].opcode = OpCodes.Call;
                codes[i].operand = mtd_test_any_set;
                var insert = new CodeInstruction(OpCodes.Ldloca_S, lbd_tags);
                codes[i - 8].MoveLabelsTo(insert);
                codes.RemoveRange(i - 8, 6);
                codes.Insert(i - 8, insert);
                i -= 5;
            }
        }

        return codes;
    }

    /// <summary>
    /// when installing new mod, use modified tags to check for compatibility
    /// </summary>
    /// <param name="instructions"></param>
    /// <param name="generator"></param>
    /// <returns></returns>
    [HarmonyPatch(typeof(XUiM_AssembleItem), nameof(XUiM_AssembleItem.AddPartToItem))]
    [HarmonyTranspiler]
    private static IEnumerable<CodeInstruction> Transpiler_AddPartToItem_XUiM_AssembleItem(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
    {
        var codes = instructions.ToList();

        LocalBuilder lbd_tags_cur = generator.DeclareLocal(typeof(FastTags<TagGroup.Global>));
        LocalBuilder lbd_tags_after_install = generator.DeclareLocal(typeof(FastTags<TagGroup.Global>));
        MethodInfo mtd_has_any_tags = AccessTools.Method(typeof(ItemClass), nameof(ItemClass.HasAnyTags));
        MethodInfo mtd_test_any_set = AccessTools.Method(typeof(FastTags<TagGroup.Global>), nameof(FastTags<TagGroup.Global>.Test_AnySet));
        MethodInfo mtd_get_item_class = AccessTools.PropertyGetter(typeof(ItemValue), nameof(ItemValue.ItemClass));
        MethodInfo mtd_get_cur_item = AccessTools.PropertyGetter(typeof(XUiM_AssembleItem), nameof(XUiM_AssembleItem.CurrentItem));
        MethodInfo mtd_is_empty = AccessTools.Method(typeof(ItemValue), nameof(ItemValue.IsEmpty));
        FieldInfo fld_cos = AccessTools.Field(typeof(ItemValue), nameof(ItemValue.CosmeticMods));
        FieldInfo fld_mod = AccessTools.Field(typeof(ItemValue), nameof(ItemValue.Modifications));
        FieldInfo fld_installable_tags = AccessTools.Field(typeof(ItemClassModifier), nameof(ItemClassModifier.InstallableTags));
        
        for (int i = 3; i < codes.Count; i++)
        {
            //get current tags
            if (codes[i].opcode == OpCodes.Stloc_0)
            {
                codes.InsertRange(i + 1, new[]
                {
                    new CodeInstruction(OpCodes.Ldarg_0),
                    new CodeInstruction(OpCodes.Call, mtd_get_cur_item),
                    CodeInstruction.LoadField(typeof(ItemStack), nameof(ItemStack.itemValue)),
                    new CodeInstruction(OpCodes.Dup),
                    CodeInstruction.Call(typeof(LocalItemTagsManager), nameof(LocalItemTagsManager.GetTags)),
                    new CodeInstruction(OpCodes.Stloc_S, lbd_tags_cur),
                    new CodeInstruction(OpCodes.Ldloc_0),
                    CodeInstruction.Call(typeof(LocalItemTagsManager), nameof(LocalItemTagsManager.GetTagsAsIfInstalled)),
                    new CodeInstruction(OpCodes.Stloc_S, lbd_tags_after_install)
                });
                i += 9;
            }
            //do not touch check on the modification item, check if current mod can be installed
            else if (codes[i].Calls(mtd_has_any_tags) && codes[i - 3].Calls(mtd_get_item_class))
            {
                if (codes[i - 1].LoadsField(fld_installable_tags))
                {
                    codes.InsertRange(i + 2, new[]
                    {
                        new CodeInstruction(OpCodes.Ldarg_0),
                        new CodeInstruction(OpCodes.Call, mtd_get_cur_item),
                        CodeInstruction.LoadField(typeof(ItemStack), nameof(ItemStack.itemValue)),
                        new CodeInstruction(OpCodes.Ldloc_0),
                        CodeInstruction.Call(typeof(LocalItemTagsManager), nameof(LocalItemTagsManager.CanInstallMod)),
                        new CodeInstruction(OpCodes.Brfalse, codes[i + 1].operand)
                    });
                }
                codes[i].opcode = OpCodes.Call;
                codes[i].operand = mtd_test_any_set;
                var insert = new CodeInstruction(OpCodes.Ldloca_S, lbd_tags_cur);
                codes[i - 6].MoveLabelsTo(insert);
                codes.RemoveRange(i - 6, 4);
                codes.Insert(i - 6, insert);
                i -= 3;
            }
        }

        return codes;
    }

    #endregion

    //change when aiming events are fired
    [HarmonyPatch(typeof(EntityPlayerLocal), nameof(EntityPlayerLocal.SetMoveState))]
    [HarmonyTranspiler]
    private static IEnumerable<CodeInstruction> Transpiler_SetMoveState_EntityPlayerLocal(IEnumerable<CodeInstruction> instructions)
    {
        var codes = instructions.ToList();

        FieldInfo fld_msa = AccessTools.Field(typeof(EntityPlayerLocal), nameof(EntityPlayerLocal.moveStateAiming));

        for (int i = 0; i < codes.Count - 2; i++)
        {
            if (codes[i].LoadsField(fld_msa) && codes[i + 2].opcode == OpCodes.Ldloc_1)
            {
                codes[i - 2].MoveLabelsTo(codes[i + 13]);
                codes.RemoveRange(i - 2, 15);
                break;
            }
        }

        return codes;
    }

    [HarmonyPatch(typeof(EntityAlive), nameof(EntityAlive.AimingGun), MethodType.Setter)]
    [HarmonyPrefix]
    private static bool Prefix_AimingGun_EntityAlive(bool value, EntityAlive __instance)
    {
        if (__instance is EntityPlayerLocal && __instance.inventory != null)
        {
            bool isAimingGun = __instance.AimingGun;
            if (value != isAimingGun)
            {
                __instance.FireEvent(value ? MinEventTypes.onSelfAimingGunStart : MinEventTypes.onSelfAimingGunStop, true);
#if DEBUG
                Log.Out(value ? "START AIMING GUN FIRED" : "STOP AIMING GUN FIRED");
#endif
            }
        }
        return true;
    }

    [HarmonyPatch(typeof(ItemActionAttack), nameof(ItemActionAttack.HasRadial))]
    [HarmonyPostfix]
    private static void Postfix_HasRadial_ItemActionAttack(ref bool __result)
    {
        EntityPlayerLocal player = GameManager.Instance.World?.GetPrimaryPlayer();
        int index = MultiActionManager.GetActionIndexForEntity(player);
        List<ItemActionData> actionDatas = player.inventory?.holdingItemData?.actionData;
        if (actionDatas != null && actionDatas.Count > index && actionDatas[index] is ItemActionRanged.ItemActionDataRanged rangedData && (rangedData.isReloading || rangedData.isWeaponReloading))
        {
            __result = false;
        }
    }

    [HarmonyPatch(typeof(ItemActionAttack), nameof(ItemActionAttack.SetupRadial))]
    [HarmonyTranspiler]
    private static IEnumerable<CodeInstruction> Transpiler_SetupRadial_ItemActionAttack(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
    {
        var codes = instructions.ToList();

        var fld_usable = AccessTools.Field(typeof(ItemClass), nameof(ItemClass.UsableUnderwater));

        var lbd_states = generator.DeclareLocal(typeof(bool[]));

        for (int i = 0; i < codes.Count; i++)
        {
            if (codes[i].opcode == OpCodes.Stloc_0)
            {
                codes.InsertRange(i + 1, new[]
                {
                    new CodeInstruction(OpCodes.Ldloc_0),
                    new CodeInstruction(OpCodes.Ldarg_2),
                    new CodeInstruction(OpCodes.Ldc_I4_M1),
                    CodeInstruction.Call(typeof(CommonUtilityPatch), nameof(CommonUtilityPatch.GetUnusableItemEntries)),
                    new CodeInstruction(OpCodes.Stloc_S, lbd_states)
                });
                i += 4;
            }
            else if (codes[i].LoadsField(fld_usable))
            {
                codes.InsertRange(i + 2, new[]
                {
                    new CodeInstruction(OpCodes.Ldloc_S, lbd_states).WithLabels(codes[i + 2].ExtractLabels()),
                    new CodeInstruction(OpCodes.Ldloc_2),
                    CodeInstruction.Call(typeof(CommonUtilityPatch), nameof(CommonUtilityPatch.IsAmmoDisabled)),
                    new CodeInstruction(OpCodes.Brtrue_S, codes[i + 1].operand)
                });
                break;
            }
        }

        return codes;
    }

    [HarmonyPatch(typeof(ItemAction), nameof(ItemAction.StartHolding))]
    [HarmonyPostfix]
    private static void Postfix_StartHolding_ItemAction(ItemActionData _data, ItemAction __instance)
    {
        if (__instance is ItemActionAttack itemActionAttack && _data.invData.holdingEntity is EntityPlayerLocal player)
        {
            var arr_disabled_ammo = GetUnusableItemEntries(itemActionAttack.MagazineItemNames, player, _data.indexInEntityOfAction);
            if (arr_disabled_ammo == null)
            {
                return;
            }
            var itemValue = _data.invData.itemValue;
            int cur_index = itemValue.GetSelectedAmmoIndexByActionIndex(_data.indexInEntityOfAction);
            if (arr_disabled_ammo[cur_index])
            {
                int first_enabled_index = Mathf.Max(Array.IndexOf(arr_disabled_ammo, false), 0);

                var mapping = MultiActionManager.GetMappingForEntity(player.entityId);
                if (mapping != null)
                {
                    if (_data.indexInEntityOfAction == mapping.CurMetaIndex)
                    {
                        itemValue.SelectedAmmoTypeIndex = (byte)first_enabled_index;
                    }
                    else
                    {
                        itemValue.SetMetadata(MultiActionUtils.ActionSelectedAmmoNames[mapping.indices.GetMetaIndexForActionIndex(_data.indexInEntityOfAction)], first_enabled_index, TypedMetadataValue.TypeTag.Integer);
                    }
                    _data.invData.holdingEntity.inventory.CallOnToolbeltChangedInternal();
                }
                else
                {
                    itemValue.SelectedAmmoTypeIndex = (byte)(first_enabled_index);
                }
            }
        }
    }

    public static bool[] GetUnusableItemEntries(string[] ammoNames, EntityPlayerLocal player, int actionIndex = -1)
    {
        if (ammoNames == null)
        {
            return null;
        }
        if (actionIndex < 0)
        {
            actionIndex = MultiActionManager.GetActionIndexForEntity(player);
        }
        string str_disabled_ammo_names = player.inventory.holdingItemItemValue.GetPropertyOverrideForAction("DisableAmmo", "", actionIndex);
        //Log.Out($"checking disabled ammo: {str_disabled_ammo_names}\n{StackTraceUtility.ExtractStackTrace()}");
        bool[] arr_disable_states = new bool[ammoNames.Length];
        if(!string.IsNullOrEmpty(str_disabled_ammo_names))
        {
            string[] arr_disabled_ammo_names = str_disabled_ammo_names.Split(',', StringSplitOptions.RemoveEmptyEntries);
            foreach (var name in arr_disabled_ammo_names)
            {
                int index = Array.IndexOf(ammoNames, name.Trim());
                if (index >= 0)
                {
                    arr_disable_states[index] = true;
                    if (ConsoleCmdReloadLog.LogInfo)
                        Log.Out($"ammo {ammoNames[index]} is disabled");
                }
            }
        }
        return arr_disable_states;
    }

    private static bool IsAmmoDisabled(bool[] ammoStates, int index)
    {
        if (ammoStates == null || ammoStates.Length <= index)
        {
            return false;
        }
        return ammoStates[index];
    }

    //dont spread onSelfItemActivate/onSelfItemDeactivate to attachments
    //handle start holding
    [HarmonyPatch(typeof(Inventory), nameof(Inventory.syncHeldItem))]
    [HarmonyTranspiler]
    private static IEnumerable<CodeInstruction> Transpiler_syncHeldItem_Inventory(IEnumerable<CodeInstruction> instructions)
    {
        var codes = instructions.ToList();

        var prop_itemvalue = AccessTools.PropertyGetter(typeof(Inventory), nameof(Inventory.holdingItemItemValue));
        var mtd_fireevent = AccessTools.Method(typeof(ItemValue), nameof(ItemValue.FireEvent));

        for (int i = 0; i < codes.Count; i++)
        {
            if (codes[i].Calls(mtd_fireevent) && codes[i - 5].Calls(prop_itemvalue))
            {
                codes[i] = CodeInstruction.Call(typeof(MinEffectController), nameof(MinEffectController.FireEvent));
                codes.InsertRange(i - 4, new[]
                {
                    CodeInstruction.Call(typeof(ItemValue), "get_ItemClass"),
                    CodeInstruction.LoadField(typeof(ItemClass), nameof(ItemClass.Effects))
                });
                i += 2;
            }
        }

        return codes;
    }

    //handle radial activation
    [HarmonyPatch(typeof(XUiC_Radial), nameof(XUiC_Radial.handleActivatableItemCommand))]
    [HarmonyTranspiler]
    private static IEnumerable<CodeInstruction> Transpiler_handleActivatableItemCommand_XUiC_Radial(IEnumerable<CodeInstruction> instructions)
    {
        var codes = instructions.ToList();

        var mtd_fireevent = AccessTools.Method(typeof(ItemValue), nameof(ItemValue.FireEvent));
        for (int i = 0; i < codes.Count; i++)
        {
            if (codes[i].Calls(mtd_fireevent))
            {
                codes[i] = CodeInstruction.Call(typeof(MinEffectController), nameof(MinEffectController.FireEvent));
                codes.InsertRange(i - 2, new[]
                {
                    CodeInstruction.Call(typeof(ItemValue), "get_ItemClass"),
                    CodeInstruction.LoadField(typeof(ItemClass), nameof(ItemClass.Effects))
                });
                i += 2;
            }
        }
        return codes;
    }

    //handle equipments
    [HarmonyPatch(typeof(Equipment), nameof(Equipment.SetSlotItem))]
    [HarmonyTranspiler]
    private static IEnumerable<CodeInstruction> Transpiler_SetSlotItem_Equipment(IEnumerable<CodeInstruction> instructions)
    {
        var codes = instructions.ToList();

        var mtd_fireevent = AccessTools.Method(typeof(ItemValue), nameof(ItemValue.FireEvent));
        for (int i = 0; i < codes.Count; i++)
        {
            if (codes[i].Calls(mtd_fireevent) && codes[i - 5].opcode == OpCodes.Ldloc_0 && codes[i - 4].OperandIs((int)MinEventTypes.onSelfItemDeactivate))
            {
                codes[i] = CodeInstruction.Call(typeof(MinEffectController), nameof(MinEffectController.FireEvent));
                codes.InsertRange(i - 4, new[]
                {
                    CodeInstruction.Call(typeof(ItemValue), "get_ItemClass"),
                    CodeInstruction.LoadField(typeof(ItemClass), nameof(ItemClass.Effects))
                });
                i += 2;
            }
        }
        return codes;
    }

    [HarmonyPatch(typeof(GameManager), nameof(GameManager.DropContentOfLootContainerServer))]
    [HarmonyTranspiler]
    private static IEnumerable<CodeInstruction> Transpiler_DropContentOfLootContainerServer_GameManager(IEnumerable<CodeInstruction> instructions)
    {
        var codes = instructions.ToList();

        var setter_localscale = AccessTools.PropertySetter(typeof(Transform), nameof(Transform.localScale));

        for (var i = 0; i < codes.Count; i++)
        {
            if (codes[i].Calls(setter_localscale))
            {
                codes.RemoveRange(i - 6, 7);
                break;
            }
        }

        return codes;
    }

    [HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.onHoldingEntityFired))]
    [HarmonyTranspiler]
    private static IEnumerable<CodeInstruction> Transpiler_onHoldingEntityFired_ItemActionRanged(IEnumerable<CodeInstruction> instructions)
    {
        var codes = instructions.ToList();

        for (int i = 0; i < codes.Count; i++)
        {
            if (codes[i].opcode == OpCodes.Ldc_R4 && codes[i].operand is 5f)
            {
                codes.RemoveAt(i);
                codes.InsertRange(i, new[]
                {
                    new CodeInstruction(OpCodes.Ldarg_1),
                    CodeInstruction.Call(typeof(CommonUtilityPatch), nameof(CommonUtilityPatch.GetMaxSpread))
                });
                break;
            }
        }
        return codes;
    }

    private static float GetMaxSpread(ItemActionData _data)
    {
        return EffectManager.GetValue(CustomEnums.MaxWeaponSpread, _data.invData.itemValue, 5f, _data.invData.holdingEntity);
    }

    [HarmonyPatch(typeof(NetEntityDistributionEntry), nameof(NetEntityDistributionEntry.getSpawnPacket))]
    [HarmonyPrefix]
    private static bool Prefix_getSpawnPacket_NetEntityDistributionEntry(NetEntityDistributionEntry __instance, ref NetPackage __result)
    {
        if (__instance.trackedEntity is EntityAlive ea)
        { 
            __result = NetPackageManager.GetPackage<NetPackageEntitySpawnWithCVar>().Setup(new EntityCreationData(__instance.trackedEntity, true), (EntityAlive)__instance.trackedEntity);
            return false;
        }
        return true;
    }

    [HarmonyPatch(typeof(World), nameof(World.SpawnEntityInWorld))]
    [HarmonyPostfix]
    private static void Postfix_SpawnEntityInWorld_World(Entity _entity)
    {
        if (_entity is EntityAlive ea && !ea.isEntityRemote)
        {
            ea.FireEvent(CustomEnums.onSelfFirstCVarSync);
        }
    }

    [HarmonyPatch(typeof(ItemActionRanged), nameof(ItemActionRanged.ItemActionEffects))]
    [HarmonyTranspiler]
    private static IEnumerable<CodeInstruction> Transpiler_ItemActionEffects_ItemActionRanged(IEnumerable<CodeInstruction> instructions)
    {
        var codes = instructions.ToList();

        for (int i = 0; i < codes.Count - 2; i++)
        {
            if (codes[i].opcode == OpCodes.Ldloc_S && ((LocalBuilder)codes[i].operand).LocalIndex == 9 && codes[i + 2].Branches(out _))
            {
                codes.InsertRange(i + 3, new[]
                {
                    new CodeInstruction(OpCodes.Ldloc_S, codes[i].operand).WithLabels(codes[i + 3].ExtractLabels()),
                    CodeInstruction.Call(typeof(CommonUtilityPatch), nameof(AddTmpMuzzleFlash)),
                });
                i += 5;
            }
            else if (codes[i].opcode == OpCodes.Ldloc_S && ((LocalBuilder)codes[i].operand).LocalIndex == 12 && codes[i + 2].Branches(out _))
            {
                codes.InsertRange(i + 3, new[]
                {
                    new CodeInstruction(OpCodes.Ldloc_S, codes[i].operand).WithLabels(codes[i + 3].ExtractLabels()),
                    CodeInstruction.Call(typeof(CommonUtilityPatch), nameof(AddTmpMuzzleFlash)),
                });
                i += 5;
            }
        }
        return codes;
    }

    private static void AddTmpMuzzleFlash(Transform trans)
    {
        if (trans.TryGetComponent<TemporaryObject>(out var tmp))
        {
            tmp.StopAllCoroutines();
            Component.Destroy(tmp);
        }
        tmp = trans.AddMissingComponent<TemporaryMuzzleFlash>();
        tmp.life = 5f;
    }

    [HarmonyPatch(typeof(vp_FPCamera), nameof(vp_FPCamera.UpdateShakes))]
    [HarmonyTranspiler]
    private static IEnumerable<CodeInstruction> Transpiler_UpdateShakes_vp_FPCamera(IEnumerable<CodeInstruction> instructions)
    {
        var codes = instructions.ToList();

        var fld_shake = AccessTools.Field(typeof(vp_FPCamera), nameof(vp_FPCamera.m_Shake));

        for (int i = 0; i < codes.Count; i++)
        {
            if (codes[i].StoresField(fld_shake))
            {
                codes.InsertRange(i + 1, new[]
                {
                    new CodeInstruction(OpCodes.Ldarg_0),
                    CodeInstruction.Call(typeof(CommonUtilityPatch), nameof(CheckShakeNaN))
                });
                break;
            }
        }
        return codes;
    }

    private static void CheckShakeNaN(vp_FPCamera fpcamera)
    {
        if (float.IsNaN(fpcamera.m_Shake.x) || float.IsNaN(fpcamera.m_Shake.y) || float.IsNaN(fpcamera.m_Shake.z))
        {
            Log.Warning("Shake1 NaN {0}, time {1}, speed {2}, amp {3}", new object[]
            {
                fpcamera.m_Shake,
                Time.time,
                fpcamera.ShakeSpeed,
                fpcamera.ShakeAmplitude
            });
            fpcamera.ShakeSpeed = 0f;
            fpcamera.m_Shake = Vector3.zero;
            fpcamera.m_Pitch += -1f;
        }
    }

    //[HarmonyPatch(typeof(EntityBuffs), nameof(EntityBuffs.AddBuff), typeof(string), typeof(Vector3i), typeof(int), typeof(bool), typeof(bool), typeof(float))]
    //[HarmonyPostfix]
    //private static void Postfix_AddBuff_EntityBuffs(string _name, EntityBuffs __instance, EntityBuffs.BuffStatus __result, bool _netSync)
    //{
    //    if (_name.StartsWith("eftZombieRandomArmor") || _name.StartsWith("eftZombieArmor"))
    //        Log.Out($"AddBuff [{_name}] on entity {__instance.parent.GetDebugName()} should sync {_netSync} result {__result.ToStringCached()}\n{StackTraceUtility.ExtractStackTrace()}");
    //}

    //[HarmonyPatch(typeof(EntityBuffs), nameof(EntityBuffs.RemoveBuff))]
    //[HarmonyPostfix]
    //private static void Postfix_RemoveBuff_EntityBuffs(string _name, EntityBuffs __instance, bool _netSync)
    //{
    //    if (_name.StartsWith("eftZombieRandomArmor") || _name.StartsWith("eftZombieArmor"))
    //        Log.Out($"RemoveBuff [{_name}] on entity {__instance.parent.GetDebugName()} should sync {_netSync}\n{StackTraceUtility.ExtractStackTrace()}");
    //}

    //[HarmonyPatch(typeof(Inventory), nameof(Inventory.Execute))]
    //[HarmonyPrefix]
    //private static void Prefix_Execute_Inventory(Inventory __instance, int _actionIdx, bool _bReleased, PlayerActionsLocal _playerActions = null)
    //{
    //    Log.Out($"Execute Inventory holding item {__instance.holdingItem.Name} slot {__instance.holdingItemIdx} action index {_actionIdx} released {_bReleased} is holster delay {__instance.IsHolsterDelayActive()} is unholster delay {__instance.IsUnholsterDelayActive()}");
    //}

    //[HarmonyPatch(typeof(Inventory), nameof(Inventory.updateHoldingItem))]
    //[HarmonyTranspiler]
    //private static IEnumerable<CodeInstruction> Transpiler_updateHoldingItem_Inventory(IEnumerable<CodeInstruction> instructions)
    //{
    //    var codes = instructions.ToList();

    //    var mtd_setholdingtrans = AccessTools.Method(typeof(Inventory), nameof(Inventory.setHoldingItemTransfrom));
    //    var mtd_showrighthand = AccessTools.Method(typeof(Inventory), nameof(Inventory.ShowRightHand));
    //    int insert = -1, take = -1;

    //    for (int i = 0; i < codes.Count; i++)
    //    {
    //        if (codes[i].Calls(mtd_showrighthand))
    //        {
    //            insert = i + 1;
    //        }
    //        else if (codes[i].Calls(mtd_setholdingtrans))
    //        {
    //            take = i - 6;
    //        }
    //    }

    //    if (take > insert)
    //    {
    //        var list_take = codes.GetRange(take, 7);
    //        codes.RemoveRange(take, 7);
    //        codes.InsertRange(insert, list_take);
    //    }

    //    return codes;
    //}
    //private static bool exported = false;
    //[HarmonyPatch(typeof(EModelSDCS), nameof(EModelSDCS.createModel))]
    //[HarmonyPostfix]
    //private static void Postfix_test(EModelSDCS __instance)
    //{
    //    if (!exported)
    //    {
    //        exported = true;
    //        var objects = new[] { __instance.entity.RootTransform.gameObject.GetComponentsInChildren<Animator>()[1] };
    //        Log.Out($"exporting objs: {objects.Length} avatar {objects[0].avatar.name} is human {objects[0].avatar.isHuman}");
    //        FbxExporter07.OnExport(objects, @"E:\Unity Projects\AnimationPlayground\Assets\ExportedProject\example_skinned_mesh_with_bones.fbx");
    //        Application.Quit();
    //    }
    //}
}

