TriX Executor (BETA)

BEST TERRITORIAL.IO EXECUTOR. A powerful in-game developer toolkit with a multi-tab script injector and syntax highlighting. | Recent Changes (v0.1.1): Initial BETA release of TriX Executor. Added multi-tab script editor, syntax highlighting, and persistent script saving.

当前为 2025-09-11 提交的版本,查看 最新版本

// ==UserScript==
// @name         TriX Executor (BETA)
// @namespace    http://tampermonkey.net/
// @version      0.1.1
// @description  BEST TERRITORIAL.IO EXECUTOR. A powerful in-game developer toolkit with a multi-tab script injector and syntax highlighting. | Recent Changes (v0.1.1): Initial BETA release of TriX Executor. Added multi-tab script editor, syntax highlighting, and persistent script saving.
// @author       Your Name
// @match        *://territorial.io/*
// @icon         https://i.imgur.com/O2s5H3I.png
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @grant        GM_getResourceText
// @require      https://openuserjs.org/src/libs/sizzle/GM_config.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js
// @resource     PRISM_CSS https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css
// ==/UserScript==

(function() {
    'use strict';

    // -----------------------------------------------------------------------------
    // 1. SETTINGS MODULE (GM_config)
    // -----------------------------------------------------------------------------
    const SettingsModule = {
        init: function() {
            GM_config.init({
                'id': 'TriX_Executor_Config',
                'title': "TriX Executor Settings",
                'fields': {
                    'uiTheme': {
                        'label': 'UI Theme',
                        'type': 'select',
                        'options': ['Dark Knight', 'Arctic Light', 'Crimson'],
                        'default': 'Dark Knight',
                        'section': ['Appearance'],
                    },
                    'showFPS': {
                        'label': 'Enable FPS Counter',
                        'type': 'checkbox',
                        'default': true,
                    }
                },
                'css': `
                    #TriX_Executor_Config { background-color: #2a2a30; color: #fff; border: 1px solid #555; }
                    #TriX_Executor_Config .section_header { background-color: #3a3a42; border-bottom: 1px solid #555; }
                    #TriX_Executor_Config .config_header { background-color: #1e1e22; }
                    #TriX_Executor_Config button { background-color: #007bff; color: white; border: none; padding: 8px 12px; cursor: pointer; transition: background-color 0.2s; border-radius: 4px; }
                    #TriX_Executor_Config button:hover { background-color: #0069d9; }
                    #TriX_Executor_Config .reset { background-color: #f44336; }
                    #TriX_Executor_Config .reset:hover { background-color: #da190b; }
                `,
                'events': {
                    'save': () => this.applyTheme(),
                    'open': (doc) => {
                        doc.getElementById('TriX_Executor_Config').style.animation = 'trix-fade-in 0.3s ease-out';
                    }
                }
            });
            GM_registerMenuCommand("TriX Settings", () => GM_config.open());
        },
        applyTheme: function() {
            const theme = GM_config.get('uiTheme');
            const container = document.getElementById('trix-container');
            if (container) {
                container.setAttribute('data-theme', theme.replace(' ', '-').toLowerCase());
            }
        },
        get: (key) => GM_config.get(key)
    };

    // -----------------------------------------------------------------------------
    // 2. STYLE MODULE
    // -----------------------------------------------------------------------------
    const StyleModule = {
        inject: function() {
            // Inject Prism.js theme CSS from @resource
            const prismCSS = GM_getResourceText('PRISM_CSS');
            GM_addStyle(prismCSS);

            GM_addStyle(`
                /* --- Animations --- */
                @keyframes trix-fade-in { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }
                @keyframes trix-slide-in { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }

                /* --- Main UI & Toggle --- */
                #trix-toggle-btn {
                    position: fixed; top: 15px; right: 15px; z-index: 99999; width: 50px; height: 50px;
                    background-color: rgba(30, 30, 34, 0.8); color: #00aaff; border: 2px solid #00aaff;
                    border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center;
                    font-size: 24px; transition: all 0.3s ease; backdrop-filter: blur(5px); font-family: monospace;
                }
                #trix-toggle-btn:hover { transform: scale(1.1) rotate(15deg); box-shadow: 0 0 15px #00aaff; }

                #trix-container {
                    position: fixed; top: 80px; right: 15px; width: 450px; min-height: 400px; z-index: 99998;
                    color: #fff; border-radius: 10px; overflow: hidden; box-shadow: 0 5px 25px rgba(0,0,0,0.5);
                    display: flex; flex-direction: column; backdrop-filter: blur(10px);
                    animation: trix-slide-in 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards; resize: both;
                }
                #trix-container.hidden { display: none; }

                /* --- Theming --- */
                #trix-container[data-theme='dark-knight'] { background-color: rgba(30, 30, 34, 0.9); border: 1px solid #444; }
                #trix-container[data-theme='arctic-light'] { background-color: rgba(240, 240, 255, 0.9); border: 1px solid #ccc; color: #111; }
                #trix-container[data-theme='crimson'] { background-color: rgba(43, 8, 8, 0.9); border: 1px solid #8B0000; color: #f1f1f1; }

                /* --- Header --- */
                #trix-header { padding: 10px 15px; cursor: move; user-select: none; font-weight: bold; font-size: 16px; display: flex; justify-content: space-between; align-items: center; }
                #trix-container[data-theme='dark-knight'] #trix-header { background-color: rgba(0,0,0,0.3); }
                #trix-container[data-theme='arctic-light'] #trix-header { background-color: rgba(0,0,0,0.1); }
                #trix-container[data-theme='crimson'] #trix-header { background-color: rgba(139, 0, 0, 0.5); }
                #trix-close-btn { cursor: pointer; font-size: 20px; font-weight: bold; padding: 0 5px; }
                #trix-close-btn:hover { color: #ff5555; }

                /* --- Content Area --- */
                #trix-content { padding: 0 15px 15px 15px; flex-grow: 1; display: flex; flex-direction: column; }

                /* --- Script Injector Specific Styles --- */
                #trix-injector-container { display: flex; flex-direction: column; flex-grow: 1; margin-top: 10px; }
                .trix-tabs { display: flex; flex-wrap: wrap; border-bottom: 1px solid #555; }
                .trix-tab { background: #2a2a30; padding: 8px 12px; cursor: pointer; border-radius: 5px 5px 0 0; margin-right: 4px; position: relative; transition: background 0.2s; }
                .trix-tab:hover { background: #3a3a42; }
                .trix-tab.active { background: #1e1e22; font-weight: bold; color: #00aaff; }
                .trix-tab-name { padding-right: 15px; }
                .trix-tab-close { position: absolute; top: 50%; right: 5px; transform: translateY(-50%); font-size: 14px; opacity: 0.6; }
                .trix-tab-close:hover { opacity: 1; color: #ff5555; }
                #trix-new-tab-btn { background: none; border: none; color: #00aaff; font-size: 20px; cursor: pointer; padding: 5px 10px; }

                .trix-editor-area { position: relative; flex-grow: 1; margin-top: -1px; background: #2d2d2d; border: 1px solid #555; border-radius: 0 0 5px 5px; }
                .trix-editor-area textarea, .trix-editor-area pre {
                    margin: 0; padding: 10px; font-family: 'Fira Code', 'Consolas', monospace; font-size: 14px;
                    line-height: 1.5; white-space: pre; word-wrap: normal;
                    width: 100%; height: 100%; box-sizing: border-box; position: absolute; top: 0; left: 0;
                    overflow: auto;
                }
                .trix-editor-area textarea {
                    z-index: 1; background: transparent; color: inherit; resize: none; border: none; outline: none;
                    -webkit-text-fill-color: transparent; /* Makes textarea text invisible */
                }
                .trix-editor-area pre { z-index: 0; pointer-events: none; }

                .trix-action-bar { display: flex; gap: 10px; margin-top: 10px; }
                .trix-button { background-color: #007bff; color: white; border: none; padding: 8px 12px; cursor: pointer; border-radius: 5px; transition: background-color 0.2s; flex-grow: 1; }
                .trix-button:hover { background-color: #0069d9; }
                #trix-execute-btn { background-color: #28a745; }
                #trix-execute-btn:hover { background-color: #218838; }

                .trix-status-bar { margin-top: 10px; padding: 5px; background: rgba(0,0,0,0.2); font-size: 12px; border-radius: 3px; min-height: 1em; }
                .trix-status-success { color: #28a745; }
                .trix-status-error { color: #dc3545; }
            `);
        }
    };

    // -----------------------------------------------------------------------------
    // 3. UI MODULE
    // -----------------------------------------------------------------------------
    const UIModule = {
        init: function() {
            this.createToggleButton();
            this.createMainPanel();
            this.addEventListeners();
        },
        createToggleButton: () => document.body.insertAdjacentHTML('beforeend', `<div id="trix-toggle-btn" title="Toggle TriX Executor">X</div>`),
        createMainPanel: function() {
            const mainPanelHTML = `
                <div id="trix-container" class="hidden">
                    <div id="trix-header">
                        <span>TriX Executor</span>
                        <span id="trix-close-btn" title="Close">✖</span>
                    </div>
                    <div id="trix-content">
                        <!-- Script Injector will be rendered here by its module -->
                    </div>
                    <div id="trix-footer" style="padding:10px; text-align:center; background:rgba(0,0,0,0.2);">
                        <button id="trix-settings-btn" class="trix-button">Open Settings</button>
                    </div>
                </div>
            `;
            document.body.insertAdjacentHTML('beforeend', mainPanelHTML);
        },
        addEventListeners: function() {
            const container = document.getElementById('trix-container');
            const header = document.getElementById('trix-header');
            const toggleBtn = document.getElementById('trix-toggle-btn');
            const closeBtn = document.getElementById('trix-close-btn');
            const settingsBtn = document.getElementById('trix-settings-btn');

            toggleBtn.addEventListener('click', () => container.classList.toggle('hidden'));
            closeBtn.addEventListener('click', () => container.classList.add('hidden'));
            settingsBtn.addEventListener('click', () => GM_config.open());

            // Drag functionality
            let isDragging = false, offset = { x: 0, y: 0 };
            header.addEventListener('mousedown', (e) => {
                isDragging = true;
                offset = { x: e.clientX - container.offsetLeft, y: e.clientY - container.offsetTop };
                header.style.cursor = 'grabbing';
            });
            document.addEventListener('mouseup', () => { isDragging = false; header.style.cursor = 'move'; });
            document.addEventListener('mousemove', (e) => {
                if (!isDragging) return;
                container.style.left = `${e.clientX - offset.x}px`;
                container.style.top = `${e.clientY - offset.y}px`;
                container.style.right = 'auto'; container.style.bottom = 'auto';
            });
        }
    };

    // -----------------------------------------------------------------------------
    // 4. SCRIPT INJECTOR MODULE
    // -----------------------------------------------------------------------------
    const ScriptInjectorModule = {
        state: {
            scripts: [],
            activeTabId: null,
        },
        elements: {},

        init: function() {
            this.loadState();
            this.render();
            this.attachEventListeners();
        },

        loadState: function() {
            const savedState = GM_getValue('TriX_Scripts', null);
            if (savedState) {
                this.state = JSON.parse(savedState);
            }
            if (this.state.scripts.length === 0) {
                // Create a default script if none exist
                const newScript = { id: Date.now(), name: 'Script 1', code: '// Welcome to TriX Executor!\n' };
                this.state.scripts.push(newScript);
                this.state.activeTabId = newScript.id;
            }
        },

        saveState: function() {
            GM_setValue('TriX_Scripts', JSON.stringify(this.state));
        },

        render: function() {
            const container = document.getElementById('trix-content');
            container.innerHTML = `
                <div id="trix-injector-container">
                    <div class="trix-tabs">
                        ${this.state.scripts.map(script => `
                            <div class="trix-tab ${script.id === this.state.activeTabId ? 'active' : ''}" data-id="${script.id}">
                                <span class="trix-tab-name">${script.name}</span>
                                <span class="trix-tab-close">×</span>
                            </div>
                        `).join('')}
                        <button id="trix-new-tab-btn" title="New Script">+</button>
                    </div>
                    <div class="trix-editor-area">
                        <textarea id="trix-editor-input" spellcheck="false"></textarea>
                        <pre id="trix-editor-highlight" class="language-js"><code class="language-js"></code></pre>
                    </div>
                    <div class="trix-action-bar">
                        <button id="trix-execute-btn" class="trix-button">Execute (Ctrl+Enter)</button>
                    </div>
                    <div id="trix-status-bar" class="trix-status-bar">Ready.</div>
                </div>
            `;

            this.elements = {
                tabsContainer: container.querySelector('.trix-tabs'),
                input: container.querySelector('#trix-editor-input'),
                highlight: container.querySelector('#trix-editor-highlight code'),
                statusBar: container.querySelector('#trix-status-bar'),
                executeBtn: container.querySelector('#trix-execute-btn'),
            };

            this.updateEditorContent();
        },

        updateEditorContent: function() {
            const activeScript = this.state.scripts.find(s => s.id === this.state.activeTabId);
            if (activeScript) {
                this.elements.input.value = activeScript.code;
                this.elements.highlight.textContent = activeScript.code;
                Prism.highlightElement(this.elements.highlight);
            }
        },

        attachEventListeners: function() {
            const container = document.getElementById('trix-content');

            // Tab management
            container.addEventListener('click', (e) => {
                const target = e.target;
                if (target.id === 'trix-new-tab-btn') this.handleNewTab();
                else if (target.classList.contains('trix-tab-close')) this.handleCloseTab(target.parentElement.dataset.id);
                else if (target.closest('.trix-tab')) this.handleTabClick(target.closest('.trix-tab').dataset.id);
            });

            // Tab renaming
            container.addEventListener('dblclick', (e) => {
                const tabNameSpan = e.target.closest('.trix-tab-name');
                if (tabNameSpan) this.handleRenameTab(tabNameSpan);
            });

            // Editor input and sync
            this.elements.input.addEventListener('input', this.handleCodeInput.bind(this));
            this.elements.input.addEventListener('scroll', () => {
                this.elements.highlight.parentElement.scrollTop = this.elements.input.scrollTop;
                this.elements.highlight.parentElement.scrollLeft = this.elements.input.scrollLeft;
            });

            // Execute button
            this.elements.executeBtn.addEventListener('click', this.handleExecute.bind(this));
            this.elements.input.addEventListener('keydown', (e) => {
                if (e.ctrlKey && e.key === 'Enter') {
                    e.preventDefault();
                    this.handleExecute();
                }
            });
        },

        handleCodeInput: function() {
            const code = this.elements.input.value;
            const activeScript = this.state.scripts.find(s => s.id === this.state.activeTabId);
            if (activeScript) {
                activeScript.code = code;
                this.elements.highlight.textContent = code;
                Prism.highlightElement(this.elements.highlight);
                this.saveState();
            }
        },

        handleNewTab: function() {
            const newId = Date.now();
            const newScript = { id: newId, name: `Script ${this.state.scripts.length + 1}`, code: '' };
            this.state.scripts.push(newScript);
            this.state.activeTabId = newId;
            this.saveState();
            this.render();
            this.attachEventListeners(); // Re-attach listeners after re-render
        },

        handleCloseTab: function(tabId) {
            tabId = Number(tabId);
            const tabIndex = this.state.scripts.findIndex(s => s.id === tabId);
            this.state.scripts.splice(tabIndex, 1);

            if (this.state.activeTabId === tabId) {
                this.state.activeTabId = this.state.scripts[tabIndex]?.id || this.state.scripts[tabIndex - 1]?.id || null;
            }
            if (this.state.scripts.length === 0) {
                this.handleNewTab(); // Don't allow zero tabs
                return;
            }

            this.saveState();
            this.render();
            this.attachEventListeners();
        },

        handleTabClick: function(tabId) {
            this.state.activeTabId = Number(tabId);
            this.saveState();
            // No full re-render needed, just update classes and content
            document.querySelectorAll('.trix-tab').forEach(t => t.classList.remove('active'));
            document.querySelector(`.trix-tab[data-id="${tabId}"]`).classList.add('active');
            this.updateEditorContent();
        },

        handleRenameTab: function(span) {
            const tabId = Number(span.closest('.trix-tab').dataset.id);
            const script = this.state.scripts.find(s => s.id === tabId);
            const input = document.createElement('input');
            input.type = 'text';
            input.value = span.textContent;
            span.replaceWith(input);
            input.focus();
            input.select();

            const saveName = () => {
                script.name = input.value || 'Untitled';
                input.replaceWith(span);
                span.textContent = script.name;
                this.saveState();
            };
            input.addEventListener('blur', saveName);
            input.addEventListener('keydown', e => e.key === 'Enter' && saveName());
        },

        handleExecute: function() {
            const code = this.elements.input.value;
            try {
                // Using new Function() is safer than eval() as it doesn't pollute the local scope
                const result = (new Function(code))();
                this.elements.statusBar.textContent = `Executed successfully at ${new Date().toLocaleTimeString()}.`;
                this.elements.statusBar.className = 'trix-status-bar trix-status-success';
                console.log('TriX Execution Result:', result);
            } catch (error) {
                this.elements.statusBar.textContent = `Error: ${error.message}`;
                this.elements.statusBar.className = 'trix-status-bar trix-status-error';
                console.error('TriX Execution Error:', error);
            }
        }
    };

    // -----------------------------------------------------------------------------
    // 5. MAIN INITIALIZATION
    // -----------------------------------------------------------------------------
    function main() {
        console.log("TriX Executor: Initializing...");

        SettingsModule.init();
        StyleModule.inject();
        UIModule.init();
        ScriptInjectorModule.init(); // Initialize our new feature

        SettingsModule.applyTheme();

        console.log("TriX Executor: Ready!");
    }

    const checkInterval = setInterval(() => {
        if (document.getElementById('canvasA')) {
            clearInterval(checkInterval);
            main();
        }
    }, 200);

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址