smoothretro1982

WallUtils by KiwiTest, modified by DG_ (w.i.p)

Jun 16th, 2026 (edited)
213
0
Never
6
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 42.19 KB | None | 0 0
  1. (async function WallUtilities() {
  2. // Invert Y helper
  3. const invertY = (y) => -y;
  4.  
  5. // Coordinate System Mapper
  6. function getTileFromXY(x, y) {
  7. const tileX = Math.floor((x - 1) / 20) + 1;
  8. const tileY = Math.floor((y - 1) / 10) + 1;
  9. return { x: tileX, y: tileY };
  10. }
  11.  
  12. // --- STATE MANAGEMENT ---
  13. const protectedChunks = {};
  14. const renderers = ["KiwiTest"];
  15. let blueprintLibrary = [];
  16. let selectedBlueprintIndex = null;
  17. let isTerminated = false;
  18.  
  19. try {
  20. let myName = "ScriptRunner";
  21. if (typeof localStorage !== 'undefined' && localStorage.username) {
  22. myName = localStorage.username;
  23. }
  24. if (!renderers.includes(myName)) renderers.push(myName);
  25. } catch(e) {}
  26.  
  27. let activeTool = null;
  28. let isInitializing = false;
  29. let currentTargetTile = { x: 0, y: 0 };
  30. let lastWorldX = 0;
  31. let lastWorldY = 0;
  32. let lineStart = null;
  33. let circleStart = null;
  34. let boxStart = null;
  35. let selectionStart = null;
  36.  
  37. // --- MOUSE BRUSH DRAW STATE ---
  38. let shiftHeld = false;
  39. let altHeld = false;
  40. let lastDrawX = null;
  41. let lastDrawY = null;
  42. let currentColorIndex = 3;
  43. const colors = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
  44.  
  45. // --- COLOR PALETTE MAPPER & FULL RGB PARSER ---
  46. const colorPalette = {
  47. 0: '#ffffff', 1: '#3b82f6', 2: '#22c55e', 3: '#06b6d4',
  48. 4: '#ef4444', 5: '#a855f7', 6: '#f97316', 7: '#94a3b8',
  49. 8: '#475569', 9: '#60a5fa', 10: '#4ade80', 11: '#22d3ee',
  50. 12: '#f87171', 13: '#c084fc', 14: '#facc15', 15: '#ffffff'
  51. };
  52.  
  53. function getCanvasColorHex(colorCode) {
  54. if (colorCode === null || colorCode === undefined) return '#ffffff';
  55.  
  56. if (typeof colorCode === 'string') {
  57. if (colorCode.startsWith('#') || colorCode.startsWith('rgb')) return colorCode;
  58. if (/^[0-9A-Fa-f]{6}$/.test(colorCode)) return '#' + colorCode;
  59. }
  60.  
  61. if (typeof colorCode === 'object' && 'r' in colorCode && 'g' in colorCode && 'b' in colorCode) {
  62. const toHex = (n) => Math.max(0, Math.min(255, n)).toString(16).padStart(2, '0');
  63. return `#${toHex(colorCode.r)}${toHex(colorCode.g)}${toHex(colorCode.b)}`;
  64. }
  65.  
  66. if (typeof colorCode === 'number') {
  67. if (colorPalette[colorCode]) return colorPalette[colorCode];
  68. if (colorCode > 15 && colorCode <= 0xFFFFFF) {
  69. return '#' + colorCode.toString(16).padStart(6, '0');
  70. }
  71. }
  72.  
  73. return '#ffffff';
  74. }
  75.  
  76. // --- INTERCEPT WRITE TO PROTECT CHUNKS ---
  77. const originalWriteCharAt = typeof writeCharAt !== 'undefined' ? writeCharAt : undefined;
  78. window.writeCharAt = function(char, color, wx, wy) {
  79. if (isTerminated) return originalWriteCharAt ? originalWriteCharAt(char, color, wx, wy) : undefined;
  80.  
  81. const worldX = wx;
  82. const worldY = -wy;
  83. const tile = getTileFromXY(worldX, worldY);
  84. const key = `${tile.x},${tile.y}`;
  85. if (protectedChunks[key]) {
  86. return 0; // Abort external unauthorized drawing operations inside protected chunks
  87. }
  88. return originalWriteCharAt ? originalWriteCharAt(char, color, wx, wy) : undefined;
  89. };
  90.  
  91. // --- CSS STYLES ---
  92. const style = document.createElement('style');
  93. style.id = 'wu-styles';
  94. style.innerHTML = `
  95. :root {
  96. --wu-r: 220; --wu-g: 20; --wu-b: 60;
  97. --wu-hud-a: 0.15; --wu-btn-a: 0.15;
  98. }
  99. .wu-glass {
  100. background: rgba(var(--wu-r), var(--wu-g), var(--wu-b), var(--wu-a, 0.15));
  101. backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
  102. border: 1px solid rgba(calc(var(--wu-r) + 35), calc(var(--wu-g) + 130), calc(var(--wu-b) + 90), 0.3);
  103. box-shadow: 0 4px 30px rgba(var(--wu-r), var(--wu-g), var(--wu-b), 0.1);
  104. color: #ffcccc; font-family: 'Segoe UI', sans-serif;
  105. border-radius: 8px; cursor: pointer;
  106. transition: all 0.2s ease;
  107. }
  108. .wu-glass:hover {
  109. background: rgba(var(--wu-r), var(--wu-g), var(--wu-b), calc(var(--wu-a, 0.15) + 0.15));
  110. color: #fff;
  111. }
  112. #wu-hotbar, #wu-confirm, #wu-info, #wu-theme, #wu-library { --wu-a: var(--wu-hud-a); }
  113. #wu-btn, #wu-hotbar button, #wu-confirm button, .wu-blueprint-card, #wu-library button { --wu-a: var(--wu-btn-a); }
  114. #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;}
  115.  
  116. #wu-hotbar {
  117. position: fixed; bottom: 75px; left: 50%; transform: translateX(-50%);
  118. display: none; padding: 6px; gap: 6px; z-index: 9998; flex-direction: row; align-items: center;
  119. }
  120. #wu-hotbar.active { display: flex; }
  121. #wu-hotbar button { padding: 5px 10px; font-size: 12px; font-weight: 600; border-radius: 6px; }
  122. #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; }
  123. #wu-confirm {
  124. position: fixed; bottom: 130px; left: 50%; transform: translateX(-50%);
  125. display: none; padding: 8px 16px; gap: 12px; z-index: 9998; flex-direction: row; align-items: center; font-size: 13px;
  126. }
  127. #wu-confirm.active { display: flex; }
  128. #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; }
  129. #wu-info, #wu-theme, #wu-library {
  130. position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
  131. display: none; padding: 20px; text-align: center; z-index: 10000; flex-direction: column; gap: 10px; min-width: 250px;
  132. }
  133. #wu-info.active, #wu-theme.active, #wu-library.active { display: flex; }
  134. #wu-info h3, #wu-theme h3, #wu-library h3 { margin: 0; color: #ff8888; }
  135. #wu-info p, #wu-theme label { margin: 5px 0; font-size: 13px; opacity: 0.9; display: flex; justify-space-between; align-items: center;}
  136. #wu-theme input[type="range"] { width: 120px; margin-left: 10px; }
  137. #wu-library { min-width: 385px; max-height: 80vh; overflow-y: auto; }
  138. .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; }
  139. .wu-library-close { background: none; border: none; color: #ff6666; font-size: 20px; cursor: pointer; font-weight: bold; line-height: 1; }
  140. .wu-library-close:hover { color: #ff3333; }
  141. .wu-blueprint-container { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; }
  142. .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); }
  143. .wu-blueprint-card.selected { border-color: #aaffaa; background: rgba(170,255,170,0.1); }
  144. .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; }
  145. .wu-preview-canvas { width: 65px; height: 65px; background: #111; border: 2px solid #444; image-rendering: pixelated; border-radius: 4px; flex-shrink: 0; order: 2; }
  146. .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; }
  147. .wu-blueprint-delete:hover { background: rgba(255, 85, 85, 0.2); color: #ff3333; }
  148. `;
  149. document.head.appendChild(style);
  150.  
  151. // --- HTML ELEMENTS ---
  152. const wuBtn = document.createElement('button');
  153. wuBtn.id = 'wu-btn'; wuBtn.className = 'wu-glass'; wuBtn.innerText = 'WallUtils';
  154. document.body.appendChild(wuBtn);
  155.  
  156. const hotbar = document.createElement('div');
  157. hotbar.id = 'wu-hotbar'; hotbar.className = 'wu-glass';
  158. hotbar.innerHTML = `
  159. <button id="btn-draw" class="wu-glass" style="color:#ffaaee;">Free Draw</button>
  160. <button id="btn-prct" class="wu-glass">Protect area</button>
  161. <button id="btn-nprt" class="wu-glass">UnProtect area</button>
  162. <button id="btn-clr" class="wu-glass">Clear area</button>
  163. <button id="btn-line" class="wu-glass">Draw Line</button>
  164. <button id="btn-circle" class="wu-glass">Draw Circle</button>
  165. <button id="btn-box" class="wu-glass">Draw Box</button>
  166. <button id="btn-save" class="wu-glass" style="color:#aaffaa;">Copy area</button>
  167. <button id="btn-lib" class="wu-glass" style="color:#ffffaa;">Blueprint library</button>
  168. <button id="theme-btn" class="wu-glass">UI color</button>
  169. <button id="btn-info" class="wu-glass">?</button>
  170. <button id="btn-quit" class="wu-glass" style="color:#ff6666; font-weight:bold;">Quit</button>
  171. `;
  172. document.body.appendChild(hotbar);
  173.  
  174. const confirmBox = document.createElement('div');
  175. confirmBox.id = 'wu-confirm'; confirmBox.className = 'wu-glass';
  176. confirmBox.innerHTML = `<span id="confirm-coords">Tile: 0, 0</span><button id="btn-confirm" class="wu-glass">OK</button>`;
  177. document.body.appendChild(confirmBox);
  178.  
  179. const infoWindow = document.createElement('div');
  180. infoWindow.id = 'wu-info'; infoWindow.className = 'wu-glass';
  181. infoWindow.innerHTML = `
  182. <h3>WallUtilities</h3>
  183. <p>WallUtilities with Protection Matrix and Full RGB Blueprint updates.</p>
  184. <p style="font-weight:bold; color:#fff;">v0.5.0 Full Spectrum RGB Edition</p>
  185. `;
  186. document.body.appendChild(infoWindow);
  187.  
  188. const themeWindow = document.createElement('div');
  189. themeWindow.id = 'wu-theme'; themeWindow.className = 'wu-glass';
  190. themeWindow.innerHTML = `
  191. <h3>UI Theme</h3>
  192. <label>Red: <input type="range" id="theme-r" min="0" max="255" value="220"></label>
  193. <label>Green: <input type="range" id="theme-g" min="0" max="255" value="20"></label>
  194. <label>Blue: <input type="range" id="theme-b" min="0" max="255" value="60"></label>
  195. <label>UI Opacity: <input type="range" id="theme-hud-a" min="0" max="100" value="15"></label>
  196. <label>Button Opacity: <input type="range" id="theme-btn-a" min="0" max="100" value="15"></label>
  197. `;
  198. document.body.appendChild(themeWindow);
  199.  
  200. const libraryWindow = document.createElement('div');
  201. libraryWindow.id = 'wu-library'; libraryWindow.className = 'wu-glass';
  202. libraryWindow.innerHTML = `
  203. <div class="wu-library-header">
  204. <h3>Blueprint Library</h3>
  205. <button id="wu-library-close-btn" class="wu-library-close">&times;</button>
  206. </div>
  207. <div style="display: flex; gap: 4px; margin-top: 8px; flex-wrap: wrap;">
  208. <button id="btn-load" class="wu-glass" style="color:#aaaaff; padding: 4px 6px; font-size: 11px; flex: 1;">Paste</button>
  209. <button id="btn-flip-h" class="wu-glass" style="color:#ffaaff; padding: 4px 6px; font-size: 11px; flex: 1;">Flip ↔</button>
  210. <button id="btn-flip-v" class="wu-glass" style="color:#ffaaff; padding: 4px 6px; font-size: 11px; flex: 1;">Flip ↕</button>
  211. <button id="btn-import" class="wu-glass" style="color:#aaffff; padding: 4px 6px; font-size: 11px; flex: 1;">Import</button>
  212. <button id="btn-export" class="wu-glass" style="color:#ffbbaa; padding: 4px 6px; font-size: 11px; flex: 1;">Export</button>
  213. </div>
  214. <div id="wu-blueprint-list" class="wu-blueprint-container"></div>
  215. <p style="font-size:11px; opacity:0.5; margin-top:10px;">Select card to activate blueprint, then use Mirror or Paste buttons</p>
  216. `;
  217. document.body.appendChild(libraryWindow);
  218.  
  219. // --- UI INTERACTIONS ---
  220. wuBtn.onclick = () => hotbar.classList.toggle('active');
  221. infoWindow.onclick = () => infoWindow.classList.remove('active');
  222. themeWindow.onclick = (e) => { if(e.target === themeWindow) themeWindow.classList.remove('active'); };
  223. libraryWindow.onclick = (e) => { if(e.target === libraryWindow) libraryWindow.classList.remove('active'); };
  224. document.getElementById('wu-library-close-btn').onclick = (e) => { e.stopPropagation(); libraryWindow.classList.remove('active'); };
  225. document.getElementById('btn-info').onclick = (e) => { e.stopPropagation(); infoWindow.classList.add('active'); themeWindow.classList.remove('active'); libraryWindow.classList.remove('active'); };
  226. document.getElementById('theme-btn').onclick = (e) => { e.stopPropagation(); themeWindow.classList.add('active'); infoWindow.classList.remove('active'); libraryWindow.classList.remove('active'); };
  227. document.getElementById('btn-lib').onclick = (e) => { e.stopPropagation(); updateLibraryHUD(); libraryWindow.classList.add('active'); infoWindow.classList.remove('active'); themeWindow.classList.remove('active'); };
  228.  
  229. ['theme-r', 'theme-g', 'theme-b', 'theme-hud-a', 'theme-btn-a'].forEach(id => {
  230. document.getElementById(id).addEventListener('input', (e) => {
  231. if (id.endsWith('-a')) {
  232. const varName = id === 'theme-hud-a' ? '--wu-hud-a' : '--wu-btn-a';
  233. document.documentElement.style.setProperty(varName, (e.target.value / 100).toString());
  234. } else {
  235. const color = id.split('-')[1];
  236. document.documentElement.style.setProperty(`--wu-${color}`, e.target.value);
  237. }
  238. });
  239. });
  240.  
  241. const tools = ['draw', 'prct', 'nprt', 'clr', 'line', 'circle', 'box', 'save', 'load', 'quit'];
  242. tools.forEach(tool => {
  243. document.getElementById(`btn-${tool}`).onclick = (e) => {
  244. e.stopPropagation();
  245. if (activeTool === tool) {
  246. activeTool = null; lineStart = null; circleStart = null; boxStart = null; selectionStart = null;
  247. confirmBox.classList.remove('active');
  248. document.getElementById(`btn-${tool}`).classList.remove('active-tool');
  249. } else {
  250. tools.forEach(t => { const el = document.getElementById(`btn-${t}`); if(el) el.classList.remove('active-tool'); });
  251. activeTool = tool; lineStart = null; circleStart = null; boxStart = null; selectionStart = null;
  252. document.getElementById(`btn-${tool}`).classList.add('active-tool');
  253. if (activeTool === 'draw') confirmBox.classList.remove('active'); else confirmBox.classList.add('active');
  254. }
  255. };
  256. });
  257.  
  258. // --- BLUEPRINT MIRROR OPERATIONS ---
  259. function flipSelectedBlueprintHorizontal() {
  260. if (selectedBlueprintIndex === null || !blueprintLibrary[selectedBlueprintIndex]) return;
  261. const bp = blueprintLibrary[selectedBlueprintIndex];
  262. bp.tiles.forEach(tile => {
  263. tile.relX = (bp.width - 1) - tile.relX;
  264. });
  265. updateLibraryHUD();
  266. }
  267.  
  268. function flipSelectedBlueprintVertical() {
  269. if (selectedBlueprintIndex === null || !blueprintLibrary[selectedBlueprintIndex]) return;
  270. const bp = blueprintLibrary[selectedBlueprintIndex];
  271. bp.tiles.forEach(tile => {
  272. tile.relY = (bp.height - 1) - tile.relY;
  273. });
  274. updateLibraryHUD();
  275. }
  276.  
  277. document.getElementById('btn-flip-h').onclick = (e) => {
  278. e.stopPropagation();
  279. if (selectedBlueprintIndex === null || !blueprintLibrary[selectedBlueprintIndex]) {
  280. alert("Please select a blueprint to flip first.");
  281. return;
  282. }
  283. flipSelectedBlueprintHorizontal();
  284. };
  285.  
  286. document.getElementById('btn-flip-v').onclick = (e) => {
  287. e.stopPropagation();
  288. if (selectedBlueprintIndex === null || !blueprintLibrary[selectedBlueprintIndex]) {
  289. alert("Please select a blueprint to flip first.");
  290. return;
  291. }
  292. flipSelectedBlueprintVertical();
  293. };
  294.  
  295. // --- IMPORT / EXPORT OPERATIONS ---
  296. document.getElementById('btn-export').onclick = (e) => {
  297. e.stopPropagation();
  298. if (selectedBlueprintIndex === null || !blueprintLibrary[selectedBlueprintIndex]) {
  299. alert("Please select a blueprint from the library list to export first.");
  300. return;
  301. }
  302. const bp = blueprintLibrary[selectedBlueprintIndex];
  303. navigator.clipboard.writeText(JSON.stringify(bp)).then(() => {
  304. alert(`Blueprint "${bp.name}" copied to clipboard!`);
  305. }).catch(err => { console.error("Export failed: ", err); });
  306. };
  307.  
  308. document.getElementById('btn-import').onclick = (e) => {
  309. e.stopPropagation();
  310. navigator.clipboard.readText().then(str => {
  311. if (!str) { alert("Clipboard is empty."); return; }
  312. let bp;
  313. try {
  314. bp = JSON.parse(str);
  315. if (!bp || !bp.name || !Array.isArray(bp.tiles)) throw new Error("Invalid format");
  316. } catch(err) {
  317. const lines = str.split(/\r?\n/); const height = lines.length; let maxWidth = 0; const relativeTiles = [];
  318. for (let y = 0; y < height; y++) {
  319. const line = lines[y]; if (line.length > maxWidth) maxWidth = line.length;
  320. for (let x = 0; x < line.length; x++) {
  321. const char = line[x]; const relY = (height - 1) - y;
  322. if (char !== ' ' && char !== '') relativeTiles.push({ relX: x, relY: relY, char: char, color: colors[currentColorIndex] });
  323. }
  324. }
  325. bp = { name: "Raw Text Import", width: maxWidth, height: height, tiles: relativeTiles };
  326. }
  327. blueprintLibrary.push(bp); selectedBlueprintIndex = blueprintLibrary.length - 1;
  328. updateLibraryHUD();
  329. alert(`Successfully imported: "${bp.name}"`);
  330. }).catch(err => { alert("Could not access system clipboard."); });
  331. };
  332.  
  333. // --- CORE PROTECTION LOGIC ---
  334. async function protectChunk(tileX, tileY) {
  335. tileX = Math.floor(Number(tileX));
  336. tileY = Math.floor(Number(tileY));
  337. if (isNaN(tileX) || isNaN(tileY)) return;
  338.  
  339. const key = `${tileX},${tileY}`;
  340. if (protectedChunks[key]) return;
  341.  
  342. isInitializing = true;
  343. const chars = Array.from({length: 10}, () => Array(20).fill(' '));
  344. const colorsArr = Array.from({length: 10}, () => Array(20).fill(-1));
  345. const writeQueue = [];
  346.  
  347. try {
  348. for (let cy = 1; cy <= 10; cy++) {
  349. for (let cx = 1; cx <= 20; cx++) {
  350. const worldX = (tileX - 1) * 20 + cx;
  351. const worldY = (tileY - 1) * 10 + cy;
  352. const invertedY = invertY(worldY);
  353.  
  354. let info = null;
  355. try { info = getCharInfoXY(worldX, invertedY); } catch(e){}
  356. let currentChar = ' ';
  357. if (info) {
  358. if (typeof info === 'string') currentChar = info;
  359. else if (typeof info === 'object' && typeof info.char === 'string') currentChar = info.char;
  360. }
  361.  
  362. let wrapCX = cx > 20 ? 1 : (cx < 1 ? 20 : cx);
  363. let wrapCY = cy > 10 ? 1 : (cy < 1 ? 10 : cy);
  364.  
  365. let currentColor = -1;
  366. let apiColor = null;
  367. try { apiColor = getCharColor(tileX, tileY, wrapCX, wrapCY); } catch(e){}
  368. if (apiColor !== null && apiColor !== undefined) currentColor = apiColor;
  369. else if (info && typeof info === 'object' && info.color !== undefined) currentColor = info.color;
  370.  
  371. chars[cy-1][cx-1] = currentChar;
  372. colorsArr[cy-1][cx-1] = currentColor;
  373. if (currentChar === ' ') {
  374. chars[cy-1][cx-1] = '.';
  375. colorsArr[cy-1][cx-1] = 30;
  376. writeQueue.push({ char: '.', color: 30, x: worldX, y: invertedY });
  377. }
  378. }
  379. }
  380. } catch(err) { console.error("[WU] Protection trace fault: ", err); }
  381.  
  382. protectedChunks[key] = { chars, colors: colorsArr, lastModified: Date.now() };
  383. const writer = originalWriteCharAt || writeCharAt;
  384. for (let i = 0; i < writeQueue.length; i += 200) {
  385. if (isTerminated) return;
  386. const batch = writeQueue.slice(i, i + 200);
  387. batch.forEach(item => { try { writer(item.char, item.color, item.x, item.y); } catch(e){} });
  388. if (i + 200 < writeQueue.length) await new Promise(r => setTimeout(r, 400));
  389. }
  390.  
  391. setTimeout(() => isInitializing = false, 1000);
  392. }
  393.  
  394. // --- BACKGROUND REGENERATION LOOP ---
  395. const regenInterval = setInterval(async () => {
  396. if (isInitializing || isTerminated) return;
  397. let writeQueue = [];
  398.  
  399. for (const [key, chunk] of Object.entries(protectedChunks)) {
  400. if (Date.now() - chunk.lastModified < 1500) continue;
  401. const [tileX, tileY] = key.split(',').map(Number);
  402.  
  403. try {
  404. for (let cy = 1; cy <= 10; cy++) {
  405. for (let cx = 1; cx <= 20; cx++) {
  406. const worldX = (tileX - 1) * 20 + cx;
  407. const worldY = (tileY - 1) * 10 + cy;
  408. const invertedY = invertY(worldY);
  409.  
  410. let info = null;
  411. try { info = getCharInfoXY(worldX, invertedY); } catch(e){}
  412. let currentChar = ' ';
  413. if (info) {
  414. if (typeof info === 'string') currentChar = info;
  415. else if (typeof info === 'object' && typeof info.char === 'string') currentChar = info.char;
  416. }
  417.  
  418. let currentColor = -1;
  419. let apiColor = null;
  420. try { apiColor = getCharColor(tileX, tileY, cx, cy); } catch(e){}
  421. if (apiColor !== null && apiColor !== undefined) currentColor = apiColor;
  422. else if (info && typeof info === 'object' && info.color !== undefined) currentColor = info.color;
  423.  
  424. const savedChar = chunk.chars[cy-1][cx-1];
  425. const savedColor = chunk.colors[cy-1][cx-1];
  426. let needsCharRegen = (currentChar !== savedChar);
  427. let needsColorRegen = (savedColor !== -1 && currentColor !== savedColor);
  428. if (needsCharRegen || needsColorRegen) {
  429. const finalChar = needsCharRegen ? savedChar : currentChar;
  430. const finalColor = needsColorRegen ? savedColor : (savedColor === -1 ? currentColor : savedColor);
  431. writeQueue.push({ char: finalChar, color: finalColor, x: worldX, y: invertedY });
  432. }
  433. }
  434. }
  435. } catch(e) {}
  436. }
  437.  
  438. const writer = originalWriteCharAt || writeCharAt;
  439. for (let i = 0; i < writeQueue.length; i += 200) {
  440. if (isTerminated) return;
  441. const batch = writeQueue.slice(i, i + 200);
  442. batch.forEach(item => { try { writer(item.char, item.color, item.x, item.y); } catch(e){} });
  443. if (i + 200 < writeQueue.length) await new Promise(r => setTimeout(r, 400));
  444. }
  445. }, 1000);
  446.  
  447. // --- CROSSHAIR FREE DRAW ENGINE INTERPOLATION ---
  448. function drawAtCrosshairCoordinate() {
  449. const x = Math.floor(lastWorldX);
  450. const y = Math.floor(lastWorldY);
  451. const activeColor = colors[currentColorIndex];
  452.  
  453. if (lastDrawX !== null && lastDrawY !== null) {
  454. const dx = x - lastDrawX;
  455. const dy = y - lastDrawY;
  456. const steps = Math.max(Math.abs(dx), Math.abs(dy));
  457. for (let i = 0; i <= steps; i++) {
  458. const xi = lastDrawX + (steps === 0 ? 0 : Math.round(dx * (i / steps)));
  459. const yi = lastDrawY + (steps === 0 ? 0 : Math.round(dy * (i / steps)));
  460. try { writeCharAt('█', activeColor, xi, invertY(yi)); } catch(e) {}
  461. }
  462. } else {
  463. try { writeCharAt('█', activeColor, x, invertY(y)); } catch(e) {}
  464. }
  465. lastDrawX = x;
  466. lastDrawY = y;
  467. }
  468.  
  469. // --- KEY LISTENERS ---
  470. const keydownHandler = (e) => {
  471. if (e.key === "Shift") { shiftHeld = true; if (activeTool === 'draw') drawAtCrosshairCoordinate(); }
  472. if (e.key === "Alt") altHeld = true;
  473. };
  474. const keyupHandler = (e) => {
  475. if (e.key === "Shift") { shiftHeld = false; lastDrawX = null; lastDrawY = null; }
  476. if (e.key === "Alt") altHeld = false;
  477. };
  478.  
  479. // --- MOUSE TRACKING & LOOK LOGIC ---
  480. const mouseMoveHandler = (e) => {
  481. if (isTerminated) return;
  482. let mouseX, mouseY;
  483. if (typeof cursor !== 'undefined' && typeof cursor.x === 'number' && typeof cursor.y === 'number') {
  484. mouseX = cursor.x;
  485. mouseY = invertY(cursor.y);
  486. } else if (typeof w !== 'undefined' && w.mouse && typeof w.mouse.x === 'number') {
  487. mouseX = w.mouse.x;
  488. mouseY = w.mouse.y;
  489. } else {
  490. mouseX = e.clientX;
  491. mouseY = e.clientY;
  492. }
  493. if (!isNaN(mouseX) && !isNaN(mouseY)) { lastWorldX = mouseX; lastWorldY = mouseY; }
  494. if (activeTool === 'draw' && shiftHeld) drawAtCrosshairCoordinate();
  495. };
  496. const wheelHandler = (e) => {
  497. if (activeTool === 'draw' && altHeld) {
  498. e.preventDefault();
  499. if (e.deltaY < 0) currentColorIndex = (currentColorIndex + 1) % colors.length;
  500. else currentColorIndex = (currentColorIndex - 1 + colors.length) % colors.length;
  501. }
  502. };
  503.  
  504. document.addEventListener('keydown', keydownHandler);
  505. document.addEventListener('keyup', keyupHandler);
  506. document.addEventListener('mousemove', mouseMoveHandler);
  507. document.addEventListener('wheel', wheelHandler, { passive: false });
  508.  
  509. // Coordinate Preview Update Loop
  510. const previewInterval = setInterval(() => {
  511. if (isTerminated || !activeTool) return;
  512. if (activeTool === 'draw') return;
  513. if (activeTool === 'quit') { document.getElementById('confirm-coords').innerText = `Click Confirm to exit.`; return; }
  514. if (activeTool === 'clr') { document.getElementById('confirm-coords').innerText = `Click Confirm to start selection box clear.`; return; }
  515. if (activeTool === 'line' && lineStart) { document.getElementById('confirm-coords').innerText = `Line Start: ${lineStart.x}, ${lineStart.y} | Select End...`; return; }
  516. if (activeTool === 'circle' && circleStart) { document.getElementById('confirm-coords').innerText = `Circle Center: ${circleStart.x}, ${circleStart.y} | Select Edge...`; return; }
  517. if (activeTool === 'box' && boxStart) { document.getElementById('confirm-coords').innerText = `Box Corner 1: ${boxStart.x}, ${boxStart.y} | Select Corner 2...`; return; }
  518. if (activeTool === 'save' && selectionStart) { document.getElementById('confirm-coords').innerText = `Copier Pos 1: ${selectionStart.x}, ${selectionStart.y} | Select Pos 2...`; return; }
  519. if (activeTool === 'load') {
  520. if (selectedBlueprintIndex === null || !blueprintLibrary[selectedBlueprintIndex]) {
  521. document.getElementById('confirm-coords').innerText = `Error: Select a blueprint from Library first.`;
  522. } else {
  523. document.getElementById('confirm-coords').innerText = `Stamp Active Slot #${selectedBlueprintIndex + 1} Anchor: ${Math.floor(lastWorldX)}, ${Math.floor(lastWorldY)}`;
  524. }
  525. return;
  526. }
  527. const tile = getTileFromXY(lastWorldX, lastWorldY);
  528. if (tile && !isNaN(tile.x) && !isNaN(tile.y)) {
  529. currentTargetTile = { x: tile.x, y: tile.y };
  530. document.getElementById('confirm-coords').innerText = `Tile: ${currentTargetTile.x}, ${currentTargetTile.y} | Pos: ${Math.floor(lastWorldX)}, ${Math.floor(lastWorldY)}`;
  531. }
  532. }, 250);
  533.  
  534. // --- CONFIRM BUTTON LOGIC ---
  535. document.getElementById('btn-confirm').onclick = async () => {
  536. if (!activeTool || isTerminated) return;
  537. const { x, y } = currentTargetTile;
  538. const key = `${x},${y}`;
  539. if (activeTool === 'quit') {
  540. terminateScript(); return;
  541. } else if (activeTool === 'prct') {
  542. await protectChunk(x, y);
  543. if (typeof w !== 'undefined' && w.showToast) w.showToast(`Tile ${key} protected successfully.`, 2500);
  544. } else if (activeTool === 'nprt') {
  545. if (protectedChunks[key]) delete protectedChunks[key];
  546. if (typeof w !== 'undefined' && w.showToast) w.showToast(`Tile ${key} unprotected successfully.`, 2500);
  547. } else if (activeTool === 'clr') {
  548. if (typeof RegionSelection !== 'undefined') {
  549. const sel = new RegionSelection();
  550. sel.onSelection(function(sx, sy, ex, ey) {
  551. 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);
  552. for (let y = minY; y <= maxY; y++) {
  553. for (let x = minX; x <= maxX; x++) {
  554. let info = null; try { info = getCharInfoXY(x, y); } catch(e){}
  555. let currentChar = (info && typeof info === 'string') ? info : (info?.char || ' ');
  556. if (currentChar !== " ") { if (writeCharAt(" ", 0, x, y) !== 0) c += 1; }
  557. }
  558. }
  559. if (typeof w !== 'undefined' && w.showToast) w.showToast(`Area cleared successfully. Chars: ${c}`, 4500);
  560. });
  561. sel.startSelection();
  562. }
  563. activeTool = null; confirmBox.classList.remove('active');
  564. tools.forEach(t => { const el = document.getElementById(`btn-${t}`); if(el) el.classList.remove('active-tool'); });
  565. return;
  566. } else if (activeTool === 'line') {
  567. if (!lineStart) { lineStart = { x: Math.floor(lastWorldX), y: Math.floor(lastWorldY) }; }
  568. else { const lineEnd = { x: Math.floor(lastWorldX), y: Math.floor(lastWorldY) };
  569. await drawLine(lineStart.x, lineStart.y, lineEnd.x, lineEnd.y); lineStart = null; }
  570. return;
  571. } else if (activeTool === 'circle') {
  572. if (!circleStart) { circleStart = { x: Math.floor(lastWorldX), y: Math.floor(lastWorldY) }; }
  573. else { const circleEnd = { x: Math.floor(lastWorldX), y: Math.floor(lastWorldY) };
  574. await drawCircle(circleStart.x, circleStart.y, circleEnd.x, circleEnd.y); circleStart = null; }
  575. return;
  576. } else if (activeTool === 'box') {
  577. if (!boxStart) { boxStart = { x: Math.floor(lastWorldX), y: Math.floor(lastWorldY) }; }
  578. else { const boxEnd = { x: Math.floor(lastWorldX), y: Math.floor(lastWorldY) };
  579. await drawBoxOutline(boxStart.x, boxStart.y, boxEnd.x, boxEnd.y); boxStart = null; }
  580. return;
  581. } else if (activeTool === 'save') {
  582. if (!selectionStart) { selectionStart = { x: Math.floor(lastWorldX), y: Math.floor(lastWorldY) }; }
  583. else {
  584. const selectionEnd = { x: Math.floor(lastWorldX), y: Math.floor(lastWorldY) };
  585. saveRegionToBlueprint(selectionStart.x, selectionStart.y, selectionEnd.x, selectionEnd.y);
  586. selectionStart = null; activeTool = null; confirmBox.classList.remove('active');
  587. tools.forEach(t => { const el = document.getElementById(`btn-${t}`); if(el) el.classList.remove('active-tool'); });
  588. }
  589. return;
  590. } else if (activeTool === 'load') {
  591. if (selectedBlueprintIndex !== null && blueprintLibrary[selectedBlueprintIndex]) {
  592. await stampBlueprint(Math.floor(lastWorldX), Math.floor(lastWorldY));
  593. }
  594. activeTool = null; confirmBox.classList.remove('active');
  595. tools.forEach(t => { const el = document.getElementById(`btn-${t}`); if(el) el.classList.remove('active-tool'); });
  596. return;
  597. }
  598.  
  599. activeTool = null; confirmBox.classList.remove('active');
  600. tools.forEach(t => { const el = document.getElementById(`btn-${t}`); if(el) el.classList.remove('active-tool'); });
  601. };
  602.  
  603. // --- HUD LIBRARY PREVIEW GENERATOR ---
  604. function updateLibraryHUD() {
  605. const container = document.getElementById('wu-blueprint-list');
  606. container.innerHTML = '';
  607. if (blueprintLibrary.length === 0) { container.innerHTML = '<div style="font-size:12px; opacity:0.5; padding:10px;">Library is empty.</div>'; return; }
  608.  
  609. blueprintLibrary.forEach((bp, index) => {
  610. const card = document.createElement('div'); card.className = `wu-blueprint-card ${selectedBlueprintIndex === index ? 'selected' : ''}`; card.dataset.index = index;
  611. const canvas = document.createElement('canvas'); canvas.className = 'wu-preview-canvas';
  612. const rowHeightScale = 2; canvas.width = Math.min(bp.width, 120); canvas.height = Math.min(bp.height * rowHeightScale, 100);
  613. const ctx = canvas.getContext('2d'); ctx.fillStyle = '#111111'; ctx.fillRect(0, 0, canvas.width, canvas.height);
  614.  
  615. bp.tiles.forEach(t => {
  616. const invertedRelY = (bp.height - 1) - t.relY; const scaledY = invertedRelY * rowHeightScale;
  617. if (t.relX < canvas.width && scaledY < canvas.height) {
  618. ctx.fillStyle = (t.char !== ' ' && t.char !== '') ? getCanvasColorHex(t.color) : '#333333';
  619. ctx.fillRect(t.relX, scaledY, 1, rowHeightScale);
  620. }
  621. });
  622.  
  623. const meta = document.createElement('div'); meta.className = 'wu-blueprint-meta';
  624. meta.innerHTML = `<strong>${bp.name}</strong><br>Size: ${bp.width}×${bp.height}<br>chars: ${bp.tiles.length}`;
  625. const deleteBtn = document.createElement('button'); deleteBtn.className = 'wu-blueprint-delete'; deleteBtn.innerHTML = 'X';
  626. deleteBtn.onclick = (e) => {
  627. e.stopPropagation();
  628. const currentIndex = parseInt(card.dataset.index, 10); blueprintLibrary.splice(currentIndex, 1);
  629. if (selectedBlueprintIndex === currentIndex) selectedBlueprintIndex = blueprintLibrary.length > 0 ? 0 : null;
  630. else if (selectedBlueprintIndex > currentIndex) selectedBlueprintIndex--;
  631. updateLibraryHUD();
  632. };
  633. card.appendChild(meta); card.appendChild(canvas); card.appendChild(deleteBtn);
  634. card.onclick = (e) => { e.stopPropagation(); selectedBlueprintIndex = parseInt(card.dataset.index, 10); updateLibraryHUD(); };
  635. container.appendChild(card);
  636. });
  637. }
  638.  
  639. // --- BLUEPRINT COPIER ENGINE ---
  640. function saveRegionToBlueprint(x0, y0, x1, y1) {
  641. const name = prompt("Enter a name for this blueprint:", "New Blueprint");
  642. if (name === null) return;
  643.  
  644. const minX = Math.min(x0, x1); const maxX = Math.max(x0, x1);
  645. const minY = Math.min(y0, y1); const maxY = Math.max(y0, y1);
  646.  
  647. const relativeTiles = [];
  648. const width = (maxX - minX) + 1;
  649. const height = (maxY - minY) + 1;
  650.  
  651. for (let y = minY; y <= maxY; y++) {
  652. for (let x = minX; x <= maxX; x++) {
  653. const invertedY = invertY(y);
  654. let info = null;
  655. try { info = getCharInfoXY(x, invertedY); } catch(e) {}
  656. let currentChar = (info && typeof info === 'string') ? info : (info?.char || ' ');
  657.  
  658. const tile = getTileFromXY(x, y);
  659. const cx = ((x - 1) % 20 + 20) % 20 + 1;
  660. const cy = ((y - 1) % 10 + 10) % 10 + 1;
  661.  
  662. let currentColor = null;
  663. try {
  664. let apiColor = getCharColor(tile.x, tile.y, cx, cy);
  665. if (apiColor !== null && apiColor !== undefined) {
  666. currentColor = apiColor;
  667. } else if (info && typeof info === 'object' && info.color !== undefined) {
  668. currentColor = info.color;
  669. }
  670. } catch(e) {
  671. if (info && typeof info === 'object' && info.color !== undefined) {
  672. currentColor = info.color;
  673. }
  674. }
  675.  
  676. if (currentColor === null || currentColor === undefined) {
  677. currentColor = colors[currentColorIndex] ?? 0;
  678. }
  679.  
  680. if (currentChar === ' ' && (currentColor === 0 || currentColor === -1 || currentColor === '#ffffff')) continue;
  681. relativeTiles.push({
  682. relX: x - minX,
  683. relY: y - minY,
  684. char: currentChar,
  685. color: currentColor
  686. });
  687. }
  688. }
  689.  
  690. blueprintLibrary.push({ name, width, height, tiles: relativeTiles });
  691. selectedBlueprintIndex = blueprintLibrary.length - 1;
  692. updateLibraryHUD();
  693. }
  694.  
  695. // --- BLUEPRINT STAMPER ENGINE ---
  696. async function stampBlueprint(anchorX, anchorY) {
  697. if (selectedBlueprintIndex === null || !blueprintLibrary[selectedBlueprintIndex]) return;
  698. const activeBlueprint = blueprintLibrary[selectedBlueprintIndex];
  699. let writeQueue = [];
  700.  
  701. activeBlueprint.tiles.forEach(tile => {
  702. writeQueue.push({
  703. char: tile.char,
  704. color: tile.color ?? 0,
  705. x: anchorX + tile.relX,
  706. y: invertY(anchorY + tile.relY)
  707. });
  708. });
  709. const writer = originalWriteCharAt || writeCharAt;
  710.  
  711. for (let i = 0; i < writeQueue.length; i += 200) {
  712. if (isTerminated) return;
  713. const batch = writeQueue.slice(i, i + 200);
  714. batch.forEach(item => { try { writer(item.char, item.color, item.x, item.y); } catch(e){} });
  715. if (i + 200 < writeQueue.length) await new Promise(r => setTimeout(r, 400));
  716. }
  717. }
  718.  
  719. // --- LINE DRAWING ALGORITHM ---
  720. async function drawLine(x0, y0, x1, y1) {
  721. const dx = Math.abs(x1 - x0);
  722. const dy = Math.abs(y1 - y0); const sx = (x0 < x1) ? 1 : -1;
  723. const sy = (y0 < y1) ? 1 : -1;
  724. let err = dx - dy; let queue = [];
  725. while (true) {
  726. queue.push({ x: x0, y: y0 });
  727. if (x0 === x1 && y0 === y1) break;
  728. const e2 = 2 * err;
  729. if (e2 > -dy) { err -= dy; x0 += sx; } if (e2 < dx) { err += dx; y0 += sy; }
  730. }
  731. await batchWrite(queue, '█', colors[currentColorIndex]);
  732. }
  733.  
  734. // --- CIRCLE DRAWING ALGORITHM ---
  735. async function drawCircle(cx, cy, ex, ey) {
  736. const r = Math.round(Math.sqrt((ex - cx) ** 2 + (ey - cy) ** 2));
  737. if (r === 0) { await batchWrite([{ x: cx, y: cy }], '█', colors[currentColorIndex]); return; }
  738. let queue = []; let x = r; let y = 0;
  739. let err = 0;
  740. while (x >= y) {
  741. 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 },
  742. { x: cx - x, y: cy - y }, { x: cx - y, y: cy - x }, { x: cx + y, y: cy - x }, { x: cx + x, y: cy - y });
  743. if (err <= 0) { y += 1; err += 2 * y + 1; }
  744. else if (err > 0) { x -= 1; err -= 2 * x + 1; }
  745. }
  746. 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 }; });
  747. await batchWrite(uniqueQueue, '█', colors[currentColorIndex]);
  748. }
  749.  
  750. // --- BOX OUTLINE DRAWING ALGORITHM ---
  751. async function drawBoxOutline(x0, y0, x1, y1) {
  752. const minX = Math.min(x0, x1);
  753. const maxX = Math.max(x0, x1); const minY = Math.min(y0, y1); const maxY = Math.max(y0, y1);
  754. let queue = [];
  755. for (let x = minX; x <= maxX; x++) { queue.push({ x: x, y: minY }, { x: x, y: maxY }); }
  756. for (let y = minY; y <= maxY; y++) { queue.push({ x: minX, y: y }, { x: maxX, y: y }); }
  757. 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 }; });
  758. await batchWrite(uniqueQueue, '█', colors[currentColorIndex]);
  759. }
  760.  
  761. // --- BATCH WRITER HELPER ---
  762. async function batchWrite(queue, char, color) {
  763. for (let i = 0; i < queue.length; i += 200) {
  764. if (isTerminated) return;
  765. queue.slice(i, i + 200).forEach(p => { try { writeCharAt(char, color, p.x, invertY(p.y)); } catch(e){} });
  766. if (i + 200 < queue.length) await new Promise(r => setTimeout(r, 400));
  767. }
  768. }
  769.  
  770. // --- TERMINATION ENGINE ---
  771. function terminateScript() {
  772. isTerminated = true;
  773. clearInterval(previewInterval);
  774. clearInterval(regenInterval);
  775. blueprintLibrary = []; selectedBlueprintIndex = null;
  776.  
  777. document.removeEventListener('keydown', keydownHandler);
  778. document.removeEventListener('keyup', keyupHandler);
  779. document.removeEventListener('mousemove', mouseMoveHandler);
  780. document.removeEventListener('wheel', wheelHandler);
  781. if (originalWriteCharAt) window.writeCharAt = originalWriteCharAt;
  782.  
  783. ['wu-btn', 'wu-hotbar', 'wu-confirm', 'wu-info', 'wu-theme', 'wu-library', 'wu-styles'].forEach(id => {
  784. const element = document.getElementById(id); if (element) element.remove();
  785. });
  786. console.log("%c[WallUtilities] Script successfully exited.", "color: #ff6666");
  787. }
  788. })();
Advertisement
Comments
Add Comment
Please, Sign In to add comment