Evolve Auto-Library

Evolve Auto 的靜態工具庫 (UI, Utils, Scanner, GameData)。供主邏輯腳本調用。

Ce script ne doit pas être installé directement. C'est une librairie destinée à être incluse dans d'autres scripts avec la méta-directive // @require https://update.greasyfork.org/scripts/564998/1748004/Evolve%20Auto-Library.js

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name         Evolve Auto-Library
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  強制 UI 恢復版。
// @author       Gemini & User
// @match        https://pmotschmann.github.io/Evolve/
// @grant        none
// ==/UserScript==

(function() {
    'use strict';
    window.EvolveAuto = window.EvolveAuto || {};
    const GameData = {
        jobs: [
            { id: 'civ-banker', name: '銀行', tier: 1, type: 'gold' },
            { id: 'civ-professor', name: '教授', tier: 1, type: 'gold' },
            { id: 'civ-scientist', name: '科學家', tier: 1, type: 'science' },
            { id: 'civ-miner', name: '礦工', tier: 1, type: 'gold' },
            { id: 'civ-coal_miner', name: '煤礦', tier: 1, type: 'gold' },
            { id: 'virtual-steel', name: '鋼 (監控)', tier: 1, type: 'resource', produces: 'Steel', isVirtual: true },
            { id: 'craftPlywood', name: '膠合板', tier: 2, type: 'craft' },
            { id: 'craftBrick', name: '砌磚', tier: 2, type: 'craft' },
            { id: 'craftWrought_Iron', name: '鍛鐵', tier: 2, type: 'craft' },
            { id: 'craftSheet_Metal', name: '板金', tier: 2, type: 'craft' },
            { id: 'civ-cement_worker', name: '水泥', tier: 2, type: 'civ-craft' },
            { id: 'civ-lumberjack', name: '伐木', tier: 3, type: 'junk', produces: 'Lumber' },
            { id: 'civ-quarry_worker', name: '石工', tier: 3, type: 'junk', produces: 'Stone' },
            { id: 'civ-entertainer', name: '藝人', tier: 1, type: 'morale' },
            { id: 'civ-farmer', name: '農夫', tier: 4, type: 'food' }
        ],
        resToJobReverse: { 'civ-lumberjack': 'Lumber', 'civ-quarry_worker': 'Stone', 'civ-miner': 'Iron', 'civ-coal_miner': 'Coal', 'civ-cement_worker': 'Cement', 'craftPlywood': 'Plywood', 'craftBrick': 'Brick', 'craftWrought_Iron': 'Wrought_Iron', 'craftSheet_Metal': 'Sheet_Metal' },
        crateResources: [ 'Food', 'Lumber', 'Stone', 'Copper', 'Iron', 'Aluminium', 'Cement', 'Coal', 'Steel' ],
        mapping: { 'lumber': 'Lumber', 'stone': 'Stone', 'plywood': 'Plywood', 'brick': 'Brick', 'wrought_iron': 'Wrought_Iron', 'sheet_metal': 'Sheet_Metal', 'steel': 'Steel', 'cement': 'Cement' }
    };

    const Utils = {
        state: { vueRoot: null, resKey: null },
        log: function(msg, color="white") { console.log(`%c[Auto] ${msg}`, `color: ${color}`); },
        parseNum: function(str) { if (!str) return 0; str = String(str).trim(); let mult = 1; if (str.includes('k')) mult = 1e3; else if (str.includes('M')) mult = 1e6; return parseFloat(str.replace(/[^\d.\-]/g, '')) * mult; },
        getRes: function(name) {
            let res = { visible: false, cur: 0, max: 0, pct: 0, rate: 0 };
            const el = document.getElementById(`res${name}`);
            if (el && el.style.display !== 'none') {
                const countTxt = el.querySelector('.count')?.textContent || "0/0";
                const parts = countTxt.split('/');
                res.cur = this.parseNum(parts[0]);
                res.max = this.parseNum(parts[1]);
                res.pct = res.max > 0 ? res.cur / res.max : 0;
                res.visible = true;
                const diffEl = el.querySelector('.diff');
                if (diffEl) res.rate = this.parseNum(diffEl.textContent);
            }
            return res;
        },
        getJobInfo: function(id) {
            const el = document.getElementById(id);
            if (!el) return { id, cur: 0, max: 0, isFull: true };
            const countTxt = el.querySelector('.count')?.textContent || "0/0";
            let cur = 0, max = 999;
            if (countTxt.includes('/')) { const p = countTxt.split('/'); cur = this.parseNum(p[0]); max = this.parseNum(p[1]); }
            else cur = this.parseNum(countTxt);
            return { id, cur, max, isFull: cur >= max };
        },
        getFoundry: function() {
            const el = document.querySelector('#foundry .foundry .count');
            if (!el) return { isFull: false };
            const p = el.textContent.split('/');
            return { isFull: this.parseNum(p[0]) >= this.parseNum(p[1]) };
        },
        getCrateInfo: function() {
            const el = document.querySelector('#crateTotal .crtTotal');
            if (!el) return { free: 0, max: 0 };
            const m = el.textContent.match(/(\d+)\s*\/\s*(\d+)/);
            return m ? { free: parseInt(m[1]), max: parseInt(m[2]) } : { free: 0, max: 0 };
        },
        getContainerInfo: function() {
            const el = document.querySelector('#crateTotal .cntTotal');
            if (!el) return { free: 0, max: 0 };
            const m = el.textContent.match(/(\d+)\s*\/\s*(\d+)/);
            return m ? { free: parseInt(m[1]), max: parseInt(m[2]) } : { free: 0, max: 0 };
        },
        click: function(id, type='add') {
            const el = document.getElementById(id);
            if (!el) return false;
            const btn = type === 'sub' ? el.querySelector('.sub') : (el.querySelector('.add') || el);
            if (btn && !btn.classList.contains('disabled')) { btn.click(); return true; }
            return false;
        },
        findButtonByTitle: function(title) {
            const btns = document.querySelectorAll('button, .button');
            for (let b of btns) { if (b.textContent.includes(title)) return b; }
            return null;
        },
        getQueueItemData: function() { return document.querySelector('#buildQueue .buildList li:first-child a'); }
    };

    const UI = {
        init: function() {
            if (document.getElementById('rim-hud')) return;
            const box = document.createElement('div');
            box.id = 'rim-hud';
            box.style.cssText = "position: fixed; bottom: 20px; right: 20px; width: 450px; background: rgba(0,0,0,0.9); border: 1px solid #00ffea; color: #00ffea; font-family: monospace; font-size: 11px; z-index: 10000; display: flex;";
            document.body.appendChild(box);

            const sidebar = document.createElement('div');
            sidebar.style.cssText = "width: 50px; border-right: 1px solid #00ffea; padding: 5px; display: flex; flex-direction: column; gap: 5px;";
            box.appendChild(sidebar);

            const createBtn = (txt, color, fn) => {
                const b = document.createElement('button');
                b.innerText = txt;
                b.style.cssText = `background:${color}; color:#fff; border:1px solid #fff; font-size:9px; cursor:pointer; padding:2px;`;
                b.onclick = (e) => { e.stopPropagation(); if(window.EvolveAuto.Core) fn(window.EvolveAuto.Core); };
                sidebar.appendChild(b);
            };

            createBtn('RUN', '#27ae60', (c) => c.togglePause());
            createBtn('WS', '#e67e22', (c) => c.toggleWorkshopPriority());
            // ★ 強制補回 COPY 按鈕
            createBtn('COPY', '#222', () => {
                const txt = document.getElementById('rim-content').innerText;
                navigator.clipboard.writeText(txt);
            });

            const main = document.createElement('div');
            main.style.cssText = "flex: 1; display: flex; flex-direction: column;";
            box.appendChild(main);

            const header = document.createElement('div');
            header.innerText = 'Evolve Auto V4.73';
            header.style.cssText = "background:#00574f; color:#fff; padding:3px; text-align:center;";
            main.appendChild(header);

            const content = document.createElement('div');
            content.id = 'rim-content';
            content.style.cssText = "padding:5px; height:350px; overflow-y:auto; white-space:pre-wrap;";
            main.appendChild(content);
        },
        update: function(html) {
            const el = document.getElementById('rim-content');
            if (el) el.innerHTML = html;
        },
        updatePauseState: function(p) { Utils.log(p ? "Paused" : "Running"); }
    };

    window.EvolveAuto.GameData = GameData;
    window.EvolveAuto.Utils = Utils;
    window.EvolveAuto.UI = UI;
})();