/*:
 * @target MZ
 * @plugindesc (v1.0.0) Adds a Gacha minigame system to your RPG Maker MZ project. Set items, weapons, and armors with specific odds of being drawn.
 * @author Undermax Games | Maxii1996
 * @url https://undermax.itch.io/
 * @help
 * ⠀
 *   _______________________________________________________________
 * 
 * 
 *          Plugin Version:          1.0.0
 *          Developed by:            Undermax | Maxii1996
 *          Web:                     https://undermax.itch.io
 * 
 *    _______________________________________________________________
 *             
 *                      _____            _          
 *                     |  __ \          | |            
 *                     | |  \/ __ _  ___| |__   __ _      
 *                     | | __ / _` |/ __| '_ \ / _` |               
 *                     | |_\ \ (_| | (__| | | | (_| |         
 *                      \____/\__,_|\___|_| |_|\__,_|          
 *       ___  ____       _                            
 *       |  \/  (_)     (_)     
 *       | .  . |_ _ __  _  __ _  __ _ _ __ ___   ___ 
 *       | |\/| | | '_ \| |/ _` |/ _` | '_ ` _ \ / _ \
 *       | |  | | | | | | | (_| | (_| | | | | | |  __/
 *       \_|  |_/_|_| |_|_|\__, |\__,_|_| |_| |_|\___|
 *                          __/ |                     
 *                         |___/                      
 * 
 * 
 *     ============================= CHANGE LOG =============================
 *
 * 
 *                        (v1.0.0) INITIAL RELEASE 
 * 
 * 
 *     ============================ INTRODUCTION ============================ 
 *
 *   
 *        This plugin introduces a dynamic
 *        Gacha system to your RPG Maker
 *        MZ project. It allows developers
 *        to create a customizable gacha
 *        draw scene, complete with visual
 *        effects and item distribution
 *        based on specified odds.
 *    
 *        If you need to modify more
 *        detailed aspects of the effects
 *        or settings not available in the
 *        editor, you can directly modify
 *        the plugin code.
 * 
 *
 * ======================== END OF DOCUMENTATION ========================
 * 
 *
 * @param Default Texts
 * @text Default UI Texts
 * @desc Default texts for the UI elements in the Gacha System
 * @type struct<DefaultTexts>
 * 
 * @param Confetti
 * @text Confetti Effect Settings
 * @type struct<Confetti>
 * @default {"confettiCount":"100","confettiColors":"0xFF0000,0x00FF00,0x0000FF,0xFFFF00"}
 * 
 * @param Fireworks
 * @text Fireworks Effect Settings
 * @type struct<Fireworks>
 * @default {"explosionsCount":"2","particlesPerExplosion":"75","explosionColors":"0xFF0000,0x00FF00,0x0000FF,0xFFFF00,0xFF00FF"}
 * 
 * @param Sparkling Stars
 * @text Sparkling Stars Effect Settings
 * @type struct<SparklingStars>
 * @default {"starsCount":"50","starColors":"0xFFFFFF,0xFFE4B5,0xB0C4DE"}
 * 
 * @param Glowing Orbs
 * @text Glowing Orbs Effect Settings
 * @type struct<GlowingOrbs>
 * @default {"orbsCount":"30","orbsColors":"0xFF6347,0x4682B4,0x32CD32"}
 * 
 * @param Falling Leaves
 * @text Falling Leaves Effect Settings
 * @type struct<FallingLeaves>
 * @default {"leavesCount":"50","leavesColors":"0x8B4513,0xF4A460,0xDEB887"}
 * 
 * @command startGacha
 * @text Start Gacha Minigame
 * @desc Starts the Gacha minigame with the specified items, weapons, and armors.
 *
 * @arg items
 * @type struct<Item>[]
 * @text Items for Gacha
 * @desc Define items, their types (item, weapon, armor), and odds of being obtained in the Gacha game.
 * 
 * @arg shuffleIcons
 * @text Shuffle Icons
 * @desc Determines if the item icons should be shuffled. Set to true to shuffle, false to keep them in order.
 * @type boolean
 * @default false
 * 
 */

/*~struct~Item:
 * @param type
 * @text Type
 * @type select
 * @option Item
 * @option Weapon
 * @option Armor
 * @desc The type of the gacha item.
 *
 * @param id
 * @text Item ID
 * @type number
 * @desc The ID of the item, weapon, or armor in the database.
 * 
 * @param requiredSwitches
 * @text Required Switches
 * @type switch[]
 * @desc List of switches that must be ON for the item to be available in the Gacha.
 *
 * @param odds
 * @text Odds of Obtaining
 * @type string
 * @min 0
 * @max 100
 * @desc The odds (%) of obtaining this item. You can use "var(ID)" or a formula like "10 + var(ID)". (No Quotes).
 *
 * @param rarityColor
 * @text Rarity Color
 * @type string
 * @desc The HEX color code for the rarity of the item. If not provided, a default color will be used.
 * @default #FFFFFF
 * 
 * @param customSound
 * @text Custom Sound (SE)
 * @type file
 * @dir audio/se/
 * @desc The sound effect to play when this item is selected. Leave blank for no sound.
 * 
 * @param customText
 * @text Custom Text
 * @type string
 * @desc The custom text to display when this item is selected. Defaults to "Good Luck!" if not provided.
 *
 * @param effectToUse
 * @text Effect to Use
 * @type select
 * @option None
 * @option Fireworks
 * @option Confetti
 * @option Sparkling Stars
 * @option Glowing Orbs
 * @option Falling Leaves
 * @option Magic Rotating Star (Requires custom image)
 * @option Ultra Mega Legendary Effect (Requires custom image)
 * @desc The visual effect to play when this item is selected. Choose from None, Fireworks, Confetti, Sparkling Stars, Glowing Orbs, Falling Leaves, Magic Rotating Star, or Ultra Mega Legendary Effect.
 *
 * @param magicStarSettings
 * @text Magic Star Settings
 * @type struct<MagicRotatingStar>
 * @desc Settings for the Magic Rotating Star effect if selected.
 */

/*~struct~DefaultTexts:
 * @param defaultCustomText
 * @text Default Custom Text
 * @desc Default text when obtaining an item. Default is "Good Luck!".
 * @default Good Luck!
 *
 * @param obtainedItemText
 * @text Obtained Item Text
 * @desc Text to display when an item is obtained. Use %1 to represent the item name.
 * @default You Got %1!
 *
 * @param finishActionText
 * @text Finish Action Text
 * @desc Text to indicate action to finish. For example, "Press Enter to Finish".
 * @default Press Enter to Finish
 *
 * @param actionStopText
 * @text Action Stop Text
 * @desc Text to indicate action to stop the spinning. Default is "Press Action to stop!".
 * @default Press Action to stop!
 *
 * @param finishFontSize
 * @text Finish Font Size
 * @type number
 * @min 8
 * @max 48
 * @desc Font size for the finish action text.
 * @default 15
 *
 * @param finishTextColor
 * @text Finish Text Color
 * @desc Color for the finish action text. Use CSS color values.
 * @default #f4ff91
 * 
 * @param rewardFontSize
 * @text Reward Font Size
 * @type number
 * @min 8
 * @max 48
 * @desc Font size for the reward text.
 * @default 34
 *
 * @param finishTextX
 * @text Finish Text X Position
 * @type number
 * @desc X position for the finish text.
 * @default 0
 *
 * @param finishTextY
 * @text Finish Text Y Position
 * @type number
 * @desc Y position for the finish text.
 * @default 30
 * 
 * @param actionTextFontSize
 * @text Action Text Font Size
 * @type number
 * @min 8
 * @max 48
 * @desc Font size for the action text.
 * @default 33
 * 
 * @param actionTextY
 * @text Action Text Y Position
 * @type number
 * @desc Y position for the action text.
 * @default -24
 * 
 */

/*~struct~Confetti:
 * @param confettiCount
 * @text Confetti Count
 * @type number
 * @default 100
 * 
 * @param confettiColors
 * @text Confetti Colors
 * @type text
 * @default 0xFF0000,0x00FF00,0x0000FF,0xFFFF00
 */

/*~struct~Fireworks:
 * @param explosionsCount
 * @text Explosions Count
 * @type number
 * @default 2
 * 
 * @param particlesPerExplosion
 * @text Particles Per Explosion
 * @type number
 * @default 75
 * 
 * @param explosionColors
 * @text Explosion Colors
 * @type text
 * @default 0xFF0000,0x00FF00,0x0000FF,0xFFFF00,0xFF00FF
 */

/*~struct~SparklingStars:
 * @param starsCount
 * @text Stars Count
 * @type number
 * @default 50
 * 
 * @param starColors
 * @text Star Colors
 * @type text
 * @default 0xFFFFFF,0xFFE4B5,0xB0C4DE
 */

/*~struct~GlowingOrbs:
 * @param orbsCount
 * @text Orbs Count
 * @type number
 * @default 30
 * 
 * @param orbsColors
 * @text Orbs Colors
 * @type text
 * @default 0xFF6347,0x4682B4,0x32CD32
 */

/*~struct~FallingLeaves:
 * @param leavesCount
 * @text Leaves Count
 * @type number
 * @default 50
 * 
 * @param leavesColors
 * @text Leaves Colors
 * @type text
 * @default 0x8B4513,0xF4A460,0xDEB887
 */

/*~struct~MagicRotatingStar:
 * @param starTexture
 * @text Star Texture
 * @type string
 * @default img/system/Starburst.png
 * 
 * @param rotateSpeed
 * @text Rotate Speed
 * @type number
 * @decimals 2
 * @default 0.01
 * 
 * @param maxScale
 * @text Max Scale
 * @type number
 * @decimals 2
 * @default 2.2
 * 
 * @param minScale
 * @text Min Scale
 * @type number
 * @decimals 2
 * @default 1.5
 * 
 * @param scaleSpeed
 * @text Scale Speed
 * @type number
 * @decimals 2
 * @default 0.005
 * 
 * @param enableParticles
 * @text Enable Particles
 * @type boolean
 * @default true
 * @desc If true, particles will be generated around the rotating star.
 */



(() => {
    const pluginName = "GachaSystem";
    let uniqueIdCounter = 0;
    let parameters = PluginManager.parameters(pluginName);
    let defaultTexts = JSON.parse(parameters["Default Texts"] || "{}");
    let defaultCustomText = defaultTexts.defaultCustomText || "Good Luck!";
    let obtainedItemText = defaultTexts.obtainedItemText || "You Got %1!";
    let finishActionText = defaultTexts.finishActionText || "Press Enter to Finish";
    let actionStopText = defaultTexts.actionStopText || "Press Action to stop!";
    let confettiSettings = JSON.parse(parameters['Confetti'] || '{}');
    let confettiCount = Number(confettiSettings.confettiCount) || 100;
    let confettiColors = confettiSettings.confettiColors.split(',').map(color => parseInt(color)) || [0xFF0000, 0x00FF00, 0x0000FF, 0xFFFF00];
    let fireworksSettings = JSON.parse(parameters['Fireworks'] || '{}');
    let explosionsCount = Number(fireworksSettings.explosionsCount) || 2;
    let particlesPerExplosion = Number(fireworksSettings.particlesPerExplosion) || 75;
    let explosionColors = fireworksSettings.explosionColors.split(',').map(color => parseInt(color)) || [0xFF0000, 0x00FF00, 0x0000FF, 0xFFFF00, 0xFF00FF];
    let sparklingStarsSettings = JSON.parse(parameters['Sparkling Stars'] || '{}');
    let starsCount = Number(sparklingStarsSettings.starsCount) || 50;
    let starColors = sparklingStarsSettings.starColors.split(',').map(color => parseInt(color)) || [0xFFFFFF, 0xFFE4B5, 0xB0C4DE];
    let glowingOrbsSettings = JSON.parse(parameters['Glowing Orbs'] || '{}');
    let orbsCount = Number(glowingOrbsSettings.orbsCount) || 30;
    let orbsColors = glowingOrbsSettings.orbsColors.split(',').map(color => parseInt(color)) || [0xFF6347, 0x4682B4, 0x32CD32];
    let fallingLeavesSettings = JSON.parse(parameters['Falling Leaves'] || '{}');
    let leavesCount = Number(fallingLeavesSettings.leavesCount) || 50;
    let leavesColors = fallingLeavesSettings.leavesColors.split(',').map(color => parseInt(color)) || [0x8B4513, 0xF4A460, 0xDEB887];
    let magicRotatingStarSettings = JSON.parse(parameters['Magic Rotating Star'] || '{}');
    let starTexturePath = magicRotatingStarSettings.starTexture || 'img/system/Starburst2.png';
    let rotateSpeed = Number(magicRotatingStarSettings.rotateSpeed) || 0.01;
    let maxScale = Number(magicRotatingStarSettings.maxScale) || 2.2;
    let minScale = Number(magicRotatingStarSettings.minScale) || 1.5;
    let scaleSpeed = Number(magicRotatingStarSettings.scaleSpeed) || 0.005;
    let enableParticles = Boolean(magicRotatingStarSettings.enableParticles) || true;


    PluginManager.registerCommand(pluginName, "startGacha", args => {
        let uniqueIdCounter = 0;
        const itemsMap = new Map();

        JSON.parse(args.items).forEach(item => {
            const parsedItem = JSON.parse(item);
            if (typeof parsedItem.requiredSwitches === 'string') {
                try {
                    parsedItem.requiredSwitches = JSON.parse(parsedItem.requiredSwitches);
                } catch (e) {
                    parsedItem.requiredSwitches = [];
                }
            }

            if (!Array.isArray(parsedItem.requiredSwitches)) {
                parsedItem.requiredSwitches = [];
            }

            const allSwitchesActive = parsedItem.requiredSwitches.every(switchId => $gameSwitches.value(switchId));
            if (allSwitchesActive) {
                const uniqueKey = `${parsedItem.type}_${parsedItem.id}`;
                if (!itemsMap.has(uniqueKey)) {
                    parsedItem.customText = parsedItem.customText || defaultCustomText;
                    parsedItem._uniqueId = uniqueIdCounter++;
                    itemsMap.set(uniqueKey, parsedItem);
                }
            }
        });

        const shuffleIcons = args.shuffleIcons && args.shuffleIcons.toLowerCase() === "true";

        Scene_Gacha.itemsToPrepare = Array.from(itemsMap.values());

        if (shuffleIcons) {
            Scene_Gacha.itemsToPrepare = shuffle(Scene_Gacha.itemsToPrepare);
        }

        SceneManager.push(Scene_Gacha);
    });

    function shuffle(array) {
        for (let i = array.length - 1; i > 0; i--) {
            const j = Math.floor(Math.random() * (i + 1));
            [array[i], array[j]] = [array[j], array[i]];
        }
        return array;
    }

    class Scene_Gacha extends Scene_MenuBase {

        static itemsToPrepare = [];

        constructor() {
            super();
            this.initializeEffects();
            this._items = [];
            this._activeEffects = [];
            this._pathwayHeight = 100;
            this._iconWidth = 32;
            this._spacing = 48;
            this._speed = 2;
            this._deceleration = 0.05;
            this._textSprite = null;
            this._finishTextSprite = null;
        }

        initializeEffects() {
            this._stars = [];
            this._glowingOrbs = [];
            this._fallingLeaves = [];
            this._magicStars = [];
            this._particleIntervals = [];
        }

        start() {
            if (this._animationFrameId) {
                cancelAnimationFrame(this._animationFrameId);
                this._animationFrameId = null;
            }
            super.start();
            this._items = Scene_Gacha.itemsToPrepare;
            Scene_Gacha.itemsToPrepare = [];
            this._allowExit = false;
            this.createPathway();
            this.loadItemIcons();
            this.createGachaWindow();
            this._itemObtained = false;
            this._isSpinning = true;
            this._decelerating = false;
        }

        createGachaWindow() {
            this._gachaWindow = new Window_Base(new Rectangle(0, 0, Graphics.boxWidth, Graphics.boxHeight));
            this._gachaWindow.setBackgroundType(2);
            this.addWindow(this._gachaWindow);
            this.initializeActionText();
        }

        initializeActionText() {
            const text = actionStopText;
            this._actionTextSprite = new Sprite(new Bitmap(Graphics.boxWidth * 3, 48));
            this._actionTextSprite.bitmap.fontSize = Number(defaultTexts.actionTextFontSize || 33);
            this._actionTextSprite.bitmap.drawText(text, Graphics.boxWidth, 0, Graphics.boxWidth, 48, 'center');
            this._actionTextSprite.bitmap.drawText(text, 0, 0, Graphics.boxWidth, 48, 'center');
            this._actionTextSprite.bitmap.drawText(text, Graphics.boxWidth * 2, 0, Graphics.boxWidth, 48, 'center');
            this._actionTextSprite.y = this._gachaWindow.height / 2 + (Number(defaultTexts.actionTextY || -24));
            this._actionTextSprite.opacity = 240;
            this._actionTextX = -Graphics.boxWidth;
            this._actionTextSpeed = 2;
            this._opacityChangeDirection = 1;
            this._opacityTimer = 0;
            this.addChild(this._actionTextSprite);
        }

        createPathway() {
            this._pathway = new PIXI.Graphics();
            this._pathwayHeightStart = 0;
            this._pathwayHeightEnd = 100;
            this._pathwayHeightFull = Graphics.boxHeight;
            this._pathwayGrowthSpeed = 5;
            this._expandPathway = false;
            this._pathway.beginFill(0x000000, 0.5);
            this._pathway.drawRect(0, Graphics.boxHeight / 2, Graphics.boxWidth, this._pathwayHeightStart);
            this._pathway.endFill();
            this.addChild(this._pathway);
            this.growPathway();
        }

        growPathway() {
            if (this._pathwayHeightStart < this._pathwayHeightEnd) {
                this._pathwayHeightStart += this._pathwayGrowthSpeed;
                this._pathway.clear();
                this._pathway.beginFill(0x000000, 0.5);
                this._pathway.drawRect(0, (Graphics.boxHeight - this._pathwayHeightStart) / 2, Graphics.boxWidth * 1.01, this._pathwayHeightStart);
                this._pathway.endFill();
                requestAnimationFrame(this.growPathway.bind(this));
            }
        }

        loadItemIcons() {
            let posX = -50;
            this._icons = this._items.map((item) => {
                const iconIndex = this.getItemIconIndex(item);
                const iconBitmap = ImageManager.loadSystem('IconSet');
                const sprite = new Sprite();
                sprite.bitmap = new Bitmap(this._iconWidth, this._iconWidth);
                const sx = iconIndex % 16 * this._iconWidth;
                const sy = Math.floor(iconIndex / 16) * this._iconWidth;
                sprite.bitmap.blt(iconBitmap, sx, sy, this._iconWidth, this._iconWidth, 0, 0);
                sprite.x = posX;
                sprite.y = (Graphics.boxHeight - this._pathwayHeight) / 2 + (this._pathwayHeight - this._iconWidth) / 2;
                posX += this._spacing;
                this.addChild(sprite);
                return { sprite, dx: this._speed, item };
            });

            this._animation = true;
            this.animateIcons();
        }

        exitScene() {
            this._itemObtained = false;
            SceneManager.pop();
        }

        selectGachaItem() {
            const totalOdds = this._items.reduce((acc, item) => {
                let oddsValue = this.evaluateOddsExpression(item.odds);
                return acc + oddsValue;
            }, 0);

            let randomPick = Math.random() * totalOdds;
            let selectedItem;

            for (const item of this._items) {
                let itemOdds = this.evaluateOddsExpression(item.odds);
                randomPick -= itemOdds;
                if (randomPick <= 0) {
                    selectedItem = item;
                    break;
                }
            }

            return this._items.find(item => item._uniqueId === selectedItem._uniqueId);
        }

        evaluateOddsExpression(expression) {
            if (typeof expression === 'string') {
                const processedExpression = expression.replace(/var\((\d+)\)/g, (_, varId) => $gameVariables.value(Number(varId)));
                try {
                    const result = Number(eval(processedExpression));
                    return result;
                } catch (error) {
                    console.error("Error evaluating odds expression: ", expression, error);
                    return 0;
                }
            }
            return Number(expression);
        }

        fadeOutActionText() {
            this.removeChild(this._actionTextSprite);
            this._actionTextSprite = null;
            const newText = this._selectedItem.customText || defaultCustomText;
            const rarityColor = this._selectedItem.rarityColor || "#FFFFFF";
            const newTextSprite = new Sprite(new Bitmap(Graphics.boxWidth, 48));
            newTextSprite.bitmap.fontSize = 28;
            newTextSprite.bitmap.textColor = rarityColor;
            newTextSprite.bitmap.drawText(newText, 0, 0, Graphics.boxWidth, 48, 'center');
            newTextSprite.x = (Graphics.boxWidth - newTextSprite.width) / 2;
            newTextSprite.y = (Graphics.boxHeight - newTextSprite.height) / 2;
            newTextSprite.opacity = 255;
            this.addChild(newTextSprite);

            if (this._selectedItem.customSound) {
                const sound = {
                    name: this._selectedItem.customSound,
                    volume: 90,
                    pitch: 100,
                    pan: 0
                };
                AudioManager.playSe(sound);
            }

            const pulseDuration = 40;
            let pulseFrame = 0;
            const maxScale = 1;
            const minScale = 1.0;
            const pulse = () => {
                if (pulseFrame < pulseDuration) {
                    const progress = (Math.sin((pulseFrame / pulseDuration) * Math.PI * 2) + 1) / 2;
                    const scale = minScale + (maxScale - minScale) * progress;
                    newTextSprite.scale.x = newTextSprite.scale.y = scale;
                    pulseFrame++;
                    requestAnimationFrame(pulse);
                } else {
                    fadeOut();
                }
            };

            const fadeDuration = 20;
            let fadeFrame = 0;
            const fadeOut = () => {
                if (fadeFrame < fadeDuration) {
                    const progress = fadeFrame / fadeDuration;
                    newTextSprite.opacity = 255 * (1 - progress);
                    newTextSprite.scale.x = newTextSprite.scale.y = maxScale - (maxScale - minScale) * progress;
                    fadeFrame++;
                    requestAnimationFrame(fadeOut);
                } else {
                    this.removeChild(newTextSprite);
                    this.animateSelection(this._selectedItem);

                }
            };

            pulse();
        }

        deliverSelectedItem() {
            let gameItem;
            switch (this._selectedItem.type) {
                case "Item":
                    gameItem = $dataItems[this._selectedItem.id];
                    break;
                case "Weapon":
                    gameItem = $dataWeapons[this._selectedItem.id];
                    break;
                case "Armor":
                    gameItem = $dataArmors[this._selectedItem.id];
                    break;
            }

            if (gameItem) {
                $gameParty.gainItem(gameItem, 1);
            }
        }

        displayObtainedItem(itemName, color, iconIndex) {

            this._gachaWindow.contents.clear();
            this._obtainedTextSprite = new Sprite(new Bitmap(Graphics.boxWidth, 48));
            this._obtainedTextSprite.bitmap.fontSize = Number(defaultTexts.rewardFontSize || 34);
            this._obtainedTextSprite.bitmap.textColor = color;
            const text = obtainedItemText.replace('%1', itemName);
            this._obtainedTextSprite.bitmap.drawText(text, 0, 0, Graphics.boxWidth, 48, 'center');
            let rewardTextY = Graphics.boxHeight / 2 + this._iconWidth / 2 + Number(defaultTexts.rewardTextY || 70);
            this._obtainedTextSprite.y = rewardTextY;
            this.addChild(this._obtainedTextSprite);
            this.animateOpacityLoop(this._obtainedTextSprite, 850);

            if (!this._finishTextSprite) {
                this._finishTextSprite = new Sprite(new Bitmap(Graphics.boxWidth, 48));
                this._finishTextSprite.bitmap.fontSize = Number(defaultTexts.finishFontSize || 15);
                this._finishTextSprite.bitmap.textColor = defaultTexts.finishTextColor || "#f4ff91";
                this._finishTextSprite.bitmap.drawText(finishActionText, 0, 0, Graphics.boxWidth, 48, 'center');
                this._finishTextSprite.x = Number(defaultTexts.finishTextX || 0);
                this._finishTextSprite.y = Number(defaultTexts.finishTextY || 30);
                this.addChild(this._finishTextSprite);
            }

            this.animateOpacityLoop(this._finishTextSprite, 3000);
            this._allowExit = true;
        }

        getItemIconIndex(item) {
            switch (item.type) {
                case "Item":
                    return $dataItems[item.id].iconIndex;
                case "Weapon":
                    return $dataWeapons[item.id].iconIndex;
                case "Armor":
                    return $dataArmors[item.id].iconIndex;
            }
            return 0;
        }

        getItemName(item) {
            switch (item.type) {
                case "Item":
                    return $dataItems[item.id].name;
                case "Weapon":
                    return $dataWeapons[item.id].name;
                case "Armor":
                    return $dataArmors[item.id].name;
            }
            return "";
        }

        update() {
            super.update();
            if (!this._itemObtained && Input.isTriggered('ok') && !this._decelerating) {
                this._selectedItem = this.selectGachaItem();
                this.fadeOutActionText();
                this.startDeceleration();
                this._itemObtained = true;
                this.deliverSelectedItem();

            } else if (this._itemObtained && Input.isTriggered('ok') && this._allowExit) {
                this.exitScene();
            }

            if (this._allowExit && (Input.isTriggered('cancel') || Input.isTriggered('escape'))) {
                this.exitScene();
            }

            if (this._expandPathway && !this._pathwayExpanded) {
                this.animatePathwayExpansion();
            }

            this.animateActionText();
        }

        terminate() {
            super.terminate();

            if (this._animationFrameId) {
                cancelAnimationFrame(this._animationFrameId);
                this._animationFrameId = null;
            }

            if (this._particleIntervals) {
                this._particleIntervals.forEach(interval => clearInterval(interval));
                this._particleIntervals = [];
            }

            this._magicStars?.forEach(star => {
                star.parent?.removeChild(star);
                star.destroy();
            });
            this._magicStars = [];

            this._stars?.forEach(star => {
                star.parent?.removeChild(star);
                star.destroy();
            });
            this._glowingOrbs?.forEach(orb => {
                orb.parent?.removeChild(orb);
                orb.destroy();
            });
            this._fallingLeaves?.forEach(leaf => {
                leaf.parent?.removeChild(leaf);
                leaf.destroy();
            });

            this._stars = [];
            this._glowingOrbs = [];
            this._fallingLeaves = [];

            this._icons.forEach(icon => {
                if (icon.sprite.parent) {
                    icon.sprite.parent.removeChild(icon.sprite);
                }
                if (icon.sprite) {
                    icon.sprite.destroy();
                }
            });
            this._icons = [];


            if (this._finishTextSprite && this._finishTextSprite.parent) {
                this._finishTextSprite.parent.removeChild(this._finishTextSprite);
            }
            if (this._finishTextSprite) {
                this._finishTextSprite.destroy();
                this._finishTextSprite = null;
            }

            if (this._obtainedTextSprite && this._obtainedTextSprite.parent) {
                this._obtainedTextSprite.parent.removeChild(this._obtainedTextSprite);
            }
            if (this._obtainedTextSprite) {
                this._obtainedTextSprite.destroy();
                this._obtainedTextSprite = null;
            }

            if (this._pathway) {
                this.removeChild(this._pathway);
                this._pathway.destroy();
                this._pathway = null;
            }
        }

        animateSelection(selectedItem) {
            this.clearActiveEffects();
            this._icons.forEach(iconData => {
                if (iconData.item._uniqueId !== selectedItem._uniqueId) {
                    iconData.sprite.visible = false;
                }
            });

            this._expandPathway = true;
            const selectedIcon = this._icons.find(icon => icon.item._uniqueId === selectedItem._uniqueId);
            if (selectedIcon) {

                const targetX = (Graphics.boxWidth / 2) - this._iconWidth / 2;
                const targetY = (Graphics.boxHeight / 2) - this._iconWidth / 2;
                selectedIcon.sprite.x = targetX - 16;
                selectedIcon.sprite.y = targetY - 16;
                selectedIcon.sprite.scale.x = 2;
                selectedIcon.sprite.scale.y = 2;


                this.addChild(selectedIcon.sprite);
                this.animateIconPulse(selectedIcon.sprite);
            }

            if (!this._pathwayExpanded) {
                this.displayObtainedItem(this.getItemName(selectedItem), selectedItem.rarityColor, this.getItemIconIndex(selectedItem));
                const selectedIcon = this._icons.find(icon => icon.item._uniqueId === selectedItem._uniqueId);

                switch (selectedItem.effectToUse) {
                    case "Fireworks":
                        this.createFireworks();
                        break;
                    case "Confetti":
                        this.createConfetti();
                        break;
                    case "Sparkling Stars":
                        this.createSparklingStars();
                        break;
                    case "Glowing Orbs":
                        this.createGlowingOrbs();
                        break;
                    case "Falling Leaves":
                        this.createFallingLeaves();
                        break;
                    case "Magic Rotating Star (Requires custom image)" || "Magic Rotating Star":
                        const magicStarConfig = selectedItem.magicStarSettings ? JSON.parse(selectedItem.magicStarSettings) : null;
                        if (selectedIcon && magicStarConfig) {
                            const magicStarConfig = JSON.parse(selectedItem.magicStarSettings);
                            this.createMagicRotatingStar(selectedIcon, magicStarConfig);
                        }
                        break;

                    case "Ultra Mega Legendary Effect (Requires custom image)" || "Ultra Mega Legendary Effect":
                        const ultraMegaLegendaryConfig = selectedItem.magicStarSettings ? JSON.parse(selectedItem.magicStarSettings) : null;
                        if (selectedIcon && ultraMegaLegendaryConfig) {
                            this.createUltraMegaLegendaryEffect(selectedIcon, ultraMegaLegendaryConfig);
                        }
                        break;
                }
            }
        }

        animateIcons() {
            if (!this._animation) return;

            this._icons.forEach((iconData) => {
                if (!iconData || !iconData.sprite) return;

                if (this._decelerating && iconData.dx > 0) {
                    iconData.dx -= this._deceleration;
                    iconData.dx = Math.max(iconData.dx, 0);

                    if (!this._itemObtained || iconData.item._uniqueId !== this._selectedItem?._uniqueId) {
                        const fadeOutSpeed = 10;
                        iconData.sprite.opacity -= fadeOutSpeed;
                        iconData.sprite.opacity = Math.max(iconData.sprite.opacity, 0);
                    }
                }

                if (iconData.sprite.parent) {
                    iconData.sprite.x += iconData.dx;
                    if (iconData.sprite.x > Graphics.width + this._iconWidth) {
                        iconData.sprite.x = -this._iconWidth;
                    }
                }
            });

            if (this._decelerating && this._icons.every(icon => icon.dx === 0)) {
                this._animation = false;
                this.selectGachaItem();
            }

            if (this._animation) {
                this._animationFrameId = requestAnimationFrame(this.animateIcons.bind(this));
            }
        }

        animateIconPulse(iconSprite) {

            iconSprite.anchor.x = 0.5;
            iconSprite.anchor.y = 0.5;
            iconSprite.x += iconSprite.width / 2;
            iconSprite.y += iconSprite.height / 2;
            iconSprite.scale.x = iconSprite.scale.y = 2;
            let scaleDirection = -1;
            const scaleSpeed = 0.002;
            const scaleMax = 2.1;
            const scaleMin = 1.9;
            const opacitySpeed = 0.01;
            const opacityMax = 1;
            const opacityMin = 0.5;
            iconSprite.opacity = 255;

            const animate = () => {
                if (!this._itemObtained) return;

                iconSprite.scale.x += scaleSpeed * scaleDirection;
                iconSprite.scale.y += scaleSpeed * scaleDirection;

                if (iconSprite.scale.x > scaleMax || iconSprite.scale.x < scaleMin) {
                    scaleDirection *= -1;
                }

                iconSprite.opacity += opacitySpeed * 255 * (scaleDirection > 0 ? 1 : -1);
                if (iconSprite.opacity > opacityMax * 255) {
                    iconSprite.opacity = opacityMax * 255;
                } else if (iconSprite.opacity < opacityMin * 255) {
                    iconSprite.opacity = opacityMin * 255;
                }

                requestAnimationFrame(animate);
            };

            animate();
        }

        animateActionText() {
            if (!this._actionTextSprite) return;

            this._actionTextX += this._actionTextSpeed;
            if (this._actionTextX >= 0) {
                this._actionTextX = -Graphics.boxWidth;
            }
            this._actionTextSprite.x = this._actionTextX;
            const opacityRange = 230 - 150;
            const cycleDuration = 4000;
            const opacityChangePerFrame = opacityRange / (cycleDuration / 16.666);
            this._opacityTimer += 16.666;
            if (this._opacityTimer >= cycleDuration) {
                this._opacityTimer = 0;
            }
            const halfCycle = cycleDuration / 2;
            if (this._opacityTimer <= halfCycle) {
                this._actionTextSprite.opacity = 230 - (opacityChangePerFrame * (this._opacityTimer / 16.666));
            } else {
                this._actionTextSprite.opacity = 150 + (opacityChangePerFrame * ((this._opacityTimer - halfCycle) / 16.666));
            }
        }

        animateOpacityLoop(sprite, duration) {
            let startTime = performance.now();
            let opacityDirection = 1;

            const animate = (time) => {
                const elapsed = time - startTime;
                const progress = (elapsed % duration) / duration;
                const opacityChange = Math.sin(progress * Math.PI * 2) * 0.5 + 0.5;

                sprite.opacity = 255 * opacityChange;

                if (this._itemObtained) {
                    requestAnimationFrame(animate);
                }
            };

            requestAnimationFrame(animate);
        }

        animatePulse(iconSprite) {
            let pulseFrame = 0;
            const pulseDuration = 120;
            const minScale = 1;
            const maxScale = 1.2;
            const animate = () => {
                if (this._itemObtained && pulseFrame < pulseDuration) {
                    const progress = Math.sin((pulseFrame / pulseDuration) * Math.PI * 2) * 0.5 + 0.5;
                    const scale = minScale + (maxScale - minScale) * progress;
                    iconSprite.scale.x = iconSprite.scale.y = scale;
                    pulseFrame++;
                    requestAnimationFrame(animate);
                } else if (!this._itemObtained) {
                    this.removeChild(iconSprite);
                }
            };
            animate();
        }

        animatePathwayExpansion() {
            if (!this._pathway) return;

            this._pathwayHeightStart += this._pathwayGrowthSpeed;
            if (this._pathwayHeightStart >= this._pathwayHeightFull) {
                this._pathwayHeightStart = this._pathwayHeightFull;
                this._pathwayExpanded = true;
            }

            this._pathway.clear();
            this._pathway.beginFill(0x080808, 0.88);
            this._pathway.drawRect(0, (Graphics.boxHeight - this._pathwayHeightStart) / 2, Graphics.boxWidth * 1.5, this._pathwayHeightStart * 1.5);
            this._pathway.endFill();

            if (!this._pathwayExpanded) {
                requestAnimationFrame(this.animatePathwayExpansion.bind(this));
            } else {
                this._allowExit = true;
            }
            this._animationFrameId = requestAnimationFrame(this.animatePathwayExpansion.bind(this));

            if (this._gachaWindow) {
                this.addChild(this._gachaWindow);
            }
        }

        startDeceleration() {
            this._decelerating = true;
            this._deceleration = 0.02;
        }

        createConfetti() {

            for (let i = 0; i < confettiCount; i++) {
                const confetti = new PIXI.Graphics();
                const color = confettiColors[Math.floor(Math.random() * confettiColors.length)];
                confetti.beginFill(color);
                confetti.drawRect(0, 0, 10, 7);
                confetti.endFill();
                confetti.x = Math.random() * Graphics.width;
                confetti.y = -Math.random() * Graphics.height;
                confetti.vx = Math.random() * 2 - 1;
                confetti.vy = Math.random() * 5 + 2;
                SceneManager._scene.addChild(confetti);

                const animateConfetti = () => {
                    if (!SceneManager._scene._itemObtained) {
                        SceneManager._scene.removeChild(confetti);
                        return;
                    }

                    confetti.x += confetti.vx;
                    confetti.y += confetti.vy;
                    confetti.rotation += 0.1;

                    if (confetti.y > Graphics.height) {
                        confetti.y = -10;
                        confetti.x = Math.random() * Graphics.width;
                    }

                    requestAnimationFrame(animateConfetti);
                };

                requestAnimationFrame(animateConfetti);
            }
        }

        createFireworks() {
            const createExplosion = () => {
                if (!SceneManager._scene._itemObtained) {
                    return;
                }

                for (let i = 0; i < explosionsCount; i++) {
                    const explosionX = Math.random() * Graphics.width;
                    const explosionY = Math.random() * Graphics.height * 0.5;

                    for (let j = 0; j < particlesPerExplosion; j++) {
                        const particle = new PIXI.Graphics();
                        const color = explosionColors[Math.floor(Math.random() * explosionColors.length)];
                        particle.beginFill(color);
                        particle.drawCircle(0, 0, Math.random() * 2 + 1);
                        particle.endFill();

                        particle.x = explosionX;
                        particle.y = explosionY;
                        const angle = Math.random() * Math.PI * 2;
                        const speed = Math.random() * 2 + 2;
                        particle.vx = Math.cos(angle) * speed;
                        particle.vy = Math.sin(angle) * speed;
                        particle.alpha = 1;

                        SceneManager._scene.addChild(particle);
                        SceneManager._scene._activeEffects.push(particle);

                        const animateParticle = () => {
                            if (particle.alpha <= 0 || !particle.parent) {
                                SceneManager._scene.removeChild(particle);
                                return;
                            }

                            particle.x += particle.vx;
                            particle.y += particle.vy;
                            particle.vy += 0.05;
                            particle.alpha -= 0.02;

                            requestAnimationFrame(animateParticle);
                        };

                        requestAnimationFrame(animateParticle);
                    }
                }

                setTimeout(createExplosion, Math.random() * 500 + 200);
            };
            createExplosion();
        }

        clearActiveEffects() {
            this._activeEffects.forEach(effect => {
                if (effect && effect.parent) {
                    effect.parent.removeChild(effect);
                    effect.destroy();
                }
            });
            this._activeEffects = [];
        }

        createSparklingStars() {

            for (let i = 0; i < starsCount; i++) {
                const star = new PIXI.Graphics();
                const color = starColors[Math.floor(Math.random() * starColors.length)];
                star.beginFill(color);
                const size = Math.random() * 4 + 2;
                star.drawStar(0, 0, 5, size);
                star.endFill();
                star.x = Math.random() * Graphics.width;
                star.y = Math.random() * Graphics.height;
                star.vy = Math.random() * -0.5 - 0.5;
                SceneManager._scene.addChild(star);
                SceneManager._scene._stars.push(star);
                let alphaDirection = 0.01;
                let scaleDirection = 0.005;

                const animateStar = () => {
                    if (!star.parent) {
                        return;
                    }

                    star.y += star.vy;
                    star.alpha += alphaDirection;
                    if (star.alpha <= 0.5 || star.alpha >= 1) {
                        alphaDirection *= -1;
                    }

                    if (star.scale.x < 0.8 || star.scale.x > 1.2) {
                        scaleDirection *= -1;
                    }
                    star.scale.x += scaleDirection;
                    star.scale.y += scaleDirection;

                    if (star.y < 0) {
                        star.y = Graphics.height + 10;
                        star.x = Math.random() * Graphics.width;
                    }

                    requestAnimationFrame(animateStar);
                };

                requestAnimationFrame(animateStar);
            }
        }

        createGlowingOrbs() {
            SceneManager._scene._glowingOrbs = SceneManager._scene._glowingOrbs || [];
            for (let i = 0; i < orbsCount; i++) {
                const orb = new PIXI.Graphics();
                const color = orbsColors[Math.floor(Math.random() * orbsColors.length)];
                orb.beginFill(color);
                orb.drawCircle(0, 0, Math.random() * 10 + 5);
                orb.endFill();
                orb.x = Math.random() * Graphics.width;
                orb.y = Math.random() * Graphics.height;
                orb.alpha = 0.5 + Math.random() * 0.5;
                orb.filters = [new PIXI.filters.BlurFilter(5)];

                SceneManager._scene.addChild(orb);
                SceneManager._scene._glowingOrbs.push(orb);

                const animateOrb = () => {
                    if (!orb.parent) {
                        return;
                    }

                    orb.y -= 0.5;
                    if (orb.y < -10) {
                        orb.y = Graphics.height + 10;
                        orb.x = Math.random() * Graphics.width;
                    }

                    requestAnimationFrame(animateOrb);
                };

                requestAnimationFrame(animateOrb);
            }
        }

        createMagicRotatingStar(selectedIcon, starSettings) {
            if (!selectedIcon || !selectedIcon.sprite) {
                return;
            }


            if (!starSettings.starTexture) {
                return;
            }

            const pathParts = starSettings.starTexture.split('/');
            const folder = pathParts.slice(0, -1).join('/');
            const filenameWithoutExtension = pathParts.pop().split('.')[0];
            const bitmap = ImageManager.loadBitmap(folder + '/', filenameWithoutExtension);

            const starSprite = new Sprite(bitmap);
            starSprite.anchor.set(0.5);
            starSprite.x = selectedIcon.sprite.x + selectedIcon.sprite.width / 2;
            starSprite.y = selectedIcon.sprite.y + selectedIcon.sprite.height / 2;
            starSprite.scale.set(starSettings.minScale ? Number(starSettings.minScale) : 1.5);

            this.addChildAt(starSprite, this.getChildIndex(selectedIcon.sprite));
            this._magicStars.push(starSprite);

            let scaleDirection = 1;
            const rotateSpeed = Number(starSettings.rotateSpeed) || 0.01;
            const maxScale = Number(starSettings.maxScale) || 2.2;
            const minScale = Number(starSettings.minScale) || 1.5;
            const scaleSpeed = Number(starSettings.scaleSpeed) || 0.005;

            const animateRotationAndScale = () => {
                if (!starSprite.parent) {
                    return;
                }
                starSprite.rotation += rotateSpeed;

                if (starSprite.scale.x >= maxScale || starSprite.scale.x <= minScale) {
                    scaleDirection *= -1;
                }
                starSprite.scale.x += scaleSpeed * scaleDirection;
                starSprite.scale.y += scaleSpeed * scaleDirection;

                requestAnimationFrame(animateRotationAndScale);
            };
            requestAnimationFrame(animateRotationAndScale);

            if (starSettings.enableParticles && starSettings.enableParticles !== "false") {
                this.generateParticles(starSprite);
            }
        }

        generateParticles(starSprite) {

            const bounds = { x: -50, y: -50, width: Graphics.width + 100, height: Graphics.height + 100 };
            const generateSingleParticle = () => {
                const particle = new PIXI.Graphics();
                const color = [0xdefff0, 0x8aefff, 0xebfffc, 0xb0ffea][Math.floor(Math.random() * 4)];
                particle.beginFill(color);
                particle.drawCircle(0, 0, 0.65 + Math.random() * 1.55);
                particle.endFill();
                particle.x = starSprite.x;
                particle.y = starSprite.y;
                this.addChild(particle);
                const angle = Math.random() * Math.PI * 2;
                const speed = 1 + Math.random() * 3;

                const moveParticle = () => {
                    if (!particle.parent) {
                        return;
                    }

                    particle.x += Math.cos(angle) * speed;
                    particle.y += Math.sin(angle) * speed;

                    if (particle.x < bounds.x || particle.x > bounds.width || particle.y < bounds.y || particle.y > bounds.height) {
                        particle.parent.removeChild(particle);
                        particle.destroy();
                    } else {
                        requestAnimationFrame(moveParticle);
                    }
                };

                moveParticle();
            };

            const particleInterval = setInterval(generateSingleParticle, 100);
            this._particleIntervals = this._particleIntervals || [];
            this._particleIntervals.push(particleInterval);
        }

        createUltraMegaLegendaryEffect(selectedIcon, starSettings) {
            if (!selectedIcon || !selectedIcon.sprite) {
                return;
            }

            if (!starSettings.starTexture) {
                return;
            }

            const pathParts = starSettings.starTexture.split('/');
            const folder = pathParts.slice(0, -1).join('/');
            const filenameWithoutExtension = pathParts.pop().split('.')[0];
            const bitmap = ImageManager.loadBitmap(folder + '/', filenameWithoutExtension);
            const starSprites = [new Sprite(bitmap), new Sprite(bitmap)];
            const rotateSpeedRange = starSettings.rotateSpeedRange || [0.012, 0.017];
            const scaleSpeedRange = starSettings.scaleSpeedRange || [0.002, 0.005];
            const maxScale = Number(starSettings.maxScale) || 2.2;
            const minScale = Number(starSettings.minScale) || 1.5;
            const enableParticles = starSettings.enableParticles === true;

            starSprites.forEach((starSprite, index) => {
                let rotateSpeed = Math.random() * (rotateSpeedRange[1] - rotateSpeedRange[0]) + rotateSpeedRange[0];
                let scaleSpeed = Math.random() * (scaleSpeedRange[1] - scaleSpeedRange[0]) + scaleSpeedRange[0];

                rotateSpeed *= (index === 0) ? 1 : -1;
                starSprite.anchor.set(0.5);
                starSprite.x = selectedIcon.sprite.x + selectedIcon.sprite.width / 2;
                starSprite.y = selectedIcon.sprite.y + selectedIcon.sprite.height / 2;
                starSprite.scale.set(minScale);

                this.addChildAt(starSprite, this.getChildIndex(selectedIcon.sprite));
                if (!this._magicStars) this._magicStars = [];
                this._magicStars.push(starSprite);

                let scaleDirection = 1;

                const animateRotationAndScale = () => {
                    if (!starSprite.parent) return;
                    starSprite.rotation += rotateSpeed;

                    if (starSprite.scale.x >= maxScale || starSprite.scale.x <= minScale) {
                        scaleDirection *= -1;
                        scaleSpeed = Math.random() * (scaleSpeedRange[1] - scaleSpeedRange[0]) + scaleSpeedRange[0];
                        rotateSpeed = Math.random() * (rotateSpeedRange[1] - rotateSpeedRange[0]) + rotateSpeedRange[0];
                        rotateSpeed *= (index === 0) ? 1 : -1;
                    }

                    starSprite.scale.x += scaleSpeed * scaleDirection;
                    starSprite.scale.y += scaleSpeed * scaleDirection;

                    requestAnimationFrame(animateRotationAndScale);
                };

                requestAnimationFrame(animateRotationAndScale);
            });

            if (enableParticles) {
                this.generateParticles(starSprites[0]);
            }
        }

        createFallingLeaves() {

            this._fallingLeaves = this._fallingLeaves || [];
            for (let i = 0; i < leavesCount; i++) {
                const leaf = new PIXI.Sprite(PIXI.Texture.WHITE);
                leaf.tint = leavesColors[Math.floor(Math.random() * leavesColors.length)];
                leaf.width = 10;
                leaf.height = 20;
                leaf.x = Math.random() * Graphics.width;
                leaf.y = -Math.random() * Graphics.height;
                leaf.vx = Math.random() * 1 - 0.5;
                leaf.vy = Math.random() * 2 + 1;
                leaf.rotationSpeed = Math.random() * 0.1 - 0.05;

                this.addChild(leaf);
                this._fallingLeaves.push(leaf);

                const animateLeaf = () => {
                    if (!leaf.parent) {
                        return;
                    }

                    leaf.x += leaf.vx;
                    leaf.y += leaf.vy;
                    leaf.rotation += leaf.rotationSpeed;

                    if (leaf.y > Graphics.height) {
                        leaf.y = -10;
                        leaf.x = Math.random() * Graphics.width;
                    }

                    requestAnimationFrame(animateLeaf);
                };

                requestAnimationFrame(animateLeaf);
            }
        }
    }

})();
