Automated clicker, auto-buy, auto-harvest, garden manager, stock market, season manager, Santa evolver, and Smart Sugar Lump harvester.
// ==UserScript==
// @name Cookie Clicker Ultimate Automation
// @name:zh-TW 餅乾點點樂全自動掛機輔助 (Cookie Clicker)
// @name:zh-CN 饼干点点乐全自动挂机辅助 (Cookie Clicker)
// @namespace http://tampermonkey.net/
// @version 8.5.4-hotfix
// @description Automated clicker, auto-buy, auto-harvest, garden manager, stock market, season manager, Santa evolver, and Smart Sugar Lump harvester.
// @description:zh-TW 全功能自動掛機腳本 v8.5.4 Hotfix:修復網格定位抖動 + 盾牌歸屬錯誤
// @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: {
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),
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', 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,
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),
// ⭐ v8.5.3 新增 UI 記憶
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
}),
// ⭐ v8.5.4 新增記憶
GardenProtectionMinimized: GM_getValue('gardenProtectionMinimized', false),
GardenShieldX: GM_getValue('gardenShieldX', 20),
GardenShieldY: GM_getValue('gardenShieldY', 100)
}
};
// 運行時計時器與緩存
const Runtime = {
Timers: {
NextBuy: 0,
NextRestart: 0,
NextGarden: 0,
NextStock: 0,
NextSeasonCheck: 0,
NextSpontaneousEdifice: 0,
NextGodzamokCombo: 0
},
Stats: {
ClickCount: 0,
BuyUpgradeCount: 0,
BuyBuildingCount: 0,
SEFailCount: 0
},
GodzamokState: {
isActive: false,
soldAmount: 0,
originalBuyState: true
},
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) {
const formatted = `[${module}] ${message}`;
console.error(`❌ ${formatted}`);
if (UI.ActionLog && UI.ActionLog.append) UI.ActionLog.append(formatted, 'error');
},
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');
}
};
// ═══════════════════════════════════════════════════════════════
// 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; }
.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 - v8.5.4 響應式修復 */
.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 V8.5.3 Drawer Styles - ✅ v8.5.4-hotfix 修正 */
.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); }
/* v8.5.4-hotfix 修正:小盾牌圖示 */
.cc-garden-shield {
position: absolute !important; /* ✅ 改為 absolute,附著在花園面板上 */
width: 32px;
height: 32px;
background: linear-gradient(135deg, #81c784 0%, #4caf50 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
z-index: 10001; /* ✅ 更高 z-index */
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
font-size: 18px;
border: 2px solid #fff;
transition: transform 0.3s, box-shadow 0.3s;
right: 45px; /* ✅ 固定位置在花園面板右上角 */
top: 4px;
}
.cc-garden-shield:hover {
transform: scale(1.1);
box-shadow: 0 4px 15px rgba(129, 199, 132, 0.6);
}
`;
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 = $(`
<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, background 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) {
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+=`<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.5.4-hotfix</span>
<div class="cc-close-btn" id="main-panel-close">✕</div>
</div>
<div id="global-status-bar" style="
padding: 10px 15px; background: #4caf50; color: white; font-weight: bold;
font-size: 14px; text-align: center; display: flex; align-items: center; justify-content: center; gap: 10px;
">
<span id="status-icon">🟢</span>
<span id="status-text">系統運行中</span>
<button id="btn-toggle-master" style="
padding: 4px 12px; background: rgba(255, 255, 255, 0.2); color: white;
border: 1px solid rgba(255, 255, 255, 0.5); border-radius: 4px; cursor: pointer; font-size: 12px;
">暫停 (F8)</button>
</div>
<div class="cc-tab-header">
<button class="cc-tab-btn active" data-target="core">核心</button>
<button class="cc-tab-btn" data-target="advanced">進階</button>
<button class="cc-tab-btn" data-target="garden">花園</button>
<button class="cc-tab-btn" data-target="settings">設定</button>
</div>
<div style="padding-bottom: 20px; overflow-y: auto; max-height: calc(85vh - 165px);">
<!-- 1. 核心頁籤 -->
<div id="tab-core" class="cc-tab-pane active">
<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>
<label><input type="checkbox" id="chk-research" ${Config.Flags.Research?'checked':''}> 🔬 自動科技研發</label>
<label><input type="checkbox" id="chk-wrinkler" ${Config.Flags.AutoWrinkler?'checked':''}> 🐛 自動戳皺紋蟲</label>
</div>
<div id="lump-status" style="margin-top: 10px; padding: 6px; background: rgba(0,0,0,0.05); border-radius: 4px; font-size: 12px; color: #666; border-left: 3px solid #ccc;">🍬 糖塊監控:初始化中...</div>
</div>
<div class="panel-section" style="margin-bottom:15px; padding:10px; background:#fff3e0; border-radius:8px;">
<div style="font-weight:bold; color:#e65100; margin-bottom:5px;">🛒 購買策略</div>
<select id="buy-strategy" style="width:100%; padding:5px;">
<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>
<!-- 2. 進階頁籤 -->
<div id="tab-advanced" class="cc-tab-pane">
<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':''}> 🍂 季節管理</label>
<label><input type="checkbox" id="chk-santa" ${Config.Flags.Santa?'checked':''}> 🎅 聖誕老人進化</label>
</div>
<div style="margin-top:10px; padding-top:10px; border-top:1px dashed #bcd; display:block;">
<label style="font-weight:bold; color:#01579b;">
<input type="checkbox" id="chk-godzamok" ${Config.Flags.GodzamokCombo?'checked':''}> 🐲 Godzamok 連擊
</label>
<div style="font-size:11px; color:#555; margin-left:20px; margin-top:4px;">
賣出 <input type="number" id="val-godzamok-amount" value="${Config.Settings.GodzamokSellAmount}" style="width:40px;"> 座
<select id="val-godzamok-target" style="width:80px; padding:2px; border-radius:4px; border:1px solid #ccc;">
<option value="Farm" ${Config.Settings.GodzamokTargetBuilding==='Farm'?'selected':''}>Farm (農場)</option>
<option value="Mine" ${Config.Settings.GodzamokTargetBuilding==='Mine'?'selected':''}>Mine (礦坑)</option>
<option value="Factory" ${Config.Settings.GodzamokTargetBuilding==='Factory'?'selected':''}>Factory (工廠)</option>
</select>
(倍率 > <input type="number" id="val-godzamok-min" value="${Config.Settings.GodzamokMinMult}" style="width:50px;">x)
</div>
</div>
</div>
</div>
<!-- 3. 花園頁籤 -->
<div id="tab-garden" class="cc-tab-pane">
<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:#1565c0;">
<input type="checkbox" id="chk-show-garden-protection" ${Config.Flags.ShowGardenProtection?'checked':''}> 🖥️ 於花園顯示保護介面
</label>
<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>
<div style="display:flex; gap:5px;">
<button id="garden-save-btn" style="flex:1; padding:6px; background:#2196f3; color:white; border:none; border-radius:4px; cursor:pointer;">💾 記憶陣型</button>
<button id="btn-show-grid" style="flex:1; padding:6px; background:#8d6e63; color:white; border:none; border-radius:4px; cursor:pointer;">🗺️ 顯示記憶</button>
</div>
</div>
</div>
<!-- 4. 設定頁籤 -->
<div id="tab-settings" class="cc-tab-pane">
<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>
<label style="display:block; font-size:13px; color:#333; font-weight: bold; margin-top: 5px;">
<input type="checkbox" id="chk-ui-log" checked> 📜 操作日誌面板
</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>
</div>
`);
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 = $(`
<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.5.4-hotfix
<div class="cc-close-btn" id="countdown-close" style="top:2px; right:2px; font-size:10px;">✕</div>
</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.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 = $(`
<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 class="cc-close-btn" id="buff-monitor-close">✕</div>
</div>
<div id="buff-list-content" style="display: flex; flex-direction: column; gap: 8px;"></div>
</div>
`);
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');
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>`;
}
if (typeof Game.computedMouseCps !== 'undefined') {
let clickVal = Game.computedMouseCps;
let formattedClick = typeof Beautify !== 'undefined' ? Beautify(clickVal) : Math.round(clickVal).toLocaleString();
summaryHtml += `<div style="margin-top:5px; padding-top:5px; border-top:1px solid rgba(255,255,255,0.1); color:#fff; font-size:15px; font-family:monospace;">👆 點擊力: <span style="color: #ffd700;">+${formattedClick}</span></div>`;
}
summaryHtml += '</div>';
buffList.append(summaryHtml);
},
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();
});
};
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');
$('#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();
});
$('#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 = $(`
<div id="action-log-panel" style="
position: fixed; left: ${Config.Memory.ActionLogX}px; top: ${Config.Memory.ActionLogY}px;
width: 400px; max-height: 400px; background: rgba(0, 0, 0, ${opacity}); color: white;
border: 2px solid #2196f3; border-radius: 8px; padding: 0; z-index: 999995;
font-family: 'Consolas', 'Monaco', monospace; font-size: ${fontSize}px;
box-shadow: 0 4px 20px rgba(33, 150, 243, 0.5); cursor: move; display: none; overflow: hidden;
">
<div style="padding: 10px; font-weight: bold; font-size: 14px; text-align: center; border-bottom: 1px solid #2196f3; display: flex; justify-content: space-between; align-items: center; background: rgba(33, 150, 243, 0.2);">
<span>📜 操作日誌</span>
<div>
<span id="log-counter" style="font-size: 11px; color: #64b5f6; margin-right: 15px;">0 條</span>
<div class="cc-close-btn" id="action-log-close">✕</div>
</div>
</div>
<div class="cc-log-controls">
<span>A</span>
<input type="range" class="cc-range-mini" id="log-font-slider" min="10" max="30" value="${fontSize}">
<span>A</span>
<span style="border-left:1px solid #555; height:12px; margin:0 5px;"></span>
<span>👁️</span>
<input type="range" class="cc-range-mini" id="log-opacity-slider" min="30" max="100" value="${opacity * 100}">
</div>
<div id="log-list" style="max-height: 250px; overflow-y: auto; overflow-x: hidden; padding: 5px;">
<div style="text-align: center; color: #999; padding: 20px;">尚無日誌</div>
</div>
<div style="padding: 6px; border-top: 1px solid #444; display: flex; gap: 5px; background: rgba(0,0,0,0.2);">
<button id="btn-clear-log" style="flex: 1; padding: 4px; background: #f44336; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 11px;">🗑️ 清空</button>
</div>
</div>
`);
$('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(`<span class="log-count" style="float:right; background:#ffd700; color:#000; padding:0 4px; border-radius:4px; font-size:10px; font-weight:bold;">x${this.lastMsgCount}</span>`);
} 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 = $(`
<div style="padding: 4px; margin-bottom: 2px; background: rgba(255, 255, 255, 0.05); border-left: 3px solid ${color}; border-radius: 4px; line-height: 1.4; word-break: break-word;">
<span class="log-time" style="color: #999;">${timestamp}</span>
<span style="color: ${color}; margin: 0 4px;">${icon}</span>
<span class="log-content" style="color: #fff;">${cleanMsg}</span>
</div>
`);
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(`<div style="text-align: center; color: #999; padding: 20px;">尚無日誌</div>`);
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);
}
};
// ═══════════════════════════════════════════════════════════════
// 花園陣型可視化 (✅ v8.5.4-hotfix 修正:抽屜定位抖動問題)
// ═══════════════════════════════════════════════════════════════
UI.GardenGrid = {
Elements: { Container: null },
create: function() {
if (this.Elements.Container) return;
// 構建 DOM:核心三欄 + 底部清單
this.Elements.Container = $(`
<div id="garden-grid-panel" style="
position: fixed; left: ${Config.Memory.GardenGridX}px; top: ${Config.Memory.GardenGridY}px;
background: rgba(0,0,0,0.95); color: white; border: 2px solid #8d6e63;
border-radius: 8px; padding: 15px; z-index: 999997; display: none;
cursor: move; font-family: Arial;
/* 寬度隨內容變化,transition 讓展開平滑 */
width: fit-content; transition: all 0.3s ease;
max-height: 85vh; overflow-y: auto;
">
<div style="font-weight:bold; font-size:15px; margin-bottom:12px; border-bottom:1px solid #8d6e63; padding-bottom:6px; display:flex; justify-content:space-between;">
<span>🗺️ 記憶陣型預覽</span>
<div class="cc-close-btn" id="garden-grid-close" style="font-size:14px;">✕</div>
</div>
<!-- 核心三欄區 -->
<div class="cc-garden-row">
<!-- 左抽屜:已解鎖 -->
<div id="drawer-left" class="cc-drawer cc-garden-side">
<div style="padding: 10px; width: 220px; box-sizing: border-box;">
<div style="text-align:center; font-weight:bold; color:#81c784; font-size:14px; border-bottom:1px solid #444; margin-bottom:5px; padding-bottom:3px;">
已解鎖圖鑑
</div>
<ul id="cc-garden-list-unlocked" style="font-size:13px;"></ul>
</div>
</div>
<!-- 中央:田 + 按鈕 (定海神針) -->
<div class="cc-center-stage">
<!-- 控制列 -->
<div style="display:flex; justify-content:center; align-items:center; gap:10px; margin-bottom:12px; width:100%;">
<button id="cc-btn-expand-left" style="
padding: 4px 10px; background: #333; color: white; border: 1px solid #555;
border-radius: 4px; cursor: pointer; font-size: 13px; font-weight:bold;
">◀ 展開</button>
<span id="cc-garden-progress" style="
font-size: 18px; font-weight: bold; color: #ffd700;
min-width: 80px; text-align: center;
">--/--</span>
<button id="cc-btn-expand-right" style="
padding: 4px 10px; background: #333; color: white; border: 1px solid #555;
border-radius: 4px; cursor: pointer; font-size: 13px; font-weight:bold;
">展開 ▶</button>
</div>
<!-- 6×6 網格 -->
<div id="garden-grid-content" style="
display: grid; grid-template-columns: repeat(6, 48px); grid-template-rows: repeat(6, 48px);
gap: 4px; justify-content: center; background: #111; padding: 10px;
border: 2px solid #8d6e63; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.5);
"></div>
</div>
<!-- 右抽屜:未解鎖 -->
<div id="drawer-right" class="cc-drawer cc-garden-side">
<div style="padding: 10px; width: 220px; box-sizing: border-box;">
<div style="text-align:center; font-weight:bold; color:#ffcc80; font-size:14px; border-bottom:1px solid #444; margin-bottom:5px; padding-bottom:3px;">
未解鎖清單
</div>
<ul id="cc-garden-list-locked" style="font-size:13px;"></ul>
</div>
</div>
</div>
<!-- 底部:當前陣型清單 (永遠顯示) -->
<div style="margin-top: 15px; padding-top: 10px; border-top: 2px solid #8d6e63; width: 100%;">
<div style="text-align:center; font-weight:bold; color:#64b5f6; font-size:14px; margin-bottom:8px;">
📋 當前陣型清單
</div>
<ul id="cc-garden-current-layout" style="
list-style: none; padding: 0; margin: 0;
display: grid; grid-template-columns: repeat(2, 1fr); gap: 6px;
font-size: 13px; color: #ccc;
"></ul>
<div id="cc-garden-empty-hint" style="text-align: center; color: #777; font-size: 13px; padding: 10px; display: none;">
尚未記憶任何陣型
</div>
</div>
</div>
`);
$('body').append(this.Elements.Container);
this.Elements.Container.find('#garden-grid-close').click(() => this.toggle());
// 綁定按鈕
$('#cc-btn-expand-left').click(() => this.toggleSide('left'));
$('#cc-btn-expand-right').click(() => this.toggleSide('right'));
// 恢復記憶的狀態
if (Config.Memory.GardenLeftExpanded) this.toggleSide('left', true);
if (Config.Memory.GardenRightExpanded) this.toggleSide('right', true);
UI.makeDraggable(this.Elements.Container, 'gardenGridX', 'gardenGridY');
},
toggle: function() {
if (!this.Elements.Container) this.create();
if (this.Elements.Container.is(':visible')) {
this.Elements.Container.fadeOut(200);
} else {
this.update();
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;
// ✅ v8.5.4-hotfix 修正:不再修改 centerStage 寬度,只移動容器位置
const drawerWidth = 220;
const currentX = parseInt(container.css('left')) || Config.Memory.GardenGridX;
const isOpen = forceOpen !== null ? forceOpen : !drawer.hasClass('open');
if (isOpen) {
// 開啟抽屜
if (side === 'left') {
// 左抽屜開啟:左移面板防止Grid右跑
const newX = Math.max(0, currentX - drawerWidth);
container.css('left', newX + 'px');
// ✅ 刪除 centerStage 寬度修改
} else {
// 右抽屜開啟:保持位置,不修改寬度
// ✅ 刪除 centerStage 寬度修改
}
drawer.addClass('open');
btn.text(side === 'left' ? '▶ 收起' : '收起 ◀');
Config.Memory[side === 'left' ? 'GardenLeftExpanded' : 'GardenRightExpanded'] = true;
GM_setValue(side === 'left' ? 'gardenLeftExpanded' : 'gardenRightExpanded', true);
// 保存新位置(左抽屜)
if (side === 'left') {
Config.Memory.GardenGridX = newX;
GM_setValue('gardenGridX', newX);
}
} else {
// 關閉抽屜
if (side === 'left') {
// 左抽屜關閉:右移面板恢復位置
const newX = currentX + drawerWidth;
container.css('left', newX + 'px');
// ✅ 刪除 centerStage 寬度修改
// 保存新位置
Config.Memory.GardenGridX = newX;
GM_setValue('gardenGridX', newX);
} else {
// 右抽屜關閉:不修改寬度
// ✅ 刪除 centerStage 寬度修改
}
drawer.removeClass('open');
btn.text(side === 'left' ? '◀ 展開' : '展開 ▶');
Config.Memory[side === 'left' ? 'GardenLeftExpanded' : 'GardenRightExpanded'] = false;
GM_setValue(side === 'left' ? 'gardenLeftExpanded' : 'gardenRightExpanded', false);
}
},
update: function() {
const plot = Config.Memory.SavedGardenPlot;
const gridContent = $('#garden-grid-content').empty();
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;
let unlockedCount = 0;
// 1. 渲染網格
plot.forEach(row => {
row.forEach(cellId => {
let bg = '#222';
let text = '';
let title = '空';
if (cellId > 0) {
const plant = M.plantsById[cellId - 1];
if (plant) {
bg = '#4caf50';
text = cellId;
title = plant.name;
// 統計
if (!plantStats[cellId]) plantStats[cellId] = { name: plant.name, count: 0 };
plantStats[cellId].count++;
}
} else if (cellId === -1) {
bg = '#111';
title = '未解鎖格子';
}
gridContent.append(`
<div style="
background:${bg}; border:1px solid #444; border-radius:4px;
display:flex; align-items:center; justify-content:center;
font-size:14px; font-weight:bold; color:white;
transition: transform 0.2s; cursor: default;
" title="${title}">
${text}
</div>
`);
});
});
// 2. 左側:已解鎖
for (let i = 1; i <= totalPlants; i++) {
const plant = M.plantsById[i];
if (plant && plant.unlocked) {
unlockedCount++;
unlockedList.append(`
<li><span style="color:#aaa; display:inline-block; width:24px;">[${plant.id}]</span> ${plant.name}</li>
`);
}
}
if (unlockedCount === 0) unlockedList.append('<li style="color:#777;">(無)</li>');
// 3. 右側:未解鎖
let lockedCount = 0;
for (let i = 1; i <= totalPlants; i++) {
const plant = M.plantsById[i];
if (plant && !plant.unlocked) {
lockedCount++;
lockedList.append(`
<li><span style="color:#aaa; display:inline-block; width:24px;">[${plant.id}]</span> ${plant.name}</li>
`);
}
}
if (lockedCount === 0) lockedList.append('<li style="color:#4caf50;">✓ 全解鎖!</li>');
// 4. 底部:當前陣型
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(`
<li style="
padding: 6px 10px; background: rgba(13, 71, 161, 0.3);
border-left: 4px solid #42a5f5; border-radius: 4px;
display: flex; justify-content: space-between; align-items: center;
">
<div><span style="color:#aaa;">[${id}]</span> ${data.name}</div>
<b style="color:#ffd700;">x${data.count}</b>
</li>
`);
});
} else {
currentLayoutList.hide();
emptyHint.show();
}
// 5. 進度文字
const progressColor = unlockedCount === totalPlants ? '#4caf50' : '#ffd700';
$('#cc-garden-progress').text(`${unlockedCount}/${totalPlants}`).css('color', progressColor);
} else {
gridContent.html('<div style="grid-column:1/-1; text-align:center; color:#999; padding:20px;">花園未加載</div>');
emptyHint.show();
currentLayoutList.hide();
}
}
};
// ═══════════════════════════════════════════════════════════════
// 花園保護模組 (✅ v8.5.4-hotfix 修正:盾牌歸屬錯誤)
// ═══════════════════════════════════════════════════════════════
UI.GardenProtection = {
Elements: {
Container: null,
Shield: 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 gardenPanel = document.getElementById('gardenPanel');
if (!gardenPanel) {
setTimeout(checkGardenPanel, 500);
return;
}
this.createProtectionUI(gardenPanel);
this.createShield(gardenPanel);
};
checkGardenPanel();
},
createProtectionUI: function(gardenPanel) {
// ✅ v8.5.4 改版:按鈕拆分為兩個
this.Elements.Container = $(`
<div id="garden-protection-ui" style="
position: absolute; left: ${Config.Memory.GardenProtectionX}px; top: ${Config.Memory.GardenProtectionY}px;
width: 260px; background: rgba(0, 0, 0, 0.9); color: white; border: 2px solid #81c784;
border-radius: 8px; padding: 12px; z-index: 10000; font-family: Arial, sans-serif;
box-shadow: 0 4px 20px rgba(129, 199, 132, 0.5); cursor: move; display: none;
">
<div style="font-weight: bold; font-size: 14px; margin-bottom: 10px; text-align: center; border-bottom: 1px solid #81c784; padding-bottom: 8px; display: flex; justify-content: space-between; align-items: center;">
<span>🛡️ 花園保護模式</span>
<div class="cc-close-btn" id="garden-prot-minimize">_</div>
</div>
<label id="spending-lock-label" style="display: flex; align-items: center; font-size: 13px; cursor: pointer; padding: 8px; background: rgba(255, 255, 255, 0.1); border-radius: 4px; transition: background 0.3s;">
<input type="checkbox" id="chk-spending-lock" style="margin-right: 8px; width: 16px; height: 16px;">
<span style="flex: 1;">🔒 立刻停止支出</span>
</label>
<!-- ✅ v8.5.4 改版:兩個按鈕並排 -->
<div style="display: flex; gap: 10px; margin-top: 10px;">
<button id="btn-save-garden-layout" style="
flex: 1; padding: 8px; background: #2196f3; color: white; border: none;
border-radius: 4px; cursor: pointer; font-size: 13px; font-weight: bold;
transition: background 0.3s;
">💾 記憶陣型</button>
<button id="btn-show-grid" style="
flex: 1; padding: 8px; background: #8d6e63; color: white; border: none;
border-radius: 4px; cursor: pointer; font-size: 13px; font-weight: bold;
transition: background 0.3s;
">🗺️ 顯示記憶</button>
</div>
<div style="margin-top: 10px; font-size: 11px; color: #ffcccc; text-align: center; line-height: 1.4;">
勾選後將鎖定:<br>購買 (誓約除外) | 花園 | 科技 | 股市
</div>
</div>
`);
$(gardenPanel).append(this.Elements.Container);
// ✅ v8.5.4 改版:最小化按鈕行為
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');
},
createShield: function(gardenPanel) {
if (this.Elements.Shield) return;
// ✅ v8.5.4-hotfix 修正:盾牌附著在花園面板上
this.Elements.Shield = $(`
<div class="cc-garden-shield">🛡️</div>
`);
$(gardenPanel).append(this.Elements.Shield);
// 點擊盾牌恢復面板
this.Elements.Shield.click(() => {
this.restore();
});
// ✅ v8.5.4-hotfix 修正:移除拖曳功能,固定在花園工具欄右上角
this.Elements.Shield.hide();
},
bindEvents: function() {
$('#chk-spending-lock').change(function() { UI.GardenProtection.toggle(this.checked); });
$('#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.Shield) {
this.Elements.Shield.show();
}
// 保存最小化狀態
Config.Memory.GardenProtectionMinimized = true;
GM_setValue('gardenProtectionMinimized', true);
Logger.log('花園保護', '面板已最小化(點擊盾牌恢復)');
},
restore: function() {
// 隱藏小盾牌
if (this.Elements.Shield) {
this.Elements.Shield.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);
}
// ✅ v8.5.2: 不關閉 Config.Flags.Buy,只關閉其他
Config.Flags.Garden = false;
Config.Flags.Research = false;
Config.Flags.Stock = false;
// UI 視覺更新
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 = $(`
<div id="spending-lock-warning" style="
background: linear-gradient(135deg, #ff4444 0%, #cc0000 100%); color: white; padding: 12px;
text-align: center; font-weight: bold; font-size: 14px; border-bottom: 2px solid rgba(255,255,255,0.3);
animation: pulse 2s infinite;
">🔒 支出已鎖定 | 花園保護模式啟用中</div>
<style>@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.7; } }</style>
`);
panel.prepend(warning);
}
},
hideLockWarning: function() { $('#spending-lock-warning').remove(); },
updateVisibility: function() {
if (!this.Elements.Container || !this.Elements.Shield) return;
// ✅ v8.5.4-hotfix 修正:使用 jQuery 檢查花園面板可見性
const gardenPanel = $('#gardenPanel');
const isGardenOpen = gardenPanel.length > 0 && gardenPanel.is(':visible');
if (isGardenOpen && Config.Flags.ShowGardenProtection) {
if (Config.Memory.GardenProtectionMinimized) {
// 最小化狀態:顯示盾牌,隱藏面板
this.Elements.Container.hide();
this.Elements.Shield.show();
} else {
// 正常狀態:顯示面板,隱藏盾牌
this.Elements.Container.fadeIn(200);
this.Elements.Shield.hide();
}
} else {
// 花園沒開,或是保護功能被禁用 -> 全部隱藏
this.Elements.Container.fadeOut(200);
this.Elements.Shield.hide();
}
},
saveCurrentLayout: function() {
if (typeof Game === 'undefined' || !Game.Objects['Farm'].minigame) {
Logger.warn('花園保護', '花園未就緒,無法記憶陣型');
if (typeof Game !== 'undefined' && Game.Notify) Game.Notify('花園未就緒', '請先解鎖花園功能', [10, 6]);
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];
const tileId = tile[0];
if (tileId === 0) {
row.push(-1);
} else {
const plant = M.plantsById[tileId - 1];
if (plant && plant.unlocked) {
row.push(tileId);
savedCount++;
} else {
row.push(-1);
}
}
} else {
row.push(-1);
}
}
newLayout.push(row);
}
Config.Memory.SavedGardenPlot = newLayout;
GM_setValue('savedGardenPlot', Config.Memory.SavedGardenPlot);
Logger.success('花園保護', `花園陣型已記憶(${savedCount} 種種子)`);
if (typeof Game !== 'undefined' && Game.Notify) Game.Notify('花園陣型已記憶', `已記錄 ${savedCount} 種種子`, [10, 6], 4);
const btn = $('#btn-save-garden-layout');
const originalText = btn.text();
const originalBg = btn.css('background-color');
btn.text('✅ 已儲存!').css('background', '#4caf50');
setTimeout(() => { btn.text(originalText).css('background', originalBg); }, 1500);
}
};
// ═══════════════════════════════════════════════════════════════
// 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();
}
}
}
},
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;
// ⭐ 1. 誓約例外 (最高優先)
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;
}
}
// ⭐ 2. 智慧鎖定攔截
if (Config.Flags.SpendingLocked) return;
// 3. 科技研發
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 class="name">(.+?)<\/div>/.exec(onMouseOver);
if (match) resName = match[1];
}
}
Logger.log('自動購買', `研發科技:${UI.cleanName(resName)}`);
item.click();
Runtime.Timers.NextBuy = now + Config.Settings.BuyIntervalMs;
return;
}
}
// 4. 升級購買
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;
}
// 5. 種子檢查
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 - 1];
if (seed && seed.unlocked) {
needsSeeds = true;
break outerLoop;
}
}
}
}
}
if (needsSeeds) {
if (Math.random() < 0.1) Logger.log('Resource', '資金保護中:優先保留給花園種子');
return;
}
}
}
// 6. 建築購買
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;
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);
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 (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: {
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];
if (savedId > -1 && currentTile[0] === 0) {
const seed = M.plantsById[savedId - 1];
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 = {
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秒),部分功能可能無法使用');
}
},
init: function() {
Logger.success('Core', 'Cookie Clicker Ultimate v8.5.4-hotfix Loading...');
// ✅ 1. 優先恢復鎖定狀態 (Fix Race Condition)
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();
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);
}
};
restoreLockState();
// ✅ v8.5.4 新增:恢復最小化狀態
if (Config.Memory.GardenProtectionMinimized) {
// 延遲執行以確保花園面板已加載
setTimeout(() => {
UI.GardenProtection.minimize();
}, 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();
}
});
// ✅ Debug 接口:讓控制台可以訪問
window.CookieBot = { UI, Logic, Config, Core };
},
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();
setInterval(() => {
const now = Date.now();
Logic.Buy.update(now);
Logic.Garden.update(now);
Logic.Garden.updateOverlay();
Logic.SugarLump.update(now);
Logic.Wrinkler.update(now);
Logic.Stock.update(now);
Logic.Season.update(now);
Logic.Santa.update(now);
Logic.GodzamokCombo.update(now);
Logic.updateTitle();
Logic.Click.handlePrompts();
UI.updateBuffDisplay();
UI.GardenProtection.updateVisibility();
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', () => { Core.waitForGame(() => Core.init()); });
} else {
Core.waitForGame(() => Core.init());
}
})();