Cookie Clicker Ultimate Automation

Automated clicker, auto-buy, auto-harvest, garden manager, stock market trader, and HUD overlay.

当前为 2025-12-03 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Cookie Clicker Ultimate Automation
// @name:zh-TW   餅乾點點樂全自動掛機輔助 (Cookie Clicker)
// @name:zh-CN   饼干点点乐全自动挂机辅助 (Cookie Clicker)
// @namespace    http://tampermonkey.net/
// @version      7.0
// @description  Automated clicker, auto-buy, auto-harvest, garden manager, stock market trader, and HUD overlay.
// @description:zh-TW 全功能自動掛機腳本:自動點擊、智慧購買、自動收成糖塊、花園自動補種、股市自動交易、閒置魔法。
// @description:zh-CN 全功能自动挂机脚本:自动点击、智慧购买、自动收成糖块、花园自动补种、股市自动交易、闲置魔法。
// @author       You
// @match        https://wws.justnainai.com/*
// @match        https://orteil.dashnet.org/cookieclicker/*
// @icon         https://orteil.dashnet.org/cookieclicker/img/favicon.ico
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_openInTab
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// @license      MIT
// @homepage     https://greasyfork.org/zh-CN/scripts/557692-cookie-clicker-ultimate-automation
// @homepageURL  https://greasyfork.org/zh-CN/scripts/557692-cookie-clicker-ultimate-automation
// @supportURL   https://greasyfork.org/zh-CN/scripts/557692-cookie-clicker-ultimate-automation/feedback
// ==/UserScript==

(function() {
    'use strict';

    // ═══════════════════════════════════════════════════════════════
    // 1. 核心變量 & 狀態記憶
    // ═══════════════════════════════════════════════════════════════
    // v6.x 基礎功能
    let isClickEnabled = GM_getValue('isClickEnabled', false);
    let isBuyEnabled = GM_getValue('isBuyEnabled', false);
    let isGoldenEnabled = GM_getValue('isGoldenEnabled', false);
    let isSpellEnabled = GM_getValue('isSpellEnabled', true); // 基礎連擊魔法
    let isGardenEnabled = GM_getValue('isGardenEnabled', false);
    let isResearchEnabled = GM_getValue('isResearchEnabled', true);

    // v7.0 新增功能
    let isStockEnabled = GM_getValue('isStockEnabled', false); // 股市開關
    let isSEEnabled = GM_getValue('isSEEnabled', false);       // 閒置建築魔法 (Spontaneous Edifice)

    // 統計計數
    let clickCount = 0;
    let buyUpgradeCount = 0;
    let buyBuildingCount = 0;
    let goldenCookieCount = 0;

    // Interval IDs
    let intervalId = null;
    let buyIntervalId = null;
    let goldenCheckIntervalId = null;
    let gardenIntervalId = null;
    let stockIntervalId = null; // v7.0
    let restartIntervalId = null;
    let countdownIntervalId = null;
    let promptCheckIntervalId = null;
    let buffCheckIntervalId = null;
    let ahkSignalIntervalId = null;

    // 時間控制
    let nextRestartTime = 0;
    let nextBuyTime = 0;

    // 設定參數
    let gameVolume = GM_getValue('gameVolume', 50);
    let clickInterval = GM_getValue('clickInterval', 50);
    let buyStrategy = GM_getValue('buyStrategy', 'expensive');
    
    // 購買間隔
    let buyIntervalHours = GM_getValue('buyIntervalHours', 0);
    let buyIntervalMinutes = GM_getValue('buyIntervalMinutes', 3);
    let buyIntervalSeconds = GM_getValue('buyIntervalSeconds', 0);

    // 重啟間隔
    let restartIntervalHours = GM_getValue('restartIntervalHours', 1);
    let restartIntervalMinutes = GM_getValue('restartIntervalMinutes', 0);
    let restartIntervalSeconds = GM_getValue('restartIntervalSeconds', 0);

    const MAX_WIZARD_TOWERS = 800;
    
    // 花園陣型記憶
    let savedGardenPlot = GM_getValue('savedGardenPlot', Array(6).fill().map(() => Array(6).fill(-1)));

    // UI 位置
    let showCountdown = GM_getValue('showCountdown', true);
    let showBuffMonitor = GM_getValue('showBuffMonitor', true);
    let countdownX = GM_getValue('countdownX', window.innerWidth - 170);
    let countdownY = GM_getValue('countdownY', 10);
    let buffX = GM_getValue('buffX', window.innerWidth - 340);
    let buffY = GM_getValue('buffY', 150);
    let currentX = GM_getValue('buttonX', 50);
    let currentY = GM_getValue('buttonY', 50);

    // DOM 元素引用
    let countdownDisplay = null;
    let buffDisplay = null;
    let controlPanel = null;
    let floatingButton = null;

    const targetSelector = '#bigCookie';
    let originalTitle = document.title;

    // ═══════════════════════════════════════════════════════════════
    // 2. 初始化流程
    // ═══════════════════════════════════════════════════════════════
    function init() {
        console.log('🍪 Cookie Clicker Ultimate Automation v7.0 Loaded');

        const scriptRestarted = localStorage.getItem('cookieScriptRestarted');
        if (scriptRestarted) {
            console.log('🔄 Script restarted automatically.');
            localStorage.removeItem('cookieScriptRestarted');
        }

        if (currentX < 0 || currentX > window.innerWidth) currentX = 50;
        
        createFloatingButton();
        createControlPanel();
        createCountdownDisplay();
        createBuffDisplay();
        setGameVolume(gameVolume);

        // 啟動各模組循環
        goldenCheckIntervalId = setInterval(checkGoldenCookie, 1000); // 含魔法檢查
        promptCheckIntervalId = setInterval(autoConfirmPrompt, 1000);
        buffCheckIntervalId = setInterval(updateBuffDisplay, 500);
        ahkSignalIntervalId = setInterval(updateTitleForAHK, 1000);
        gardenIntervalId = setInterval(autoGarden, 2500);
        
        // v7.0 股市循環 (3秒一次即可,股市tick為60秒)
        stockIntervalId = setInterval(autoStock, 3000);

        startCountdown();
        scheduleAutoRestart();

        setTimeout(() => {
            if (isClickEnabled) startAutoClick();
            if (isBuyEnabled) startAutoBuy();
            updateAllDisplays();
        }, 2000);

        document.addEventListener('keydown', function(e) {
            if (e.key === 'F8') {
                e.preventDefault();
                toggleAutoClick();
            }
        });
    }

    // ═══════════════════════════════════════════════════════════════
    // 3. AHK 通訊協議 (v6.5 Logic)
    // ═══════════════════════════════════════════════════════════════
    function updateTitleForAHK() {
        if (typeof Game === 'undefined') return;

        let totalMult = 1;
        let isWorthClicking = false;

        if (Game.buffs) {
            for (let i in Game.buffs) {
                const buff = Game.buffs[i];
                if (buff.multCpS > 0) totalMult *= buff.multCpS;
                if (buff.multClick > 0) totalMult *= buff.multClick;

                if (buff.multClick > 1 || buff.multCpS > 7) {
                    isWorthClicking = true;
                }
            }
        }

        let coords = "0,0";
        const bigCookie = document.querySelector('#bigCookie');
        if (bigCookie) {
            const rect = bigCookie.getBoundingClientRect();
            const cx = Math.round(rect.left + rect.width / 2);
            const cy = Math.round(rect.top + rect.height / 2);
            coords = `${cx},${cy}`;
        }

        const signal = isWorthClicking ? "⚡ATTACK" : "💤IDLE";
        const displayMult = totalMult > 1000 ? (totalMult/1000).toFixed(1) + 'k' : Math.round(totalMult);
        document.title = `[${signal}|${displayMult}x|${coords}] ${originalTitle}`;
    }

    // ═══════════════════════════════════════════════════════════════
    // 4. 核心功能模組 (Click, Garden, Buy)
    // ═══════════════════════════════════════════════════════════════

    // --- 點擊模組 ---
    function autoClick() {
        const element = document.querySelector(targetSelector);
        if (element) {
            element.click();
            clickCount++;
        }
    }

    function startAutoClick() {
        if (intervalId) clearInterval(intervalId);
        intervalId = setInterval(autoClick, clickInterval);
        isClickEnabled = true;
        updateAllDisplays();
    }

    function stopAutoClick() {
        if (intervalId) clearInterval(intervalId);
        isClickEnabled = false;
        updateAllDisplays();
    }

    function toggleAutoClick() {
        isClickEnabled = !isClickEnabled;
        GM_setValue('isClickEnabled', isClickEnabled);
        if (isClickEnabled) startAutoClick();
        else stopAutoClick();
    }

    // --- 花園模組 ---
    function autoGarden() {
        if (!isGardenEnabled) return;
        if (typeof Game === 'undefined' || !Game.Objects['Farm'].minigameLoaded) return;
        
        const M = Game.Objects['Farm'].minigame;
        if (!M) return;

        for (let y = 0; y < 6; y++) {
            for (let x = 0; x < 6; x++) {
                if (!M.isTileUnlocked(x, y)) continue;

                const tile = M.plot[y][x];
                const tileId = tile[0];
                const tileAge = tile[1];
                
                if (tileId > 0) {
                    const plant = M.plantsById[tileId - 1];
                    if (plant.weed || plant.key === 'crumbspore') {
                        M.harvest(x, y);
                        continue;
                    }
                    if (tileAge >= 98 && tileAge >= plant.mature) {
                        M.harvest(x, y);
                        continue;
                    }
                }

                if (tileId === 0) {
                    const targetPlantId = savedGardenPlot[y][x];
                    if (targetPlantId !== -1 && targetPlantId !== null) {
                        const seed = M.plantsById[targetPlantId - 1];
                        if (seed && seed.unlocked && M.canPlant(seed)) {
                            M.useTool(seed.id, x, y);
                        }
                    }
                }
            }
        }
    }

    function saveGardenLayout() {
        if (typeof Game === 'undefined' || !Game.Objects['Farm'].minigame) {
            alert('花園尚未解鎖或載入!');
            return;
        }
        const M = Game.Objects['Farm'].minigame;
        let newLayout = [];
        for (let y = 0; y < 6; y++) {
            let row = [];
            for (let x = 0; x < 6; x++) {
                if (M.isTileUnlocked(x, y)) {
                    const tile = M.plot[y][x];
                    row.push(tile[0] === 0 ? -1 : tile[0]);
                } else {
                    row.push(-1);
                }
            }
            newLayout.push(row);
        }
        savedGardenPlot = newLayout;
        GM_setValue('savedGardenPlot', savedGardenPlot);
        
        const btn = $('#garden-save-btn');
        const originalText = btn.text();
        btn.text('✅ 已儲存!').css('background', '#4caf50');
        setTimeout(() => btn.text(originalText).css('background', '#2196f3'), 1500);
    }

    // --- 購買模組 ---
    function autoBuy() {
        if (typeof Game === 'undefined') return;

        // 0. 誓約絕對優先
        if (Game.Upgrades['Elder Pledge']) {
            const pledge = Game.Upgrades['Elder Pledge'];
            if (pledge.unlocked && pledge.bought === 0 && pledge.canBuy()) {
                console.log('🛡️ [AutoBuy] 誓約過期,強制後台購買!');
                pledge.buy(); 
                nextBuyTime = Date.now() + getBuyInterval();
                return;
            }
        }

        // 1. 科技
        if (isResearchEnabled) {
            const research = document.querySelectorAll('#techUpgrades .crate.upgrade.enabled');
            if (research.length > 0) {
                const item = research[0];
                console.log(`🛒 [購買-科技] 研究: ${getUpgradeName(item)}`);
                item.click();
                nextBuyTime = Date.now() + getBuyInterval();
                return;
            }
        }

        // 2. 升級
        const upgradesList = document.querySelectorAll('#upgrades .crate.upgrade.enabled');
        if (upgradesList.length > 0) {
            const selected = selectByStrategy(upgradesList, true);
            if (selected) {
                console.log(`🛒 [購買-升級] 購買: ${getUpgradeName(selected)}`);
                selected.click();
                buyUpgradeCount++;
                nextBuyTime = Date.now() + getBuyInterval();
                return;
            }
        }

        // 3. 建築
        const buildings = document.querySelectorAll('.product.unlocked.enabled');
        if (buildings.length > 0) {
            const selected = selectByStrategy(buildings, false);
            if (selected) {
                const buildId = selected.id.replace('product', '');
                let buildName = "未知建築";
                if(Game.ObjectsById[buildId]) buildName = Game.ObjectsById[buildId].displayName;

                console.log(`🛒 [購買-建築] 建造: ${buildName}`);
                selected.click();
                buyBuildingCount++;
                nextBuyTime = Date.now() + getBuyInterval();
            }
        }
    }

    function getUpgradeName(element) {
        let name = "未知物品";
        try {
            const id = element.getAttribute('data-id');
            if (id && Game.UpgradesById[id]) name = `${Game.UpgradesById[id].name}`;
        } catch(e) {}
        return name;
    }

    function selectByStrategy(items, isUpgrade) {
        if (!items || items.length === 0) return null;
        let itemsArray = Array.from(items);

        itemsArray = itemsArray.filter(item => {
            const id = item.getAttribute('data-id');
            if (isUpgrade && id === '84') return false; 
            
            if (!isUpgrade) {
                 const bId = item.id.replace('product', '');
                 if (Game.ObjectsById[bId] && Game.ObjectsById[bId].name === 'Wizard tower') {
                     if (Game.ObjectsById[bId].amount >= MAX_WIZARD_TOWERS) return false;
                 }
            }
            return true;
        });

        if (itemsArray.length === 0) return null;

        if (buyStrategy === 'random') return itemsArray[Math.floor(Math.random() * itemsArray.length)];
        if (buyStrategy === 'cheapest') return itemsArray[0];
        return itemsArray[itemsArray.length - 1];
    }

    function startAutoBuy() {
        if (buyIntervalId) clearInterval(buyIntervalId);
        const interval = getBuyInterval();
        buyIntervalId = setInterval(autoBuy, interval);
        nextBuyTime = Date.now() + interval;
        autoBuy();
    }

    function stopAutoBuy() {
        if (buyIntervalId) clearInterval(buyIntervalId);
        nextBuyTime = 0;
    }

    function getBuyInterval() {
        return (buyIntervalHours * 3600 + buyIntervalMinutes * 60 + buyIntervalSeconds) * 1000;
    }

    // ═══════════════════════════════════════════════════════════════
    // 5. 股市模組 (v7.0 New)
    // ═══════════════════════════════════════════════════════════════
    function autoStock() {
        if (!isStockEnabled) return;
        if (typeof Game === 'undefined') return;
        
        const Bank = Game.Objects['Bank'];
        if (!Bank || !Bank.minigameLoaded || !Bank.minigame) return;
        const M = Bank.minigame;

        // 遍歷所有股票 (ID 0-15)
        for (let i = 0; i < M.goodsById.length; i++) {
            const good = M.goodsById[i];
            const price = M.getGoodPrice(good);
            // 獲取靜止價值 (Resting Value)
            const rv = M.getRestingVal(good.id);
            
            // 策略:低買高賣 (考慮 20% Overhead)
            // 買入閾值:低於 RV 的 50%
            // 賣出閾值:高於 RV 的 150% (50% profit margin)

            // 1. 買入邏輯
            if (price < rv * 0.5) {
                // 檢查是否還有庫存空間與現金
                const maxStock = M.getGoodMaxStock(good);
                if (good.stock < maxStock && Game.cookies > price) {
                    // 10000 代表買最大值
                    M.buyGood(good.id, 10000);
                    // console.log(`📈 [股市] 買入 ${good.name} @ $${price.toFixed(2)} (RV: ${rv.toFixed(2)})`);
                }
            }

            // 2. 賣出邏輯
            if (price > rv * 1.5) {
                if (good.stock > 0) {
                    M.sellGood(good.id, 10000); // 賣出所有
                    console.log(`📉 [股市] 獲利賣出 ${good.name} @ $${price.toFixed(2)} (RV: ${rv.toFixed(2)})`);
                }
            }
        }
    }

    // ═══════════════════════════════════════════════════════════════
    // 6. 金餅乾/糖塊/魔法模組 (v7.0 Updated)
    // ═══════════════════════════════════════════════════════════════
    function checkGoldenCookie() {
        if (isGoldenEnabled) {
            autoHarvestLump();
            const goldenCookies = document.querySelectorAll('#shimmers > div.shimmer');
            if (goldenCookies.length > 0) {
                goldenCookies.forEach(cookie => cookie.click());
                goldenCookieCount++;
            }
        }
        
        // 魔法控制 (基礎與進階)
        if (isSpellEnabled || isSEEnabled) {
            autoCastSpell();
        }
    }

    function autoHarvestLump() {
        if (typeof Game === 'undefined') return;
        if (Game.canLumps()) {
            if (Game.lumpCurrentType === 3) return;
            let age = Date.now() - Game.lumpT;
            if (age >= Game.lumpRipeAge) {
                Game.clickLump();
            }
        }
    }

    function autoCastSpell() {
        if (typeof Game === 'undefined' || !Game.Objects['Wizard tower'].minigame) return;
        const M = Game.Objects['Wizard tower'].minigame;
        const spellHoF = M.spells['hand of fate'];
        const spellSE = M.spells['spontaneous edifice'];

        // --- 優先級 1: 命運之手 (Hand of Fate) ---
        // 只有在開啟基礎魔法開關時執行
        if (isSpellEnabled && M.magic >= M.getSpellCost(spellHoF)) {
            let shouldCast = false;
            for (let i in Game.buffs) {
                const buff = Game.buffs[i];
                if (buff.multCpS > 7 || buff.multClick > 10) { shouldCast = true; break; }
                if (buff.multCpS === 7 && M.magic >= M.magicM * 0.95) { shouldCast = true; break; }
            }
            if (shouldCast) {
                M.castSpell(spellHoF);
                console.log('🧙‍♂️ [AutoSpell] 觸發連擊:命運之手');
                return; // 施法後直接結束,避免同時施放兩個魔法
            }
        }

        // --- 優先級 2: 憑空建築 (Spontaneous Edifice) ---
        // v7.0 新增:閒置期免費獲取建築
        if (isSEEnabled && M.magic >= M.getSpellCost(spellSE)) {
            // 條件 A: 魔力接近全滿 (避免影響 HoF 回魔)
            if (M.magic < M.magicM * 0.95) return;

            // 條件 B: 場上沒有 Buff (確保不是連擊準備期)
            if (Object.keys(Game.buffs).length > 0) return;

            // 條件 C: 場上沒有金餅乾
            if (document.querySelectorAll('.shimmer').length > 0) return;

            // 施法
            console.log('🧙‍♂️ [AutoSpell] 閒置期:免費召喚了一座建築 (Spontaneous Edifice)');
            M.castSpell(spellSE);
        }
    }

    // ═══════════════════════════════════════════════════════════════
    // 7. 彈窗確認
    // ═══════════════════════════════════════════════════════════════
    function autoConfirmPrompt() {
        const promptBox = document.querySelector('#prompt');
        if (!promptBox) return;

        const isVisible = promptBox.style.display !== 'none' && promptBox.offsetHeight > 0;

        if (isVisible) {
            const promptContent = document.querySelector('#promptContent');
            const yesButton = document.querySelector('#promptOption0');

            if (promptContent && yesButton) {
                const txt = promptContent.textContent;
                // 中英雙語關鍵字
                const keywords = ['Warning', 'One Mind', 'revoke', '警告', '不好的结果'];
                if (keywords.some(k => txt.includes(k))) {
                    console.log('⚠️ [Safe] Auto-confirming prompt:', txt);
                    yesButton.click();
                }
            }
        }
    }

    // ═══════════════════════════════════════════════════════════════
    // 8. UI 顯示與管理
    // ═══════════════════════════════════════════════════════════════
    function createBuffDisplay() {
        if (buffDisplay) return;
        buffDisplay = $(`
            <div id="cookie-buff-monitor" style="
                position: fixed; left: ${buffX}px; top: ${buffY}px;
                padding: 10px; background: rgba(0,0,0,0.85);
                color: white; border-radius: 12px; font-family: 'Microsoft YaHei', sans-serif;
                z-index: 999996;
                box-shadow: 0 4px 20px rgba(0,0,0,0.6);
                display: ${showBuffMonitor ? 'block' : 'none'};
                cursor: move; user-select: none;
                width: 320px; box-sizing: border-box;
                border: 1px solid rgba(255,255,255,0.2); backdrop-filter: blur(4px);
            ">
                <div style="
                    font-weight: bold; margin-bottom: 10px;
                    border-bottom: 2px solid rgba(255,255,255,0.2);
                    padding-bottom: 8px; text-align: center;
                    color: #ffd700; font-size: 16px; letter-spacing: 1px;
                ">🔥 Buff 監控</div>
                <div id="buff-list-content" style="display: flex; flex-direction: column; gap: 8px;"></div>
            </div>
        `);
        makeDraggable(buffDisplay, 'buffX', 'buffY');
        $('body').append(buffDisplay);
    }

    function updateBuffDisplay() {
        if (!showBuffMonitor || !buffDisplay) return;
        if (typeof Game === 'undefined') return;

        const buffList = $('#buff-list-content');
        buffList.empty();

        let totalCpsMult = 1;
        let totalClickMult = 1; 
        let hasBuff = false;

        if (Game.buffs) {
            for (let i in Game.buffs) {
                hasBuff = true;
                const buff = Game.buffs[i];

                if (buff.multCpS > 0) totalCpsMult *= buff.multCpS;
                if (buff.multClick > 0) totalClickMult *= buff.multClick;

                const timeLeft = Math.ceil(buff.time / 30);
                const iconX = buff.icon[0] * 48;
                const iconY = buff.icon[1] * 48;
                const iconUrl = 'img/icons.png';
                const isPowerful = buff.multCpS > 50 || buff.multClick > 10;
                const textColor = isPowerful ? '#ff4444' : 'white';
                const bgColor = isPowerful ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.2)';

                let multDisplay = '';
                if (buff.multCpS > 0 && buff.multCpS !== 1) multDisplay = `產量 x${Math.round(buff.multCpS*10)/10}`;
                if (buff.multClick > 0 && buff.multClick !== 1) {
                    if(multDisplay) multDisplay += ', ';
                    multDisplay += `點擊 x${Math.round(buff.multClick)}`;
                }

                buffList.append(`
                    <div style="
                        display: flex; align-items: center;
                        padding: 6px; background: ${bgColor};
                        border-radius: 8px; border: 1px solid rgba(255,255,255,0.05);
                    ">
                        <div style="
                            width: 48px; height: 48px;
                            background: url(${iconUrl}) -${iconX}px -${iconY}px;
                            margin-right: 12px; border-radius: 4px;
                            box-shadow: 0 0 5px rgba(0,0,0,0.5); flex-shrink: 0;
                        "></div>
                        <div style="flex: 1; overflow: hidden;">
                            <div style="font-size: 15px; font-weight: bold; color: ${textColor}; margin-bottom: 2px;">
                                ${buff.dname ? buff.dname : buff.name}
                            </div>
                            <div style="font-size: 13px; color: #ffd700;">${multDisplay}</div>
                            <div style="font-size: 12px; color: #ccc;">剩餘: ${formatTime(timeLeft)}</div>
                        </div>
                    </div>
                `);
            }
        }

        if (!hasBuff) buffList.append('<div style="text-align: center; color: #666; font-size: 13px; padding: 10px;">無活性效果</div>');

        let summaryHtml = '<div style="margin-top: 8px; padding-top: 8px; border-top: 1px dashed rgba(255,255,255,0.3); text-align: right;">';
        let effectiveClickPower = totalCpsMult * totalClickMult;

        if (totalCpsMult !== 1) {
            let val = totalCpsMult < 1 ? totalCpsMult.toFixed(2) : Math.round(totalCpsMult).toLocaleString();
            summaryHtml += `<div style="color: #4caf50; font-weight: bold; font-size: 14px;">🏭 總產量: x${val}</div>`;
        }

        if (effectiveClickPower > 1) {
            let val = Math.round(effectiveClickPower).toLocaleString();
            summaryHtml += `<div style="color: #ff9800; font-weight: bold; font-size: 16px; margin-top: 2px;">⚡ 點擊倍率: x${val}</div>`;
        }

        if (typeof Game.computedMouseCps !== 'undefined') {
            let clickVal = Game.computedMouseCps;
            let formattedClick = typeof Beautify !== 'undefined' ? Beautify(clickVal) : Math.round(clickVal).toLocaleString();
            summaryHtml += `<div style="margin-top:5px; padding-top:5px; border-top:1px solid rgba(255,255,255,0.1); color:#fff; font-size:15px; font-family:monospace;">👆 點擊力: <span style="color: #ffd700;">+${formattedClick}</span></div>`;
        }
        summaryHtml += '</div>';
        buffList.append(summaryHtml);
    }

    function createCountdownDisplay() {
        if (countdownDisplay) return;
        countdownDisplay = $(`
            <div id="cookie-countdown" style="
                position: fixed; left: ${countdownX}px; top: ${countdownY}px;
                padding: 8px; background: rgba(0,0,0,0.85); color: white;
                border-radius: 8px; font-family: monospace; font-size: 12px;
                z-index: 999997; display: ${showCountdown ? 'block' : 'none'};
                width: 150px; cursor: move; border: 1px solid #444;
            ">
                <div style="text-align: center; border-bottom: 1px solid #555; margin-bottom: 4px;">⏱️ 倒數計時</div>
                <div style="display:flex; justify-content:space-between;"><span>🔄 重啟:</span><span id="txt-rst">--:--</span></div>
                <div style="display:flex; justify-content:space-between; margin-bottom:5px;"><span>🛒 購買:</span><span id="txt-buy">--:--</span></div>
                <div style="border-top:1px solid #555; padding-top:5px; text-align:center;">
                    <div style="font-size:10px; margin-bottom:2px;">🔊 音量: <span id="vol-disp">${gameVolume}</span>%</div>
                    <input type="range" id="volume-slider-mini" min="0" max="100" value="${gameVolume}" style="width:90%; cursor:pointer;">
                </div>
            </div>
        `);

        countdownDisplay.find('#volume-slider-mini').on('input', function() {
            gameVolume = parseInt($(this).val());
            $('#vol-disp').text(gameVolume);
            setGameVolume(gameVolume);
            GM_setValue('gameVolume', gameVolume);
        });

        makeDraggable(countdownDisplay, 'countdownX', 'countdownY');
        $('body').append(countdownDisplay);
    }

    function startCountdown() {
        if (countdownIntervalId) clearInterval(countdownIntervalId);
        countdownIntervalId = setInterval(() => {
            if (!showCountdown || !countdownDisplay) return;
            const now = Date.now();
            const rstRem = Math.max(0, nextRestartTime - now);
            const buyRem = Math.max(0, nextBuyTime - now);
            
            $('#txt-rst').text(formatMs(rstRem));
            $('#txt-buy').text(isBuyEnabled ? formatMs(buyRem) : '--:--');
            
            if (rstRem < 30000 && rstRem > 0) countdownDisplay.css('background', 'rgba(255,0,0,0.8)');
            else countdownDisplay.css('background', 'rgba(0,0,0,0.85)');
        }, 1000);
    }

    function createControlPanel() {
        if (controlPanel) return;
        const panelX = GM_getValue('panelX', window.innerWidth / 2 - 200);
        const panelY = GM_getValue('panelY', 100);

        controlPanel = $(`
            <div id="cookie-control-panel" style="
                position: fixed; left: ${panelX}px; top: ${panelY}px; width: 420px;
                max-height: 85vh; background: #fff; border-radius: 12px;
                box-shadow: 0 10px 40px rgba(0,0,0,0.4); z-index: 999998;
                font-family: Arial, sans-serif; display: none; overflow: hidden;
                color: #333;
            ">
                <div id="panel-header" style="
                    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                    color: white; padding: 15px; font-weight: bold; font-size: 18px;
                    cursor: move; display: flex; justify-content: space-between; align-items: center;
                ">
                    <span>🍪 控制面板 v7.0</span>
                    <span style="font-size:12px; opacity:0.8;">Ultimate Edition</span>
                </div>
                <div style="padding: 20px; overflow-y: auto; max-height: calc(85vh - 60px);">
                    
                    <div class="panel-section" style="margin-bottom:15px; padding:10px; background:#f5f7fa; border-radius:8px;">
                        <div style="font-weight:bold; color:#222; margin-bottom:10px; border-bottom:2px solid #ddd; padding-bottom:5px;">🎛️ 核心模組開關</div>
                        <div style="display:grid; grid-template-columns: 1fr 1fr; gap:10px; color:#333;">
                            <label><input type="checkbox" id="chk-auto-click" ${isClickEnabled?'checked':''}> 👉 自動點擊</label>
                            <label><input type="checkbox" id="chk-auto-buy" ${isBuyEnabled?'checked':''}> 🛒 自動購買</label>
                            <label><input type="checkbox" id="chk-auto-golden" ${isGoldenEnabled?'checked':''}> ⭐ 金餅乾/糖塊</label>
                            <label><input type="checkbox" id="chk-auto-garden" ${isGardenEnabled?'checked':''}> 🌻 花園維護</label>
                        </div>
                    </div>

                    <!-- v7.0 New Features -->
                    <div class="panel-section" style="margin-bottom:15px; padding:10px; background:#e1f5fe; border-radius:8px;">
                        <div style="font-weight:bold; color:#0277bd; margin-bottom:5px;">📈 v7.0 進階掛機</div>
                        <div style="color:#333; font-size:13px; display:grid; gap:8px;">
                            <label><input type="checkbox" id="chk-stock" ${isStockEnabled?'checked':''}> 股市自動交易 (低買高賣, RV演算法)</label>
                            <label><input type="checkbox" id="chk-se" ${isSEEnabled?'checked':''}> 🧙‍♂️ 閒置魔法: 憑空建築 (免費獲取建築)</label>
                        </div>
                        <div style="font-size:11px; color:#555; margin-top:5px;">
                            * 憑空建築僅在「滿魔、無Buff、無金餅乾」時觸發,不影響連擊。
                        </div>
                    </div>

                    <div class="panel-section" style="margin-bottom:15px; padding:10px; background:#e8f5e9; border-radius:8px;">
                        <div style="font-weight:bold; color:#1b5e20; margin-bottom:5px;">🌻 花園自動化設定</div>
                        <div style="font-size:12px; color:#333; margin-bottom:8px;">包含:除草、收割瀕死植物、自動補種</div>
                        <button id="garden-save-btn" style="
                            width:100%; padding:8px; background:#2196f3; color:white; border:none; border-radius:4px; cursor:pointer;
                        ">💾 記憶當前陣型 (用於自動補種)</button>
                        <div style="font-size:11px; color:#444; margin-top:5px; text-align:center;">
                            點擊按鈕後,當格子變空時,將自動補種當前位置的植物。
                        </div>
                    </div>

                    <div class="panel-section" style="margin-bottom:15px; padding:10px; background:#fff3e0; border-radius:8px;">
                        <div style="font-weight:bold; color:#e65100; margin-bottom:5px;">🛒 購買策略 (含巫師塔上限)</div>
                        <select id="buy-strategy" style="width:100%; padding:5px; margin-bottom:5px; color:#333;">
                            <option value="expensive" ${buyStrategy==='expensive'?'selected':''}>最貴優先 (衝產量)</option>
                            <option value="cheapest" ${buyStrategy==='cheapest'?'selected':''}>最便宜優先</option>
                        </select>
                        <div style="display:flex; gap:5px; align-items:center; margin-top:5px; color:#333;">
                            <span style="font-size:13px;">間隔:</span>
                            <select id="buy-min" style="color:#333;">${generateOptions(0, 59, buyIntervalMinutes, '分')}</select>
                            <select id="buy-sec" style="color:#333;">${generateOptions(0, 59, buyIntervalSeconds, '秒')}</select>
                        </div>
                        <div style="font-size:11px; color:#b71c1c; margin-top:5px; line-height:1.4;">
                            ⚠️ 巫師塔上限: ${MAX_WIZARD_TOWERS}座 (保留魔力上限)<br>
                            ⚠️ 始終拒買: 長者盟約 (Elder Covenant)
                        </div>
                        <label style="display:block; margin-top:5px; font-size:12px; color:#333;">
                            <input type="checkbox" id="chk-research" ${isResearchEnabled?'checked':''}> 包含科技研發
                        </label>
                    </div>

                    <div class="panel-section" style="margin-bottom:10px; padding:10px; background:#f3e5f5; border-radius:8px;">
                        <div style="font-weight:bold; color:#4a148c; margin-bottom:5px;">⚙️ 其他設定</div>
                        <label style="display:block; font-size:13px; color:#333;"><input type="checkbox" id="chk-spell" ${isSpellEnabled?'checked':''}> 🧙‍♂️ 基礎魔法連擊 (命運之手)</label>
                        <label style="display:block; font-size:13px; color:#333;"><input type="checkbox" id="chk-ui-count" ${showCountdown?'checked':''}> ⏱️ 倒數計時</label>
                        <label style="display:block; font-size:13px; color:#333;"><input type="checkbox" id="chk-ui-buff" ${showBuffMonitor?'checked':''}> 🔥 Buff 監控</label>
                        
                        <div style="margin-top:12px; color:#333;">
                            <span style="font-size:13px; font-weight:bold;">點擊速度: <span id="spd-val">${clickInterval}</span>ms</span>
                            <input type="range" id="spd-slider" min="10" max="200" value="${clickInterval}" style="width:100%; margin-top: 8px;">
                        </div>
                        
                        <div style="margin-top:10px; border-top:1px solid #ccc; padding-top:8px; color:#333;">
                             <span style="font-size:13px;">自動重啟:</span>
                             <select id="rst-hr" style="color:#333;">${generateOptions(0, 24, restartIntervalHours, '時')}</select>
                             <select id="rst-min" style="color:#333;">${generateOptions(0, 59, restartIntervalMinutes, '分')}</select>
                             <button id="btn-force-restart" style="float:right; background:#ff5252; color:white; border:none; padding:4px 10px; border-radius:4px; cursor:pointer;">立即重啟</button>
                        </div>
                    </div>

                    <div style="text-align:center; color:#777; font-size:11px; margin-top:10px;">
                        Cookie Clicker Ultimate v7.0
                    </div>
                </div>
            </div>
        `);

        makeDraggable(controlPanel, 'panelX', 'panelY', '#panel-header');
        $('body').append(controlPanel);
        bindPanelEvents();
    }

    function formatTime(seconds) {
        if (seconds < 60) return seconds + "s";
        const m = Math.floor(seconds / 60);
        const s = seconds % 60;
        return `${m}m ${s}s`;
    }

    function formatMs(ms) {
        if (ms <= 0) return "00:00";
        const totalSec = Math.floor(ms / 1000);
        const h = Math.floor(totalSec / 3600);
        const m = Math.floor((totalSec % 3600) / 60);
        const s = totalSec % 60;
        if (h > 0) return `${h}:${pad(m)}:${pad(s)}`;
        return `${pad(m)}:${pad(s)}`;
    }
    function pad(n) { return n < 10 ? '0'+n : n; }

    function generateOptions(min, max, selected, unit) {
        let html = '';
        for (let i = min; i <= max; i++) html += `<option value="${i}" ${i === selected ? 'selected' : ''}>${i}${unit}</option>`;
        return html;
    }

    function bindPanelEvents() {
        $('#chk-auto-click').change(function() { 
            isClickEnabled = this.checked; 
            GM_setValue('isClickEnabled', isClickEnabled);
            isClickEnabled ? startAutoClick() : stopAutoClick();
        });
        $('#chk-auto-buy').change(function() {
            isBuyEnabled = this.checked;
            GM_setValue('isBuyEnabled', isBuyEnabled);
            isBuyEnabled ? startAutoBuy() : stopAutoBuy();
        });
        $('#chk-auto-golden').change(function() {
            isGoldenEnabled = this.checked;
            GM_setValue('isGoldenEnabled', isGoldenEnabled);
        });
        $('#chk-auto-garden').change(function() {
            isGardenEnabled = this.checked;
            GM_setValue('isGardenEnabled', isGardenEnabled);
        });

        // v7.0 新開關
        $('#chk-stock').change(function() {
            isStockEnabled = this.checked;
            GM_setValue('isStockEnabled', isStockEnabled);
        });
        $('#chk-se').change(function() {
            isSEEnabled = this.checked;
            GM_setValue('isSEEnabled', isSEEnabled);
        });

        $('#garden-save-btn').click(saveGardenLayout);

        $('#buy-strategy').change(function() { buyStrategy = $(this).val(); GM_setValue('buyStrategy', buyStrategy); });
        $('#chk-research').change(function() { isResearchEnabled = this.checked; GM_setValue('isResearchEnabled', isResearchEnabled); });
        $('#chk-spell').change(function() { isSpellEnabled = this.checked; GM_setValue('isSpellEnabled', isSpellEnabled); });
        
        $('#chk-ui-count').change(function() { showCountdown = this.checked; GM_setValue('showCountdown', showCountdown); if(countdownDisplay) countdownDisplay.toggle(showCountdown); });
        $('#chk-ui-buff').change(function() { showBuffMonitor = this.checked; GM_setValue('showBuffMonitor', showBuffMonitor); if(buffDisplay) buffDisplay.toggle(showBuffMonitor); });

        $('#spd-slider').on('input', function() {
            clickInterval = parseInt($(this).val());
            $('#spd-val').text(clickInterval);
            GM_setValue('clickInterval', clickInterval);
            if (isClickEnabled) startAutoClick();
        });
        
        $('#buy-min, #buy-sec').change(function() {
            buyIntervalMinutes = parseInt($('#buy-min').val());
            buyIntervalSeconds = parseInt($('#buy-sec').val());
            GM_setValue('buyIntervalMinutes', buyIntervalMinutes);
            GM_setValue('buyIntervalSeconds', buyIntervalSeconds);
            if (isBuyEnabled) startAutoBuy();
        });

        $('#rst-hr').change(function() {
            restartIntervalHours = parseInt($(this).val());
            GM_setValue('restartIntervalHours', restartIntervalHours);
            scheduleAutoRestart();
        });

        $('#rst-min').change(function() {
            restartIntervalMinutes = parseInt($(this).val());
            GM_setValue('restartIntervalMinutes', restartIntervalMinutes);
            scheduleAutoRestart();
        });

        $('#btn-force-restart').click(function() {
            if(confirm('確定要刷新頁面嗎?')) performRestart();
        });
    }

    function makeDraggable(element, keyX, keyY, handleSelector = null) {
        let isDragging = false, startX = 0, startY = 0;
        const handle = handleSelector ? element.find(handleSelector) : element;
        
        handle.on('mousedown', function(e) {
            if ($(e.target).is('input, select, button')) return;
            isDragging = true;
            startX = e.clientX - parseInt(element.css('left'));
            startY = e.clientY - parseInt(element.css('top'));
            element.css('cursor', 'grabbing');
        });

        $(document).on('mousemove', function(e) {
            if (isDragging) {
                const x = e.clientX - startX;
                const y = e.clientY - startY;
                element.css({left: x + 'px', top: y + 'px'});
            }
        }).on('mouseup', function() {
            if (isDragging) {
                isDragging = false;
                element.css('cursor', 'default');
                GM_setValue(keyX, parseInt(element.css('left')));
                GM_setValue(keyY, parseInt(element.css('top')));
            }
        });
    }

    function createFloatingButton() {
        if (floatingButton) return;
        floatingButton = $(`
            <div id="cookie-floating-button" style="
                position: fixed; left: ${currentX}px; top: ${currentY}px;
                width: 50px; height: 50px;
                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                color: white; border-radius: 50%;
                display: flex; align-items: center; justify-content: center;
                cursor: pointer; z-index: 999999; font-size: 24px;
                box-shadow: 0 4px 15px rgba(0,0,0,0.3); user-select: none;
                transition: transform 0.1s;
            ">🍪</div>
        `);
        floatingButton.click((e) => { e.stopPropagation(); toggleControlPanel(); });
        
        let isD = false;
        floatingButton.mousedown(() => isD = true);
        $(document).mousemove((e) => {
            if(isD) {
                currentX = e.clientX - 25; currentY = e.clientY - 25;
                floatingButton.css({left: currentX, top: currentY});
            }
        }).mouseup(() => { 
            if(isD) { isD = false; GM_setValue('buttonX', currentX); GM_setValue('buttonY', currentY); }
        });
        
        $('body').append(floatingButton);
    }

    function toggleControlPanel() {
        if (!controlPanel) createControlPanel();
        controlPanel.is(':visible') ? controlPanel.fadeOut(200) : controlPanel.fadeIn(200);
    }

    function performRestart() {
        if (typeof Game !== 'undefined' && Game.WriteSave) Game.WriteSave();
        localStorage.setItem('cookieScriptRestarted', 'true');
        setTimeout(() => {
            GM_openInTab(window.location.href, { active: true, insert: true, setParent: false });
            setTimeout(() => window.close(), 1000);
        }, 500);
    }

    function scheduleAutoRestart() {
        if (restartIntervalId) clearInterval(restartIntervalId);
        let interval = (restartIntervalHours * 3600 + restartIntervalMinutes * 60 + restartIntervalSeconds) * 1000;
        if (interval < 60000) interval = 60000;
        nextRestartTime = Date.now() + interval;
        restartIntervalId = setInterval(performRestart, interval);
    }

    function setGameVolume(volume) {
        try { if (typeof Game !== 'undefined' && Game.setVolume) Game.setVolume(volume); } catch(e) {}
    }

    function updateAllDisplays() {
        if (floatingButton) {
            if (isClickEnabled) floatingButton.css('filter', 'hue-rotate(0deg)');
            else floatingButton.css('filter', 'grayscale(100%)');
        }
    }

    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', () => setTimeout(init, 1500));
    else setTimeout(init, 1500);

})();