// ==UserScript== // @name The Tree Enchanted丨被附魔的树 // @namespace http://tampermonkey.net/ // @version 2.10.1 // @description 针对 TMT 的自定义增强功能,为移动端和桌面端分别添加了超级便捷的QOL按钮! // @description:en Enhanced QOL features for TMT, adding convenient buttons for both mobile and desktop! // @description:zh-TW 針對 TMT 的自訂增強功能,為行動端和桌面端分別添加了超級便利的QOL按鈕! // @author LinLei_Baruch & Google Gemini & Assistant // @original-author Dimava & Assistant // @original-license 暂无 // @original-script https://greasyfork.org/zh-CN/scripts/425404-the-tree-enchanted // @match *://*/* // @icon https://img.cdn1.vip/i/68d4f4068bac5_1758786566.png // @grant none // ==/UserScript== if (globalThis.TREE_LAYERS) void function() { 'use strict'; const isNewTmtVersion = typeof TMT_VERSION !== 'undefined' && TMT_VERSION.newtmtNum; const translations = { en: { title: "🌳 The Enchanted Tree", setSpeed: "Set Speed", buy: "Buy (X)", reset: "Reset (Z)", combo: "Buy & Reset (C)", importSave: "Import", exportSave: "Export", setDelay: "Delay: {delay}ms", autoBuy: "Auto Buy", autoReset: "Auto Reset", autoCombo: "Auto Buy&Reset", fontToggle: "Font", bgKeepAlive: "Background", pendingFeature: "Pending...", fontOriginal: "Default", fontSystem: "System", fontCustom: "Custom", fontConfirmPrompt: "Load a custom font? This will open a file dialog.", fontImportError: "Failed to load font file. Please ensure it's a valid font format.", toggleHotkeys: "Hotkeys", simulateShift: "Simulate Shift", shiftOn: "Shift ON", on: "ON", off: "OFF", speedPrompt: "Enter game speed (e.g., 10).\nNote 1: High speeds may cause bugs.\nNote 2: Some mods have anti-cheat and this may not work.\nNote 3: It is recommended to back up your save before changing the speed.", speedInvalid: "Invalid input. Please enter a number!", exportError: "An error occurred while exporting the save!", exportNaN: "Save data is abnormal (NaN) and cannot be exported! Please refresh the page and try again.", importError: "Import failed! The file may be corrupted or in the wrong format.\nError details: {error}", importEmpty: "File is empty or could not be read.", importLzError: "Detected standard game save, but couldn't find the in-game importSave function!", importManualError: "Decoded successfully, but missing core game functions needed for manual loading.", importInvalidFormat: "The file content is not a valid save format.", importReadError: "An error occurred while reading the file!", delayPrompt: "Enter long-press/auto-action delay (100-1000ms).", delayInvalid: "Invalid input! Please enter a number between 100 and 1000." }, 'zh-CN': { title: "🌳 被附魔的树 QOL", setSpeed: "设定速度", buy: "购买 (X)", reset: "重置 (Z)", combo: "购买&重置 (C)", importSave: "导入存档", exportSave: "导出存档", setDelay: "延迟: {delay}ms", autoBuy: "自动购买", autoReset: "自动重置", autoCombo: "自动购买&重置", fontToggle: "切换字体", bgKeepAlive: "后台挂机", pendingFeature: "待开发", fontOriginal: "默认", fontSystem: "系统", fontCustom: "自定义", fontConfirmPrompt: "确定要加载自定义字体吗?这将打开文件选择对话框。", fontImportError: "字体文件加载失败,请确保文件是有效的字体格式。", toggleHotkeys: "快捷键", simulateShift: "模拟Shift", shiftOn: "Shift开启", on: "开", off: "关", speedPrompt: "请输入游戏速度 (例如: 10)丨注意1:不建议设定过快的游戏速度,否则可能会出bug丨注意2:部分模组树存在无法生效的情况,那就是有防作弊丨注意3:输入之前,建议你备份存档,否则出了什么问题,你会骂死我的qwq……", speedInvalid: "输入无效,请输入一个数字!", exportError: "导出存档时发生错误!", exportNaN: "存档数据异常 (NaN),无法导出!请刷新页面后再试。", importError: "导入失败!文件可能已损坏或格式不正确。\n错误详情: {error}", importEmpty: "文件为空或无法读取。", importLzError: "检测到标准游戏存档,但未找到游戏内的 importSave 函数!", importManualError: "解码成功,但缺少手动加载所需的游戏核心函数。", importInvalidFormat: "文件内容不是有效的存档格式。", importReadError: "读取文件时发生错误!", delayPrompt: "请输入长按/自动执行的延迟(100-1000毫秒)", delayInvalid: "输入无效!请输入一个100到1000之间的数字。" }, 'zh-TW': { title: "🌳 被附魔的樹 QOL", setSpeed: "設定速度", buy: "購買 (X)", reset: "重置 (Z)", combo: "購買並重置 (C)", importSave: "匯入存檔", exportSave: "匯出存檔", setDelay: "延遲: {delay}ms", autoBuy: "自動購買", autoReset: "自動重置", autoCombo: "自動購買&重置", fontToggle: "切換字體", bgKeepAlive: "後台掛機", pendingFeature: "待開發", fontOriginal: "預設", fontSystem: "系統", fontCustom: "自訂", fontConfirmPrompt: "確定要載入自訂字體嗎?這將打開檔案選擇對話框。", fontImportError: "字體檔案載入失敗,請確保檔案是有效的字體格式。", toggleHotkeys: "快捷鍵", simulateShift: "模擬Shift", shiftOn: "Shift開啟", on: "開", off: "關", speedPrompt: "請輸入遊戲速度(例如:10)。\n注意1:不建議設定過快的遊戲速度,否則可能導致錯誤。\n注意2:部分模組樹有防作弊機制,可能無法生效。\n注意3:建議在變更前備份您的存檔。", speedInvalid: "輸入無效,請輸入一個數字!", exportError: "匯出存檔時發生錯誤!", exportNaN: "存檔資料異常(NaN),無法匯出!請重新整理頁面後再試。", importError: "匯入失敗!檔案可能已損壞或格式不正確。\n錯誤詳情: {error}", importEmpty: "檔案為空或無法讀取。", importLzError: "偵測到標準遊戲存檔,但找不到遊戲內的 importSave 函數!", importManualError: "解碼成功,但缺少手動載入所需的遊戲核心函數。", importInvalidFormat: "檔案內容不是有效的存檔格式。", importReadError: "讀取檔案時發生錯誤!", delayPrompt: "請輸入長按/自動執行的延遲(100-1000毫秒)。", delayInvalid: "輸入無效!請輸入一個100到1000之間的數字。" } }; let currentLang = 'en'; const browserLang = navigator.language; if (browserLang.startsWith('zh-TW') || browserLang.startsWith('zh-HK')) { currentLang = 'zh-TW'; } else if (browserLang.startsWith('zh')) { currentLang = 'zh-CN'; } function getText(key, replacements = {}) { let text = (translations[currentLang] && translations[currentLang][key]) || translations.en[key] || `[${key}]`; for (const placeholder in replacements) { text = text.replace(`{${placeholder}}`, replacements[placeholder]); } return text; } const q = s => document.querySelector(s); const qq = s => [...document.querySelectorAll(s)]; const elm = function elm(sel = '', ...children) { let el = document.createElement('div'); sel.replace(/([\w-]+)|#([\w-]+)|\.([\w-]+)|\[([^\]=]+)(?:=([^\]]*))\]/g, (s, tag, id, cls, attr, val) => { if (tag) el = document.createElement(tag); if (id) el.id = id; if (cls) el.classList.add(cls); if (attr) el.setAttribute(attr, val ?? true); }); for (let f of children.filter(e => typeof e == 'function')) { let name = f.name || (f + '').match(/\w+/); if (!name.startsWith('on')) name = 'on' + name; el[name] = f; } el.append(...children.filter(e => typeof e != 'function')); return el; }; Object.defineValue = function defineValue(o, p, value) { if (typeof p == 'function') { [p, value] = [p.name, p]; } return Object.defineProperty(o, p, { value, configurable: true, enumerable: false, writable: true, }); }; Object.defineValue(Element.prototype, function q(sel) { return this.querySelector(sel); }); Object.defineValue(Element.prototype, function qq(sel) { return [...this.querySelectorAll(sel)]; }); Object.map = function(o, mapper) { return Object.fromEntries(Object.entries(o).map(([k, v]) => [k, mapper(v, k, o)])); }; Array.map = function map(length, mapper = i => i) { return Array(length).fill(0).map((e, i, a) => mapper(i)); }; const SETTINGS_PREFIX = 'theTreeEnchanted_'; const getSetting = (key, defaultValue) => localStorage.getItem(SETTINGS_PREFIX + key) ?? defaultValue; const setSetting = (key, value) => localStorage.setItem(SETTINGS_PREFIX + key, value); let areShortcutsEnabled = getSetting('shortcutsEnabled', 'true') === 'true'; let isShiftSimulated = getSetting('isShiftSimulated', 'false') === 'true'; let longPressDelay = parseInt(getSetting('longPressDelay', '100'), 10); let autoBuyActive = getSetting('autoBuyActive', 'false') === 'true'; let autoResetActive = getSetting('autoResetActive', 'false') === 'true'; let autoComboActive = getSetting('autoComboActive', 'false') === 'true'; let isBgKeepAlive = getSetting('bgKeepAlive', 'false') === 'true'; let fontState = getSetting('fontState', 'original'); let customFontData = getSetting('customFontData', null); // --- 后台挂机核心逻辑 (音频防休眠 + Visibility 欺骗) --- let bgAudio = null; function applyBgKeepAlive() { if (isBgKeepAlive) { if (!bgAudio) { bgAudio = new Audio('https://download.samplelib.com/mp3/sample-9s.mp3'); bgAudio.loop = true; bgAudio.volume = 0.01; } // 尝试播放音频,解决浏览器自动播放限制 bgAudio.play().catch(e => { console.warn("后台挂机音频自动播放被拦截,等待用户交互...", e); const playOnInteraction = () => { if (isBgKeepAlive) bgAudio.play(); document.removeEventListener('click', playOnInteraction); }; document.addEventListener('click', playOnInteraction); }); } else { if (bgAudio) { bgAudio.pause(); } } } (function hackVisibility() { const originalRAF = window.requestAnimationFrame; const autoRAF = (callback) => setTimeout(() => callback(performance.now()), 1000 / 60); Object.defineProperty(document, 'hidden', { get: () => isBgKeepAlive ? false : eventTargetHidden }); Object.defineProperty(document, 'visibilityState', { get: () => isBgKeepAlive ? 'visible' : eventTargetVisibility }); let eventTargetHidden = false; let eventTargetVisibility = 'visible'; document.addEventListener('visibilitychange', (e) => { if (isBgKeepAlive) e.stopImmediatePropagation(); }, true); window.requestAnimationFrame = function(callback) { // 如果处于后台,改用 setTimeout 维持帧率,同时借助音频突破浏览器降频限制 if (isBgKeepAlive && document.visibilityState === 'hidden') return autoRAF(callback); return originalRAF(callback); }; })(); function performReset() { q('.reset.can')?.click(); } function performBuyAll() { for (let e of qq(`.upg.can:not(.reset), .buyable.can:not(.reset), .canComplete .longUpg`).reverse()) { e.click(); } } function performBuyAndReset() { performBuyAll(); setTimeout(performReset, 10); } function setGameSpeed(newSpeed, fromPrompt = false) { let speedValue; if (fromPrompt) { const currentSpeed = (typeof player.devSpeed === 'number') ? player.devSpeed : 1; const input = prompt(getText('speedPrompt'), currentSpeed); if (input === null) return; speedValue = parseFloat(input); if (isNaN(speedValue)) { alert(getText('speedInvalid')); return; } } else { speedValue = parseFloat(newSpeed); } player.devSpeed = Math.max(0.1, speedValue); console.log(`游戏速度已设置为: ${player.devSpeed}`); updateUIDisplay(); } function exportSaveAsFile() { if (typeof NaNcheck === 'function') { NaNcheck(player); if (window.NaNalert) { alert(getText('exportNaN')); return; } } try { let saveData; if (typeof LZString !== 'undefined' && typeof LZString.compressToBase64 === 'function') { saveData = LZString.compressToBase64(JSON.stringify(player)); } else { try { saveData = btoa(JSON.stringify(player)); } catch (err) { if (typeof formatsave !== 'undefined' && typeof formatsave.encode === 'function') { saveData = formatsave.encode(player); } else { throw new Error("无法进行存档编码(Base64 失败且无备用算法)。"); } } } const blob = new Blob([saveData], { type: "text/plain;charset=utf-8" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; const date = new Date(); const formattedDate = `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`; const fileName = (typeof modInfo !== 'undefined' && modInfo.id) ? `${modInfo.id}_save_${formattedDate}.txt` : `TMT_save_${formattedDate}.txt`; a.download = fileName; document.body.appendChild(a); a.click(); setTimeout(() => { document.body.removeChild(a); URL.revokeObjectURL(url); }, 0); } catch (e) { alert(getText('exportError')); console.error("导出存档失败:", e); } } function importSaveFromFile() { const fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.accept = '.txt,text/plain'; fileInput.style.display = 'none'; fileInput.onchange = event => { const file = event.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = function(e) { try { const saveDataString = e.target.result.trim(); if (!saveDataString) { throw new Error(getText('importEmpty')); } if (saveDataString.startsWith("N4IgLghg")) { if (typeof importSave === 'function') { importSave(saveDataString); return; } else { throw new Error(getText('importLzError')); } } let isBase64 = false; try { const decodedJson = atob(saveDataString); if (decodedJson.trim().startsWith('{')) { isBase64 = true; if (typeof getStartPlayer === 'function' && typeof fixSave === 'function' && typeof versionCheck === 'function' && typeof save === 'function') { player = Object.assign(getStartPlayer(), JSON.parse(decodedJson)); fixSave(); versionCheck(); save(); window.location.reload(); return; } else { throw new Error(getText('importManualError')); } } } catch (err) {} if (!isBase64 && typeof formatsave !== 'undefined' && formatsave.startString && saveDataString.startsWith(formatsave.startString)) { if (typeof importSave === 'function') { importSave(saveDataString); return; } else { const decodedObj = formatsave.decode(saveDataString); if (typeof getStartPlayer === 'function' && typeof fixSave === 'function' && typeof versionCheck === 'function' && typeof save === 'function') { player = Object.assign(getStartPlayer(), decodedObj); fixSave(); versionCheck(); save(); window.location.reload(); return; } else { throw new Error(getText('importManualError')); } } } throw new Error(getText('importInvalidFormat')); } catch (error) { alert(getText('importError', { error: error.message })); console.error("存档导入失败:", error); } }; reader.onerror = function() { alert(getText('importReadError')); console.error("FileReader error:", reader.error); }; reader.readAsText(file); }; document.body.appendChild(fileInput); fileInput.click(); document.body.removeChild(fileInput); } addEventListener('keydown', async event => { if (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA') return; if (areShortcutsEnabled) { if (event.key == 'z' || event.key == 'Z') { performReset(); } if (event.key == 'x' || event.key == 'X') { performBuyAll(); } if (event.key == 'c' || event.key == 'C') { performBuyAndReset(); } } let treeNode = qq('.treeNode.can:not(.ghost)').find(e => e.innerText.startsWith(event.key)); treeNode?.click(); if (event.key == 'Tab') { let tabs = qq('.tabButton'); let i = tabs.findIndex(e => e.innerText.includes(player.subtabs[player.tab].mainTabs)) + 1; if (event.shiftKey) i += tabs.length - 2; tabs[i % tabs.length].click(); event.preventDefault(); } switch (event.key) { case 'ArrowUp': ArrowLayerMove.moveUp(); break; case 'ArrowLeft': ArrowLayerMove.moveLeft(); break; case 'ArrowDown': ArrowLayerMove.moveDown(); break; case 'ArrowRight': ArrowLayerMove.moveRight(); break; } }); let Layer = class { static hasAllUpgrades(layer) { return Object.values(tmp[layer].upgrades).every(e => !hasUpgrade(layer, e.id)); } static status(layerId) { let layer = tmp[layerId]; let ups = Object.values(layer.upgrades || {}).filter(e => e.id); let ms = Object.values(layer.milestones || {}).filter(e => e.id); let cha = Object.values(layer.challenges || {}).filter(e => e.id); return { upgrades: { total: ups.length, unlocked: ups.filter(e => e.unlocked || hasUpgrade(layerId, e.id)).length, done: ups.filter(e => hasUpgrade(layerId, e.id)).length, available: ups.filter(e => e.unlocked && !hasUpgrade(layerId, e.id) && canAffordUpgrade(layerId, e.id)).length, }, milestones: { total: ms.length, unlocked: ms.filter(e => e.unlocked).length, done: ms.filter(e => e.done).length, }, challenges: { total: cha.length, unlocked: cha.filter(e => e.unlocked).length, done: cha.filter(e => player[layerId].challenges[e.id] >= e.completionLimit).length, active: !!player[layerId].activeChallenge, canComplete: player[layerId].activeChallenge && canCompleteChallenge(layerId, player[layerId].activeChallenge), }, } } static shortStatus(layerId) { let s = this.status(layerId); return { upgrades: `${s.upgrades.bought}/${s.upgrades.unlocked}/${s.upgrades.total}`, } } static showStars(layerId) { function star(color, empty) { return elm(`.statusStar[style=color:${color};]`, typeof empty == 'string' ? empty : empty ? '☆' : '★') } let node = q(`.treeNode[class~="${layerId}"]`); if (!node) return; let s = this.status(layerId); let ups = s.upgrades; let ms = s.milestones; let cha = s.challenges; ups = ups.total && star(ups.available ? 'yellowgreen' : ups.unlocked < ups.total ? 'silver' : 'gold', ups.done < ups.unlocked); ms = ms.total && star(ms.unlocked < ms.total ? 'silver' : 'gold', ms.done < ms.unlocked); cha = cha.total && star(cha.active ? 'red' : cha.unlocked < cha.total ? 'silver' : 'gold', cha.done < cha.unlocked && !cha.canComplete); let sel = player.tab == layerId && star('white', '\xa0\xa0^'); const starElements = isNewTmtVersion ? [cha, ms, ups, sel].filter(Boolean) : [sel, cha, ms, ups].filter(Boolean); let container = elm('.sscon', ...starElements); let oldContainer = node.q('.sscon'); if (!node.q('.sscon')) { node.append(container); } else if (oldContainer.outerHTML != container.outerHTML) { oldContainer.replaceWith(container); } } static showAllStars() { Object.values(tmp).filter(e => e && e.layerShown === true).map(e => this.showStars(e.layer)); } }; let layInt = window.layInt; if (layInt) clearInterval(layInt); layInt = setInterval(() => Layer.showAllStars(), 200); let ArrowLayerMove = class ArrowLayerMove { static get tree() { if (this._tree) return this._tree; if (typeof Object.values(TREE_LAYERS)[0][0] == 'object') { return this._tree = TREE_LAYERS; } return this._tree = Object.fromEntries(Object.entries(TREE_LAYERS).map(([k, r]) => { return [k, r.map((layer, position) => ({ layer, position }))] })); } static get y() { return +Object.entries(this.tree).find(([k, r]) => r.find(e => e.layer == player.tab))[0]; } static get x() { return Object.values(this.tree).map(r => r.find(e => e.layer == player.tab)).find(Boolean).position; } static changeLayer(layer) { q(`.treeNode.can.${layer}`)?.click(); } static best(a, f) { return a.map((e, i, a) => ({ e, v: f(e, i, a) })).sort((a, b) => a.v - b.v)[0].e; } static moveUp() { let row = this.tree[this.y - 1]; if (!row) return; let layer = this.best(row, e => Math.abs(this.x - e.position) + 1000 * !tmp[e.layer].layerShown).layer; this.changeLayer(layer); } static moveLeft() { let row = this.tree[this.y]; let layer = row.filter(e => e.position < this.x).pop()?.layer; if (!layer) return; this.changeLayer(layer); } static moveDown() { let row = this.tree[this.y + 1]; if (!row) return; let layer = this.best(row, e => Math.abs(this.x - e.position) + 1000 * !tmp[e.layer].layerShown).layer; this.changeLayer(layer); } static moveRight() { let row = this.tree[this.y]; let layer = row.filter(e => e.position > this.x)[0]?.layer; if (!layer) return; this.changeLayer(layer); } }; let TabStarrer = class { static layerContent(layerId) { if (!layers[layerId] || !layers[layerId].tabFormat) { return {}; } let _tab = player.tab; player.tab = layerId; let data = Object.fromEntries(Object.entries(layers[layerId].tabFormat).map(([k, v]) => [k, parseTab(k, v)])); player.tab = _tab; return data; function parseTab(id, tab) { if (!player.subtabs[layerId]) { return {}; } let _mainTabs = player.subtabs[layerId].mainTabs; player.subtabs[layerId].mainTabs = id; let data = {}; function parseItem(e) { if (typeof e == 'function') return parseItem(e()); if (Array.isArray(e)) { if (e[0] == 'row' || e[0] == 'column') { return e.slice(1).flat().map(parseItem); } data[e[0]] ??= []; if (typeof e[1] != 'object') { data[e[0]].push(e[1]); } else if (e[0] == 'upgrades') { let ups = e[1].flatMap(e => Array.map(10, i => e * 10 + i)).filter(e => layers[layerId].upgrades[e]); data.upgrade ??= []; data.upgrade.push(...ups); } else { debugger; } return; } data[e] = true; } tab.content?.map(parseItem); player.subtabs[layerId].mainTabs = _mainTabs; return data; } } static layerTabStatus(layerId) { let content = this.layerContent(layerId); return Object.map(content, (tabContent, k) => { let layer = tmp[layerId]; let ups = tabContent.upgrades != true ? (tabContent.upgrade || []).map(e => layer.upgrades[e]) : Object.values(layer.upgrades || {}).filter(e => e.id); let ms = !tabContent.milestones ? [] : Object.values(layer.milestones || {}).filter(e => e.id); let cha = !tabContent.challenges ? [] : Object.values(layer.challenges || {}).filter(e => e.id); return { upgrades: { total: ups.length, unlocked: ups.filter(e => e.unlocked || hasUpgrade(layerId, e.id)).length, done: ups.filter(e => hasUpgrade(layerId, e.id)).length, available: ups.filter(e => e.unlocked && !hasUpgrade(layerId, e.id) && canAffordUpgrade(layerId, e.id)).length, }, milestones: { total: ms.length, unlocked: ms.filter(e => e.unlocked).length, done: ms.filter(e => e.done).length, }, challenges: { total: cha.length, unlocked: cha.filter(e => e.unlocked).length, done: cha.filter(e => player[layerId].challenges[e.id] >= e.completionLimit).length, active: !!player[layerId].activeChallenge, canComplete: player[layerId].activeChallenge && canCompleteChallenge(layerId, player[layerId].activeChallenge), }, } }); } static starsElement(status) { function star(color, empty) { return elm(`.tabStar[style=color:${color};]`, typeof empty == 'string' ? empty : empty ? '☆' : '★') } let ups = status.upgrades; let ms = status.milestones; let cha = status.challenges; ups = ups.total && star(ups.available ? 'yellowgreen' : ups.unlocked < ups.total ? 'silver' : 'gold', ups.done < ups.unlocked); ms = ms.total && star(ms.unlocked < ms.total ? 'silver' : 'gold', ms.done < ms.unlocked); cha = cha.total && star(cha.active ? 'red' : cha.unlocked < cha.total ? 'silver' : 'gold', cha.done < cha.unlocked && !cha.canComplete); return elm('.tscon', ...[cha, ms, ups].filter(Boolean)); } static makeTabStars() { if (!player) return; let layerId = player.tab; let layerTabStatus = this.layerTabStatus(layerId); qq('.tabButton').map(e => { if (!e.q('.tscon')) e.append(elm('.tscon')); let tabId = e.childNodes[0].nodeValue; let old = e.q('.tscon'); if (!layerTabStatus[tabId]) return; let con = this.starsElement(layerTabStatus[tabId]); if (con.outerHTML != old.outerHTML) { old.replaceWith(con); } }); } }; let _tsint = window._tsint; if (_tsint) clearInterval(_tsint); _tsint = setInterval(() => TabStarrer.makeTabStars(), 200); q('head').append(elm('style', `#the-tree-enchanted-font-override {} .sscon { position:absolute; font-size: 33.333%; font-family: initial; text-shadow: 0 0 4px gray, 0 0 3px black; display: flex; pointer-events: none; ${isNewTmtVersion ? `bottom: 0; right: 0; flex-direction: row;` : `flex-direction: row-reverse;`} } .statusStar { display: inline-block; transform: scale(3); } .tabButton { position: relative; } .tscon { position: relative; display: inline-block; left: 9px; } .tabStar { display: inline-block; }`)); function makeButtonLongPressable(button, actionFn) { let intervalId = null; function startPress(event) { event.preventDefault(); actionFn(); if (intervalId) return; intervalId = setInterval(actionFn, longPressDelay); } function endPress() { if (intervalId) { clearInterval(intervalId); intervalId = null; } } button.addEventListener('mousedown', startPress); button.addEventListener('touchstart', startPress, { passive: false }); button.addEventListener('mouseup', endPress); button.addEventListener('mouseleave', endPress); button.addEventListener('touchend', endPress); button.addEventListener('touchcancel', endPress); } let autoActionsInterval = null; function autoActionsLoop() { if (autoBuyActive) performBuyAll(); if (autoResetActive) performReset(); else if (autoComboActive) performBuyAndReset(); } function startOrUpdateAutoActionsInterval() { if (autoActionsInterval) { clearInterval(autoActionsInterval); } const effectiveDelay = Math.max(longPressDelay, 16); autoActionsInterval = setInterval(autoActionsLoop, effectiveDelay); } const fontStyleElement = elm('style#the-tree-enchanted-font-override'); q('head').append(fontStyleElement); function updateFontOverride() { const fontFaceName = 'TheTreeEnchantedCustomFont'; let css = ''; switch (fontState) { case 'system': css = `* { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !important; }`; break; case 'custom': if (customFontData) { css = `@font-face { font-family: '${fontFaceName}'; src: url(${customFontData}); } * { font-family: '${fontFaceName}', sans-serif !important; }`; } else { fontState = 'original'; setSetting('fontState', 'original'); } break; case 'original': default: css = ''; break; } fontStyleElement.innerHTML = css; } function importCustomFont(onSuccess) { const fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.accept = '.ttf,.otf,.woff,.woff2'; fileInput.style.display = 'none'; fileInput.onchange = event => { const file = event.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = e => { try { customFontData = e.target.result; setSetting('customFontData', customFontData); onSuccess(); } catch (error) { alert(getText('fontImportError')); console.error("Font import error:", error); } }; reader.readAsDataURL(file); }; document.body.appendChild(fileInput); fileInput.click(); document.body.removeChild(fileInput); } function dispatchShiftEvent(type) { const event = new KeyboardEvent(type, { key: 'Shift', keyCode: 16, shiftKey: type === 'keydown', bubbles: true, cancelable: true }); document.dispatchEvent(event); } function updateUIDisplay() { const ui = document.getElementById('tree-enchanted-ui'); if (!ui) return; const updateToggleButton = (id, status, onText, offText) => { const btn = ui.querySelector(`#${id}`); if (status) { btn.style.background = '#2ecc71'; btn.textContent = onText; } else { btn.style.background = '#34495e'; btn.textContent = offText; } }; const speed = (typeof player.devSpeed === 'number') ? player.devSpeed : 1; ui.querySelector('#te-speed-status').textContent = speed.toFixed(1) + 'x'; // 顶部三个自动按钮保持两端对齐 const updateAutoBtn = (id, status, label) => { const btn = ui.querySelector(`#${id}`); const statusSpan = btn.querySelector('span:last-child'); if (status) { btn.style.background = '#2ecc71'; statusSpan.textContent = getText('on'); } else { btn.style.background = '#34495e'; statusSpan.textContent = getText('off'); } }; updateAutoBtn('te-auto-buy', autoBuyActive, getText('autoBuy')); updateAutoBtn('te-auto-reset', autoResetActive, getText('autoReset')); updateAutoBtn('te-auto-combo', autoComboActive, getText('autoCombo')); // 下方功能按钮采取居中统一样式 updateToggleButton('te-hotkey-toggle-btn', areShortcutsEnabled, `${getText('toggleHotkeys')}: ${getText('on')}`, `${getText('toggleHotkeys')}: ${getText('off')}`); updateToggleButton('te-bg-keepalive-btn', isBgKeepAlive, `${getText('bgKeepAlive')}: ${getText('on')}`, `${getText('bgKeepAlive')}: ${getText('off')}`); const fontBtn = ui.querySelector('#te-font-btn'); let fontStatusText; switch (fontState) { case 'system': fontStatusText = getText('fontSystem'); break; case 'custom': fontStatusText = getText('fontCustom'); break; default: fontStatusText = getText('fontOriginal'); break; } fontBtn.textContent = `${getText('fontToggle')}: ${fontStatusText}`; const shiftBtn = ui.querySelector('#te-shift-sim-btn'); if (isShiftSimulated) { shiftBtn.style.background = '#2ecc71'; shiftBtn.textContent = getText('shiftOn'); } else { shiftBtn.style.background = '#34495e'; shiftBtn.textContent = getText('simulateShift'); } const delayBtn = ui.querySelector('#te-delay-btn'); if (delayBtn) delayBtn.textContent = getText('setDelay', { delay: longPressDelay }); } function createExtraControls() { const defaultUIState = { left: '10px', top: '10px', isCollapsed: false }; let uiState; try { uiState = JSON.parse(getSetting('uiState', JSON.stringify(defaultUIState))); } catch (e) { uiState = defaultUIState; } function saveUIState() { setSetting('uiState', JSON.stringify(uiState)); } let initLeft = parseInt(uiState.left) || 10; let initTop = parseInt(uiState.top) || 10; if (initLeft < 0) initLeft = 10; if (initTop < 0) initTop = 10; if (initLeft > window.innerWidth - 50) initLeft = Math.max(10, window.innerWidth - 260); if (initTop > window.innerHeight - 50) initTop = Math.max(10, window.innerHeight - 100); const ui = document.createElement('div'); ui.id = 'tree-enchanted-ui'; ui.style.cssText = ` position: fixed; top: ${initTop}px; left: ${initLeft}px; width: 240px; background: rgba(16, 20, 25, 0.95); color: #fff; padding: 10px; border-radius: 8px; font-family: 'Segoe UI', Arial, sans-serif; font-size: 12px; box-shadow: 0 8px 32px rgba(0,0,0,0.6); z-index: 2147483647; backdrop-filter: blur(5px); user-select: none; border: 1px solid rgba(255,255,255,0.1); box-sizing: border-box; transition: none !important; transform: translateZ(0); `; ui.innerHTML = `