Cookie Clicker Ultimate Automation

Automated clicker, auto-buy, auto-harvest, garden manager, stock market, season manager, and Santa evolver.

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴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      8.0.8
// @description  Automated clicker, auto-buy, auto-harvest, garden manager, stock market, season manager, and Santa evolver.
// @description:zh-TW 全功能自動掛機腳本 v8.0.8:修復牛奶選擇器(Milk Selector)導致的購買死結、統一日誌中文化。
// @author       You & AI Architect
// @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
// ==/UserScript==

(function() {
    'use strict';

    // ═══════════════════════════════════════════════════════════════
    // 0. 全域配置與狀態 (Configuration & State)
    // ═══════════════════════════════════════════════════════════════
    const Config = {
        // 開關狀態 (預設全開)
        Flags: {
            Click: GM_getValue('isClickEnabled', true),
            Buy: GM_getValue('isBuyEnabled', true),
            Golden: GM_getValue('isGoldenEnabled', true),
            Spell: GM_getValue('isSpellEnabled', true),
            Garden: GM_getValue('isGardenEnabled', true),
            Research: GM_getValue('isResearchEnabled', true),
            Stock: GM_getValue('isStockEnabled', true),
            SE: GM_getValue('isSEEnabled', true),
            Season: GM_getValue('isSeasonEnabled', true),
            Santa: GM_getValue('isSantaEnabled', true),
            
            GardenOverlay: GM_getValue('isGardenOverlayEnabled', true),
            GardenAvoidBuff: GM_getValue('isGardenAvoidBuff', true),
            GardenMutation: GM_getValue('isGardenMutationEnabled', false),
            
            ShowCountdown: GM_getValue('showCountdown', true),
            ShowBuffMonitor: GM_getValue('showBuffMonitor', true)
        },
        // 參數
        Settings: {
            Volume: GM_getValue('gameVolume', 50),
            ClickInterval: GM_getValue('clickInterval', 10), // Performance: 10ms
            BuyStrategy: GM_getValue('buyStrategy', 'expensive'),
            BuyIntervalMs: (GM_getValue('buyIntervalHours', 0) * 3600 + GM_getValue('buyIntervalMinutes', 3) * 60 + GM_getValue('buyIntervalSeconds', 0)) * 1000,
            RestartIntervalMs: (GM_getValue('restartIntervalHours', 1) * 3600 + GM_getValue('restartIntervalMinutes', 0) * 60 + GM_getValue('restartIntervalSeconds', 0)) * 1000,
            MaxWizardTowers: 800
        },
        // 記憶
        Memory: {
            SavedGardenPlot: GM_getValue('savedGardenPlot', Array(6).fill().map(() => Array(6).fill(-1))),
            PanelX: GM_getValue('panelX', window.innerWidth / 2 - 200),
            PanelY: GM_getValue('panelY', 100),
            BuffX: GM_getValue('buffX', window.innerWidth - 340),
            BuffY: GM_getValue('buffY', 150),
            CountdownX: GM_getValue('countdownX', window.innerWidth - 170),
            CountdownY: GM_getValue('countdownY', 10),
            ButtonX: GM_getValue('buttonX', 50),
            ButtonY: GM_getValue('buttonY', 50)
        }
    };

    // 運行時計時器與緩存
    const Runtime = {
        Timers: {
            NextBuy: 0,
            NextRestart: 0,
            NextGarden: 0,
            NextStock: 0,
            NextSeasonCheck: 0
        },
        Stats: {
            ClickCount: 0,
            BuyUpgradeCount: 0,
            BuyBuildingCount: 0
        },
        OriginalTitle: document.title,
        SeasonState: {
            CurrentStage: 0,
            Roadmap: [
                { name: 'Valentine', id: 'fools', target: 'BuyAllUpgrades' },
                { name: 'Christmas', id: 'christmas', target: 'MaxSanta' }
            ]
        }
    };

    // ═══════════════════════════════════════════════════════════════
    // 1. UI 與 日誌模組
    // ═══════════════════════════════════════════════════════════════
    const UI = {
        Elements: {
            Panel: null,
            FloatingBtn: null,
            Countdown: null,
            BuffMonitor: null
        },

        initStyles: function() {
            const style = document.createElement('style');
            style.type = 'text/css';
            style.innerHTML = `
                .cc-overlay-missing { border: 3px dashed #2196f3 !important; box-sizing: border-box; background: rgba(33, 150, 243, 0.1); }
                .cc-overlay-anomaly { border: 3px solid #ff4444 !important; box-shadow: inset 0 0 15px rgba(255, 0, 0, 0.6) !important; box-sizing: border-box; z-index: 10; }
                .cc-overlay-new { border: 3px solid #9c27b0 !important; box-shadow: inset 0 0 15px rgba(156, 39, 176, 0.8), 0 0 10px rgba(156, 39, 176, 0.5) !important; box-sizing: border-box; z-index: 12; }
                .cc-overlay-correct { border: 1px solid rgba(76, 175, 80, 0.4) !important; box-sizing: border-box; }
            `;
            document.head.appendChild(style);
        },

        formatMs: function(ms) {
            const s = Math.floor(ms/1000);
            return `${Math.floor(s/60)}:${s%60<10?'0':''}${s%60}`;
        },

        cleanName: function(str) {
            if (!str) return '';
            if (typeof str !== 'string') return String(str);
            return str.replace(/<[^>]*>/g, '').trim();
        },

        createFloatingButton: function() {
            if (this.Elements.FloatingBtn) return;
            this.Elements.FloatingBtn = $(`
                <div id="cookie-floating-button" style="
                    position: fixed; left: ${Config.Memory.ButtonX}px; top: ${Config.Memory.ButtonY}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); transition: filter 0.3s;
                ">🍪</div>
            `);
            
            this.Elements.FloatingBtn.click((e) => { e.stopPropagation(); this.togglePanel(); });
            
            let isD = false;
            this.Elements.FloatingBtn.mousedown(() => isD = true);
            $(document).mousemove((e) => {
                if(isD) { 
                    Config.Memory.ButtonX = e.clientX - 25; 
                    Config.Memory.ButtonY = e.clientY - 25; 
                    this.Elements.FloatingBtn.css({left: Config.Memory.ButtonX, top: Config.Memory.ButtonY}); 
                }
            }).mouseup(() => { 
                if(isD) { 
                    isD = false; 
                    GM_setValue('buttonX', Config.Memory.ButtonX); 
                    GM_setValue('buttonY', Config.Memory.ButtonY); 
                }
            });
            $('body').append(this.Elements.FloatingBtn);
            this.updateButtonState();
        },

        updateButtonState: function() {
            if (this.Elements.FloatingBtn) {
                this.Elements.FloatingBtn.css('filter', Config.Flags.Click ? 'hue-rotate(0deg)' : 'grayscale(100%)');
            }
        },

        createControlPanel: function() {
            if (this.Elements.Panel) return;
            const generateOptions = (min, max, sel, unit) => { 
                let h=''; for(let i=min; i<=max; i++) h+=`<option value="${i}" ${i === sel?'selected':''}>${i}${unit}</option>`; return h; 
            };

            const buyMin = Math.floor(Config.Settings.BuyIntervalMs / 60000);
            const buySec = (Config.Settings.BuyIntervalMs % 60000) / 1000;
            const rstHr = Math.floor(Config.Settings.RestartIntervalMs / 3600000);
            const rstMin = (Config.Settings.RestartIntervalMs % 3600000) / 60000;

            this.Elements.Panel = $(`
                <div id="cookie-control-panel" style="
                    position: fixed; left: ${Config.Memory.PanelX}px; top: ${Config.Memory.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>🍪 控制面板 v8.0.8</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" ${Config.Flags.Click?'checked':''}> 👉 自動點擊</label>
                                <label><input type="checkbox" id="chk-auto-buy" ${Config.Flags.Buy?'checked':''}> 🛒 自動購買</label>
                                <label><input type="checkbox" id="chk-auto-golden" ${Config.Flags.Golden?'checked':''}> ⭐ 金餅乾/糖塊</label>
                                <label><input type="checkbox" id="chk-auto-garden" ${Config.Flags.Garden?'checked':''}> 🌻 花園維護</label>
                            </div>
                        </div>

                        <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;">📈 進階掛機</div>
                            <div style="color:#333; font-size:13px; display:grid; gap:8px;">
                                <label><input type="checkbox" id="chk-stock" ${Config.Flags.Stock?'checked':''}> 股市自動交易</label>
                                <label><input type="checkbox" id="chk-se" ${Config.Flags.SE?'checked':''}> 🧙‍♂️ 閒置魔法: 憑空建築</label>
                                <label><input type="checkbox" id="chk-season" ${Config.Flags.Season?'checked':''}> 🍂 季節管理 (v8.0)</label>
                                <label><input type="checkbox" id="chk-santa" ${Config.Flags.Santa?'checked':''}> 🎅 聖誕老人進化 (v8.0)</label>
                            </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>
                            <label style="display:block; margin-bottom:8px; font-weight:bold; color:#2e7d32;">
                                <input type="checkbox" id="chk-garden-overlay" ${Config.Flags.GardenOverlay?'checked':''}> [x] 顯示陣型輔助網格
                            </label>
                            <label style="display:block; margin-bottom:8px; font-weight:bold; color:#d84315;">
                                <input type="checkbox" id="chk-garden-mutation" ${Config.Flags.GardenMutation?'checked':''}> 🧬 啟用自動突變管理
                            </label>
                            <label style="display:block; margin-bottom:8px; font-weight:bold; color:#1565c0;">
                                <input type="checkbox" id="chk-garden-smart" ${Config.Flags.GardenAvoidBuff?'checked':''}> 🧠 聰明補種 (Buff 期間暫停)
                            </label>
                            <button id="garden-save-btn" style="
                                width:100%; padding:8px; background:#2196f3; color:white; border:none; border-radius:4px; cursor:pointer;
                            ">💾 記憶當前陣型</button>
                        </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;">
                                <option value="expensive" ${Config.Settings.BuyStrategy==='expensive'?'selected':''}>最貴優先</option>
                                <option value="cheapest" ${Config.Settings.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">${generateOptions(0, 59, buyMin, '分')}</select>
                                <select id="buy-sec">${generateOptions(0, 59, buySec, '秒')}</select>
                            </div>
                        </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" ${Config.Flags.Spell?'checked':''}> 🧙‍♂️ 基礎魔法連擊</label>
                            <label style="display:block; font-size:13px; color:#333;"><input type="checkbox" id="chk-ui-count" ${Config.Flags.ShowCountdown?'checked':''}> ⏱️ 倒數計時</label>
                            <label style="display:block; font-size:13px; color:#333;"><input type="checkbox" id="chk-ui-buff" ${Config.Flags.ShowBuffMonitor?'checked':''}> 🔥 Buff 監控</label>
                            
                            <div style="margin-top:12px; color:#333;">
                                <span style="font-size:13px; font-weight:bold;">點擊速度: <span id="spd-val">${Config.Settings.ClickInterval}</span>ms</span>
                                <input type="range" id="spd-slider" min="10" max="200" value="${Config.Settings.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">${generateOptions(0, 24, rstHr, '時')}</select>
                                 <select id="rst-min">${generateOptions(0, 59, rstMin, '分')}</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>
                </div>
            `);
            this.makeDraggable(this.Elements.Panel, 'panelX', 'panelY', '#panel-header');
            $('body').append(this.Elements.Panel);
            this.bindEvents();
        },

        togglePanel: function() {
            if (!this.Elements.Panel) this.createControlPanel();
            this.Elements.Panel.is(':visible') ? this.Elements.Panel.fadeOut(200) : this.Elements.Panel.fadeIn(200);
        },

        createCountdown: function() {
            if (this.Elements.Countdown) return;
            this.Elements.Countdown = $(`
                <div id="cookie-countdown" style="
                    position: fixed; left: ${Config.Memory.CountdownX}px; top: ${Config.Memory.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: ${Config.Flags.ShowCountdown ? 'block' : 'none'};
                    width: 150px; cursor: move; border: 1px solid #444;
                ">
                    <div style="text-align: center; border-bottom: 1px solid #555; margin-bottom: 4px;">⏱️ 倒數計時 v8.0.8</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">${Config.Settings.Volume}</span>%</div>
                        <input type="range" id="volume-slider-mini" min="0" max="100" value="${Config.Settings.Volume}" style="width:90%;">
                    </div>
                </div>
            `);
            this.Elements.Countdown.find('#volume-slider-mini').on('input', function() {
                Config.Settings.Volume = parseInt($(this).val());
                $('#vol-disp').text(Config.Settings.Volume);
                try { if (Game.setVolume) Game.setVolume(Config.Settings.Volume); } catch(e) {}
                GM_setValue('gameVolume', Config.Settings.Volume);
            });
            this.makeDraggable(this.Elements.Countdown, 'countdownX', 'countdownY');
            $('body').append(this.Elements.Countdown);
        },

        createBuffMonitor: function() {
            if (this.Elements.BuffMonitor) return;
            this.Elements.BuffMonitor = $(`
                <div id="cookie-buff-monitor" style="
                    position: fixed; left: ${Config.Memory.BuffX}px; top: ${Config.Memory.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;
                    display: ${Config.Flags.ShowBuffMonitor ? 'block' : 'none'}; cursor: move; width: 320px;
                    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;">🔥 Buff 監控</div>
                    <div id="buff-list-content" style="display: flex; flex-direction: column; gap: 8px;"></div>
                </div>
            `);
            this.makeDraggable(this.Elements.BuffMonitor, 'buffX', 'buffY');
            $('body').append(this.Elements.BuffMonitor);
        },

        updateBuffDisplay: function() {
            if (!Config.Flags.ShowBuffMonitor || !this.Elements.BuffMonitor) 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 iconUrl = 'img/icons.png';
                    const iconX = buff.icon[0] * 48;
                    const iconY = buff.icon[1] * 48;

                    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)';

                    const buffName = UI.cleanName(buff.dname ? buff.dname : buff.name);

                    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; flex-shrink: 0; border-radius:4px; box-shadow: 0 0 5px rgba(0,0,0,0.5);"></div>
                            <div>
                                <div style="font-size: 14px; font-weight: bold; color: ${textColor};">${buffName}</div>
                                <div style="font-size: 12px; color: #ffd700;">${multDisplay}</div>
                                <div style="font-size: 12px; color: #ccc;">剩餘: ${Math.ceil(buff.time / 30)}s</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>`;
            }
            summaryHtml += '</div>';
            buffList.append(summaryHtml);
        },

        makeDraggable: function(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'));
            });
            $(document).on('mousemove', function(e) {
                if (isDragging) element.css({left: e.clientX - startX + 'px', top: e.clientY - startY + 'px'});
            }).on('mouseup', function() {
                if (isDragging) { isDragging = false; GM_setValue(keyX, parseInt(element.css('left'))); GM_setValue(keyY, parseInt(element.css('top'))); }
            });
        },

        bindEvents: function() {
            const self = this;
            const bindChk = (id, key) => {
                $('#'+id).change(function() {
                    Config.Flags[key] = this.checked;
                    GM_setValue('is'+key+(key.includes('Enabled')?'':'Enabled'), this.checked);
                    if(key==='Click') self.updateButtonState();
                    if(key==='ShowCountdown') self.Elements.Countdown.toggle(this.checked);
                    if(key==='ShowBuffMonitor') self.Elements.BuffMonitor.toggle(this.checked);
                    if(key==='GardenOverlay') Logic.Garden.clearOverlay();
                });
            };

            bindChk('chk-auto-click', 'Click');
            bindChk('chk-auto-buy', 'Buy');
            bindChk('chk-auto-golden', 'Golden');
            bindChk('chk-auto-garden', 'Garden');
            bindChk('chk-stock', 'Stock');
            bindChk('chk-se', 'SE');
            bindChk('chk-spell', 'Spell');
            bindChk('chk-ui-count', 'ShowCountdown');
            bindChk('chk-ui-buff', 'ShowBuffMonitor');
            bindChk('chk-garden-overlay', 'GardenOverlay');
            bindChk('chk-garden-mutation', 'GardenMutation');
            bindChk('chk-garden-smart', 'GardenAvoidBuff');
            bindChk('chk-season', 'Season');
            bindChk('chk-santa', 'Santa');

            $('#garden-save-btn').click(() => Logic.Garden.saveLayout());

            $('#buy-strategy').change(function() { Config.Settings.BuyStrategy = $(this).val(); GM_setValue('buyStrategy', Config.Settings.BuyStrategy); });
            $('#spd-slider').on('input', function() { Config.Settings.ClickInterval = parseInt($(this).val()); $('#spd-val').text(Config.Settings.ClickInterval); GM_setValue('clickInterval', Config.Settings.ClickInterval); });
            
            const updateBuyInt = () => {
                const min = parseInt($('#buy-min').val()); const sec = parseInt($('#buy-sec').val());
                Config.Settings.BuyIntervalMs = (min * 60 + sec) * 1000;
                GM_setValue('buyIntervalMinutes', min); GM_setValue('buyIntervalSeconds', sec);
            };
            $('#buy-min, #buy-sec').change(updateBuyInt);

            const updateRstInt = () => {
                const hr = parseInt($('#rst-hr').val()); const min = parseInt($('#rst-min').val());
                Config.Settings.RestartIntervalMs = (hr * 3600 + min * 60) * 1000;
                Core.scheduleRestart();
                GM_setValue('restartIntervalHours', hr); GM_setValue('restartIntervalMinutes', min);
            };
            $('#rst-hr, #rst-min').change(updateRstInt);

            $('#btn-force-restart').click(() => { if(confirm('確定要刷新?')) Core.performRestart(); });
        }
    };

    // ═══════════════════════════════════════════════════════════════
    // 2. 核心邏輯模組 (Business Logic)
    // ═══════════════════════════════════════════════════════════════
    const Logic = {
        
        Click: {
            lastRun: 0,
            update: function(now) {
                // 1. 大餅乾點擊
                if (Config.Flags.Click && now - this.lastRun >= Config.Settings.ClickInterval) {
                    const bigCookie = document.querySelector('#bigCookie');
                    if (bigCookie) { bigCookie.click(); Runtime.Stats.ClickCount++; }
                    this.lastRun = now;
                }

                // 2. 黃金餅乾 / 馴鹿 (高頻)
                if (Config.Flags.Golden) {
                    if (Game.canLumps() && Game.lumpCurrentType !== 3 && (Date.now() - Game.lumpT) >= Game.lumpRipeAge) Game.clickLump();
                    document.querySelectorAll('#shimmers > div.shimmer').forEach(c => c.click());
                }

                // 3. 魔法
                this.handleSpells();
            },
            handleSpells: function() {
                if ((!Config.Flags.Spell && !Config.Flags.SE) || !Game.Objects['Wizard tower'].minigame) return;
                const M = Game.Objects['Wizard tower'].minigame;

                if (Config.Flags.Spell && M.magic >= M.getSpellCost(M.spells['hand of fate'])) {
                    let shouldCast = false;
                    for (let i in Game.buffs) {
                        if (Game.buffs[i].multCpS > 7 || Game.buffs[i].multClick > 10) { shouldCast = true; break; }
                        if (Game.buffs[i].multCpS === 7 && M.magic >= M.magicM * 0.95) { shouldCast = true; break; }
                    }
                    if (shouldCast) {
                        M.castSpell(M.spells['hand of fate']);
                        console.log('🧙‍♂️ [AutoSpell] 觸發連擊:命運之手'); 
                    }
                }
                
                if (Config.Flags.SE && M.magic >= M.getSpellCost(M.spells['spontaneous edifice'])) {
                    if (M.magic >= M.magicM * 0.95 && Object.keys(Game.buffs).length === 0 && document.querySelectorAll('.shimmer').length === 0) {
                        console.log('🧙‍♂️ [AutoSpell] 閒置期:免費召喚了一座建築 (Spontaneous Edifice)'); 
                        M.castSpell(M.spells['spontaneous edifice']);
                    }
                }
            },
            handlePrompts: function() {
                const yesButton = document.querySelector('#promptOption0');
                if (yesButton && document.querySelector('#promptContent')) {
                    const txt = document.querySelector('#promptContent').textContent;
                    if (['Warning', 'One Mind', 'revoke', '警告', '不好的结果'].some(k => txt.includes(k))) {
                        console.log('⚠️ [Safe] Auto-confirming prompt:', txt); 
                        yesButton.click();
                    }
                }
            }
        },

        Buy: {
            update: function(now) {
                if (!Config.Flags.Buy || now < Runtime.Timers.NextBuy) return;
                if (typeof Game === 'undefined') return;

                // [Logic] 1. Elder Pledge (最高優先級,但須檢查冷卻)
                const pledge = Game.Upgrades['Elder Pledge'];
                if (pledge && pledge.unlocked && pledge.canBuy()) {
                    // Game.pledgeT 是誓約剩餘時間 (Frames),大於 0 代表生效中
                    if (typeof Game.pledgeT === 'undefined' || Game.pledgeT <= 0) {
                        console.log('🛡️ [自動購買] 誓約過期,強制優先購買!');
                        pledge.buy();
                        // 買了誓約後 return,確保優先級
                        Runtime.Timers.NextBuy = now + Config.Settings.BuyIntervalMs;
                        return; 
                    }
                    // 若誓約生效中,不做任何事,讓程式碼往下執行 (Fallthrough) 去買別的東西
                }

                // [Logic] 2. 科技研究 (Research)
                if (Config.Flags.Research) {
                    const research = document.querySelectorAll('#techUpgrades .crate.upgrade.enabled');
                    if (research.length > 0) {
                        const item = research[0];
                        let resName = "未知科技";
                        
                        // 嘗試抓取名稱
                        const onMouseOver = item.getAttribute('onmouseover');
                        if (onMouseOver) {
                             const match = /<div class="name">(.+?)<\/div>/.exec(onMouseOver);
                             if (match) resName = match[1];
                        }
                        
                        console.log(`🔬 [自動購買] 研發科技:${UI.cleanName(resName)}`);
                        item.click(); // 科技只能透過模擬點擊購買
                        Runtime.Timers.NextBuy = now + Config.Settings.BuyIntervalMs;
                        return; // 研發優先於普通升級
                    }
                }

                // [Logic] 3. 一般升級 (從 Data 中讀取)
                let affordable = Game.UpgradesInStore.filter(u => u.canBuy());
                
                // 過濾器
                affordable = affordable.filter(u => {
                    // 1. 排除特殊黑名單
                    if (u.id === 84) return false; // Covenant (盟約)
                    if (u.id === 74) return false; // Pledge (誓約) - 已在 VIP 通道處理

                    // 2. [Fix] 排除已購買的 Toggle 升級 (如 Milk selector, Background selector)
                    // 這些東西買了之後 bought 會變成 1,但還留在商店裡供切換
                    if (u.bought > 0) {
                        // 例外:季節開關 (ID 182~185, 209) 是需要反覆購買的,不能過濾
                        const seasonSwitchIds = [182, 183, 184, 185, 209];
                        if (!seasonSwitchIds.includes(u.id)) {
                            return false; // 踢除牛奶選擇器等裝飾品
                        }
                    }

                    // 3. 季節過濾 (若開啟季節管理,則暫時隱藏季節開關,交給季節模組處理)
                    const seasonSwitchIds = [182, 183, 184, 185, 209];
                    if (Config.Flags.Season && seasonSwitchIds.includes(u.id)) {
                        return false; 
                    }
                    
                    return true;
                });

                if (affordable.length > 0) {
                    // 排序策略
                    if (Config.Settings.BuyStrategy === 'expensive') {
                        affordable.sort((a, b) => b.getPrice() - a.getPrice());
                    } else {
                        affordable.sort((a, b) => a.getPrice() - b.getPrice());
                    }
                    
                    const target = affordable[0];
                    // v8.0.5 Localization Fix: use dname -> name
                    let upName = target.dname || target.name;
                    console.log(`🛒 [自動購買] Upgrade: ${UI.cleanName(upName)}`);
                    target.buy();
                    Runtime.Stats.BuyUpgradeCount++;
                    Runtime.Timers.NextBuy = now + Config.Settings.BuyIntervalMs;
                    return; // 買了升級就不買建築
                }

                // [Logic] 4. 建築 (從 Data 中讀取)
                let affordableBuildings = [];
                for (let i in Game.ObjectsById) {
                    const obj = Game.ObjectsById[i];
                    if (obj.locked) continue;
                    if (obj.name === 'Wizard tower' && obj.amount >= Config.Settings.MaxWizardTowers) continue;
                    if (obj.price <= Game.cookies) {
                        affordableBuildings.push(obj);
                    }
                }

                if (affordableBuildings.length > 0) {
                    if (Config.Settings.BuyStrategy === 'expensive') {
                        affordableBuildings.sort((a, b) => b.price - a.price);
                    } else {
                        affordableBuildings.sort((a, b) => a.price - b.price);
                    }
                    
                    const targetB = affordableBuildings[0];
                    
                    // v8.0.5 Hybrid Localization for Buildings
                    let buildName = targetB.displayName || targetB.name; // Fallback
                    const domElement = document.getElementById('productName' + targetB.id);
                    if (domElement) {
                        buildName = domElement.innerText; // Use DOM text if visible (Localized)
                    }
                    
                    console.log(`🛒 [自動購買] Building: ${UI.cleanName(buildName)}`);
                    targetB.buy(1);
                    Runtime.Stats.BuyBuildingCount++;
                    Runtime.Timers.NextBuy = now + Config.Settings.BuyIntervalMs;
                }
            }
        },

        Garden: {
            update: function(now) {
                if (!Config.Flags.Garden || now < Runtime.Timers.NextGarden) return;
                if (typeof Game === 'undefined' || !Game.Objects['Farm'].minigameLoaded) return;
                
                const M = Game.Objects['Farm'].minigame;
                if (!M) return;

                let isCpsBuffActive = false;
                if (Config.Flags.GardenAvoidBuff) {
                    for (let i in Game.buffs) {
                        if (Game.buffs[i].multCpS > 1) { isCpsBuffActive = true; break; }
                    }
                }

                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];
                        const savedId = Config.Memory.SavedGardenPlot[y][x];

                        if (tileId > 0) {
                            const plant = M.plantsById[tileId - 1];
                            const isAnomaly = (savedId !== -1 && tileId !== savedId) || (savedId === -1);
                            const plantName = UI.cleanName(plant.name);

                            if (!isAnomaly) {
                                if (tileAge >= 98 && tileAge >= plant.mature) M.harvest(x, y); 
                                continue; 
                            }

                            if (Config.Flags.GardenMutation) {
                                if (plant.unlocked) {
                                    M.harvest(x, y);
                                    console.log(`🧹 [花園] 鏟除雜物/已知變異 (紅框): ${plantName}`);
                                } else {
                                    if (tileAge >= plant.mature) {
                                        M.harvest(x, y);
                                        console.log(`🎉 [花園] 成功收割新品種種子 (紫框): ${plantName}`);
                                    }
                                }
                            } else {
                                if (plant.weed) M.harvest(x, y);
                            }
                            continue;
                        }

                        if (tileId === 0) {
                            if (savedId !== -1 && savedId !== null) {
                                const seed = M.plantsById[savedId - 1];
                                if (seed && seed.unlocked && M.canPlant(seed)) {
                                    if (Config.Flags.GardenAvoidBuff && isCpsBuffActive) continue;
                                    M.useTool(seed.id, x, y);
                                }
                            }
                        }
                    }
                }
                Runtime.Timers.NextGarden = now + 2500;
            },
            updateOverlay: function() {
                if (!Config.Flags.GardenOverlay) 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++) {
                        const tileDiv = document.getElementById(`gardenTile-${x}-${y}`);
                        if (!tileDiv) continue;
                        tileDiv.classList.remove('cc-overlay-missing', 'cc-overlay-anomaly', 'cc-overlay-correct', 'cc-overlay-new');
                        if (!M.isTileUnlocked(x, y)) continue;

                        const savedId = Config.Memory.SavedGardenPlot[y][x];
                        const realId = M.plot[y][x][0];

                        if (realId === 0 && savedId !== -1) {
                            tileDiv.classList.add('cc-overlay-missing');
                        } else if (realId !== 0) {
                            const plant = M.plantsById[realId - 1];
                            const isAnomaly = (savedId !== -1 && realId !== savedId) || (savedId === -1);
                            if (isAnomaly) {
                                if (plant.unlocked) tileDiv.classList.add('cc-overlay-anomaly');
                                else tileDiv.classList.add('cc-overlay-new');
                            } else if (realId === savedId) {
                                tileDiv.classList.add('cc-overlay-correct');
                            }
                        }
                    }
                }
            },
            clearOverlay: function() {
                $('.cc-overlay-missing, .cc-overlay-anomaly, .cc-overlay-correct, .cc-overlay-new').removeClass('cc-overlay-missing cc-overlay-anomaly cc-overlay-correct cc-overlay-new');
            },
            saveLayout: function() {
                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);
                }
                Config.Memory.SavedGardenPlot = newLayout;
                GM_setValue('savedGardenPlot', Config.Memory.SavedGardenPlot);
                const btn = $('#garden-save-btn');
                const originalText = btn.text();
                btn.text('✅ 已儲存!').css('background', '#4caf50');
                setTimeout(() => btn.text(originalText).css('background', '#2196f3'), 1500);
            }
        },

        Stock: {
            update: function(now) {
                if (!Config.Flags.Stock || now < Runtime.Timers.NextStock) return;
                const Bank = Game.Objects['Bank'];
                if (!Bank || !Bank.minigameLoaded || !Bank.minigame) return;
                const M = Bank.minigame;

                for (let i = 0; i < M.goodsById.length; i++) {
                    const good = M.goodsById[i];
                    const price = M.getGoodPrice(good);
                    const rv = M.getRestingVal(good.id);
                    const goodName = UI.cleanName(good.name);
                    
                    if (price < rv * 0.5) {
                        const maxStock = M.getGoodMaxStock(good);
                        if (good.stock < maxStock && Game.cookies > price) {
                            M.buyGood(good.id, 10000);
                        }
                    }
                    if (price > rv * 1.5) {
                        if (good.stock > 0) {
                            M.sellGood(good.id, 10000);
                            console.log(`📉 [股市] 獲利賣出 ${goodName} @ $${price.toFixed(2)} (RV: ${rv.toFixed(2)})`);
                        }
                    }
                }
                Runtime.Timers.NextStock = now + 3000;
            }
        },

        Season: {
            update: function(now) {
                if (!Config.Flags.Season || now < Runtime.Timers.NextSeasonCheck) return;
                
                const currentStage = Runtime.SeasonState.Roadmap[Runtime.SeasonState.CurrentStage];
                if (!currentStage) return;

                const currentSeason = Game.season;
                const targetSeasonId = currentStage.id;

                if (currentSeason !== targetSeasonId) {
                    const switcher = Object.values(Game.Upgrades).find(u => u.toggle && u.season === targetSeasonId);
                    if (switcher) {
                        if (!switcher.bought && switcher.canBuy()) {
                            console.log(`🍂 [Season] 切換季節至: ${currentStage.name}`);
                            switcher.buy();
                        }
                    }
                    Runtime.Timers.NextSeasonCheck = now + 2000;
                    return;
                }

                let isComplete = false;
                if (currentStage.target === 'BuyAllUpgrades') {
                    const remaining = Object.values(Game.Upgrades).filter(u => u.season === targetSeasonId && !u.bought && u.unlocked);
                    if (remaining.length === 0) isComplete = true;
                    else {
                        remaining.forEach(u => { if (u.canBuy()) { u.buy(); console.log(`🍂 [Season] 購買季節餅乾: ${u.name}`); } });
                    }
                } else if (currentStage.target === 'MaxSanta') {
                    if (Game.santaLevel >= 14) { 
                        const remaining = Object.values(Game.Upgrades).filter(u => u.season === targetSeasonId && !u.bought && u.unlocked);
                        if (remaining.length === 0) isComplete = true;
                    }
                }

                if (isComplete) {
                    console.log(`🍂 [Season] 階段完成: ${currentStage.name}`);
                    Runtime.SeasonState.CurrentStage++;
                }

                Runtime.Timers.NextSeasonCheck = now + 5000;
            }
        },

        Santa: {
            update: function(now) {
                if (!Config.Flags.Santa || Game.season !== 'christmas') return;
                
                if (Game.Has('A festive hat') && !Game.Has('Santa Claus')) {
                }

                if (Game.santaLevel < 14) { 
                    if (typeof Game.UpgradeSanta === 'function') {
                        Game.UpgradeSanta(); 
                    }
                }
            }
        },

        updateTitle: function() {
            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();
                coords = `${Math.round(rect.left + rect.width / 2)},${Math.round(rect.top + rect.height / 2)}`;
            }
            const signal = isWorthClicking ? "⚡ATTACK" : "💤IDLE";
            const displayMult = totalMult > 1000 ? (totalMult/1000).toFixed(1) + 'k' : Math.round(totalMult);
            document.title = `[${signal}|${displayMult}x|${coords}] ${Runtime.OriginalTitle}`;
        }
    };

    // ═══════════════════════════════════════════════════════════════
    // 3. 系統核心 (System Core)
    // ═══════════════════════════════════════════════════════════════
    const Core = {
        init: function() {
            console.log('🍪 Cookie Clicker Ultimate v8.0.8 (Milk Loop Fix) Loaded');
            
            const scriptRestarted = localStorage.getItem('cookieScriptRestarted');
            if (scriptRestarted) {
                console.log('🔄 Script restarted automatically.');
                localStorage.removeItem('cookieScriptRestarted');
            }

            UI.initStyles();
            UI.createFloatingButton();
            UI.createControlPanel();
            UI.createCountdown();
            UI.createBuffMonitor();
            
            try { if (Game.setVolume) Game.setVolume(Config.Settings.Volume); } catch(e) {}

            this.scheduleRestart();
            this.startHeartbeat();
            
            setTimeout(() => {
                Logic.Garden.clearOverlay();
                UI.updateButtonState();
            }, 3000);

            document.addEventListener('keydown', function(e) {
                if (e.key === 'F8') {
                    e.preventDefault();
                    Config.Flags.Click = !Config.Flags.Click;
                    GM_setValue('isClickEnabled', Config.Flags.Click);
                    UI.updateButtonState();
                    if(UI.Elements.Panel) $('#chk-auto-click').prop('checked', Config.Flags.Click);
                }
            });
        },

        startHeartbeat: function() {
            const self = this;
            
            const fastLoop = () => {
                const now = Date.now();
                Logic.Click.update(now);
                setTimeout(fastLoop, Math.max(10, Config.Settings.ClickInterval)); // Min 10ms
            };
            fastLoop();

            setInterval(() => {
                const now = Date.now();
                Logic.Buy.update(now);
                Logic.Garden.update(now);
                Logic.Garden.updateOverlay();
                Logic.Stock.update(now);
                Logic.Season.update(now);
                Logic.Santa.update(now);
                Logic.updateTitle();
                Logic.Click.handlePrompts(); 
                
                UI.updateBuffDisplay();
                
                if (Config.Flags.ShowCountdown) {
                    $('#txt-rst').text(UI.formatMs(Math.max(0, Runtime.Timers.NextRestart - now)));
                    $('#txt-buy').text(Config.Flags.Buy ? UI.formatMs(Math.max(0, Runtime.Timers.NextBuy - now)) : '--:--');
                }
            }, 1000);
        },

        scheduleRestart: function() {
            if (Runtime.Timers.RestartInterval) clearInterval(Runtime.Timers.RestartInterval);
            let interval = Config.Settings.RestartIntervalMs;
            if (interval < 60000) interval = 60000;
            Runtime.Timers.NextRestart = Date.now() + interval;
            
            if(this.restartTimer) clearTimeout(this.restartTimer);
            this.restartTimer = setTimeout(() => this.performRestart(), interval);
        },

        performRestart: function() {
            if (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);
        }
    };

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

})();