smoothretro82

Owot wiper for Tw.2s4.me

Dec 9th, 2025 (edited)
579
0
Never
4
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 34.00 KB | None | 0 0
  1. // Owot Wiper (converted for tw.2s4.me) — Full Movable HUD with working engine and fallback renderer
  2. (function () {
  3. 'use strict';
  4.  
  5. // ---- Utilities & Fallback drawing (writeCharAt/writer) ----
  6. // We'll provide a fallback renderer that draws to a fixed-position canvas
  7. // if the environment does not provide writeCharToXY.
  8. function createRenderCanvas() {
  9. let render = document.getElementById('solar-wiper-render');
  10. if (!render) {
  11. render = document.createElement('canvas');
  12. render.id = 'solar-wiper-render';
  13. Object.assign(render.style, {
  14. position: 'fixed',
  15. top: '0',
  16. left: '0',
  17. width: '100%',
  18. height: '100%',
  19. pointerEvents: 'none',
  20. zIndex: '9998',
  21. });
  22. document.body.appendChild(render);
  23. }
  24. const setSize = () => {
  25. render.width = window.innerWidth;
  26. render.height = window.innerHeight;
  27. };
  28. setSize();
  29. window.addEventListener('resize', setSize);
  30. return render;
  31. }
  32.  
  33. // Fallback writer using a render canvas — draws glyphs at pixel positions computed from a char grid.
  34. function fallbackWriteChar(char, fgColor, x, y, bgColor, cellW = 10, cellH = 18, fontSize = 16) {
  35. const canvas = createRenderCanvas();
  36. const ctx = canvas.getContext('2d');
  37. // If background specified, fill that cell
  38. if (typeof bgColor === 'number') {
  39. ctx.fillStyle = '#' + bgColor.toString(16).padStart(6, '0');
  40. ctx.fillRect(x * cellW, y * cellH, cellW, cellH);
  41. }
  42. if (char && char !== ' ') {
  43. ctx.font = `${fontSize}px monospace`;
  44. ctx.textBaseline = 'top';
  45. ctx.fillStyle = '#' + (typeof fgColor === 'number' ? fgColor.toString(16).padStart(6, '0') : fgColor);
  46. ctx.fillText(char, x * cellW, y * cellH);
  47. }
  48. }
  49.  
  50. // Expose a safe writer function that prefers global writeCharToXY if available
  51. function safeWriteChar(char, fgColor, x, y, bgColor) {
  52. try {
  53. if (typeof writeCharToXY === 'function') {
  54. // original environment function: writeCharToXY(char, fg, x, y, bg)
  55. writeCharToXY(char, fgColor, x, y, bgColor);
  56. } else if (typeof window.writeCharAt === 'function') {
  57. // user-provided writeCharAt
  58. window.writeCharAt(char, fgColor, x, y, bgColor);
  59. } else {
  60. // fallback to canvas renderer
  61. fallbackWriteChar(char, fgColor, x, y, bgColor);
  62. }
  63. } catch (e) {
  64. // if any error, try fallback
  65. fallbackWriteChar(char, fgColor, x, y, bgColor);
  66. }
  67. }
  68.  
  69. // ---- Main class ----
  70. function initSolarWiper() {
  71. if (typeof api_chat_send === 'function') {
  72. api_chat_send("Thank you for using solar wiper demo. Enjoy the full HUD!");
  73. }
  74.  
  75. class OWOTSolarWiper {
  76. constructor() {
  77. // core state
  78. this.isWiping = false;
  79. this.stopRequested = false;
  80. this.wipeText = '█';
  81. this.wipeColor = 0xFFFF00;
  82. this.bgColor = 0x000000;
  83. this.startX = 0;
  84. this.startY = 0;
  85. this.endX = 100;
  86. this.endY = 40;
  87. this.delay = 10; // ms
  88. this.batchSize = 10;
  89. this.charsWritten = 0;
  90. this.totalChars = 0;
  91. this.thinkMode = false;
  92. this.followMode = false;
  93. this.fastMode = false;
  94. this.advancedMode = false;
  95. this.reverseDirection = false;
  96. this.randomDelay = false;
  97.  
  98. // preview & render canvases
  99. this.previewOverlay = null;
  100. this.renderCanvas = createRenderCanvas();
  101.  
  102. // create UI + preview
  103. this.createUI();
  104. this.createPreviewOverlay();
  105.  
  106. // expose globally
  107. window.OWOTSolarWiperInstance = this;
  108.  
  109. // keep preview in sync on resize
  110. window.addEventListener('resize', () => this.updatePreview());
  111. }
  112.  
  113. // ----- UI -----
  114. createUI() {
  115. const existing = document.getElementById('solar-wiper-hud');
  116. if (existing) existing.remove();
  117.  
  118. const ui = document.createElement('div');
  119. ui.id = 'solar-wiper-hud';
  120. ui.style.cssText = `
  121. position: fixed; top: 20px; right: 20px;
  122. background: #1a1a1a; color: #e0e0e0; padding: 12px;
  123. border-radius: 8px; box-shadow: 0 6px 24px rgba(0,0,0,0.6);
  124. z-index: 10010; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  125. width: 320px; border:1px solid #333; font-size:12px;
  126. `;
  127.  
  128. ui.innerHTML = `
  129. <div id="hud-header" style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid #333;">
  130. <div style="font-weight:600;font-size:13px;color:#fff;">Solar Wiper Demo <span style="font-weight:400;color:#888;font-size:11px;">v1.0</span></div>
  131. <div style="display:flex;gap:6px;align-items:center;">
  132. <button id="hud-toggle" title="Collapse/Expand" style="background:transparent;border:1px solid #444;color:#888;width:26px;height:26px;border-radius:5px;cursor:pointer;">−</button>
  133. <button id="hud-close" title="Close HUD" style="background:transparent;border:1px solid #444;color:#888;width:26px;height:26px;border-radius:5px;cursor:pointer;">×</button>
  134. </div>
  135. </div>
  136.  
  137. <div id="hud-body" style="display:block; gap:8px;">
  138.  
  139. <div>
  140. <label style="display:block;font-size:10px;color:#aaa;margin-bottom:4px;">Character</label>
  141. <input id="wipe-text" value="█" style="width:100%;padding:6px;border-radius:6px;border:1px solid #333;background:#0d0d0d;color:#fff;">
  142. </div>
  143.  
  144. <div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;">
  145. <div>
  146. <label style="display:block;font-size:10px;color:#aaa;margin-bottom:4px;">Foreground</label>
  147. <input id="fg-color" type="color" value="#ffff00" style="width:100%;height:32px;border-radius:6px;border:1px solid #333;">
  148. </div>
  149. <div>
  150. <label style="display:block;font-size:10px;color:#aaa;margin-bottom:4px;">Background</label>
  151. <input id="bg-color" type="color" value="#000000" style="width:100%;height:32px;border-radius:6px;border:1px solid #333;">
  152. </div>
  153. </div>
  154.  
  155. <div style="background:#0d0d0d;padding:8px;border-radius:6px;border:1px solid #282828;margin-top:8px;">
  156. <div style="display:grid;grid-template-columns:1fr 1fr;gap:6px;">
  157. <div>
  158. <label style="display:block;font-size:9px;color:#666;">START X</label>
  159. <input id="start-x" type="number" value="0" style="width:100%;padding:6px;border-radius:4px;background:#111;color:#fff;border:1px solid #333;">
  160. </div>
  161. <div>
  162. <label style="display:block;font-size:9px;color:#666;">START Y</label>
  163. <input id="start-y" type="number" value="0" style="width:100%;padding:6px;border-radius:4px;background:#111;color:#fff;border:1px solid #333;">
  164. </div>
  165. </div>
  166. <div style="display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-top:6px;">
  167. <div>
  168. <label style="display:block;font-size:9px;color:#666;">END X</label>
  169. <input id="end-x" type="number" value="100" style="width:100%;padding:6px;border-radius:4px;background:#111;color:#fff;border:1px solid #333;">
  170. </div>
  171. <div>
  172. <label style="display:block;font-size:9px;color:#666;">END Y</label>
  173. <input id="end-y" type="number" value="40" style="width:100%;padding:6px;border-radius:4px;background:#111;color:#fff;border:1px solid #333;">
  174. </div>
  175. </div>
  176.  
  177. <div style="display:flex;gap:6px;margin-top:8px;">
  178. <button id="use-cursor-start" style="flex:1;padding:6px;border-radius:6px;border:1px solid #333;background:#222;color:#fff;cursor:pointer;">Cursor → Start</button>
  179. <button id="use-cursor-end" style="flex:1;padding:6px;border-radius:6px;border:1px solid #333;background:#222;color:#fff;cursor:pointer;">Cursor → End</button>
  180. </div>
  181. </div>
  182.  
  183. <div style="display:flex;flex-direction:column;gap:6px;">
  184. <label style="display:flex;align-items:center;padding:6px;border-radius:6px;background:#0d0d0d;border:1px solid #282828;cursor:pointer;"><input id="think-mode" type="checkbox" style="margin-right:8px;">Think Mode (spiral)</label>
  185. <label style="display:flex;align-items:center;padding:6px;border-radius:6px;background:#0d0d0d;border:1px solid #282828;cursor:pointer;"><input id="follow-mode" type="checkbox" style="margin-right:8px;">Follow Mode (smart)</label>
  186. <label style="display:flex;align-items:center;padding:6px;border-radius:6px;background:#0d0d0d;border:1px solid #282828;cursor:pointer;"><input id="fast-mode" type="checkbox" style="margin-right:8px;">Fast Mode (batch)</label>
  187. </div>
  188.  
  189. <label style="display:flex;align-items:center;gap:8px;margin-top:6px;"><input id="advanced-toggle" type="checkbox"> Show advanced options</label>
  190.  
  191. <div id="advanced-options" style="display:none;background:#0d0d0d;padding:8px;border-radius:6px;border:1px solid #282828;">
  192. <label style="display:block;font-size:11px;color:#ddd;margin-bottom:6px;">Batch Size<input id="batch-size" type="number" value="10" min="1" style="width:100%;margin-top:6px;padding:6px;border-radius:6px;background:#111;border:1px solid #333;color:#fff;"></label>
  193. <div style="display:grid;grid-template-columns:1fr 1fr;gap:6px;">
  194. <label style="display:block;font-size:11px;color:#ddd;">Min Delay<input id="min-delay" type="number" value="0" style="width:100%;margin-top:6px;padding:6px;border-radius:6px;background:#111;border:1px solid #333;color:#fff;"></label>
  195. <label style="display:block;font-size:11px;color:#ddd;">Max Delay<input id="max-delay" type="number" value="100" style="width:100%;margin-top:6px;padding:6px;border-radius:6px;background:#111;border:1px solid #333;color:#fff;"></label>
  196. </div>
  197. <label style="display:flex;align-items:center;margin-top:6px;"><input id="reverse-direction" type="checkbox" style="margin-right:8px;">Reverse direction</label>
  198. <label style="display:flex;align-items:center;margin-top:4px;"><input id="random-delay" type="checkbox" style="margin-right:8px;">Randomize delay</label>
  199. </div>
  200.  
  201. <div style="display:flex;gap:8px;margin-top:8px;">
  202. <button id="start-wipe" style="flex:1;padding:10px;border-radius:6px;border:none;background:#fff;color:#000;font-weight:600;cursor:pointer;">Start Wipe</button>
  203. <button id="stop-wipe" style="flex:1;padding:10px;border-radius:6px;border:none;background:#c0392b;color:#fff;font-weight:600;display:none;cursor:pointer;">Stop</button>
  204. </div>
  205.  
  206. <div id="progress-wrap" style="display:none;margin-top:8px;">
  207. <div style="background:#0d0d0d;border-radius:6px;height:8px;overflow:hidden;">
  208. <div id="progress-bar" style="width:0%;height:100%;background:#2ecc71;transition:width 0.12s;"></div>
  209. </div>
  210. <div id="progress-text" style="text-align:center;color:#aaa;font-size:11px;margin-top:6px;">0 / 0</div>
  211. </div>
  212.  
  213. <div id="hud-status" style="margin-top:8px;padding:8px;background:#0d0d0d;border-radius:6px;text-align:center;color:#aaa;font-size:12px;">Ready</div>
  214.  
  215. </div>
  216. `;
  217.  
  218. document.body.appendChild(ui);
  219.  
  220. // draggable header
  221. this.makeHUDDraggable(ui);
  222.  
  223. // collapse/close
  224. document.getElementById('hud-toggle').onclick = () => {
  225. const body = document.getElementById('hud-body');
  226. if (body.style.display === 'none') { body.style.display = 'block'; document.getElementById('hud-toggle').textContent = '−'; }
  227. else { body.style.display = 'none'; document.getElementById('hud-toggle').textContent = '+'; }
  228. };
  229. document.getElementById('hud-close').onclick = () => { ui.remove(); this.hidePreview(); };
  230.  
  231. // wire inputs
  232. document.getElementById('wipe-text').oninput = (e) => this.wipeText = e.target.value || '█';
  233. document.getElementById('fg-color').oninput = (e) => this.wipeColor = parseInt(e.target.value.slice(1), 16);
  234. document.getElementById('bg-color').oninput = (e) => this.bgColor = parseInt(e.target.value.slice(1), 16);
  235.  
  236. const sx = document.getElementById('start-x'), sy = document.getElementById('start-y'), ex = document.getElementById('end-x'), ey = document.getElementById('end-y');
  237. sx.oninput = (e) => this.startX = parseInt(e.target.value) || 0;
  238. sy.oninput = (e) => this.startY = parseInt(e.target.value) || 0;
  239. ex.oninput = (e) => this.endX = parseInt(e.target.value) || 0;
  240. ey.oninput = (e) => this.endY = parseInt(e.target.value) || 0;
  241.  
  242. document.getElementById('use-cursor-start').onclick = () => {
  243. if (typeof cursorCoords === 'undefined') { this.updateStatus('cursorCoords not found', '#c0392b'); return; }
  244. const x = cursorCoords[0] * 16 + cursorCoords[2];
  245. const y = cursorCoords[1] * 8 + cursorCoords[3];
  246. sx.value = x; sy.value = y; sx.oninput({target:{value:x}}); sy.oninput({target:{value:y}});
  247. this.updatePreview();
  248. };
  249. document.getElementById('use-cursor-end').onclick = () => {
  250. if (typeof cursorCoords === 'undefined') { this.updateStatus('cursorCoords not found', '#c0392b'); return; }
  251. const x = cursorCoords[0] * 16 + cursorCoords[2];
  252. const y = cursorCoords[1] * 8 + cursorCoords[3];
  253. ex.value = x; ey.value = y; ex.oninput({target:{value:x}}); ey.oninput({target:{value:y}});
  254. this.updatePreview();
  255. };
  256.  
  257. document.getElementById('think-mode').onchange = (e) => { this.thinkMode = e.target.checked; if (e.target.checked) { document.getElementById('follow-mode').checked = false; this.followMode = false; } };
  258. document.getElementById('follow-mode').onchange = (e) => { this.followMode = e.target.checked; if (e.target.checked) { document.getElementById('think-mode').checked = false; this.thinkMode = false; } };
  259. document.getElementById('fast-mode').onchange = (e) => { this.fastMode = e.target.checked; };
  260.  
  261. document.getElementById('advanced-toggle').onchange = (e) => {
  262. this.advancedMode = e.target.checked;
  263. document.getElementById('advanced-options').style.display = e.target.checked ? 'block' : 'none';
  264. };
  265.  
  266. document.getElementById('batch-size').oninput = (e) => { this.batchSize = Math.max(1, parseInt(e.target.value) || 1); };
  267. document.getElementById('min-delay').oninput = (e) => { document.getElementById('min-delay').value = e.target.value; };
  268. document.getElementById('max-delay').oninput = (e) => { document.getElementById('max-delay').value = e.target.value; };
  269. document.getElementById('reverse-direction').onchange = (e) => { this.reverseDirection = e.target.checked; };
  270. document.getElementById('random-delay').onchange = (e) => { this.randomDelay = e.target.checked; };
  271.  
  272. // Start / stop
  273. document.getElementById('start-wipe').onclick = () => this.startWipe();
  274. document.getElementById('stop-wipe').onclick = () => this.stopWipe();
  275.  
  276. // Prevent HUD from stealing key events from the main page
  277. ui.addEventListener('keydown', (e) => e.stopPropagation());
  278. ui.addEventListener('keyup', (e) => e.stopPropagation());
  279. ui.addEventListener('keypress', (e) => e.stopPropagation());
  280. }
  281.  
  282. makeHUDDraggable(hud) {
  283. const header = hud.querySelector('#hud-header');
  284. if (!header) return;
  285. let isDragging = false;
  286. let offsetX = 0, offsetY = 0;
  287.  
  288. header.style.cursor = 'move';
  289. header.onmousedown = (e) => {
  290. isDragging = true;
  291. offsetX = e.clientX - hud.getBoundingClientRect().left;
  292. offsetY = e.clientY - hud.getBoundingClientRect().top;
  293. document.body.style.userSelect = 'none';
  294. };
  295. document.addEventListener('mouseup', () => { isDragging = false; document.body.style.userSelect = ''; });
  296. document.addEventListener('mousemove', (e) => {
  297. if (!isDragging) return;
  298. hud.style.left = (e.clientX - offsetX) + 'px';
  299. hud.style.top = (e.clientY - offsetY) + 'px';
  300. hud.style.right = 'auto';
  301. });
  302. }
  303.  
  304. createPreviewOverlay() {
  305. const overlay = document.getElementById('solar-wiper-preview') || document.createElement('canvas');
  306. overlay.id = 'solar-wiper-preview';
  307. overlay.style.position = 'fixed';
  308. overlay.style.top = '0';
  309. overlay.style.left = '0';
  310. overlay.style.pointerEvents = 'none';
  311. overlay.style.zIndex = '10005';
  312. overlay.style.display = 'none';
  313. // Append if not present
  314. if (!overlay.parentElement) document.body.appendChild(overlay);
  315. // proper resolution
  316. const resize = () => {
  317. overlay.width = window.innerWidth;
  318. overlay.height = window.innerHeight;
  319. };
  320. resize();
  321. window.addEventListener('resize', resize);
  322. this.previewOverlay = overlay;
  323. }
  324.  
  325. showPreview() {
  326. if (this.previewOverlay) {
  327. this.previewOverlay.style.display = 'block';
  328. this.updatePreview();
  329. }
  330. }
  331.  
  332. hidePreview() {
  333. if (this.previewOverlay) this.previewOverlay.style.display = 'none';
  334. }
  335.  
  336. updatePreview() {
  337. const canvas = this.previewOverlay;
  338. if (!canvas || canvas.style.display === 'none') return;
  339. const ctx = canvas.getContext('2d');
  340. canvas.width = window.innerWidth;
  341. canvas.height = window.innerHeight;
  342. ctx.clearRect(0, 0, canvas.width, canvas.height);
  343.  
  344. // defensive about environment variables
  345. const charW = (typeof cellW !== 'undefined') ? cellW : 10;
  346. const charH = (typeof cellH !== 'undefined') ? cellH : 18;
  347. const posX = (typeof positionX !== 'undefined') ? positionX : 0;
  348. const posY = (typeof positionY !== 'undefined') ? positionY : 0;
  349. const zoom = (typeof zoom !== 'undefined') ? zoom : 1;
  350.  
  351. const minX = Math.min(this.startX, this.endX);
  352. const maxX = Math.max(this.startX, this.endX);
  353. const minY = Math.min(this.startY, this.endY);
  354. const maxY = Math.max(this.startY, this.endY);
  355.  
  356. const screenX1 = (minX * charW - posX) * zoom;
  357. const screenY1 = (minY * charH - posY) * zoom;
  358. const screenX2 = ((maxX + 1) * charW - posX) * zoom;
  359. const screenY2 = ((maxY + 1) * charH - posY) * zoom;
  360.  
  361. // Draw translucent fill
  362. ctx.fillStyle = 'rgba(155,89,182,0.08)';
  363. ctx.fillRect(screenX1, screenY1, screenX2 - screenX1, screenY2 - screenY1);
  364.  
  365. // dashed border
  366. ctx.lineWidth = 2;
  367. ctx.setLineDash([8, 6]);
  368. ctx.strokeStyle = '#e74c3c';
  369. ctx.beginPath();
  370. ctx.moveTo(screenX1, screenY1);
  371. ctx.lineTo(screenX2, screenY1);
  372. ctx.lineTo(screenX2, screenY2);
  373. ctx.lineTo(screenX1, screenY2);
  374. ctx.closePath();
  375. ctx.stroke();
  376.  
  377. // corners
  378. ctx.setLineDash([]);
  379. ctx.fillStyle = '#e74c3c';
  380. ctx.beginPath();
  381. ctx.arc(screenX1, screenY1, 6, 0, Math.PI * 2);
  382. ctx.fill();
  383. ctx.fillStyle = '#3498db';
  384. ctx.beginPath();
  385. ctx.arc(screenX2, screenY2, 6, 0, Math.PI * 2);
  386. ctx.fill();
  387.  
  388. // text label
  389. ctx.font = 'bold 12px Arial';
  390. ctx.fillStyle = '#9b59b6';
  391. ctx.fillText(`${maxX - minX + 1} × ${maxY - minY + 1}`, screenX1 + 6, screenY1 - 8);
  392. }
  393.  
  394. updateStatus(msg, color = '#888') {
  395. const el = document.getElementById('hud-status');
  396. if (el) { el.textContent = msg; el.style.color = color; }
  397. }
  398.  
  399. updateProgress() {
  400. const bar = document.getElementById('progress-bar');
  401. const text = document.getElementById('progress-text');
  402. if (!bar || !text) return;
  403. const pct = (this.totalChars === 0) ? 0 : Math.round((this.charsWritten / this.totalChars) * 100);
  404. bar.style.width = pct + '%';
  405. text.textContent = `${this.charsWritten} / ${this.totalChars}`;
  406. }
  407.  
  408. // ----- Wipe algorithms -----
  409. async startWipe() {
  410. if (this.isWiping) return;
  411. // ensure coordinates valid
  412. if (this.startX > this.endX) { const t = this.startX; this.startX = this.endX; this.endX = t; document.getElementById('start-x').value = this.startX; document.getElementById('end-x').value = this.endX; }
  413. if (this.startY > this.endY) { const t = this.startY; this.startY = this.endY; this.endY = t; document.getElementById('start-y').value = this.startY; document.getElementById('end-y').value = this.endY; }
  414.  
  415. // set delays from advanced options
  416. const minDelay = parseInt(document.getElementById('min-delay').value) || 0;
  417. const maxDelay = parseInt(document.getElementById('max-delay').value) || 100;
  418. this.delay = Math.max(0, Math.min(this.delay || 10, maxDelay));
  419. this.batchSize = Math.max(1, parseInt(document.getElementById('batch-size').value) || 1);
  420.  
  421. // UI
  422. this.isWiping = true;
  423. this.stopRequested = false;
  424. document.getElementById('start-wipe').style.display = 'none';
  425. document.getElementById('stop-wipe').style.display = 'block';
  426. document.getElementById('progress-wrap').style.display = 'block';
  427. this.charsWritten = 0;
  428. this.totalChars = 0;
  429. this.updateStatus('Preparing wipe...', '#888');
  430.  
  431. // Choose workflow
  432. const minX = Math.min(this.startX, this.endX);
  433. const maxX = Math.max(this.startX, this.endX);
  434. const minY = Math.min(this.startY, this.endY);
  435. const maxY = Math.max(this.startY, this.endY);
  436.  
  437. if (this.followMode) {
  438. await this.followWipe(minX, maxX, minY, maxY);
  439. } else if (this.thinkMode) {
  440. await this.thinkWipe(minX, maxX, minY, maxY);
  441. } else {
  442. await this.dynamicSideWipe(minX, maxX, minY, maxY);
  443. }
  444.  
  445. this.isWiping = false;
  446. document.getElementById('start-wipe').style.display = 'block';
  447. document.getElementById('stop-wipe').style.display = 'none';
  448. this.updateStatus(this.stopRequested ? `Stopped at ${this.charsWritten}` : `Wipe complete: ${this.charsWritten}`, '#888');
  449. }
  450.  
  451. async processCells(cells) {
  452. // cells: array of {x,y}
  453. this.totalChars = cells.length;
  454. this.charsWritten = 0;
  455. this.updateProgress();
  456.  
  457. let index = 0;
  458. const batchSize = this.fastMode ? this.batchSize : 1;
  459.  
  460. while (index < cells.length && !this.stopRequested) {
  461. const batchEnd = Math.min(index + batchSize, cells.length);
  462. for (let i = index; i < batchEnd; i++) {
  463. const cell = cells[i];
  464. // cycle through characters if multiple provided
  465. const charIndex = i % this.wipeText.length;
  466. const ch = this.wipeText[charIndex] || '█';
  467. safeWriteChar(ch, this.wipeColor, cell.x, cell.y, this.bgColor);
  468. this.charsWritten++;
  469. }
  470. index = batchEnd;
  471. this.updateProgress();
  472.  
  473. // compute delay
  474. let d = this.delay || 10;
  475. if (this.randomDelay) {
  476. const minD = parseInt(document.getElementById('min-delay').value) || 0;
  477. const maxD = parseInt(document.getElementById('max-delay').value) || 100;
  478. d = Math.floor(Math.random() * (maxD - minD + 1)) + minD;
  479. }
  480. // await next tick
  481. await this.sleep(d);
  482. }
  483. }
  484.  
  485. async dynamicSideWipe(minX, maxX, minY, maxY) {
  486. this.updateStatus('Computing dynamic sides...', '#888');
  487. await this.sleep(20);
  488.  
  489. const width = maxX - minX + 1;
  490. const height = maxY - minY + 1;
  491. const maxDim = Math.max(width, height);
  492. const numSides = Math.max(4, 4 + Math.floor(maxDim / 20));
  493.  
  494. // generate perimeter start points
  495. const perimeter = 2 * (width + height);
  496. const step = perimeter / numSides;
  497. const starts = [];
  498. for (let i = 0; i < numSides; i++) {
  499. let d = i * step;
  500. let px, py;
  501. if (d < width) { px = minX + Math.floor(d); py = minY; }
  502. else if (d < width + height) { px = maxX; py = minY + Math.floor(d - width); }
  503. else if (d < 2 * width + height) { px = maxX - Math.floor(d - (width + height)); py = maxY; }
  504. else { px = minX; py = maxY - Math.floor(d - (2 * width + height)); }
  505. starts.push({ x: px, y: py });
  506. }
  507.  
  508. // compute distance to nearest start for each cell
  509. const cells = [];
  510. for (let y = minY; y <= maxY; y++) {
  511. for (let x = minX; x <= maxX; x++) {
  512. let minDist = Infinity;
  513. for (const p of starts) {
  514. const dist = Math.hypot(x - p.x, y - p.y);
  515. if (dist < minDist) minDist = dist;
  516. }
  517. cells.push({ x, y, dist: minDist });
  518. }
  519. }
  520.  
  521. // sort by distance then group and shuffle each group
  522. cells.sort((a, b) => a.dist - b.dist);
  523. const grouped = [];
  524. let group = [], last = Math.floor(cells[0] ? cells[0].dist : 0);
  525. for (const c of cells) {
  526. const d = Math.floor(c.dist);
  527. if (d !== last) {
  528. OWOTSolarWiper.shuffleArray(group);
  529. grouped.push(...group);
  530. group = [];
  531. last = d;
  532. }
  533. group.push({ x: c.x, y: c.y });
  534. }
  535. if (group.length) { OWOTSolarWiper.shuffleArray(group); grouped.push(...group); }
  536.  
  537. // optional reverse
  538. if (this.reverseDirection) grouped.reverse();
  539.  
  540. this.updateStatus(`Wiping ${grouped.length} cells...`, '#888');
  541. await this.processCells(grouped);
  542. }
  543.  
  544. async followWipe(minX, maxX, minY, maxY) {
  545. this.updateStatus('Scanning for non-empty cells...', '#888');
  546. await this.sleep(10);
  547. const cellsToWipe = [];
  548.  
  549. for (let y = minY; y <= maxY; y++) {
  550. for (let x = minX; x <= maxX; x++) {
  551. let shouldWipe = true;
  552. try {
  553. if (typeof getChar === 'function') {
  554. const tileX = Math.floor(x / 16);
  555. const tileY = Math.floor(y / 8);
  556. const charX = ((x % 16) + 16) % 16;
  557. const charY = ((y % 8) + 8) % 8;
  558. const data = getChar(tileX, tileY, charX, charY);
  559. if (!data) shouldWipe = false;
  560. else {
  561. const ch = data.char ?? (Array.isArray(data) ? data[0] : '') ?? '';
  562. const color = data.color ?? (Array.isArray(data) ? data[1] : 0);
  563. if (ch === '' || ch === ' ' || ch === '\x00' || color === 0xffffff || color === 16777215) {
  564. shouldWipe = false;
  565. }
  566. }
  567. } else {
  568. // if no getChar, assume we want to wipe everything
  569. shouldWipe = true;
  570. }
  571. } catch (e) {
  572. shouldWipe = true;
  573. }
  574. if (shouldWipe) cellsToWipe.push({ x, y });
  575. }
  576. }
  577.  
  578. this.updateStatus(`Found ${cellsToWipe.length} cells`, '#888');
  579. await this.processCells(cellsToWipe);
  580. }
  581.  
  582. async thinkWipe(minX, maxX, minY, maxY) {
  583. this.updateStatus('Computing spiral path...', '#888');
  584. await this.sleep(20);
  585. const path = [];
  586. let left = minX, right = maxX, top = minY, bottom = maxY;
  587. while (left <= right && top <= bottom) {
  588. for (let x = left; x <= right; x++) path.push({ x, y: top });
  589. top++;
  590. for (let y = top; y <= bottom; y++) path.push({ x: right, y });
  591. right--;
  592. if (top <= bottom) {
  593. for (let x = right; x >= left; x--) path.push({ x, y: bottom });
  594. bottom--;
  595. }
  596. if (left <= right) {
  597. for (let y = bottom; y >= top; y--) path.push({ x: left, y });
  598. left++;
  599. }
  600. }
  601. if (this.reverseDirection) path.reverse();
  602. this.updateStatus(`Path length ${path.length}`, '#888');
  603. await this.processCells(path);
  604. }
  605.  
  606. stopWipe() {
  607. this.stopRequested = true;
  608. this.updateStatus('Stop requested...', '#c0392b');
  609. }
  610.  
  611. sleep(ms) {
  612. return new Promise(resolve => setTimeout(resolve, ms));
  613. }
  614.  
  615. static shuffleArray(arr) {
  616. for (let i = arr.length - 1; i > 0; i--) {
  617. const j = Math.floor(Math.random() * (i + 1));
  618. [arr[i], arr[j]] = [arr[j], arr[i]];
  619. }
  620. }
  621. }
  622.  
  623. // expose class and instance
  624. window.OWOTSolarWiper = OWOTSolarWiper;
  625. window.OWOTSolarWiperInstance = new OWOTSolarWiper();
  626. }
  627.  
  628. // initialize when ready
  629. if (document.readyState === 'complete' || document.readyState === 'interactive') {
  630. setTimeout(initSolarWiper, 0);
  631. } else {
  632. window.addEventListener('load', initSolarWiper);
  633. }
  634. })();
  635.  
Advertisement
Comments
  • User was banned
  • User was banned
  • User was banned
  • User was banned
Add Comment
Please, Sign In to add comment