Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- (async function WallUtilities() {
- // Invert Y helper
- const invertY = (y) => -y;
- // Coordinate System Mapper
- function getTileFromXY(x, y) {
- const tileX = Math.floor((x - 1) / 20) + 1;
- const tileY = Math.floor((y - 1) / 10) + 1;
- return { x: tileX, y: tileY };
- }
- // --- STATE MANAGEMENT ---
- const protectedChunks = {};
- const renderers = ["KiwiTest"];
- let blueprintLibrary = [];
- let selectedBlueprintIndex = null;
- let isTerminated = false;
- try {
- let myName = "ScriptRunner";
- if (typeof localStorage !== 'undefined' && localStorage.username) {
- myName = localStorage.username;
- }
- if (!renderers.includes(myName)) renderers.push(myName);
- } catch(e) {}
- let activeTool = null;
- let isInitializing = false;
- let currentTargetTile = { x: 0, y: 0 };
- let lastWorldX = 0;
- let lastWorldY = 0;
- let lineStart = null;
- let circleStart = null;
- let boxStart = null;
- let selectionStart = null;
- // --- MOUSE BRUSH DRAW STATE ---
- let shiftHeld = false;
- let altHeld = false;
- let lastDrawX = null;
- let lastDrawY = null;
- let currentColorIndex = 3;
- const colors = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
- // --- COLOR PALETTE MAPPER & FULL RGB PARSER ---
- const colorPalette = {
- 0: '#ffffff', 1: '#3b82f6', 2: '#22c55e', 3: '#06b6d4',
- 4: '#ef4444', 5: '#a855f7', 6: '#f97316', 7: '#94a3b8',
- 8: '#475569', 9: '#60a5fa', 10: '#4ade80', 11: '#22d3ee',
- 12: '#f87171', 13: '#c084fc', 14: '#facc15', 15: '#ffffff'
- };
- function getCanvasColorHex(colorCode) {
- if (colorCode === null || colorCode === undefined) return '#ffffff';
- if (typeof colorCode === 'string') {
- if (colorCode.startsWith('#') || colorCode.startsWith('rgb')) return colorCode;
- if (/^[0-9A-Fa-f]{6}$/.test(colorCode)) return '#' + colorCode;
- }
- if (typeof colorCode === 'object' && 'r' in colorCode && 'g' in colorCode && 'b' in colorCode) {
- const toHex = (n) => Math.max(0, Math.min(255, n)).toString(16).padStart(2, '0');
- return `#${toHex(colorCode.r)}${toHex(colorCode.g)}${toHex(colorCode.b)}`;
- }
- if (typeof colorCode === 'number') {
- if (colorPalette[colorCode]) return colorPalette[colorCode];
- if (colorCode > 15 && colorCode <= 0xFFFFFF) {
- return '#' + colorCode.toString(16).padStart(6, '0');
- }
- }
- return '#ffffff';
- }
- // --- INTERCEPT WRITE TO PROTECT CHUNKS ---
- const originalWriteCharAt = typeof writeCharAt !== 'undefined' ? writeCharAt : undefined;
- window.writeCharAt = function(char, color, wx, wy) {
- if (isTerminated) return originalWriteCharAt ? originalWriteCharAt(char, color, wx, wy) : undefined;
- const worldX = wx;
- const worldY = -wy;
- const tile = getTileFromXY(worldX, worldY);
- const key = `${tile.x},${tile.y}`;
- if (protectedChunks[key]) {
- return 0; // Abort external unauthorized drawing operations inside protected chunks
- }
- return originalWriteCharAt ? originalWriteCharAt(char, color, wx, wy) : undefined;
- };
- // --- CSS STYLES ---
- const style = document.createElement('style');
- style.id = 'wu-styles';
- style.innerHTML = `
- :root {
- --wu-r: 220; --wu-g: 20; --wu-b: 60;
- --wu-hud-a: 0.15; --wu-btn-a: 0.15;
- }
- .wu-glass {
- background: rgba(var(--wu-r), var(--wu-g), var(--wu-b), var(--wu-a, 0.15));
- backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
- border: 1px solid rgba(calc(var(--wu-r) + 35), calc(var(--wu-g) + 130), calc(var(--wu-b) + 90), 0.3);
- box-shadow: 0 4px 30px rgba(var(--wu-r), var(--wu-g), var(--wu-b), 0.1);
- color: #ffcccc; font-family: 'Segoe UI', sans-serif;
- border-radius: 8px; cursor: pointer;
- transition: all 0.2s ease;
- }
- .wu-glass:hover {
- background: rgba(var(--wu-r), var(--wu-g), var(--wu-b), calc(var(--wu-a, 0.15) + 0.15));
- color: #fff;
- }
- #wu-hotbar, #wu-confirm, #wu-info, #wu-theme, #wu-library { --wu-a: var(--wu-hud-a); }
- #wu-btn, #wu-hotbar button, #wu-confirm button, .wu-blueprint-card, #wu-library button { --wu-a: var(--wu-btn-a); }
- #wu-btn { position: fixed; bottom: 20px; left: 29%; transform: translateX(-50%); width: 112px; height: 38px; font-weight: bold; font-size: 17px; z-index: 9999; color: #fff;}
- #wu-hotbar {
- position: fixed; bottom: 75px; left: 50%; transform: translateX(-50%);
- display: none; padding: 6px; gap: 6px; z-index: 9998; flex-direction: row; align-items: center;
- }
- #wu-hotbar.active { display: flex; }
- #wu-hotbar button { padding: 5px 10px; font-size: 12px; font-weight: 600; border-radius: 6px; }
- #wu-hotbar button.active-tool, #wu-library button.active-tool { background: rgba(var(--wu-r), calc(var(--wu-g) + 60), calc(var(--wu-b) + 20), 0.5); border-color: #fff; color: #fff; }
- #wu-confirm {
- position: fixed; bottom: 130px; left: 50%; transform: translateX(-50%);
- display: none; padding: 8px 16px; gap: 12px; z-index: 9998; flex-direction: row; align-items: center; font-size: 13px;
- }
- #wu-confirm.active { display: flex; }
- #wu-confirm button { padding: 4px 8px; background: rgba(var(--wu-r), var(--wu-g), var(--wu-b), 0.2); border-color: rgba(var(--wu-r), var(--wu-g), var(--wu-b), 0.5); color: #fff; font-size: 12px; border-radius: 5px; }
- #wu-info, #wu-theme, #wu-library {
- position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
- display: none; padding: 20px; text-align: center; z-index: 10000; flex-direction: column; gap: 10px; min-width: 250px;
- }
- #wu-info.active, #wu-theme.active, #wu-library.active { display: flex; }
- #wu-info h3, #wu-theme h3, #wu-library h3 { margin: 0; color: #ff8888; }
- #wu-info p, #wu-theme label { margin: 5px 0; font-size: 13px; opacity: 0.9; display: flex; justify-space-between; align-items: center;}
- #wu-theme input[type="range"] { width: 120px; margin-left: 10px; }
- #wu-library { min-width: 385px; max-height: 80vh; overflow-y: auto; }
- .wu-library-header { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid rgba(255,255,255,0.1); padding-bottom: 8px; }
- .wu-library-close { background: none; border: none; color: #ff6666; font-size: 20px; cursor: pointer; font-weight: bold; line-height: 1; }
- .wu-library-close:hover { color: #ff3333; }
- .wu-blueprint-container { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; }
- .wu-blueprint-card { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 8px; border: 1px solid rgba(255,255,255,0.1); border-radius: 6px; background: rgba(0,0,0,0.2); }
- .wu-blueprint-card.selected { border-color: #aaffaa; background: rgba(170,255,170,0.1); }
- .wu-blueprint-meta { flex: 1; min-width: 0; max-width: calc(100% - 110px); text-align: right; font-size: 12px; color: #eee; order: 1; word-wrap: break-word; overflow-wrap: break-word; }
- .wu-preview-canvas { width: 65px; height: 65px; background: #111; border: 2px solid #444; image-rendering: pixelated; border-radius: 4px; flex-shrink: 0; order: 2; }
- .wu-blueprint-delete { background: none; border: none; color: #ff5555; font-size: 16px; cursor: pointer; padding: 4px; display: flex; align-items: center; justify-content: center; border-radius: 4px; transition: background 0.2s; order: 3; flex-shrink: 0; }
- .wu-blueprint-delete:hover { background: rgba(255, 85, 85, 0.2); color: #ff3333; }
- `;
- document.head.appendChild(style);
- // --- HTML ELEMENTS ---
- const wuBtn = document.createElement('button');
- wuBtn.id = 'wu-btn'; wuBtn.className = 'wu-glass'; wuBtn.innerText = 'WallUtils';
- document.body.appendChild(wuBtn);
- const hotbar = document.createElement('div');
- hotbar.id = 'wu-hotbar'; hotbar.className = 'wu-glass';
- hotbar.innerHTML = `
- <button id="btn-draw" class="wu-glass" style="color:#ffaaee;">Free Draw</button>
- <button id="btn-prct" class="wu-glass">Protect area</button>
- <button id="btn-nprt" class="wu-glass">UnProtect area</button>
- <button id="btn-clr" class="wu-glass">Clear area</button>
- <button id="btn-line" class="wu-glass">Draw Line</button>
- <button id="btn-circle" class="wu-glass">Draw Circle</button>
- <button id="btn-box" class="wu-glass">Draw Box</button>
- <button id="btn-save" class="wu-glass" style="color:#aaffaa;">Copy area</button>
- <button id="btn-lib" class="wu-glass" style="color:#ffffaa;">Blueprint library</button>
- <button id="theme-btn" class="wu-glass">UI color</button>
- <button id="btn-info" class="wu-glass">?</button>
- <button id="btn-quit" class="wu-glass" style="color:#ff6666; font-weight:bold;">Quit</button>
- `;
- document.body.appendChild(hotbar);
- const confirmBox = document.createElement('div');
- confirmBox.id = 'wu-confirm'; confirmBox.className = 'wu-glass';
- confirmBox.innerHTML = `<span id="confirm-coords">Tile: 0, 0</span><button id="btn-confirm" class="wu-glass">OK</button>`;
- document.body.appendChild(confirmBox);
- const infoWindow = document.createElement('div');
- infoWindow.id = 'wu-info'; infoWindow.className = 'wu-glass';
- infoWindow.innerHTML = `
- <h3>WallUtilities</h3>
- <p>WallUtilities with Protection Matrix and Full RGB Blueprint updates.</p>
- <p style="font-weight:bold; color:#fff;">v0.5.0 Full Spectrum RGB Edition</p>
- `;
- document.body.appendChild(infoWindow);
- const themeWindow = document.createElement('div');
- themeWindow.id = 'wu-theme'; themeWindow.className = 'wu-glass';
- themeWindow.innerHTML = `
- <h3>UI Theme</h3>
- <label>Red: <input type="range" id="theme-r" min="0" max="255" value="220"></label>
- <label>Green: <input type="range" id="theme-g" min="0" max="255" value="20"></label>
- <label>Blue: <input type="range" id="theme-b" min="0" max="255" value="60"></label>
- <label>UI Opacity: <input type="range" id="theme-hud-a" min="0" max="100" value="15"></label>
- <label>Button Opacity: <input type="range" id="theme-btn-a" min="0" max="100" value="15"></label>
- `;
- document.body.appendChild(themeWindow);
- const libraryWindow = document.createElement('div');
- libraryWindow.id = 'wu-library'; libraryWindow.className = 'wu-glass';
- libraryWindow.innerHTML = `
- <div class="wu-library-header">
- <h3>Blueprint Library</h3>
- <button id="wu-library-close-btn" class="wu-library-close">×</button>
- </div>
- <div style="display: flex; gap: 4px; margin-top: 8px; flex-wrap: wrap;">
- <button id="btn-load" class="wu-glass" style="color:#aaaaff; padding: 4px 6px; font-size: 11px; flex: 1;">Paste</button>
- <button id="btn-flip-h" class="wu-glass" style="color:#ffaaff; padding: 4px 6px; font-size: 11px; flex: 1;">Flip ↔</button>
- <button id="btn-flip-v" class="wu-glass" style="color:#ffaaff; padding: 4px 6px; font-size: 11px; flex: 1;">Flip ↕</button>
- <button id="btn-import" class="wu-glass" style="color:#aaffff; padding: 4px 6px; font-size: 11px; flex: 1;">Import</button>
- <button id="btn-export" class="wu-glass" style="color:#ffbbaa; padding: 4px 6px; font-size: 11px; flex: 1;">Export</button>
- </div>
- <div id="wu-blueprint-list" class="wu-blueprint-container"></div>
- <p style="font-size:11px; opacity:0.5; margin-top:10px;">Select card to activate blueprint, then use Mirror or Paste buttons</p>
- `;
- document.body.appendChild(libraryWindow);
- // --- UI INTERACTIONS ---
- wuBtn.onclick = () => hotbar.classList.toggle('active');
- infoWindow.onclick = () => infoWindow.classList.remove('active');
- themeWindow.onclick = (e) => { if(e.target === themeWindow) themeWindow.classList.remove('active'); };
- libraryWindow.onclick = (e) => { if(e.target === libraryWindow) libraryWindow.classList.remove('active'); };
- document.getElementById('wu-library-close-btn').onclick = (e) => { e.stopPropagation(); libraryWindow.classList.remove('active'); };
- document.getElementById('btn-info').onclick = (e) => { e.stopPropagation(); infoWindow.classList.add('active'); themeWindow.classList.remove('active'); libraryWindow.classList.remove('active'); };
- document.getElementById('theme-btn').onclick = (e) => { e.stopPropagation(); themeWindow.classList.add('active'); infoWindow.classList.remove('active'); libraryWindow.classList.remove('active'); };
- document.getElementById('btn-lib').onclick = (e) => { e.stopPropagation(); updateLibraryHUD(); libraryWindow.classList.add('active'); infoWindow.classList.remove('active'); themeWindow.classList.remove('active'); };
- ['theme-r', 'theme-g', 'theme-b', 'theme-hud-a', 'theme-btn-a'].forEach(id => {
- document.getElementById(id).addEventListener('input', (e) => {
- if (id.endsWith('-a')) {
- const varName = id === 'theme-hud-a' ? '--wu-hud-a' : '--wu-btn-a';
- document.documentElement.style.setProperty(varName, (e.target.value / 100).toString());
- } else {
- const color = id.split('-')[1];
- document.documentElement.style.setProperty(`--wu-${color}`, e.target.value);
- }
- });
- });
- const tools = ['draw', 'prct', 'nprt', 'clr', 'line', 'circle', 'box', 'save', 'load', 'quit'];
- tools.forEach(tool => {
- document.getElementById(`btn-${tool}`).onclick = (e) => {
- e.stopPropagation();
- if (activeTool === tool) {
- activeTool = null; lineStart = null; circleStart = null; boxStart = null; selectionStart = null;
- confirmBox.classList.remove('active');
- document.getElementById(`btn-${tool}`).classList.remove('active-tool');
- } else {
- tools.forEach(t => { const el = document.getElementById(`btn-${t}`); if(el) el.classList.remove('active-tool'); });
- activeTool = tool; lineStart = null; circleStart = null; boxStart = null; selectionStart = null;
- document.getElementById(`btn-${tool}`).classList.add('active-tool');
- if (activeTool === 'draw') confirmBox.classList.remove('active'); else confirmBox.classList.add('active');
- }
- };
- });
- // --- BLUEPRINT MIRROR OPERATIONS ---
- function flipSelectedBlueprintHorizontal() {
- if (selectedBlueprintIndex === null || !blueprintLibrary[selectedBlueprintIndex]) return;
- const bp = blueprintLibrary[selectedBlueprintIndex];
- bp.tiles.forEach(tile => {
- tile.relX = (bp.width - 1) - tile.relX;
- });
- updateLibraryHUD();
- }
- function flipSelectedBlueprintVertical() {
- if (selectedBlueprintIndex === null || !blueprintLibrary[selectedBlueprintIndex]) return;
- const bp = blueprintLibrary[selectedBlueprintIndex];
- bp.tiles.forEach(tile => {
- tile.relY = (bp.height - 1) - tile.relY;
- });
- updateLibraryHUD();
- }
- document.getElementById('btn-flip-h').onclick = (e) => {
- e.stopPropagation();
- if (selectedBlueprintIndex === null || !blueprintLibrary[selectedBlueprintIndex]) {
- alert("Please select a blueprint to flip first.");
- return;
- }
- flipSelectedBlueprintHorizontal();
- };
- document.getElementById('btn-flip-v').onclick = (e) => {
- e.stopPropagation();
- if (selectedBlueprintIndex === null || !blueprintLibrary[selectedBlueprintIndex]) {
- alert("Please select a blueprint to flip first.");
- return;
- }
- flipSelectedBlueprintVertical();
- };
- // --- IMPORT / EXPORT OPERATIONS ---
- document.getElementById('btn-export').onclick = (e) => {
- e.stopPropagation();
- if (selectedBlueprintIndex === null || !blueprintLibrary[selectedBlueprintIndex]) {
- alert("Please select a blueprint from the library list to export first.");
- return;
- }
- const bp = blueprintLibrary[selectedBlueprintIndex];
- navigator.clipboard.writeText(JSON.stringify(bp)).then(() => {
- alert(`Blueprint "${bp.name}" copied to clipboard!`);
- }).catch(err => { console.error("Export failed: ", err); });
- };
- document.getElementById('btn-import').onclick = (e) => {
- e.stopPropagation();
- navigator.clipboard.readText().then(str => {
- if (!str) { alert("Clipboard is empty."); return; }
- let bp;
- try {
- bp = JSON.parse(str);
- if (!bp || !bp.name || !Array.isArray(bp.tiles)) throw new Error("Invalid format");
- } catch(err) {
- const lines = str.split(/\r?\n/); const height = lines.length; let maxWidth = 0; const relativeTiles = [];
- for (let y = 0; y < height; y++) {
- const line = lines[y]; if (line.length > maxWidth) maxWidth = line.length;
- for (let x = 0; x < line.length; x++) {
- const char = line[x]; const relY = (height - 1) - y;
- if (char !== ' ' && char !== '') relativeTiles.push({ relX: x, relY: relY, char: char, color: colors[currentColorIndex] });
- }
- }
- bp = { name: "Raw Text Import", width: maxWidth, height: height, tiles: relativeTiles };
- }
- blueprintLibrary.push(bp); selectedBlueprintIndex = blueprintLibrary.length - 1;
- updateLibraryHUD();
- alert(`Successfully imported: "${bp.name}"`);
- }).catch(err => { alert("Could not access system clipboard."); });
- };
- // --- CORE PROTECTION LOGIC ---
- async function protectChunk(tileX, tileY) {
- tileX = Math.floor(Number(tileX));
- tileY = Math.floor(Number(tileY));
- if (isNaN(tileX) || isNaN(tileY)) return;
- const key = `${tileX},${tileY}`;
- if (protectedChunks[key]) return;
- isInitializing = true;
- const chars = Array.from({length: 10}, () => Array(20).fill(' '));
- const colorsArr = Array.from({length: 10}, () => Array(20).fill(-1));
- const writeQueue = [];
- try {
- for (let cy = 1; cy <= 10; cy++) {
- for (let cx = 1; cx <= 20; cx++) {
- const worldX = (tileX - 1) * 20 + cx;
- const worldY = (tileY - 1) * 10 + cy;
- const invertedY = invertY(worldY);
- let info = null;
- try { info = getCharInfoXY(worldX, invertedY); } catch(e){}
- let currentChar = ' ';
- if (info) {
- if (typeof info === 'string') currentChar = info;
- else if (typeof info === 'object' && typeof info.char === 'string') currentChar = info.char;
- }
- let wrapCX = cx > 20 ? 1 : (cx < 1 ? 20 : cx);
- let wrapCY = cy > 10 ? 1 : (cy < 1 ? 10 : cy);
- let currentColor = -1;
- let apiColor = null;
- try { apiColor = getCharColor(tileX, tileY, wrapCX, wrapCY); } catch(e){}
- if (apiColor !== null && apiColor !== undefined) currentColor = apiColor;
- else if (info && typeof info === 'object' && info.color !== undefined) currentColor = info.color;
- chars[cy-1][cx-1] = currentChar;
- colorsArr[cy-1][cx-1] = currentColor;
- if (currentChar === ' ') {
- chars[cy-1][cx-1] = '.';
- colorsArr[cy-1][cx-1] = 30;
- writeQueue.push({ char: '.', color: 30, x: worldX, y: invertedY });
- }
- }
- }
- } catch(err) { console.error("[WU] Protection trace fault: ", err); }
- protectedChunks[key] = { chars, colors: colorsArr, lastModified: Date.now() };
- const writer = originalWriteCharAt || writeCharAt;
- for (let i = 0; i < writeQueue.length; i += 200) {
- if (isTerminated) return;
- const batch = writeQueue.slice(i, i + 200);
- batch.forEach(item => { try { writer(item.char, item.color, item.x, item.y); } catch(e){} });
- if (i + 200 < writeQueue.length) await new Promise(r => setTimeout(r, 400));
- }
- setTimeout(() => isInitializing = false, 1000);
- }
- // --- BACKGROUND REGENERATION LOOP ---
- const regenInterval = setInterval(async () => {
- if (isInitializing || isTerminated) return;
- let writeQueue = [];
- for (const [key, chunk] of Object.entries(protectedChunks)) {
- if (Date.now() - chunk.lastModified < 1500) continue;
- const [tileX, tileY] = key.split(',').map(Number);
- try {
- for (let cy = 1; cy <= 10; cy++) {
- for (let cx = 1; cx <= 20; cx++) {
- const worldX = (tileX - 1) * 20 + cx;
- const worldY = (tileY - 1) * 10 + cy;
- const invertedY = invertY(worldY);
- let info = null;
- try { info = getCharInfoXY(worldX, invertedY); } catch(e){}
- let currentChar = ' ';
- if (info) {
- if (typeof info === 'string') currentChar = info;
- else if (typeof info === 'object' && typeof info.char === 'string') currentChar = info.char;
- }
- let currentColor = -1;
- let apiColor = null;
- try { apiColor = getCharColor(tileX, tileY, cx, cy); } catch(e){}
- if (apiColor !== null && apiColor !== undefined) currentColor = apiColor;
- else if (info && typeof info === 'object' && info.color !== undefined) currentColor = info.color;
- const savedChar = chunk.chars[cy-1][cx-1];
- const savedColor = chunk.colors[cy-1][cx-1];
- let needsCharRegen = (currentChar !== savedChar);
- let needsColorRegen = (savedColor !== -1 && currentColor !== savedColor);
- if (needsCharRegen || needsColorRegen) {
- const finalChar = needsCharRegen ? savedChar : currentChar;
- const finalColor = needsColorRegen ? savedColor : (savedColor === -1 ? currentColor : savedColor);
- writeQueue.push({ char: finalChar, color: finalColor, x: worldX, y: invertedY });
- }
- }
- }
- } catch(e) {}
- }
- const writer = originalWriteCharAt || writeCharAt;
- for (let i = 0; i < writeQueue.length; i += 200) {
- if (isTerminated) return;
- const batch = writeQueue.slice(i, i + 200);
- batch.forEach(item => { try { writer(item.char, item.color, item.x, item.y); } catch(e){} });
- if (i + 200 < writeQueue.length) await new Promise(r => setTimeout(r, 400));
- }
- }, 1000);
- // --- CROSSHAIR FREE DRAW ENGINE INTERPOLATION ---
- function drawAtCrosshairCoordinate() {
- const x = Math.floor(lastWorldX);
- const y = Math.floor(lastWorldY);
- const activeColor = colors[currentColorIndex];
- if (lastDrawX !== null && lastDrawY !== null) {
- const dx = x - lastDrawX;
- const dy = y - lastDrawY;
- const steps = Math.max(Math.abs(dx), Math.abs(dy));
- for (let i = 0; i <= steps; i++) {
- const xi = lastDrawX + (steps === 0 ? 0 : Math.round(dx * (i / steps)));
- const yi = lastDrawY + (steps === 0 ? 0 : Math.round(dy * (i / steps)));
- try { writeCharAt('█', activeColor, xi, invertY(yi)); } catch(e) {}
- }
- } else {
- try { writeCharAt('█', activeColor, x, invertY(y)); } catch(e) {}
- }
- lastDrawX = x;
- lastDrawY = y;
- }
- // --- KEY LISTENERS ---
- const keydownHandler = (e) => {
- if (e.key === "Shift") { shiftHeld = true; if (activeTool === 'draw') drawAtCrosshairCoordinate(); }
- if (e.key === "Alt") altHeld = true;
- };
- const keyupHandler = (e) => {
- if (e.key === "Shift") { shiftHeld = false; lastDrawX = null; lastDrawY = null; }
- if (e.key === "Alt") altHeld = false;
- };
- // --- MOUSE TRACKING & LOOK LOGIC ---
- const mouseMoveHandler = (e) => {
- if (isTerminated) return;
- let mouseX, mouseY;
- if (typeof cursor !== 'undefined' && typeof cursor.x === 'number' && typeof cursor.y === 'number') {
- mouseX = cursor.x;
- mouseY = invertY(cursor.y);
- } else if (typeof w !== 'undefined' && w.mouse && typeof w.mouse.x === 'number') {
- mouseX = w.mouse.x;
- mouseY = w.mouse.y;
- } else {
- mouseX = e.clientX;
- mouseY = e.clientY;
- }
- if (!isNaN(mouseX) && !isNaN(mouseY)) { lastWorldX = mouseX; lastWorldY = mouseY; }
- if (activeTool === 'draw' && shiftHeld) drawAtCrosshairCoordinate();
- };
- const wheelHandler = (e) => {
- if (activeTool === 'draw' && altHeld) {
- e.preventDefault();
- if (e.deltaY < 0) currentColorIndex = (currentColorIndex + 1) % colors.length;
- else currentColorIndex = (currentColorIndex - 1 + colors.length) % colors.length;
- }
- };
- document.addEventListener('keydown', keydownHandler);
- document.addEventListener('keyup', keyupHandler);
- document.addEventListener('mousemove', mouseMoveHandler);
- document.addEventListener('wheel', wheelHandler, { passive: false });
- // Coordinate Preview Update Loop
- const previewInterval = setInterval(() => {
- if (isTerminated || !activeTool) return;
- if (activeTool === 'draw') return;
- if (activeTool === 'quit') { document.getElementById('confirm-coords').innerText = `Click Confirm to exit.`; return; }
- if (activeTool === 'clr') { document.getElementById('confirm-coords').innerText = `Click Confirm to start selection box clear.`; return; }
- if (activeTool === 'line' && lineStart) { document.getElementById('confirm-coords').innerText = `Line Start: ${lineStart.x}, ${lineStart.y} | Select End...`; return; }
- if (activeTool === 'circle' && circleStart) { document.getElementById('confirm-coords').innerText = `Circle Center: ${circleStart.x}, ${circleStart.y} | Select Edge...`; return; }
- if (activeTool === 'box' && boxStart) { document.getElementById('confirm-coords').innerText = `Box Corner 1: ${boxStart.x}, ${boxStart.y} | Select Corner 2...`; return; }
- if (activeTool === 'save' && selectionStart) { document.getElementById('confirm-coords').innerText = `Copier Pos 1: ${selectionStart.x}, ${selectionStart.y} | Select Pos 2...`; return; }
- if (activeTool === 'load') {
- if (selectedBlueprintIndex === null || !blueprintLibrary[selectedBlueprintIndex]) {
- document.getElementById('confirm-coords').innerText = `Error: Select a blueprint from Library first.`;
- } else {
- document.getElementById('confirm-coords').innerText = `Stamp Active Slot #${selectedBlueprintIndex + 1} Anchor: ${Math.floor(lastWorldX)}, ${Math.floor(lastWorldY)}`;
- }
- return;
- }
- const tile = getTileFromXY(lastWorldX, lastWorldY);
- if (tile && !isNaN(tile.x) && !isNaN(tile.y)) {
- currentTargetTile = { x: tile.x, y: tile.y };
- document.getElementById('confirm-coords').innerText = `Tile: ${currentTargetTile.x}, ${currentTargetTile.y} | Pos: ${Math.floor(lastWorldX)}, ${Math.floor(lastWorldY)}`;
- }
- }, 250);
- // --- CONFIRM BUTTON LOGIC ---
- document.getElementById('btn-confirm').onclick = async () => {
- if (!activeTool || isTerminated) return;
- const { x, y } = currentTargetTile;
- const key = `${x},${y}`;
- if (activeTool === 'quit') {
- terminateScript(); return;
- } else if (activeTool === 'prct') {
- await protectChunk(x, y);
- if (typeof w !== 'undefined' && w.showToast) w.showToast(`Tile ${key} protected successfully.`, 2500);
- } else if (activeTool === 'nprt') {
- if (protectedChunks[key]) delete protectedChunks[key];
- if (typeof w !== 'undefined' && w.showToast) w.showToast(`Tile ${key} unprotected successfully.`, 2500);
- } else if (activeTool === 'clr') {
- if (typeof RegionSelection !== 'undefined') {
- const sel = new RegionSelection();
- sel.onSelection(function(sx, sy, ex, ey) {
- let c = 0; const minX = Math.min(sx, ex); const maxX = Math.max(sx, ex); const minY = Math.min(sy, ey); const maxY = Math.max(sy, ey);
- for (let y = minY; y <= maxY; y++) {
- for (let x = minX; x <= maxX; x++) {
- let info = null; try { info = getCharInfoXY(x, y); } catch(e){}
- let currentChar = (info && typeof info === 'string') ? info : (info?.char || ' ');
- if (currentChar !== " ") { if (writeCharAt(" ", 0, x, y) !== 0) c += 1; }
- }
- }
- if (typeof w !== 'undefined' && w.showToast) w.showToast(`Area cleared successfully. Chars: ${c}`, 4500);
- });
- sel.startSelection();
- }
- activeTool = null; confirmBox.classList.remove('active');
- tools.forEach(t => { const el = document.getElementById(`btn-${t}`); if(el) el.classList.remove('active-tool'); });
- return;
- } else if (activeTool === 'line') {
- if (!lineStart) { lineStart = { x: Math.floor(lastWorldX), y: Math.floor(lastWorldY) }; }
- else { const lineEnd = { x: Math.floor(lastWorldX), y: Math.floor(lastWorldY) };
- await drawLine(lineStart.x, lineStart.y, lineEnd.x, lineEnd.y); lineStart = null; }
- return;
- } else if (activeTool === 'circle') {
- if (!circleStart) { circleStart = { x: Math.floor(lastWorldX), y: Math.floor(lastWorldY) }; }
- else { const circleEnd = { x: Math.floor(lastWorldX), y: Math.floor(lastWorldY) };
- await drawCircle(circleStart.x, circleStart.y, circleEnd.x, circleEnd.y); circleStart = null; }
- return;
- } else if (activeTool === 'box') {
- if (!boxStart) { boxStart = { x: Math.floor(lastWorldX), y: Math.floor(lastWorldY) }; }
- else { const boxEnd = { x: Math.floor(lastWorldX), y: Math.floor(lastWorldY) };
- await drawBoxOutline(boxStart.x, boxStart.y, boxEnd.x, boxEnd.y); boxStart = null; }
- return;
- } else if (activeTool === 'save') {
- if (!selectionStart) { selectionStart = { x: Math.floor(lastWorldX), y: Math.floor(lastWorldY) }; }
- else {
- const selectionEnd = { x: Math.floor(lastWorldX), y: Math.floor(lastWorldY) };
- saveRegionToBlueprint(selectionStart.x, selectionStart.y, selectionEnd.x, selectionEnd.y);
- selectionStart = null; activeTool = null; confirmBox.classList.remove('active');
- tools.forEach(t => { const el = document.getElementById(`btn-${t}`); if(el) el.classList.remove('active-tool'); });
- }
- return;
- } else if (activeTool === 'load') {
- if (selectedBlueprintIndex !== null && blueprintLibrary[selectedBlueprintIndex]) {
- await stampBlueprint(Math.floor(lastWorldX), Math.floor(lastWorldY));
- }
- activeTool = null; confirmBox.classList.remove('active');
- tools.forEach(t => { const el = document.getElementById(`btn-${t}`); if(el) el.classList.remove('active-tool'); });
- return;
- }
- activeTool = null; confirmBox.classList.remove('active');
- tools.forEach(t => { const el = document.getElementById(`btn-${t}`); if(el) el.classList.remove('active-tool'); });
- };
- // --- HUD LIBRARY PREVIEW GENERATOR ---
- function updateLibraryHUD() {
- const container = document.getElementById('wu-blueprint-list');
- container.innerHTML = '';
- if (blueprintLibrary.length === 0) { container.innerHTML = '<div style="font-size:12px; opacity:0.5; padding:10px;">Library is empty.</div>'; return; }
- blueprintLibrary.forEach((bp, index) => {
- const card = document.createElement('div'); card.className = `wu-blueprint-card ${selectedBlueprintIndex === index ? 'selected' : ''}`; card.dataset.index = index;
- const canvas = document.createElement('canvas'); canvas.className = 'wu-preview-canvas';
- const rowHeightScale = 2; canvas.width = Math.min(bp.width, 120); canvas.height = Math.min(bp.height * rowHeightScale, 100);
- const ctx = canvas.getContext('2d'); ctx.fillStyle = '#111111'; ctx.fillRect(0, 0, canvas.width, canvas.height);
- bp.tiles.forEach(t => {
- const invertedRelY = (bp.height - 1) - t.relY; const scaledY = invertedRelY * rowHeightScale;
- if (t.relX < canvas.width && scaledY < canvas.height) {
- ctx.fillStyle = (t.char !== ' ' && t.char !== '') ? getCanvasColorHex(t.color) : '#333333';
- ctx.fillRect(t.relX, scaledY, 1, rowHeightScale);
- }
- });
- const meta = document.createElement('div'); meta.className = 'wu-blueprint-meta';
- meta.innerHTML = `<strong>${bp.name}</strong><br>Size: ${bp.width}×${bp.height}<br>chars: ${bp.tiles.length}`;
- const deleteBtn = document.createElement('button'); deleteBtn.className = 'wu-blueprint-delete'; deleteBtn.innerHTML = 'X';
- deleteBtn.onclick = (e) => {
- e.stopPropagation();
- const currentIndex = parseInt(card.dataset.index, 10); blueprintLibrary.splice(currentIndex, 1);
- if (selectedBlueprintIndex === currentIndex) selectedBlueprintIndex = blueprintLibrary.length > 0 ? 0 : null;
- else if (selectedBlueprintIndex > currentIndex) selectedBlueprintIndex--;
- updateLibraryHUD();
- };
- card.appendChild(meta); card.appendChild(canvas); card.appendChild(deleteBtn);
- card.onclick = (e) => { e.stopPropagation(); selectedBlueprintIndex = parseInt(card.dataset.index, 10); updateLibraryHUD(); };
- container.appendChild(card);
- });
- }
- // --- BLUEPRINT COPIER ENGINE ---
- function saveRegionToBlueprint(x0, y0, x1, y1) {
- const name = prompt("Enter a name for this blueprint:", "New Blueprint");
- if (name === null) return;
- const minX = Math.min(x0, x1); const maxX = Math.max(x0, x1);
- const minY = Math.min(y0, y1); const maxY = Math.max(y0, y1);
- const relativeTiles = [];
- const width = (maxX - minX) + 1;
- const height = (maxY - minY) + 1;
- for (let y = minY; y <= maxY; y++) {
- for (let x = minX; x <= maxX; x++) {
- const invertedY = invertY(y);
- let info = null;
- try { info = getCharInfoXY(x, invertedY); } catch(e) {}
- let currentChar = (info && typeof info === 'string') ? info : (info?.char || ' ');
- const tile = getTileFromXY(x, y);
- const cx = ((x - 1) % 20 + 20) % 20 + 1;
- const cy = ((y - 1) % 10 + 10) % 10 + 1;
- let currentColor = null;
- try {
- let apiColor = getCharColor(tile.x, tile.y, cx, cy);
- if (apiColor !== null && apiColor !== undefined) {
- currentColor = apiColor;
- } else if (info && typeof info === 'object' && info.color !== undefined) {
- currentColor = info.color;
- }
- } catch(e) {
- if (info && typeof info === 'object' && info.color !== undefined) {
- currentColor = info.color;
- }
- }
- if (currentColor === null || currentColor === undefined) {
- currentColor = colors[currentColorIndex] ?? 0;
- }
- if (currentChar === ' ' && (currentColor === 0 || currentColor === -1 || currentColor === '#ffffff')) continue;
- relativeTiles.push({
- relX: x - minX,
- relY: y - minY,
- char: currentChar,
- color: currentColor
- });
- }
- }
- blueprintLibrary.push({ name, width, height, tiles: relativeTiles });
- selectedBlueprintIndex = blueprintLibrary.length - 1;
- updateLibraryHUD();
- }
- // --- BLUEPRINT STAMPER ENGINE ---
- async function stampBlueprint(anchorX, anchorY) {
- if (selectedBlueprintIndex === null || !blueprintLibrary[selectedBlueprintIndex]) return;
- const activeBlueprint = blueprintLibrary[selectedBlueprintIndex];
- let writeQueue = [];
- activeBlueprint.tiles.forEach(tile => {
- writeQueue.push({
- char: tile.char,
- color: tile.color ?? 0,
- x: anchorX + tile.relX,
- y: invertY(anchorY + tile.relY)
- });
- });
- const writer = originalWriteCharAt || writeCharAt;
- for (let i = 0; i < writeQueue.length; i += 200) {
- if (isTerminated) return;
- const batch = writeQueue.slice(i, i + 200);
- batch.forEach(item => { try { writer(item.char, item.color, item.x, item.y); } catch(e){} });
- if (i + 200 < writeQueue.length) await new Promise(r => setTimeout(r, 400));
- }
- }
- // --- LINE DRAWING ALGORITHM ---
- async function drawLine(x0, y0, x1, y1) {
- const dx = Math.abs(x1 - x0);
- const dy = Math.abs(y1 - y0); const sx = (x0 < x1) ? 1 : -1;
- const sy = (y0 < y1) ? 1 : -1;
- let err = dx - dy; let queue = [];
- while (true) {
- queue.push({ x: x0, y: y0 });
- if (x0 === x1 && y0 === y1) break;
- const e2 = 2 * err;
- if (e2 > -dy) { err -= dy; x0 += sx; } if (e2 < dx) { err += dx; y0 += sy; }
- }
- await batchWrite(queue, '█', colors[currentColorIndex]);
- }
- // --- CIRCLE DRAWING ALGORITHM ---
- async function drawCircle(cx, cy, ex, ey) {
- const r = Math.round(Math.sqrt((ex - cx) ** 2 + (ey - cy) ** 2));
- if (r === 0) { await batchWrite([{ x: cx, y: cy }], '█', colors[currentColorIndex]); return; }
- let queue = []; let x = r; let y = 0;
- let err = 0;
- while (x >= y) {
- queue.push({ x: cx + x, y: cy + y }, { x: cx + y, y: cy + x }, { x: cx - y, y: cy + x }, { x: cx - x, y: cy + y },
- { x: cx - x, y: cy - y }, { x: cx - y, y: cy - x }, { x: cx + y, y: cy - x }, { x: cx + x, y: cy - y });
- if (err <= 0) { y += 1; err += 2 * y + 1; }
- else if (err > 0) { x -= 1; err -= 2 * x + 1; }
- }
- const uniqueQueue = Array.from(new Set(queue.map(p => `${p.x},${p.y}`))).map(s => { const [px, py] = s.split(',').map(Number); return { x: px, y: py }; });
- await batchWrite(uniqueQueue, '█', colors[currentColorIndex]);
- }
- // --- BOX OUTLINE DRAWING ALGORITHM ---
- async function drawBoxOutline(x0, y0, x1, y1) {
- const minX = Math.min(x0, x1);
- const maxX = Math.max(x0, x1); const minY = Math.min(y0, y1); const maxY = Math.max(y0, y1);
- let queue = [];
- for (let x = minX; x <= maxX; x++) { queue.push({ x: x, y: minY }, { x: x, y: maxY }); }
- for (let y = minY; y <= maxY; y++) { queue.push({ x: minX, y: y }, { x: maxX, y: y }); }
- const uniqueQueue = Array.from(new Set(queue.map(p => `${p.x},${p.y}`))).map(s => { const [px, py] = s.split(',').map(Number); return { x: px, y: py }; });
- await batchWrite(uniqueQueue, '█', colors[currentColorIndex]);
- }
- // --- BATCH WRITER HELPER ---
- async function batchWrite(queue, char, color) {
- for (let i = 0; i < queue.length; i += 200) {
- if (isTerminated) return;
- queue.slice(i, i + 200).forEach(p => { try { writeCharAt(char, color, p.x, invertY(p.y)); } catch(e){} });
- if (i + 200 < queue.length) await new Promise(r => setTimeout(r, 400));
- }
- }
- // --- TERMINATION ENGINE ---
- function terminateScript() {
- isTerminated = true;
- clearInterval(previewInterval);
- clearInterval(regenInterval);
- blueprintLibrary = []; selectedBlueprintIndex = null;
- document.removeEventListener('keydown', keydownHandler);
- document.removeEventListener('keyup', keyupHandler);
- document.removeEventListener('mousemove', mouseMoveHandler);
- document.removeEventListener('wheel', wheelHandler);
- if (originalWriteCharAt) window.writeCharAt = originalWriteCharAt;
- ['wu-btn', 'wu-hotbar', 'wu-confirm', 'wu-info', 'wu-theme', 'wu-library', 'wu-styles'].forEach(id => {
- const element = document.getElementById(id); if (element) element.remove();
- });
- console.log("%c[WallUtilities] Script successfully exited.", "color: #ff6666");
- }
- })();
Advertisement
Comments
-
- Multi-blueprint saving & loading added.
-
- btw, when you run this, a button with "wu" on it will appear in the lower right corner, click it to run.
-
- The logic for the clear button is not chunk-based clearing anymore. It's selection box clearing now.
-
- Added slider controls to the UI color menu to control the transparency of both the hud & buttons separately.
-
- oh my god do you realize that you're feeding big J
-
- Fixed the colors not correctly pasting when pasting from the blueprint library.
Add Comment
Please, Sign In to add comment