private void HandleChangeAttributes(GameSession session, PacketReader packet)
        {
            short lockIndex = -1;
            long  itemUid   = packet.ReadLong();

            packet.Skip(8);
            bool useLock = packet.ReadBool();

            if (useLock)
            {
                packet.Skip(1);
                lockIndex = packet.ReadShort();
            }

            if (session.Player.Inventory.Items.TryGetValue(itemUid, out Item item))
            {
                item.TimesAttributesChanged++;
                var newItem        = new Item(item);
                int attributeCount = newItem.Stats.BonusAttributes.Count;
                var rng            = new Random();
                for (int i = 0; i < attributeCount; i++)
                {
                    if (i == lockIndex)
                    {
                        continue;
                    }
                    // TODO: Don't RNG the same attribute twice
                    newItem.Stats.BonusAttributes[i] = ItemStat.Of((ItemAttribute)rng.Next(35), 0.01f);
                }

                session.StateStorage[NEW_ITEM_KEY] = newItem;
                session.Send(ChangeAttributesPacket.PreviewNewItem(newItem));
            }
        }
        private void HandleSelectNewAttributes(GameSession session, PacketReader packet)
        {
            long itemUid = packet.ReadLong();

            if (session.StateStorage.TryGetValue(NEW_ITEM_KEY, out object obj))
            {
                if (obj is not Item item || itemUid != item.Uid)
                {
                    return;
                }

                session.Player.Inventory.Replace(item);
                session.Send(ChangeAttributesPacket.SelectNewItem(item));
            }
        }
    private static void HandleSelectNewAttributes(GameSession session, PacketReader packet)
    {
        long itemUid = packet.ReadLong();

        Inventory inventory = session.Player.Inventory;
        Item      gear      = inventory.TemporaryStorage.FirstOrDefault(x => x.Key == itemUid).Value;

        if (gear == null)
        {
            return;
        }

        inventory.TemporaryStorage.Remove(itemUid);
        inventory.Replace(gear);
        session.Send(ChangeAttributesPacket.AddNewItem(gear));
    }
    private static void HandleChangeAttributes(GameSession session, PacketReader packet)
    {
        short lockStatId    = -1;
        bool  isSpecialStat = false;
        long  itemUid       = packet.ReadLong();

        packet.Skip(8);
        bool useLock = packet.ReadBool();

        if (useLock)
        {
            isSpecialStat = packet.ReadBool();
            lockStatId    = packet.ReadShort();
        }

        Inventory inventory = session.Player.Inventory;

        int greenCrystalTotalAmount    = 0;
        int metacellTotalAmount        = 0;
        int crystalFragmentTotalAmount = 0;

        // There are multiple ids for each type of material
        List <KeyValuePair <long, Item> > greenCrystals = inventory.Items.Where(x => x.Value.Tag == "GreenCrystal").ToList();

        greenCrystals.ForEach(x => greenCrystalTotalAmount += x.Value.Amount);

        List <KeyValuePair <long, Item> > metacells = inventory.Items.Where(x => x.Value.Tag == "MetaCell").ToList();

        metacells.ForEach(x => metacellTotalAmount += x.Value.Amount);

        List <KeyValuePair <long, Item> > crystalFragments = inventory.Items.Where(x => x.Value.Tag == "CrystalPiece").ToList();

        crystalFragments.ForEach(x => crystalFragmentTotalAmount += x.Value.Amount);

        Item gear       = inventory.Items.FirstOrDefault(x => x.Key == itemUid).Value;
        Item scrollLock = null;

        // Check if gear exist in inventory
        if (gear == null)
        {
            return;
        }

        string tag = "";

        if (Item.IsAccessory(gear.ItemSlot))
        {
            tag = "LockItemOptionAccessory";
        }
        else if (Item.IsArmor(gear.ItemSlot))
        {
            tag = "LockItemOptionArmor";
        }
        else if (Item.IsWeapon(gear.ItemSlot))
        {
            tag = "LockItemOptionWeapon";
        }
        else if (Item.IsPet(gear.Id))
        {
            tag = "LockItemOptionPet";
        }

        if (useLock)
        {
            scrollLock = inventory.Items.FirstOrDefault(x => x.Value.Tag == tag && x.Value.Rarity == gear.Rarity).Value;
            // Check if scroll lock exist in inventory
            if (scrollLock == null)
            {
                return;
            }
        }

        int greenCrystalCost = 5;
        int metacellCosts    = Math.Min(11 + gear.TimesAttributesChanged, 25);

        // Relation between TimesAttributesChanged to amount of crystalFragments for epic gear
        int[] crystalFragmentsEpicGear =
        {
            200, 250, 312, 390, 488, 610, 762, 953, 1192, 1490, 1718, 2131, 2642, 3277, 4063
        };

        int crystalFragmentsCosts = crystalFragmentsEpicGear[Math.Min(gear.TimesAttributesChanged, 14)];

        if (gear.Rarity > (short)RarityType.Epic)
        {
            greenCrystalCost = 25;
            metacellCosts    = Math.Min(165 + gear.TimesAttributesChanged * 15, 375);
            if (gear.Rarity == (short)RarityType.Legendary)
            {
                crystalFragmentsCosts = Math.Min(400 + gear.TimesAttributesChanged * 400, 6000);
            }
            else if (gear.Rarity == (short)RarityType.Ascendant)
            {
                crystalFragmentsCosts = Math.Min(600 + gear.TimesAttributesChanged * 600, 9000);
            }
        }

        // Check if player has enough materials
        if (greenCrystalTotalAmount < greenCrystalCost || metacellTotalAmount < metacellCosts || crystalFragmentTotalAmount < crystalFragmentsCosts)
        {
            return;
        }

        gear.TimesAttributesChanged++;

        Item newItem = new(gear);

        // Get random stats except stat that is locked
        List <ItemStat> randomList = ItemStats.RollBonusStatsWithStatLocked(newItem, lockStatId, isSpecialStat);

        for (int i = 0; i < newItem.Stats.BonusStats.Count; i++)
        {
            // Check if BonusStats[i] is NormalStat and isSpecialStat is false
            // Check if BonusStats[i] is SpecialStat and isSpecialStat is true
            switch (newItem.Stats.BonusStats[i])
            {
            case NormalStat when !isSpecialStat:
            case SpecialStat when isSpecialStat:
                ItemStat stat = newItem.Stats.BonusStats[i];
                switch (stat)
                {
                case NormalStat ns when ns.ItemAttribute == (StatId)lockStatId:
                case SpecialStat ss when ss.ItemAttribute == (SpecialStatId)lockStatId:
                    continue;
                }
                break;
            }

            newItem.Stats.BonusStats[i] = randomList[i];
        }

        // Consume materials from inventory
        ConsumeMaterials(session, greenCrystalCost, metacellCosts, crystalFragmentsCosts, greenCrystals, metacells, crystalFragments);

        if (useLock)
        {
            session.Player.Inventory.ConsumeItem(session, scrollLock.Uid, 1);
        }
        inventory.TemporaryStorage[newItem.Uid] = newItem;

        session.Send(ChangeAttributesPacket.PreviewNewItem(newItem));
    }
    private static void HandleChangeAttributes(GameSession session, PacketReader packet)
    {
        short lockStatId    = -1;
        bool  isSpecialStat = false;
        long  itemUid       = packet.ReadLong();

        packet.Skip(8);
        bool useLock = packet.ReadBool();

        if (useLock)
        {
            isSpecialStat = packet.ReadBool();
            lockStatId    = packet.ReadShort();

            if (isSpecialStat)
            {
                // Match the enum ID for ItemAttribute
                lockStatId += 11000;
            }
        }

        IInventory inventory = session.Player.Inventory;
        Item       gear      = inventory.GetFromInventoryOrEquipped(itemUid);

        if (gear is null)
        {
            return;
        }

        Script   script        = ScriptLoader.GetScript("Functions/calcGetItemRemakeIngredient");
        DynValue scriptResults = script.RunFunction("calcGetItemRemakeIngredientNew", (int)gear.Type, gear.TimesAttributesChanged, gear.Rarity, gear.Level);

        IReadOnlyCollection <Item> ingredient1 = inventory.GetAllByTag(scriptResults.Tuple[0].String);
        int ingredient1Cost = (int)scriptResults.Tuple[1].Number;
        IReadOnlyCollection <Item> ingredient2 = inventory.GetAllByTag(scriptResults.Tuple[2].String);
        int ingredient2Cost = (int)scriptResults.Tuple[3].Number;
        IReadOnlyCollection <Item> ingredient3 = inventory.GetAllByTag(scriptResults.Tuple[4].String);
        int ingredient3Cost = (int)scriptResults.Tuple[5].Number;

        int ingredient1TotalAmount = ingredient1.Sum(x => x.Amount);
        int ingredient2TotalAmount = ingredient2.Sum(x => x.Amount);
        int ingredient3TotalAmount = ingredient3.Sum(x => x.Amount);

        // Check if player has enough materials
        if (ingredient1TotalAmount < ingredient1Cost || ingredient2TotalAmount < ingredient2Cost || ingredient3TotalAmount < ingredient3Cost)
        {
            return;
        }

        Item scrollLock = null;

        string tag = "";

        if (Item.IsAccessory(gear.ItemSlot))
        {
            tag = "LockItemOptionAccessory";
        }
        else if (Item.IsArmor(gear.ItemSlot))
        {
            tag = "LockItemOptionArmor";
        }
        else if (Item.IsWeapon(gear.ItemSlot))
        {
            tag = "LockItemOptionWeapon";
        }
        else if (gear.IsPet())
        {
            tag = "LockItemOptionPet";
        }

        if (useLock)
        {
            scrollLock = inventory.GetAllByTag(tag)
                         .FirstOrDefault(i => i.Rarity == gear.Rarity);
            // Check if scroll lock exist in inventory
            if (scrollLock == null)
            {
                return;
            }
        }

        gear.TimesAttributesChanged++;

        Item newItem = new(gear);

        // Get random stats except stat that is locked
        List <ItemStat> randomList = RandomStats.RollBonusStatsWithStatLocked(newItem, lockStatId, isSpecialStat);

        Dictionary <StatAttribute, ItemStat> newRandoms = new();

        for (int i = 0; i < newItem.Stats.Randoms.Count; i++)
        {
            ItemStat stat = newItem.Stats.Randoms.ElementAt(i).Value;
            // Check if BonusStats[i] is BasicStat and isSpecialStat is false
            // Check if BonusStats[i] is SpecialStat and isSpecialStat is true
            switch (stat)
            {
            case BasicStat when !isSpecialStat:
            case SpecialStat when isSpecialStat:
                switch (stat)
                {
                case SpecialStat ns when ns.ItemAttribute == (StatAttribute)lockStatId:
                case SpecialStat ss when ss.ItemAttribute == (StatAttribute)lockStatId:
                    newRandoms[stat.ItemAttribute] = stat;
                    continue;
                }
                break;
            }

            newRandoms[randomList[i].ItemAttribute] = randomList[i];
        }
        newItem.Stats.Randoms = newRandoms;

        // Consume materials from inventory
        ConsumeMaterials(session, ingredient1Cost, ingredient2Cost, ingredient3Cost, ingredient1, ingredient2, ingredient3);

        if (useLock)
        {
            session.Player.Inventory.ConsumeItem(session, scrollLock.Uid, 1);
        }
        inventory.TemporaryStorage[newItem.Uid] = newItem;

        session.Send(ChangeAttributesPacket.PreviewNewItem(newItem));
    }