// ==UserScript== // @name The Tree Enchanted丨被附魔的树 // @namespace http://tampermonkey.net/ // @version 2.9.2 // @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: "Set Delay ({delay}ms)", autoBuy: "Auto Buy", autoReset: "Auto Reset", autoCombo: "Auto Buy&Reset", fontToggle: "Font", 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: "切换字体", 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: "切換字體", 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 fontState = getSetting('fontState', 'original'); let customFontData = getSetting('customFontData', null); 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 (!oldContainer) { 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}`); const statusSpan = ui.querySelector(`#${id}-status`); if (status) { btn.style.background = '#2ecc71'; if (statusSpan) statusSpan.textContent = onText; } else { btn.style.background = '#34495e'; if (statusSpan) statusSpan.textContent = offText; } }; const speed = (typeof player.devSpeed === 'number') ? player.devSpeed : 1; ui.querySelector('#te-speed-status').textContent = speed.toFixed(1) + 'x'; updateToggleButton('te-auto-buy', autoBuyActive, getText('on'), getText('off')); updateToggleButton('te-auto-reset', autoResetActive, getText('on'), getText('off')); updateToggleButton('te-auto-combo', autoComboActive, getText('on'), getText('off')); updateToggleButton('te-hotkey-toggle-btn', areShortcutsEnabled, getText('on'), 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 = `
${getText('title')}
By LinLei_Baruch & Gemini
`; const style = document.createElement('style'); style.textContent = ` #tree-enchanted-ui button { background: #34495e; color: white; border: none; padding: 6px; border-radius: 4px; cursor: pointer; transition: background 0.2s; font-size:11px; display: flex; justify-content: center; align-items: center; } #tree-enchanted-ui button:hover { opacity: 0.9; filter: brightness(1.1); } #tree-enchanted-ui button:active { transform: translateY(1px); } #te-toggle-btn { background: transparent !important; padding: 0 4px !important; font-size: 14px !important; line-height: 1 !important; } #te-toggle-btn:hover { transform: scale(1.2); } .auto-btn { width:100%; display:flex; justify-content:space-between; align-items:center; padding: 6px 10px !important; } .auto-btn > span:last-child { font-weight:bold; } `; document.head.appendChild(style); document.body.appendChild(ui); const contentDiv = ui.querySelector('#te-content'); const header = ui.querySelector('#te-header'); const toggleBtn = header.querySelector('#te-toggle-btn'); if (uiState.isCollapsed) { contentDiv.style.display = 'none'; toggleBtn.textContent = '➕'; header.style.marginBottom = '0px'; } toggleBtn.onclick = (e) => { e.stopPropagation(); if (contentDiv.style.display === 'none') { contentDiv.style.display = 'block'; toggleBtn.textContent = '➖'; header.style.marginBottom = '10px'; uiState.isCollapsed = false; } else { contentDiv.style.display = 'none'; toggleBtn.textContent = '➕'; header.style.marginBottom = '0px'; uiState.isCollapsed = true; } saveUIState(); }; ui.querySelector('#te-speed-btn').onclick = () => setGameSpeed(null, true); makeButtonLongPressable(ui.querySelector('#te-buy-btn'), performBuyAll); makeButtonLongPressable(ui.querySelector('#te-reset-btn'), performReset); makeButtonLongPressable(ui.querySelector('#te-combo-btn'), performBuyAndReset); ui.querySelector('#te-auto-buy').onclick = () => { autoBuyActive = !autoBuyActive; setSetting('autoBuyActive', autoBuyActive); updateUIDisplay(); }; ui.querySelector('#te-auto-reset').onclick = () => { autoResetActive = !autoResetActive; setSetting('autoResetActive', autoResetActive); updateUIDisplay(); }; ui.querySelector('#te-auto-combo').onclick = () => { autoComboActive = !autoComboActive; setSetting('autoComboActive', autoComboActive); updateUIDisplay(); }; ui.querySelector('#te-import-btn').onclick = importSaveFromFile; ui.querySelector('#te-export-btn').onclick = exportSaveAsFile; const delayBtn = ui.querySelector('#te-delay-btn'); delayBtn.onclick = () => { const input = prompt(getText('delayPrompt'), longPressDelay); if (input === null) return; const newDelay = parseInt(input, 10); if (!isNaN(newDelay) && newDelay >= 100 && newDelay <= 1000) { longPressDelay = newDelay; setSetting('longPressDelay', longPressDelay); updateUIDisplay(); // Update delay button text startOrUpdateAutoActionsInterval(); } else { alert(getText('delayInvalid')); } }; ui.querySelector('#te-hotkey-toggle-btn').onclick = () => { areShortcutsEnabled = !areShortcutsEnabled; setSetting('shortcutsEnabled', areShortcutsEnabled); updateUIDisplay(); }; const fontBtn = ui.querySelector('#te-font-btn'); fontBtn.onclick = () => { if (fontState === 'original') { fontState = 'system'; } else if (fontState === 'system') { if (confirm(getText('fontConfirmPrompt'))) { importCustomFont(() => { fontState = 'custom'; setSetting('fontState', fontState); updateFontOverride(); updateUIDisplay(); }); return; } else { fontState = 'original'; } } else { fontState = 'original'; } setSetting('fontState', fontState); updateFontOverride(); updateUIDisplay(); }; ui.querySelector('#te-shift-sim-btn').onclick = () => { isShiftSimulated = !isShiftSimulated; setSetting('isShiftSimulated', isShiftSimulated); dispatchShiftEvent(isShiftSimulated ? 'keydown' : 'keyup'); updateUIDisplay(); }; let isDragging = false, startX, startY, initDragLeft, initDragTop, animationFrameId = null; const onDragStart = (e) => { if (e.target.tagName === 'BUTTON' || e.target.tagName === 'INPUT') return; isDragging = true; header.style.cursor = 'grabbing'; const currentX = e.type.includes('touch') ? e.touches[0].clientX : e.clientX; const currentY = e.type.includes('touch') ? e.touches[0].clientY : e.clientY; startX = currentX; startY = currentY; initDragLeft = ui.offsetLeft; initDragTop = ui.offsetTop; document.addEventListener('mousemove', onDragMove, { passive: false }); document.addEventListener('touchmove', onDragMove, { passive: false }); document.addEventListener('mouseup', onDragEnd); document.addEventListener('touchend', onDragEnd); }; const onDragMove = (e) => { if (!isDragging) return; e.preventDefault(); const currentX = e.type.includes('touch') ? e.touches[0].clientX : e.clientX; const currentY = e.type.includes('touch') ? e.touches[0].clientY : e.clientY; const newLeft = initDragLeft + (currentX - startX); const newTop = initDragTop + (currentY - startY); if (animationFrameId) cancelAnimationFrame(animationFrameId); animationFrameId = requestAnimationFrame(() => { ui.style.left = newLeft + 'px'; ui.style.top = newTop + 'px'; }); }; const onDragEnd = () => { if (isDragging) { isDragging = false; header.style.cursor = 'move'; if (animationFrameId) cancelAnimationFrame(animationFrameId); document.removeEventListener('mousemove', onDragMove); document.removeEventListener('touchmove', onDragMove); document.removeEventListener('mouseup', onDragEnd); document.removeEventListener('touchend', onDragEnd); uiState.left = ui.style.left; uiState.top = ui.style.top; saveUIState(); } }; header.addEventListener('mousedown', onDragStart); header.addEventListener('touchstart', onDragStart, { passive: false }); setTimeout(updateUIDisplay, 500); } createExtraControls(); updateFontOverride(); startOrUpdateAutoActionsInterval(); if (isShiftSimulated) { dispatchShiftEvent('keydown'); } }();