// ==UserScript== // @name Cookie Clicker Ultimate Automation // @name:zh-TW 餅乾點點樂全自動掛機輔助 (Cookie Clicker) // @name:zh-CN 饼干点点乐全自动挂机辅助 (Cookie Clicker) // @namespace http://tampermonkey.net/ // @version 8.6.3 Fix & Feature // @description Automated clicker, auto-buy, auto-harvest, garden manager, stock market, season manager, Santa evolver, Smart Sugar Lump harvester, and Dragon Aura management. // @description:zh-TW 全功能自動掛機腳本 v8.6.3:修復 Tooltip 空指標錯誤 + 花園面板新增「突變管理」快速開關 // @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 // @downloadURL none // ==/UserScript== (function() { 'use strict'; // ═══════════════════════════════════════════════════════════════ // 0. 全域配置與狀態 (Configuration & State) // ═══════════════════════════════════════════════════════════════ const Config = { // 開關狀態 Flags: { GlobalMasterSwitch: GM_getValue('isGlobalMasterSwitchEnabled', true), 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), AutoWrinkler: GM_getValue('isAutoWrinklerEnabled', true), Stock: GM_getValue('isStockEnabled', true), SE: GM_getValue('isSEEnabled', true), Season: GM_getValue('isSeasonEnabled', true), Santa: GM_getValue('isSantaEnabled', true), DragonAura: GM_getValue('isDragonAuraEnabled', 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), ShowGardenProtection: GM_getValue('showGardenProtection', true), SpendingLocked: GM_getValue('spendingLocked', false), GodzamokCombo: GM_getValue('isGodzamokComboEnabled', false), ShowGardenGrid: GM_getValue('isShowGardenGrid', false) }, // 參數 Settings: { Volume: GM_getValue('gameVolume', 50), ClickInterval: GM_getValue('clickInterval', 10), BuyStrategy: GM_getValue('buyStrategy', 'expensive'), BuyIntervalMs: (GM_getValue('buyIntervalHours', 0) * 3600 + GM_getValue('buyIntervalMinutes', 0) * 60 + GM_getValue('buyIntervalSeconds', 10)) * 1000, RestartIntervalMs: (GM_getValue('restartIntervalHours', 1) * 3600 + GM_getValue('restartIntervalMinutes', 0) * 60 + GM_getValue('restartIntervalSeconds', 0)) * 1000, MaxWizardTowers: 800, SugarLumpGoal: 100, SpellCooldownSuccess: 60000, SpellCooldownFail: 60000, SEFailThreshold: 3, SEFailResetCooldown: 300000, GodzamokMinMult: 1000, GodzamokSellAmount: 100, GodzamokTargetBuilding: 'Farm', GodzamokCooldown: 15000, GodzamokBuyBackTime: 11000 }, // 記憶 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), GardenProtectionX: GM_getValue('gardenProtectionX', 10), GardenProtectionY: GM_getValue('gardenProtectionY', 10), ActionLogX: GM_getValue('actionLogX', window.innerWidth - 420), ActionLogY: GM_getValue('actionLogY', window.innerHeight - 350), GardenGridX: GM_getValue('gardenGridX', 100), GardenGridY: GM_getValue('gardenGridY', 100), LastActiveTab: GM_getValue('lastActiveTab', 'core'), GardenLeftExpanded: GM_getValue('gardenLeftExpanded', false), GardenRightExpanded: GM_getValue('gardenRightExpanded', false), LogFontSize: GM_getValue('logFontSize', 12), LogOpacity: GM_getValue('logOpacity', 0.95), SavedSpendingStates: GM_getValue('savedSpendingStates', { Buy: true, Garden: true, Research: true, Stock: true }), GardenProtectionMinimized: GM_getValue('gardenProtectionMinimized', false), LastBuffEndTime: GM_getValue('lastBuffEndTime', 0) } }; // 運行時計時器與緩存 const Runtime = { Timers: { NextBuy: 0, NextRestart: 0, NextGarden: 0, NextStock: 0, NextSeasonCheck: 0, NextSpontaneousEdifice: 0, NextGodzamokCombo: 0, GardenWarmup: 0 }, Stats: { ClickCount: 0, BuyUpgradeCount: 0, BuyBuildingCount: 0, SEFailCount: 0 }, GodzamokState: { isActive: false, soldAmount: 0, originalBuyState: true }, DragonState: { isBursting: false, lastSwitchTime: 0, currentPhase: 'IDLE' }, ModuleFailCount: {}, OriginalTitle: document.title, SeasonState: { CurrentStage: 0, Roadmap: [ { name: 'Valentine', id: 'fools', target: 'BuyAllUpgrades' }, { name: 'Christmas', id: 'christmas', target: 'MaxSanta' } ] } }; // ═══════════════════════════════════════════════════════════════ // Logger 模組 // ═══════════════════════════════════════════════════════════════ const Logger = { log: function(module, message) { const formatted = `[${module}] ${message}`; console.log(`ℹ️ ${formatted}`); if (UI.ActionLog && UI.ActionLog.append) UI.ActionLog.append(formatted, 'info'); }, warn: function(module, message) { const formatted = `[${module}] ${message}`; console.warn(`⚠️ ${formatted}`); if (UI.ActionLog && UI.ActionLog.append) UI.ActionLog.append(formatted, 'warn'); }, error: function(module, message, error) { const formatted = `[${module}] ${message}`; console.error(`❌ ${formatted}`, error || ''); if (UI.ActionLog && UI.ActionLog.append) UI.ActionLog.append(formatted, 'error'); if (typeof Game !== 'undefined' && Game.Notify) { Game.Notify('腳本錯誤', `${module}: ${message}`, [10, 6], 10); } }, success: function(module, message) { const formatted = `[${module}] ${message}`; console.log(`%c✅ ${formatted}`, 'color: #4caf50; font-weight: bold;'); if (UI.ActionLog && UI.ActionLog.append) UI.ActionLog.append(formatted, 'success'); }, critical: function(module, message) { const formatted = `[${module}] ⚠️ CRITICAL: ${message}`; console.error(`%c❌ ${formatted}`, 'color: #ff0000; font-weight: bold; background: #000; padding: 2px 5px;'); if (UI.ActionLog && UI.ActionLog.append) UI.ActionLog.append(formatted, 'error'); if (typeof Game !== 'undefined' && Game.Notify) { Game.Notify('功能已停用', `${module}: ${message}`, [10, 6], 10); } } }; // ═══════════════════════════════════════════════════════════════ // 1. UI 模組 // ═══════════════════════════════════════════════════════════════ const UI = { Elements: { Panel: null, FloatingBtn: null, Countdown: null, BuffMonitor: null }, _lastBuffSnapshot: 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; } .cc-close-btn { position:absolute; top:5px; right:5px; cursor:pointer; color:#aaa; font-weight:bold; padding:2px 6px; z-index:100; font-family:sans-serif; } .cc-close-btn:hover { color:white; background:rgba(255,255,255,0.2); border-radius:4px; } /* Tab System */ .cc-tab-header { display: flex; border-bottom: 2px solid #ddd; background: #f5f7fa; white-space: nowrap; overflow-x: auto; overflow-y: hidden; } .cc-tab-btn { flex: 1 0 auto; padding: 12px 5px; border: none; background: transparent; cursor: pointer; font-weight: bold; color: #555; transition: all 0.2s; font-size: 14px; min-width: 0; overflow: hidden; text-overflow: ellipsis; } .cc-tab-btn:hover { background: rgba(0,0,0,0.05); color: #333; } .cc-tab-btn.active { border-bottom: 3px solid #667eea; color: #667eea; background: #fff; } .cc-tab-pane { display: none; padding: 15px; animation: fadeIn 0.2s; } .cc-tab-pane.active { display: block; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } /* Log Controls */ .cc-log-controls { display: flex; gap: 10px; padding: 5px 10px; background: rgba(0,0,0,0.3); border-bottom: 1px solid #444; font-size: 12px; align-items: center; } .cc-range-mini { width: 60px; height: 4px; } /* Garden Grid */ .cc-garden-row { display: flex; align-items: flex-start; justify-content: center; } .cc-drawer { width: 0px; opacity: 0; overflow: hidden; transition: width 0.3s ease, opacity 0.2s ease; background: rgba(0,0,0,0.3); border-radius: 6px; } .cc-drawer.open { width: 220px; opacity: 1; margin: 0 5px; border: 1px solid #555; } .cc-center-stage { width: 320px !important; flex-shrink: 0; display: flex; flex-direction: column; align-items: center; z-index: 10; } .cc-garden-side ul { list-style: none; padding: 0; margin: 0; } .cc-garden-side ul li { padding: 5px 8px; border-bottom: 1px solid #333; transition: background 0.2s; font-size: 13px; } .cc-garden-side ul li:hover { background: rgba(255, 255, 255, 0.1); } /* Embedded Controls */ #cc-embed-right { position: absolute; top: 0; right: 0; height: 100%; display: flex; flex-direction: column; justify-content: center; gap: 8px; z-index: 1000; pointer-events: none; padding-right: 2px; } .cc-embed-btn { pointer-events: auto; width: 32px; height: 32px; background: rgba(0, 0, 0, 0.8); border: 2px solid #81c784; border-radius: 50%; color: white; font-size: 14px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.2s; box-shadow: 0 2px 5px rgba(0,0,0,0.5); } .cc-embed-btn:hover { background: rgba(129, 199, 132, 0.9); transform: scale(1.1); box-shadow: 0 0 10px rgba(129, 199, 132, 0.8); } .cc-embed-btn:active { transform: scale(0.95); } /* Dashboard */ .cc-grid-tile { cursor: default; } .cc-dashboard-correct { border: 2px solid #4caf50 !important; } .cc-dashboard-anomaly { border: 2px solid #9c27b0 !important; } .cc-dashboard-missing { border: 2px solid #2196f3 !important; } /* Rich Tooltip */ #cc-tooltip { position: absolute; background: rgba(0, 0, 0, 0.95); color: white; padding: 12px 15px; border-radius: 8px; border: 1px solid #81c784; font-family: Arial, sans-serif; font-size: 13px; z-index: 2147483647 !important; pointer-events: none; box-shadow: 0 6px 20px rgba(0,0,0,0.7); max-width: 250px; line-height: 1.5; backdrop-filter: blur(3px); } #cc-tooltip .tooltip-name { font-weight: bold; color: #81c784; margin-bottom: 8px; font-size: 15px; border-bottom: 1px solid rgba(129, 199, 132, 0.3); padding-bottom: 4px; } #cc-tooltip .tooltip-price { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; } #cc-tooltip .tooltip-status { font-size: 12px; color: #ccc; margin-top: 6px; padding-top: 6px; border-top: 1px solid rgba(255,255,255,0.1); } .price-affordable { color: #4caf50; font-weight: bold; } .price-unaffordable { color: #ff5252; font-weight: bold; } .status-correct { color: #4caf50; } .status-anomaly { color: #9c27b0; } .status-missing { color: #2196f3; } .status-locked { color: #777; } /* Warmup Shield */ .cc-warmup-shield { animation: warmupPulse 1.5s infinite alternate; } @keyframes warmupPulse { 0% { background-color: #ffeb3b; box-shadow: 0 0 10px rgba(255, 235, 59, 0.7); } 100% { background-color: #ff9800; box-shadow: 0 0 15px rgba(255, 152, 0, 0.9); } } /* Scrollable Lists */ .cc-garden-list-scroll { max-height: 280px; overflow-y: auto; transition: max-height 0.3s ease; scrollbar-width: thin; scrollbar-color: #555 #222; } .cc-garden-list-scroll::-webkit-scrollbar { width: 6px; } .cc-garden-list-scroll::-webkit-scrollbar-track { background: #222; } .cc-garden-list-scroll::-webkit-scrollbar-thumb { background: #555; border-radius: 3px; } .cc-garden-list-expanded { max-height: 600px !important; } .cc-list-expand-btn { font-size: 10px; padding: 1px 6px; cursor: pointer; background: #444; border: 1px solid #666; color: #ccc; border-radius: 3px; margin-left: 5px; vertical-align: middle; } .cc-list-expand-btn:hover { background: #666; color: white; } `; document.head.appendChild(style); }, formatMs: function(ms) { if (ms < 0) return '00:00'; const totalSecs = Math.floor(ms / 1000); const h = Math.floor(totalSecs / 3600); const m = Math.floor((totalSecs % 3600) / 60); const s = totalSecs % 60; const pad = (n) => n < 10 ? '0' + n : n; return h > 0 ? `${h}:${pad(m)}:${pad(s)}` : `${pad(m)}:${pad(s)}`; }, 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 = $(` `); 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) { if (Config.Flags.GlobalMasterSwitch) { this.Elements.FloatingBtn.css({ 'filter': Config.Flags.Click ? 'hue-rotate(0deg)' : 'grayscale(100%)', 'background': 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' }).html('🍪'); } else { this.Elements.FloatingBtn.css({ 'filter': 'none', 'background': '#f44336' }).html('⏸️'); } } }, createControlPanel: function() { if (this.Elements.Panel) return; const generateOptions = (min, max, sel, unit) => { let h=''; for(let i=min; i<=max; i++) h+=``; 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 = $(` `); this.makeDraggable(this.Elements.Panel, 'panelX', 'panelY', '#panel-header'); $('body').append(this.Elements.Panel); this.bindEvents(); this.restoreTab(); }, restoreTab: function() { const lastTab = Config.Memory.LastActiveTab; $(`.cc-tab-btn[data-target="${lastTab}"]`).click(); }, togglePanel: function() { if (!this.Elements.Panel) this.createControlPanel(); this.Elements.Panel.is(':visible') ? this.Elements.Panel.fadeOut(200) : this.Elements.Panel.fadeIn(200); }, toggleMasterSwitch: function() { Config.Flags.GlobalMasterSwitch = !Config.Flags.GlobalMasterSwitch; GM_setValue('isGlobalMasterSwitchEnabled', Config.Flags.GlobalMasterSwitch); const statusBar = $('#global-status-bar'); const statusIcon = $('#status-icon'); const statusText = $('#status-text'); const btn = $('#btn-toggle-master'); if (Config.Flags.GlobalMasterSwitch) { statusBar.css('background', '#4caf50'); statusIcon.text('🟢'); statusText.text('系統運行中'); btn.text('暫停 (F8)'); Logger.success('Core', '全局自動化已啟動'); } else { statusBar.css('background', '#f44336'); statusIcon.text('🔴'); statusText.text('系統已暫停'); btn.text('恢復 (F8)'); Logger.warn('Core', '全局自動化已暫停'); } this.updateButtonState(); }, createCountdown: function() { if (this.Elements.Countdown) return; this.Elements.Countdown = $(` `); 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.Elements.Countdown.find('#countdown-close').click(() => { $('#chk-ui-count').prop('checked', false).trigger('change'); }); this.makeDraggable(this.Elements.Countdown, 'countdownX', 'countdownY'); $('body').append(this.Elements.Countdown); }, createBuffMonitor: function() { if (this.Elements.BuffMonitor) return; this.Elements.BuffMonitor = $(` `); this.Elements.BuffMonitor.find('#buff-monitor-close').click(() => { $('#chk-ui-buff').prop('checked', false).trigger('change'); }); 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'); const currentBuffKeys = []; 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]; currentBuffKeys.push(`${buff.name}_${buff.time}`); if (buff.multCpS > 0) totalCpsMult *= buff.multCpS; if (buff.multClick > 0) totalClickMult *= buff.multClick; } } const needFullRefresh = !this._lastBuffSnapshot || this._lastBuffSnapshot.length !== currentBuffKeys.length || !this._lastBuffSnapshot.every((key, idx) => key === currentBuffKeys[idx]); if (needFullRefresh) { buffList.empty(); if (Game.buffs) { for (let i in Game.buffs) { const buff = Game.buffs[i]; 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(`
${buffName}
${multDisplay}
剩餘: ${Math.ceil(buff.time / 30)}s
`); } } if (!hasBuff) { buffList.append('
無活性效果
'); } let summaryHtml = '
'; let effectiveClickPower = totalCpsMult * totalClickMult; if (totalCpsMult !== 1) { let val = totalCpsMult < 1 ? totalCpsMult.toFixed(2) : Math.round(totalCpsMult).toLocaleString(); summaryHtml += `
🏭 總產量: x${val}
`; } if (effectiveClickPower > 1) { let val = Math.round(effectiveClickPower).toLocaleString(); summaryHtml += `
⚡ 點擊倍率: x${val}
`; } if (typeof Game.computedMouseCps !== 'undefined') { let clickVal = Game.computedMouseCps; let formattedClick = typeof Beautify !== 'undefined' ? Beautify(clickVal) : Math.round(clickVal).toLocaleString(); summaryHtml += `
👆 點擊力: +${formattedClick}
`; } summaryHtml += '
'; buffList.append(summaryHtml); this._lastBuffSnapshot = currentBuffKeys; } else { if (Game.buffs) { let index = 0; for (let i in Game.buffs) { const buff = Game.buffs[i]; const timeElement = buffList.find('.buff-entry').eq(index).find('.buff-time'); if (timeElement.length) { timeElement.text(`剩餘: ${Math.ceil(buff.time / 30)}s`); } index++; } } } }, 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; $('.cc-tab-btn').click(function() { const target = $(this).data('target'); $('.cc-tab-btn').removeClass('active'); $(this).addClass('active'); $('.cc-tab-pane').removeClass('active'); $('#tab-' + target).addClass('active'); Config.Memory.LastActiveTab = target; GM_setValue('lastActiveTab', target); }); const bindChkWithLock = (id, key) => { $('#' + id).change(function() { if (Config.Flags.SpendingLocked) { this.checked = Config.Flags[key]; Logger.warn('花園保護', '支出鎖定期間無法修改此項目'); return false; } Config.Flags[key] = this.checked; GM_setValue('is' + key + 'Enabled', this.checked); if(key==='Click') self.updateButtonState(); }); }; 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(); if(key==='GardenMutation') UI.GardenGrid.updateButtonState(); if(key==='DragonAura' && this.checked && Runtime.ModuleFailCount['Dragon'] >= 10) { Runtime.ModuleFailCount['Dragon'] = 0; Logger.success('Core', '已重置巨龍光環熔斷計數器'); } }); }; bindChk('chk-auto-click', 'Click'); bindChkWithLock('chk-auto-buy', 'Buy'); bindChk('chk-auto-golden', 'Golden'); bindChkWithLock('chk-auto-garden', 'Garden'); bindChkWithLock('chk-research', 'Research'); bindChk('chk-wrinkler', 'AutoWrinkler'); bindChkWithLock('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'); bindChk('chk-dragon', 'DragonAura'); $('#chk-ui-log').change(function() { UI.ActionLog.toggle(this.checked); }); $('#btn-toggle-master').click(function() { UI.toggleMasterSwitch(); }); $('#btn-toggle-master').hover( function() { $(this).css('background', 'rgba(255, 255, 255, 0.3)'); }, function() { $(this).css('background', 'rgba(255, 255, 255, 0.2)'); } ); $('#chk-show-garden-protection').change(function() { Config.Flags.ShowGardenProtection = this.checked; GM_setValue('showGardenProtection', this.checked); UI.GardenProtection.updateVisibility(); }); $('#btn-reset-ui-pos').click(() => { Config.Memory.GardenProtectionX = 100; Config.Memory.GardenProtectionY = 100; Config.Memory.GardenGridX = 100; Config.Memory.GardenGridY = 100; GM_setValue('gardenProtectionX', 100); GM_setValue('gardenProtectionY', 100); GM_setValue('gardenGridX', 100); GM_setValue('gardenGridY', 100); $('#garden-protection-ui').css({left: '100px', top: '100px'}); $('#garden-grid-panel').css({left: '100px', top: '100px'}); Logger.success('UI', '所有懸浮面板位置已重置'); }); $('#garden-save-btn').click(() => Logic.Garden.saveLayout()); $('#main-panel-close').click(() => self.togglePanel()); $('#chk-godzamok').change(function() { Config.Flags.GodzamokCombo = this.checked; GM_setValue('isGodzamokComboEnabled', this.checked); }); $('#val-godzamok-amount').change(function() { Config.Settings.GodzamokSellAmount = parseInt(this.value); }); $('#val-godzamok-target').change(function() { Config.Settings.GodzamokTargetBuilding = this.value; }); $('#val-godzamok-min').change(function() { Config.Settings.GodzamokMinMult = parseInt(this.value); }); $('#btn-show-grid').click(() => UI.GardenGrid.toggle()); $('#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(); }); } }; // ═══════════════════════════════════════════════════════════════ // 可視化操作日誌面板 // ═══════════════════════════════════════════════════════════════ UI.ActionLog = { Elements: { Container: null, LogList: null, Counter: null }, MaxEntries: 100, lastMsgContent: null, lastMsgCount: 1, lastMsgElement: null, create: function() { if (this.Elements.Container) return; const fontSize = Config.Memory.LogFontSize; const opacity = Config.Memory.LogOpacity; this.Elements.Container = $(` `); $('body').append(this.Elements.Container); this.Elements.LogList = $('#log-list'); this.Elements.Counter = $('#log-counter'); this.bindEvents(); UI.makeDraggable(this.Elements.Container, 'actionLogX', 'actionLogY'); }, bindEvents: function() { $('#btn-clear-log').click(() => this.clear()); $('#btn-clear-log').hover( function() { $(this).css('background', '#d32f2f'); }, function() { $(this).css('background', '#f44336'); } ); this.Elements.Container.find('#action-log-close').click(() => { this.toggle(false); }); $('#log-font-slider').on('input', function() { const size = $(this).val(); $('#action-log-panel').css('font-size', size + 'px'); Config.Memory.LogFontSize = size; GM_setValue('logFontSize', size); }); $('#log-opacity-slider').on('input', function() { const opacity = $(this).val() / 100; $('#action-log-panel').css('background', `rgba(0, 0, 0, ${opacity})`); Config.Memory.LogOpacity = opacity; GM_setValue('logOpacity', opacity); }); }, append: function(message, level = 'info') { if (!this.Elements.LogList) return; const colors = { info: '#2196f3', warn: '#ff9800', error: '#f44336', success: '#4caf50' }; const icons = { info: 'ℹ️', warn: '⚠️', error: '❌', success: '✅' }; const color = colors[level] || colors.info; const icon = icons[level] || icons.info; const cleanMsg = UI.cleanName(message); if (this.lastMsgContent === cleanMsg && this.lastMsgElement && this.Elements.LogList.has(this.lastMsgElement).length) { this.lastMsgCount++; const timestamp = new Date().toLocaleTimeString('zh-TW', { hour12: false }); this.lastMsgElement.find('.log-time').text(timestamp); let countBadge = this.lastMsgElement.find('.log-count'); if (countBadge.length === 0) { this.lastMsgElement.append(`x${this.lastMsgCount}`); } else { countBadge.text(`x${this.lastMsgCount}`); } this.Elements.LogList.prepend(this.lastMsgElement); return; } this.lastMsgContent = cleanMsg; this.lastMsgCount = 1; const timestamp = new Date().toLocaleTimeString('zh-TW', { hour12: false }); const entry = $(`
${timestamp} ${icon} ${cleanMsg}
`); this.lastMsgElement = entry; this.Elements.LogList.find('div:contains("尚無日誌")').remove(); this.Elements.LogList.prepend(entry); const entries = this.Elements.LogList.children(); if (entries.length > this.MaxEntries) entries.last().remove(); this.Elements.Counter.text(`${entries.length} 條`); }, clear: function() { if (!this.Elements.LogList) return; this.Elements.LogList.html(`
尚無日誌
`); this.Elements.Counter.text('0 條'); this.lastMsgContent = null; this.lastMsgElement = null; }, toggle: function(visible) { if (!this.Elements.Container) return; visible ? this.Elements.Container.fadeIn(200) : this.Elements.Container.fadeOut(200); $('#chk-ui-log').prop('checked', visible); } }; // ═══════════════════════════════════════════════════════════════ // 花園陣型可視化 (Refactored for v8.6.2 ID Normalization + v8.6.3 Feature) // ═══════════════════════════════════════════════════════════════ UI.GardenGrid = { Elements: { Container: null }, create: function() { if (this.Elements.Container) return; this.Elements.Container = $(` `); $('body').append(this.Elements.Container); this.Elements.Container.find('#garden-grid-close').click(() => this.toggle()); $('#btn-grid-manual-refresh').click(() => UI.GardenGrid.update()); // v8.6.3: Toggle Mutation Button Handler $('#btn-grid-toggle-mutation').click(() => { Config.Flags.GardenMutation = !Config.Flags.GardenMutation; GM_setValue('isGardenMutationEnabled', Config.Flags.GardenMutation); $('#chk-garden-mutation').prop('checked', Config.Flags.GardenMutation); // Sync with main panel this.updateButtonState(); }); $('#cc-btn-expand-left').click(() => this.toggleSide('left')); $('#cc-btn-expand-right').click(() => this.toggleSide('right')); $('#btn-expand-unlocked').click(function() { const c = $('#container-unlocked'); c.toggleClass('cc-garden-list-expanded'); $(this).text(c.hasClass('cc-garden-list-expanded') ? '▲' : '▼'); }); $('#btn-expand-locked').click(function() { const c = $('#container-locked'); c.toggleClass('cc-garden-list-expanded'); $(this).text(c.hasClass('cc-garden-list-expanded') ? '▲' : '▼'); }); if (Config.Memory.GardenLeftExpanded) this.toggleSide('left', true); if (Config.Memory.GardenRightExpanded) this.toggleSide('right', true); UI.makeDraggable(this.Elements.Container, 'gardenGridX', 'gardenGridY'); this.updateButtonState(); // Initialize state $(document).on('mouseenter', '#garden-grid-content > div', function() { const normalizedId = $(this).data('normalized-id'); // Tooltip uses normalized ID for display logic if (typeof Game !== 'undefined' && Game.Objects['Farm'].minigame) { const M = Game.Objects['Farm'].minigame; // For logic, if normalizedId > -1, it corresponds to plantsById[normalizedId] // If -1, it's empty let plant = null; if (normalizedId > -1) { plant = M.plantsById[normalizedId]; } const savedId = $(this).data('saved-id'); // We pass normalizedId as 'realId' to tooltip if (plant || savedId > -1) { // If current is empty (-1), plant is null. Tooltip handles this. // We might need a reference plant if current is empty but saved is not. const refPlant = plant || (savedId > -1 ? M.plantsById[savedId] : null); // v8.6.3 Fix: Ensure we pass a valid plant or fallback if possible if (refPlant) { UI.Tooltip.show(this, refPlant, normalizedId, savedId); } } } }).on('mouseleave', '#garden-grid-content > div', function() { UI.Tooltip.hide(); }); }, updateButtonState: function() { const btn = $('#btn-grid-toggle-mutation'); if (btn.length) { if (Config.Flags.GardenMutation) { btn.text('🧬 突變管理: 開') .css({ 'background': '#2e7d32', 'border-color': '#4caf50' }); } else { btn.text('🧬 突變管理: 關') .css({ 'background': '#c62828', 'border-color': '#ff5252' }); } } }, toggle: function() { if (!this.Elements.Container) this.create(); if (this.Elements.Container.is(':visible')) { this.Elements.Container.fadeOut(200); } else { this.update(); this.updateButtonState(); this.Elements.Container.fadeIn(200); } }, toggleSide: function(side, forceOpen = null) { const drawer = $(`#drawer-${side}`); const btn = $(`#cc-btn-expand-${side}`); const container = this.Elements.Container; const drawerWidth = 220; const currentX = parseInt(container.css('left')) || Config.Memory.GardenGridX; let newX = currentX; const isOpen = forceOpen !== null ? forceOpen : !drawer.hasClass('open'); if (isOpen) { if (side === 'left') { newX = Math.max(0, currentX - drawerWidth); container.css('left', newX + 'px'); Config.Memory.GardenGridX = newX; GM_setValue('gardenGridX', newX); } drawer.addClass('open'); btn.text(side === 'left' ? '▶ 收起' : '收起 ◀'); Config.Memory[side === 'left' ? 'GardenLeftExpanded' : 'GardenRightExpanded'] = true; GM_setValue(side === 'left' ? 'gardenLeftExpanded' : 'gardenRightExpanded', true); } else { if (side === 'left') { newX = currentX + drawerWidth; container.css('left', newX + 'px'); Config.Memory.GardenGridX = newX; GM_setValue('gardenGridX', newX); } drawer.removeClass('open'); btn.text(side === 'left' ? '◀ 展開' : '展開 ▶'); Config.Memory[side === 'left' ? 'GardenLeftExpanded' : 'GardenRightExpanded'] = false; GM_setValue(side === 'left' ? 'gardenLeftExpanded' : 'gardenRightExpanded', false); } }, update: function() { $('#garden-grid-content').empty(); const plot = Config.Memory.SavedGardenPlot; const gridContent = $('#garden-grid-content'); const unlockedList = $('#cc-garden-list-unlocked').empty(); const lockedList = $('#cc-garden-list-locked').empty(); const currentLayoutList = $('#cc-garden-current-layout').empty(); const emptyHint = $('#cc-garden-empty-hint'); let plantStats = {}; if (typeof Game !== 'undefined' && Game.Objects['Farm'].minigame) { const M = Game.Objects['Farm'].minigame; const totalPlants = 34; // Count is 34 let unlockedCount = 0; for (let y = 0; y < 6; y++) { for (let x = 0; x < 6; x++) { const realTile = M.plot[y]?.[x] || [0, 0]; const gameId = realTile[0]; // Level 1: Internal Data Normalization // Game ID 0 -> Script ID -1 // Game ID X -> Script ID X-1 const normalizedId = (gameId === 0) ? -1 : gameId - 1; const savedId = plot[y][x]; // Expecting -1 or 0-33 let bg = '#111'; let stateClass = ''; let text = ''; let isUnlocked = M.isTileUnlocked(x, y); // Level 2: UI Display Logic (Grid) if (isUnlocked) { if (normalizedId > -1) { // Plant present if (normalizedId === savedId) { bg = '#4caf50'; // Green (Correct) stateClass = 'cc-dashboard-correct'; text = normalizedId; } else { bg = '#9c27b0'; // Purple (Anomaly) stateClass = 'cc-dashboard-anomaly'; text = normalizedId; } } else { // Empty tile (normalizedId === -1) if (savedId > -1) { bg = '#1565c0'; // Blue (Missing) stateClass = 'cc-dashboard-missing'; text = savedId; // Show what SHOULD be there } else { // Correctly empty text = ''; } } } else { bg = '#000'; text = ''; } // Statistics for saved layout if (savedId > -1) { const plant = M.plantsById[savedId]; if (plant) { if (!plantStats[savedId]) plantStats[savedId] = { name: plant.name, count: 0 }; plantStats[savedId].count++; } } gridContent.append(`
${text}
`); } } // Level 2: UI Display Logic (Lists) -> Ensure 0-based IDs displayed for (let i = 0; i < totalPlants; i++) { const plant = M.plantsById[i]; if (plant && plant.unlocked) { unlockedCount++; unlockedList.append(`
  • [${plant.id}] ${plant.name}
  • `); } } if (unlockedCount === 0) unlockedList.append('
  • (無)
  • '); let lockedCount = 0; for (let i = 0; i < totalPlants; i++) { const plant = M.plantsById[i]; if (plant && !plant.unlocked) { lockedCount++; lockedList.append(`
  • [${plant.id}] ${plant.name}
  • `); } } if (lockedCount === 0) lockedList.append('
  • ✓ 全解鎖!
  • '); const sortedIds = Object.keys(plantStats).map(Number).sort((a, b) => a - b); if (sortedIds.length > 0) { emptyHint.hide(); currentLayoutList.show(); sortedIds.forEach(id => { const data = plantStats[id]; currentLayoutList.append(`
  • [${id}] ${data.name}
    x${data.count}
  • `); }); } else { currentLayoutList.hide(); emptyHint.show(); } // Level 3: Statistical Logic const progressColor = unlockedCount === totalPlants ? '#4caf50' : '#ffd700'; $('#cc-garden-progress').text(`${unlockedCount}/${totalPlants}`).css('color', progressColor); } else { gridContent.html('
    花園未加載
    '); emptyHint.show(); currentLayoutList.hide(); } } }; // ═══════════════════════════════════════════════════════════════ // Rich Tooltip 模組 (Fixed for v8.6.3 Null Pointer Exception) // ═══════════════════════════════════════════════════════════════ UI.Tooltip = { Elements: { Container: null }, create: function() { if (this.Elements.Container) return; this.Elements.Container = $(`
    `); $('body').append(this.Elements.Container); }, show: function(element, plant, normalizedId, savedId) { if (!this.Elements.Container) this.create(); const M = Game.Objects['Farm'].minigame; // v8.6.3 Fix: Strict check to ensure we have a valid plant object before accessing properties or getCost // If the passed 'plant' is null, try to fallback to the savedId's plant for display let displayPlant = plant; if (!displayPlant && savedId > -1) { displayPlant = M.plantsById[savedId]; } if (!displayPlant) { this.hide(); return; } const plantName = UI.cleanName(displayPlant.name); // Safe execution for getCost let price = 0; try { price = M.getCost(displayPlant); } catch(e) { price = 0; // Fallback if getCost fails } const affordable = Game.cookies >= price; const priceText = typeof Beautify !== 'undefined' ? Beautify(price) : Math.round(price).toLocaleString(); let statusText = ''; let statusClass = ''; // Logic uses 0-based IDs and -1 for empty if (normalizedId === -1 && savedId > -1) { statusText = '🔵 缺種子'; statusClass = 'status-missing'; } else if (normalizedId === savedId) { statusText = '🟢 正確'; statusClass = 'status-correct'; } else if (normalizedId > -1) { // If there is a plant, but it doesn't match saved, or saved is -1 const realPlant = M.plantsById[normalizedId]; if (realPlant) { statusText = `🟣 變異: ${UI.cleanName(realPlant.name)}`; statusClass = 'status-anomaly'; } else { statusText = '🟣 異常'; statusClass = 'status-anomaly'; } } else if (savedId === -1) { statusText = '⚫ 未設定'; statusClass = 'status-locked'; } this.Elements.Container.html(`
    ${plantName}
    價格: ${priceText} 🍪
    ${statusText}
    `); const rect = element.getBoundingClientRect(); const tooltipWidth = this.Elements.Container.outerWidth(); const tooltipHeight = this.Elements.Container.outerHeight(); let left = rect.right + 10; let top = rect.top; if (left + tooltipWidth > window.innerWidth) { left = rect.left - tooltipWidth - 10; } if (top + tooltipHeight > window.innerHeight) { top = window.innerHeight - tooltipHeight - 10; } if (top < 10) { top = 10; } this.Elements.Container.css({ left: left + 'px', top: top + 'px', display: 'block' }); }, hide: function() { if (this.Elements.Container) { this.Elements.Container.hide(); } } }; // ═══════════════════════════════════════════════════════════════ // 花園保護模組 // ═══════════════════════════════════════════════════════════════ UI.GardenProtection = { Elements: { Container: null, EmbeddedControls: null }, SavedStates: { Buy: null, Garden: null, Research: null, Stock: null }, _cachedGardenPanel: null, create: function() { if (this.Elements.Container) return; const Farm = Game.Objects['Farm']; if (!Farm || !Farm.minigameLoaded) return; const checkGardenPanel = () => { const gardenPlot = document.getElementById('gardenPlot'); if (!gardenPlot) { setTimeout(checkGardenPanel, 500); return; } this.createProtectionUI(gardenPlot); this.createEmbeddedControls(gardenPlot); }; checkGardenPanel(); }, createProtectionUI: function(gardenPanel) { this.Elements.Container = $(` `); $(gardenPanel).append(this.Elements.Container); this.Elements.Container.find('#garden-prot-minimize').click(() => { this.minimize(); }); $('#spending-lock-label').hover( function() { $(this).css('background', 'rgba(255, 255, 255, 0.2)'); }, function() { $(this).css('background', 'rgba(255, 255, 255, 0.1)'); } ); $('#btn-save-garden-layout').hover( function() { $(this).css('background', '#1976d2'); }, function() { $(this).css('background', '#2196f3'); } ); $('#btn-show-grid').hover( function() { $(this).css('background', '#795548'); }, function() { $(this).css('background', '#8d6e63'); } ); this.bindEvents(); UI.makeDraggable(this.Elements.Container, 'gardenProtectionX', 'gardenProtectionY'); }, createEmbeddedControls: function(gardenPlot) { if (this.Elements.EmbeddedControls) return; this.Elements.EmbeddedControls = $(`
    `); $(gardenPlot).append(this.Elements.EmbeddedControls); this.bindEmbeddedEvents(); this.updateEmbeddedState(); this.updateEmbeddedVisibility(); }, bindEmbeddedEvents: function() { const self = this; $('#btn-embed-restore').click(() => { this.restore(); }); $('#btn-embed-toggle-lock').click(() => { $('#chk-spending-lock').prop('checked', !Config.Flags.SpendingLocked).trigger('change'); }); $('#btn-embed-save').click(() => { this.saveCurrentLayout(); }); $('#btn-embed-show').click(() => { UI.GardenGrid.toggle(); }); }, updateEmbeddedState: function() { const btn = $('#btn-embed-toggle-lock'); if (btn.length === 0) return; if (Config.Flags.SpendingLocked) { btn.html('鎖').css({ 'background': '#d32f2f', 'border-color': '#ffcdd2' }).attr('title', '目前已停止支出,點擊以恢復'); } else { btn.html('開').css({ 'background': '#388e3c', 'border-color': '#c8e6c9' }).attr('title', '目前允許支出,點擊以鎖定'); } }, updateEmbeddedVisibility: function() { if (!this.Elements.EmbeddedControls) return; if (Config.Memory.GardenProtectionMinimized) { this.Elements.EmbeddedControls.show(); } else { this.Elements.EmbeddedControls.hide(); } }, bindEvents: function() { $('#chk-spending-lock').change(function() { UI.GardenProtection.toggle(this.checked); UI.GardenProtection.updateEmbeddedState(); }); $('#btn-save-garden-layout').click(function() { UI.GardenProtection.saveCurrentLayout(); }); $('#btn-show-grid').click(function() { UI.GardenGrid.toggle(); }); }, minimize: function() { if (this.Elements.Container) { this.Elements.Container.hide(); } if (this.Elements.EmbeddedControls) { this.Elements.EmbeddedControls.show(); } Config.Memory.GardenProtectionMinimized = true; GM_setValue('gardenProtectionMinimized', true); Logger.log('花園保護', '面板已最小化(使用右側嵌入式控制台)'); }, restore: function() { if (this.Elements.EmbeddedControls) { this.Elements.EmbeddedControls.hide(); } if (this.Elements.Container && Config.Flags.ShowGardenProtection) { this.Elements.Container.show(); } Config.Memory.GardenProtectionMinimized = false; GM_setValue('gardenProtectionMinimized', false); Logger.log('花園保護', '面板已恢復'); }, toggle: function(enabled, uiOnly = false) { if (enabled) { if (!uiOnly) { this.SavedStates.Buy = Config.Flags.Buy; this.SavedStates.Garden = Config.Flags.Garden; this.SavedStates.Research = Config.Flags.Research; this.SavedStates.Stock = Config.Flags.Stock; Config.Memory.SavedSpendingStates = { ...this.SavedStates }; GM_setValue('savedSpendingStates', Config.Memory.SavedSpendingStates); GM_setValue('spendingLocked', true); } Config.Flags.Garden = false; Config.Flags.Research = false; Config.Flags.Stock = false; const buyChk = $('#chk-auto-buy'); if (this.SavedStates.Buy) { buyChk.prop('checked', true).prop('disabled', true).css('opacity', '0.5').parent().attr('title', '資金保護中:僅允許購買誓約'); } else { buyChk.prop('checked', false).prop('disabled', true).css('opacity', '0.5'); } $('#chk-auto-garden').prop('checked', false).prop('disabled', true).css('opacity', '0.5'); $('#chk-research').prop('checked', false).prop('disabled', true).css('opacity', '0.5'); $('#chk-stock').prop('checked', false).prop('disabled', true).css('opacity', '0.5'); this.showLockWarning(); if (!uiOnly) Logger.log('花園保護', '已啟用支出鎖定 (允許誓約)'); } else { Config.Flags.Buy = this.SavedStates.Buy !== null ? this.SavedStates.Buy : true; Config.Flags.Garden = this.SavedStates.Garden !== null ? this.SavedStates.Garden : true; Config.Flags.Research = this.SavedStates.Research !== null ? this.SavedStates.Research : true; Config.Flags.Stock = this.SavedStates.Stock !== null ? this.SavedStates.Stock : true; $('#chk-auto-buy').prop('checked', Config.Flags.Buy).prop('disabled', false).css('opacity', '1').parent().removeAttr('title'); $('#chk-auto-garden').prop('checked', Config.Flags.Garden).prop('disabled', false).css('opacity', '1'); $('#chk-research').prop('checked', Config.Flags.Research).prop('disabled', false).css('opacity', '1'); $('#chk-stock').prop('checked', Config.Flags.Stock).prop('disabled', false).css('opacity', '1'); this.hideLockWarning(); if (!uiOnly) { GM_setValue('spendingLocked', false); this.SavedStates = { Buy: null, Garden: null, Research: null, Stock: null }; Logger.log('花園保護', '已解除支出鎖定'); } } Config.Flags.SpendingLocked = enabled; }, showLockWarning: function() { const panel = $('#cookie-control-panel'); if (panel.length && !$('#spending-lock-warning').length) { const warning = $(`
    🔒 支出已鎖定 | 花園保護模式啟用中
    `); panel.prepend(warning); } }, hideLockWarning: function() { $('#spending-lock-warning').remove(); }, updateVisibility: function() { if (!this.Elements.Container || !this.Elements.EmbeddedControls) return; const gardenPanel = $('#gardenPanel'); const isGardenOpen = gardenPanel.length > 0 && gardenPanel.is(':visible'); if (isGardenOpen && Config.Flags.ShowGardenProtection) { const now = Date.now(); const warmupRemaining = Runtime.Timers.GardenWarmup - now; if (warmupRemaining > 0) { const container = this.Elements.Container; if (container && container.length) { container.addClass('cc-warmup-shield'); const title = container.find('span').first(); if (title.length) { const originalText = title.text(); const remainingSeconds = Math.ceil(warmupRemaining / 1000); title.text(`🛡️ 暖機保護 (${remainingSeconds}s)`); if (this._warmupTitleTimer) clearTimeout(this._warmupTitleTimer); this._warmupTitleTimer = setTimeout(() => { title.text(originalText); container.removeClass('cc-warmup-shield'); }, warmupRemaining); } } } else { this.Elements.Container.removeClass('cc-warmup-shield'); if (this._warmupTitleTimer) { clearTimeout(this._warmupTitleTimer); this._warmupTitleTimer = null; } } if (Config.Memory.GardenProtectionMinimized) { this.Elements.Container.hide(); this.Elements.EmbeddedControls.show(); } else { this.Elements.Container.fadeIn(200); this.Elements.EmbeddedControls.hide(); } } else { this.Elements.Container.fadeOut(200); this.Elements.EmbeddedControls.hide(); this.Elements.Container.removeClass('cc-warmup-shield'); if (this._warmupTitleTimer) { clearTimeout(this._warmupTitleTimer); this._warmupTitleTimer = null; } } }, saveCurrentLayout: function() { Logic.Garden.saveLayout(); } }; // ═══════════════════════════════════════════════════════════════ // 2. 核心邏輯模組 (Business Logic) // ═══════════════════════════════════════════════════════════════ const Logic = { Click: { lastRun: 0, isMagicSufficient: function(current, max, threshold = 0.95, tolerance = 0.001) { if (max === 0) return false; const ratio = current / max; return ratio >= (threshold - tolerance); }, update: function(now) { if (!Config.Flags.GlobalMasterSwitch) return; 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; } if (Config.Flags.Golden) { document.querySelectorAll('#shimmers > div.shimmer').forEach(c => c.click()); } this.handleSpells(); }, handleSpells: function() { if ((!Config.Flags.Spell && !Config.Flags.SE) || !Game.Objects['Wizard tower'].minigame) return; const M = Game.Objects['Wizard tower'].minigame; const now = Date.now(); 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']); Logger.log('AutoSpell', '觸發連擊:命運之手'); } } if (Config.Flags.SE && now >= Runtime.Timers.NextSpontaneousEdifice) { const spell = M.spells['spontaneous edifice']; const spellCost = M.getSpellCost(spell); if (M.magic >= spellCost && this.isMagicSufficient(M.magic, M.magicM, 0.95) && Object.keys(Game.buffs).length === 0 && document.querySelectorAll('.shimmer').length === 0) { const magicBefore = M.magic; const castResult = M.castSpell(spell); if (castResult && M.magic < magicBefore) { Logger.log('AutoSpell', '閒置期:免費召喚了一座建築 (Spontaneous Edifice)'); Runtime.Stats.SEFailCount = 0; Runtime.Timers.NextSpontaneousEdifice = now + Config.Settings.SpellCooldownSuccess; } else { Runtime.Stats.SEFailCount++; if (Runtime.Stats.SEFailCount >= Config.Settings.SEFailThreshold) { const fthof = M.spells['hand of fate']; const fthofCost = M.getSpellCost(fthof); if (M.magic >= fthofCost) { const fthofBefore = M.magic; const fthofResult = M.castSpell(fthof); if (fthofResult && M.magic < fthofBefore) { Logger.log('AutoSpell', `SE 連續失敗 ${Config.Settings.SEFailThreshold} 次,已轉為施放 FtHoF`); Runtime.Stats.SEFailCount = 0; Runtime.Timers.NextSpontaneousEdifice = now + Config.Settings.SEFailResetCooldown; } else { Logger.warn('AutoSpell', 'FtHoF 施放失敗,SE 冷卻 5 分鐘'); Runtime.Stats.SEFailCount = 0; Runtime.Timers.NextSpontaneousEdifice = now + Config.Settings.SEFailResetCooldown; } } else { Logger.warn('AutoSpell', '魔力不足以施放 FtHoF,SE 冷卻 5 分鐘'); Runtime.Stats.SEFailCount = 0; Runtime.Timers.NextSpontaneousEdifice = now + Config.Settings.SEFailResetCooldown; } } else { Logger.warn('AutoSpell', `SE 施法失敗 (${Runtime.Stats.SEFailCount}/${Config.Settings.SEFailThreshold}),冷卻 60 秒`); Runtime.Timers.NextSpontaneousEdifice = now + Config.Settings.SpellCooldownFail; } } } } }, 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))) { Logger.log('Safe', `Auto-confirming prompt: ${txt}`); yesButton.click(); } } } }, Dragon: { State: Runtime.DragonState, Auras: { RadiantAppetite: 15, Dragonflight: 18, BreathOfMilk: 5 }, update: function(now) { if (!Config.Flags.GlobalMasterSwitch || !Config.Flags.DragonAura) return; if (typeof Game.dragonLevel === 'undefined' || Game.dragonLevel < 25) return; if (now - this.State.lastSwitchTime < 5000) return; let currentMult = 1; let hasClickBuff = false; for (let i in Game.buffs) { const b = Game.buffs[i]; if (b.multCpS > 0) currentMult *= b.multCpS; if (b.multClick > 0) currentMult *= b.multClick; if (b.name === 'Click frenzy' || b.name === 'Dragonflight') { hasClickBuff = true; } } const currentAura2 = Game.dragonAuras[1]; if (hasClickBuff) { if (this.State.currentPhase !== 'BURST') { const multDisplay = currentMult > 1000 ? (currentMult/1000).toFixed(1)+'k' : Math.round(currentMult); Logger.log('Dragon', `🔥 爆發開始 (倍率 x${multDisplay})。切換光環:飛龍 -> 牛奶`); this.State.currentPhase = 'BURST'; if (currentAura2 !== this.Auras.BreathOfMilk) this._setAura(1, this.Auras.BreathOfMilk); this.State.lastSwitchTime = now; } else { if (currentAura2 !== this.Auras.BreathOfMilk) { this._setAura(1, this.Auras.BreathOfMilk); this.State.lastSwitchTime = now; } } } else { if (this.State.currentPhase !== 'IDLE') { Logger.log('Dragon', `🎣 爆發結束。切換光環:牛奶 -> 飛龍`); this.State.currentPhase = 'IDLE'; if (currentAura2 !== this.Auras.Dragonflight) this._setAura(1, this.Auras.Dragonflight); this.State.lastSwitchTime = now; } else { if (currentAura2 !== this.Auras.Dragonflight) { this._setAura(1, this.Auras.Dragonflight); this.State.lastSwitchTime = now; } } } }, _setAura: function(slot, id) { try { if (typeof Game.setDragonAura === 'function') { Game.setDragonAura(slot, id); Game.recalculateGains = 1; } } catch(e) { Logger.error('Dragon', '光環切換失敗', e); } } }, GodzamokCombo: { update: function(now) { if (!Config.Flags.GlobalMasterSwitch || !Config.Flags.GodzamokCombo) return; if (now < Runtime.Timers.NextGodzamokCombo) return; const Temple = Game.Objects['Temple']; if (!Temple || !Temple.minigameLoaded) return; const M = Temple.minigame; if (M.slot[0] !== 2) return; let totalMult = 1; for (let i in Game.buffs) { totalMult *= (Game.buffs[i].multCpS * Game.buffs[i].multClick); } if (totalMult < Config.Settings.GodzamokMinMult) return; const targetName = Config.Settings.GodzamokTargetBuilding; const building = Game.Objects[targetName]; if (!building || building.amount < Config.Settings.GodzamokSellAmount) return; this.trigger(targetName, building, now); }, trigger: function(targetName, building, now) { const ALLOWED_BUILDINGS = ['Farm', 'Mine', 'Factory']; if (!ALLOWED_BUILDINGS.includes(targetName)) { Logger.error('Godzamok', `禁止賣出 ${targetName}!已攔截操作`); return false; } const costToBuyBack = building.price * Config.Settings.GodzamokSellAmount * 1.5; if (Game.cookies < costToBuyBack) { Logger.warn('Godzamok', `觸發失敗:資金不足以買回 (需 ${costToBuyBack})`); Runtime.Timers.NextGodzamokCombo = now + 5000; return; } Logger.log('Godzamok', `觸發連擊!倍率滿足條件`); if (!Config.Flags.SpendingLocked) { $('#chk-spending-lock').prop('checked', true).trigger('change'); } building.sell(Config.Settings.GodzamokSellAmount); Runtime.GodzamokState.soldAmount = Config.Settings.GodzamokSellAmount; Runtime.GodzamokState.isActive = true; setTimeout(() => { this.buyBack(targetName); }, Config.Settings.GodzamokBuyBackTime); Runtime.Timers.NextGodzamokCombo = now + Config.Settings.GodzamokCooldown; }, buyBack: function(targetName) { const building = Game.Objects[targetName]; const targetAmount = building.amount + Runtime.GodzamokState.soldAmount; Logger.log('Godzamok', `開始買回 ${targetName}...`); let bought = 0; let loops = 0; while (building.amount < targetAmount && building.price <= Game.cookies && loops < 1000) { building.buy(1); bought++; loops++; } if (bought >= Runtime.GodzamokState.soldAmount) { Logger.success('Godzamok', `已買回 ${bought} 座建築`); } else { Logger.warn('Godzamok', `資金不足/迴圈限制,僅買回 ${bought}/${Runtime.GodzamokState.soldAmount}`); } Runtime.GodzamokState.isActive = false; Runtime.GodzamokState.soldAmount = 0; if (Config.Flags.SpendingLocked) { $('#chk-spending-lock').prop('checked', false).trigger('change'); } } }, SugarLump: { update: function(now) { const statusEl = $('#lump-status'); if (!Config.Flags.Golden) { if (statusEl.length) statusEl.text('🍬 糖塊監控:已停用').css('color', '#999').css('border-left-color', '#999'); return; } if (typeof Game === 'undefined' || !Game.canLumps()) { if (statusEl.length) statusEl.text('🍬 糖塊監控:未解鎖').css('color', '#999').css('border-left-color', '#999'); return; } const age = Date.now() - Game.lumpT; const type = Game.lumpCurrentType; const ripeAge = Game.lumpRipeAge; let statusText = ''; let statusColor = '#666'; let borderColor = '#ccc'; let action = ''; switch (type) { case 3: statusText = '⛔ [肉色糖塊] 啟動保護:等待自然掉落'; statusColor = '#d32f2f'; borderColor = '#d32f2f'; action = 'SKIP'; break; case 2: case 4: if (age >= ripeAge) action = 'HARVEST_NOW'; else { statusText = `💎 [稀有糖塊] 等待成熟 (${UI.formatMs(ripeAge - age)})`; statusColor = '#e65100'; borderColor = '#ffd700'; } break; case 1: if (age >= ripeAge) { if ((Game.lumps / Config.Settings.SugarLumpGoal) > 0.9) action = 'HARVEST_NOW'; else action = 'HARVEST_NOW'; } else { statusText = `🌿 [雙倍糖塊] 等待成熟 (${UI.formatMs(ripeAge - age)})`; statusColor = '#2e7d32'; borderColor = '#4caf50'; } break; default: if (age >= ripeAge) action = 'HARVEST_NOW'; else { statusText = `🍬 [普通糖塊] 等待成熟 (${UI.formatMs(ripeAge - age)})`; statusColor = '#555'; borderColor = '#ccc'; } break; } if (action === 'HARVEST_NOW') { if (Config.Flags.GlobalMasterSwitch) { if (Game.lumpCurrentType !== 3) { Game.clickLump(); Logger.log('SmartLump', `自動收割糖塊 (Type: ${type})`); statusText = '⚡ 正在收割...'; statusColor = '#4caf50'; borderColor = '#4caf50'; } else { Logger.warn('SmartLump', '攔截危險操作:試圖點擊肉色糖塊!'); } } } if (statusEl.length) { statusEl.text(statusText).css({ 'color': statusColor, 'border-left-color': borderColor }); } } }, Buy: { update: function(now) { if (!Config.Flags.GlobalMasterSwitch) return; if (!Config.Flags.Buy || now < Runtime.Timers.NextBuy) return; if (typeof Game === 'undefined') return; const pledge = Game.Upgrades['Elder Pledge']; if (pledge && pledge.unlocked && pledge.canBuy()) { if (typeof Game.pledgeT === 'undefined' || Game.pledgeT <= 0) { Logger.log('自動購買', '誓約過期,強制優先購買!'); pledge.buy(); Runtime.Timers.NextBuy = now + Config.Settings.BuyIntervalMs; return; } } if (Config.Flags.SpendingLocked) return; if (Config.Flags.Research) { const research = document.querySelectorAll('#techUpgrades .crate.upgrade.enabled'); if (research.length > 0) { const item = research[0]; let resName = "未知科技"; const dataId = item.getAttribute('data-id'); if (dataId && Game.UpgradesById[dataId]) { resName = Game.UpgradesById[dataId].dname || Game.UpgradesById[dataId].name; } else { const onMouseOver = item.getAttribute('onmouseover'); if (onMouseOver) { const match = /
    (.+?)<\/div>/.exec(onMouseOver); if (match) resName = match[1]; } } Logger.log('自動購買', `研發科技:${UI.cleanName(resName)}`); item.click(); Runtime.Timers.NextBuy = now + Config.Settings.BuyIntervalMs; return; } } let affordable = Game.UpgradesInStore.filter(u => u.canBuy()); affordable = affordable.filter(u => { if (u.id === 84) return false; if (u.id === 85) return false; if (u.id === 74) return false; if (u.pool === 'toggle') { const seasonSwitchIds = [182, 183, 184, 185, 209]; if (!seasonSwitchIds.includes(u.id)) return false; } if (Config.Flags.Season) { const seasonSwitchIds = [182, 183, 184, 185, 209]; if (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]; let upName = target.dname || target.name; Logger.log('自動購買-升級', UI.cleanName(upName)); target.buy(); Runtime.Stats.BuyUpgradeCount++; Runtime.Timers.NextBuy = now + Config.Settings.BuyIntervalMs; return; } if (Config.Flags.Garden) { const Farm = Game.Objects['Farm']; if (Farm.minigameLoaded && Farm.minigame) { const M = Farm.minigame; let needsSeeds = false; outerLoop: for (let y = 0; y < 6; y++) { for (let x = 0; x < 6; x++) { if (M.isTileUnlocked(x, y)) { const savedId = Config.Memory.SavedGardenPlot[y][x]; const currentTile = M.plot[y][x]; if (savedId > -1 && currentTile[0] === 0) { const seed = M.plantsById[savedId]; // savedId is now 0-based if (seed && seed.unlocked) { needsSeeds = true; break outerLoop; } } } } } if (needsSeeds) { if (Math.random() < 0.1) Logger.log('Resource', '資金保護中:優先保留給花園種子'); return; } } } 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]; let buildName = targetB.displayName || targetB.name; const domElement = document.getElementById('productName' + targetB.id); if (domElement) buildName = domElement.innerText; Logger.log('自動購買-建築', UI.cleanName(buildName)); targetB.buy(1); Runtime.Stats.BuyBuildingCount++; Runtime.Timers.NextBuy = now + Config.Settings.BuyIntervalMs; } } }, Wrinkler: { hasLoggedShiny: false, update: function(now) { if (!Config.Flags.GlobalMasterSwitch) return; if (!Config.Flags.AutoWrinkler) return; if (typeof Game === 'undefined' || !Game.wrinklers) return; let currentShinyPresent = false; for (let i in Game.wrinklers) { let w = Game.wrinklers[i]; if (w.phase > 0 && w.close === 1) { if (w.type === 1) { currentShinyPresent = true; if (!this.hasLoggedShiny) { Logger.success('Wrinkler', '發現閃光皺紋蟲!已啟動保護機制!'); this.hasLoggedShiny = true; } } else { w.hp = 0; } } } if (!currentShinyPresent) this.hasLoggedShiny = false; } }, Garden: { update: function(now) { if (!Config.Flags.GlobalMasterSwitch) return; 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; const isWarmup = Date.now() < Runtime.Timers.GardenWarmup; if (isWarmup) { if (Math.random() < 0.05) Logger.warn('花園保護', '暖機緩衝中:跳過鏟除操作'); Runtime.Timers.NextGarden = now + 2500; return; } let isCpsBuffActive = false; let shouldSkipPlanting = false; if (Config.Flags.GardenAvoidBuff) { for (let i in Game.buffs) { if (Game.buffs[i].multCpS > 1) { isCpsBuffActive = true; break; } } if (isCpsBuffActive) { Config.Memory.LastBuffEndTime = 0; } else { const currentTime = Date.now(); const buffEndTime = Config.Memory.LastBuffEndTime || 0; const bufferTime = 5000; if (currentTime - buffEndTime < bufferTime) { shouldSkipPlanting = true; } } } if (!isCpsBuffActive && Config.Memory.LastBuffEndTime === 0) { Config.Memory.LastBuffEndTime = Date.now(); GM_setValue('lastBuffEndTime', Config.Memory.LastBuffEndTime); } 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]; // Level 1: Internal Data Normalization const normalizedId = (tileId === 0) ? -1 : tileId - 1; if (normalizedId > -1) { const plant = M.plantsById[normalizedId]; // Compare using 0-based indices const isAnomaly = (savedId !== -1 && normalizedId !== 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); Logger.log('花園', `鏟除雜物/已知變異 (紅框): ${plantName}`); } else { if (tileAge >= plant.mature) { M.harvest(x, y); Logger.success('花園', `成功收割新品種種子 (紫框): ${plantName}`); } } } else { if (plant.weed) M.harvest(x, y); } continue; } if (normalizedId === -1) { // Empty if (savedId !== -1 && savedId !== null) { const seed = M.plantsById[savedId]; // savedId is 0-based if (seed && seed.unlocked && M.canPlant(seed)) { if (Config.Flags.GardenAvoidBuff && (isCpsBuffActive || shouldSkipPlanting)) 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 gameId = M.plot[y][x][0]; const normalizedId = (gameId === 0) ? -1 : gameId - 1; if (normalizedId === -1 && savedId !== -1) tileDiv.classList.add('cc-overlay-missing'); else if (normalizedId > -1) { const plant = M.plantsById[normalizedId]; const isAnomaly = (savedId !== -1 && normalizedId !== savedId) || (savedId === -1); if (isAnomaly) { if (plant.unlocked) tileDiv.classList.add('cc-overlay-anomaly'); else tileDiv.classList.add('cc-overlay-new'); } else if (normalizedId === 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 = []; let savedCount = 0; 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]; // Level 1: Save as 0-based Internal ID const gameId = tile[0]; if (gameId === 0) { row.push(-1); } else { const plantIndex = gameId - 1; const plant = M.plantsById[plantIndex]; if (plant && plant.unlocked) { row.push(plantIndex); savedCount++; } else { row.push(-1); } } } 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'); if (typeof Game !== 'undefined' && Game.Notify) Game.Notify('花園陣型已記憶', `已記錄 ${savedCount} 種種子 (v8.6.2 ID修正)`, [10, 6], 4); Logger.success('花園保護', `花園陣型已記憶(${savedCount} 種種子)`); setTimeout(() => btn.text(originalText).css('background', '#2196f3'), 1500); } }, Stock: { checkSeedPriority: function() { if (!Config.Flags.Garden) return false; const Farm = Game.Objects['Farm']; if (!Farm.minigameLoaded || !Farm.minigame) return false; const M = Farm.minigame; for (let y = 0; y < 6; y++) { for (let x = 0; x < 6; x++) { if (M.isTileUnlocked(x, y)) { const savedId = Config.Memory.SavedGardenPlot[y][x]; const currentTile = M.plot[y][x]; // Check empty logic: normalizedId is -1 const normalizedId = (currentTile[0] === 0) ? -1 : currentTile[0] - 1; if (savedId > -1 && normalizedId === -1) { const seed = M.plantsById[savedId]; // savedId is 0-based if (seed && seed.unlocked) return true; } } } } return false; }, update: function(now) { if (!Config.Flags.GlobalMasterSwitch) return; if (!Config.Flags.Stock || now < Runtime.Timers.NextStock) return; const Bank = Game.Objects['Bank']; if (!Bank || !Bank.minigameLoaded || !Bank.minigame) return; if (this.checkSeedPriority()) { if (Math.random() < 0.05) Logger.log('Stock', '暫停交易:優先保留資金給花園種子'); Runtime.Timers.NextStock = now + 5000; 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); Logger.log('股市', `獲利賣出 ${goodName} @ $${price.toFixed(2)} (RV: ${rv.toFixed(2)})`); } } } Runtime.Timers.NextStock = now + 3000; } }, Season: { update: function(now) { if (!Config.Flags.GlobalMasterSwitch) return; 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()) { Logger.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(); Logger.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) { Logger.log('Season', `階段完成: ${currentStage.name}`); Runtime.SeasonState.CurrentStage++; } Runtime.Timers.NextSeasonCheck = now + 5000; } }, Santa: { update: function(now) { if (!Config.Flags.GlobalMasterSwitch) return; 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 = { heartbeatTimer: null, waitForGame: function(callback, maxRetries = 100) { if (typeof Game !== 'undefined' && Game.ready && document.readyState === 'complete') { callback(); } else if (maxRetries > 0) { setTimeout(() => { this.waitForGame(callback, maxRetries - 1); }, 100); } else { Logger.error('Core', '遊戲初始化超時(10秒),部分功能可能無法使用'); } }, safeExecute: function(fn, moduleName) { try { fn(); } catch(e) { Logger.error('Core', `${moduleName} 異常`, e); if (!Runtime.ModuleFailCount) Runtime.ModuleFailCount = {}; Runtime.ModuleFailCount[moduleName] = (Runtime.ModuleFailCount[moduleName] || 0) + 1; if (Runtime.ModuleFailCount[moduleName] >= 10) { Logger.critical('Core', `${moduleName} 連續失敗 10 次,已自動停用`); switch(moduleName) { case 'Buy': Config.Flags.Buy = false; GM_setValue('isBuyEnabled', false); $('#chk-auto-buy').prop('checked', false); break; case 'Garden': Config.Flags.Garden = false; GM_setValue('isGardenEnabled', false); $('#chk-auto-garden').prop('checked', false); break; case 'Stock': Config.Flags.Stock = false; GM_setValue('isStockEnabled', false); $('#chk-stock').prop('checked', false); break; case 'Dragon': Config.Flags.DragonAura = false; GM_setValue('isDragonAuraEnabled', false); $('#chk-dragon').prop('checked', false); break; case 'GodzamokCombo': Config.Flags.GodzamokCombo = false; GM_setValue('isGodzamokComboEnabled', false); $('#chk-godzamok').prop('checked', false); break; case 'Season': Config.Flags.Season = false; GM_setValue('isSeasonEnabled', false); $('#chk-season').prop('checked', false); break; case 'Santa': Config.Flags.Santa = false; GM_setValue('isSantaEnabled', false); $('#chk-santa').prop('checked', false); break; case 'Wrinkler': Config.Flags.AutoWrinkler = false; GM_setValue('isAutoWrinklerEnabled', false); $('#chk-wrinkler').prop('checked', false); break; } Runtime.ModuleFailCount[moduleName] = 0; } } }, init: function() { Logger.success('Core', 'Cookie Clicker Ultimate v8.6.3 Loading...'); const safeDelay = Math.max(10000, Config.Settings.BuyIntervalMs); Runtime.Timers.NextBuy = Date.now() + safeDelay; Logger.log('Core', `自動購買已延遲 ${UI.formatMs(safeDelay)} 啟動,請利用此時間調整設定`); Runtime.Timers.GardenWarmup = Date.now() + 10000; Logger.log('Core', '已啟動花園暖機保護 (10秒)'); const savedSpendingState = GM_getValue('spendingLocked', false); Config.Flags.SpendingLocked = savedSpendingState; if (savedSpendingState) { Logger.warn('Core', '偵測到支出鎖定狀態,已優先啟用保護'); const savedStates = GM_getValue('savedSpendingStates', null); if (savedStates) { UI.GardenProtection.SavedStates = savedStates; } } const scriptRestarted = localStorage.getItem('cookieScriptRestarted'); if (scriptRestarted) { Logger.log('Core', '腳本已自動重啟'); localStorage.removeItem('cookieScriptRestarted'); } UI.initStyles(); UI.createFloatingButton(); UI.createControlPanel(); UI.createCountdown(); UI.createBuffMonitor(); UI.ActionLog.create(); UI.GardenGrid.create(); UI.Tooltip.create(); try { if (Game.setVolume) Game.setVolume(Config.Settings.Volume); } catch(e) { Logger.warn('Core', '音量設定失敗,遊戲可能未完全載入'); } this.scheduleRestart(); this.startHeartbeat(); setTimeout(() => { UI.GardenProtection.create(); const restoreLockState = () => { if ($('#chk-auto-buy').length === 0) { Logger.warn('花園保護', '主 UI 尚未就緒,延遲恢復狀態'); setTimeout(restoreLockState, 200); return; } if (Config.Flags.SpendingLocked) { $('#chk-spending-lock').prop('checked', true); UI.GardenProtection.toggle(true, true); UI.GardenProtection.updateEmbeddedState(); } }; restoreLockState(); if (Config.Memory.GardenProtectionMinimized) { setTimeout(() => { UI.GardenProtection.minimize(); UI.GardenProtection.updateEmbeddedState(); }, 1000); } }, 2000); setTimeout(() => { Logic.Garden.clearOverlay(); UI.updateButtonState(); UI.ActionLog.toggle(true); Logger.log('Core', '初始化完成,所有模組已就緒'); }, 3000); document.addEventListener('keydown', function(e) { if (e.key === 'F8') { e.preventDefault(); UI.toggleMasterSwitch(); } }); window.CookieBot = { UI, Logic, Config, Core, Runtime }; }, startHeartbeat: function() { const self = this; const fastLoop = () => { const now = Date.now(); Logic.Click.update(now); const nextDelay = Config.Flags.Click ? Math.max(10, Config.Settings.ClickInterval) : 1000; setTimeout(fastLoop, nextDelay); }; fastLoop(); this.heartbeatTimer = setInterval(() => { const now = Date.now(); self.safeExecute(() => Logic.Buy.update(now), 'Buy'); self.safeExecute(() => Logic.Garden.update(now), 'Garden'); try { Logic.Garden.updateOverlay(); } catch(e) {} self.safeExecute(() => Logic.SugarLump.update(now), 'SugarLump'); self.safeExecute(() => Logic.Wrinkler.update(now), 'Wrinkler'); self.safeExecute(() => Logic.Stock.update(now), 'Stock'); self.safeExecute(() => Logic.Season.update(now), 'Season'); self.safeExecute(() => Logic.Santa.update(now), 'Santa'); self.safeExecute(() => Logic.Dragon.update(now), 'Dragon'); self.safeExecute(() => Logic.GodzamokCombo.update(now), 'GodzamokCombo'); try { Logic.updateTitle(); } catch(e) {} try { Logic.Click.handlePrompts(); } catch(e) {} try { UI.updateBuffDisplay(); } catch(e) { console.error(e); } try { UI.GardenProtection.updateVisibility(); } catch(e) {} if (Config.Flags.ShowGardenGrid && $('#garden-grid-panel').is(':visible')) { try { UI.GardenGrid.update(); } catch(e) {} } const gardenPanel = document.getElementById('gardenPanel'); if (gardenPanel && gardenPanel.style.display !== 'none') { const embedRight = document.getElementById('cc-embed-right'); if (Config.Memory.GardenProtectionMinimized && Config.Flags.ShowGardenProtection) { if (!embedRight || embedRight.style.display === 'none') { const gardenPlot = document.getElementById('gardenPlot'); if (gardenPlot && !embedRight) { UI.GardenProtection.createEmbeddedControls(gardenPlot); UI.GardenProtection.updateEmbeddedState(); } else if (embedRight && embedRight.style.display === 'none') { embedRight.style.display = 'flex'; } } } } 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'); // 1. 開啟新分頁 GM_openInTab(window.location.href, { active: true, insert: true, setParent: false }); // 2. 進入「殭屍模式」:停止心跳,防止雙重運作 if (this.heartbeatTimer) clearInterval(this.heartbeatTimer); // 3. 嘗試關閉 (瀏覽器可能攔截) setTimeout(() => { window.close(); // 4. 若未關閉,覆蓋畫面 (補救措施) document.body.innerHTML = `

    🔄 系統已重啟

    新分頁已開啟,請手動關閉此分頁以節省資源。

    (瀏覽器安全設定攔截了自動關閉功能)

    `; document.title = "⛔ 請關閉此分頁"; }, 100); } }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { Core.waitForGame(() => Core.init()); }); } else { Core.waitForGame(() => Core.init()); } })();