// ==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);
})();