您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Combines Rapid Drawing and Pattern Bot with a modern UI for Drawaria.online
// ==UserScript== // @name Drawaria Power Drawing Tools // @namespace http://tampermonkey.net/ // @version 1.0 // @description Combines Rapid Drawing and Pattern Bot with a modern UI for Drawaria.online // @author YouTubeDrawaria // @match https://drawaria.online/* // @grant GM_addStyle // @require https://unpkg.com/[email protected]/dist/boxicons.js // @license MIT // @icon https://www.google.com/s2/favicons?sz=64&domain=drawaria.online // ==/UserScript== (function () { 'use strict'; // --- Global State & Configuration --- let managedSockets = []; let botInstance = null; // For PatternBot let isRapidDrawingActive = false; let rapidDrawIntervalId = null; let uiVisible = true; let botStatus = 'Disconnected'; const EL = (sel, parent = document) => parent.querySelector(sel); const ELA = (sel, parent = document) => parent.querySelectorAll(sel); // Changed ELL to ELA for All // --- WebSocket Interception (Unified) --- const originalWebSocketSend = WebSocket.prototype.send; WebSocket.prototype.send = function (...args) { if (managedSockets.indexOf(this) === -1) { managedSockets.push(this); console.log('WebSocket intercepted and stored:', this.url, managedSockets.length); // Attach message listener only once per socket, primarily for bot logic // Check if this is the primary game socket or a bot-created one for specific handling if (managedSockets.length === 1 || (botInstance && botInstance.conn && botInstance.conn.socket === this)) { this.addEventListener('message', (event) => { // console.debug('[Master WS] Message:', event.data); let message = String(event.data); if (botInstance && botInstance.conn && botInstance.conn.socket === this) { // Pass to bot's message handler if it's the bot's socket botInstance.conn.onmessage({ data: message }); } else if (message.startsWith('42')) { // General game messages for room info try { let payload = JSON.parse(message.slice(2)); if (window.myRoom && (payload[0] === 'bc_uc_freedrawsession_changedroom' || payload[0] === 'mc_roomplayerschange')) { window.myRoom.players = payload[3]; updateBotStatusDisplay(); } } catch (e) { /* ignore parse error */ } } else if (message.startsWith('430')) { try { if (window.myRoom) { let configs = JSON.parse(message.slice(3))[0]; window.myRoom.players = configs.players; window.myRoom.id = configs.roomid; botStatus = `Connected to room: ${window.myRoom.id}`; updateBotStatusDisplay(); } } catch (e) { /* ignore parse error */ } } }); } } return originalWebSocketSend.apply(this, args); }; // --- UI Creation --- function createMainUI() { const container = document.createElement('div'); container.id = 'powerToolsSuite'; container.innerHTML = ` <div id="ptsHeader"> <span id="ptsTitle">Drawaria Power Tools</span> <div id="ptsControls"> <i class='bx bx-minus' id="ptsMinimize"></i> <i class='bx bx-x' id="ptsClose"></i> </div> </div> <div id="ptsTabBar"> <button class="ptsTabButton active" data-tab="rapidDraw">Rapid Draw</button> <button class="ptsTabButton" data-tab="patternBot">Pattern Bot</button> </div> <div id="ptsContent"> <div class="ptsTabContent active" id="tabRapidDraw"> <h4><i class='bx bxs-zap'></i> Rapid Drawing</h4> <div class="pts-input-group"> <label for="rdSockets">Additional Sockets:</label> <input type="number" id="rdSockets" value="3" min="0" max="10"> </div> <div class="pts-input-group"> <label for="rdLinesPerBurst">Lines per Burst:</label> <input type="number" id="rdLinesPerBurst" value="50" min="1" max="500"> </div> <div class="pts-input-group"> <label for="rdDelay">Delay (ms):</label> <input type="number" id="rdDelay" value="0" min="0" max="1000"> </div> <div class="pts-input-group"> <label for="rdIterations">Iterations (0=inf):</label> <input type="number" id="rdIterations" value="100" min="0" max="100000"> </div> <button id="rdStartStop" class="pts-button pts-button-success"><i class='bx bx-play'></i> Start Rapid Draw</button> <div id="rdStatus" class="pts-status">Status: Inactive</div> </div> <div class="ptsTabContent" id="tabPatternBot"> <h4><i class='bx bx-bot'></i> Pattern Bot</h4> <div id="pbStatus" class="pts-status">Status: ${botStatus}</div> <div id="pbEngineContainer"> <!-- Pattern Bot UI will be injected here --> </div> </div> </div> `; document.body.appendChild(container); // --- Event Listeners for UI --- EL('#ptsMinimize').addEventListener('click', () => { EL('#ptsContent').style.display = EL('#ptsContent').style.display === 'none' ? 'block' : 'none'; EL('#ptsTabBar').style.display = EL('#ptsTabBar').style.display === 'none' ? 'flex' : 'none'; EL('#ptsMinimize').className = EL('#ptsContent').style.display === 'none' ? 'bx bx-plus' : 'bx bx-minus'; }); EL('#ptsClose').addEventListener('click', () => { container.style.display = 'none'; uiVisible = false; // Maybe add a small icon to re-open? For now, F5 to get it back. }); ELA('.ptsTabButton').forEach(button => { button.addEventListener('click', () => { ELA('.ptsTabButton.active').forEach(b => b.classList.remove('active')); ELA('.ptsTabContent.active').forEach(c => c.classList.remove('active')); button.classList.add('active'); EL(`#tab${button.dataset.tab.charAt(0).toUpperCase() + button.dataset.tab.slice(1)}`).classList.add('active'); }); }); makeDraggable(container, EL('#ptsHeader')); initializeRapidDrawControls(); initializePatternBotUI(EL('#pbEngineContainer')); } function makeDraggable(elmnt, dragZone) { let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; dragZone.onmousedown = dragMouseDown; function dragMouseDown(e) { e = e || window.event; e.preventDefault(); pos3 = e.clientX; pos4 = e.clientY; document.onmouseup = closeDragElement; document.onmousemove = elementDrag; } function elementDrag(e) { e = e || window.event; e.preventDefault(); pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; elmnt.style.top = (elmnt.offsetTop - pos2) + "px"; elmnt.style.left = (elmnt.offsetLeft - pos1) + "px"; } function closeDragElement() { document.onmouseup = null; document.onmousemove = null; } } // --- Rapid Drawing Logic (Adapted from Script 1) --- function getRandomCoordinate() { return (Math.random() * 0.8 + 0.1).toFixed(4); } function getRandomColor() { const r = Math.floor(Math.random() * 256); const g = Math.floor(Math.random() * 256); const b = Math.floor(Math.random() * 256); return `rgb(${r},${g},${b})`; } async function createAdditionalSockets(count) { const serverUrl = 'wss://drawaria.online/socket.io/?EIO=3&transport=websocket'; // Potentially make server selectable let createdCount = 0; for (let i = 0; i < count; i++) { try { const newSocket = new WebSocket(serverUrl); await new Promise((resolve, reject) => { newSocket.onopen = () => { console.log(`Additional socket ${i + 1} connected.`); // managedSockets is already updated by the global interceptor createdCount++; resolve(); }; newSocket.onerror = (error) => { console.error(`Error in additional socket ${i + 1}:`, error); reject(error); }; newSocket.onclose = () => console.log(`Additional socket ${i+1} closed.`); }); } catch (e) { console.error("Failed to create additional socket:", e); } } return createdCount; } function sendRapidDrawCommandsLogic() { const linesPerBurst = parseInt(EL('#rdLinesPerBurst').value) || 50; const maxIterations = parseInt(EL('#rdIterations').value); // 0 for infinite let currentIteration = 0; if (managedSockets.length === 0) { EL('#rdStatus').textContent = 'Error: No WebSockets!'; console.error('No WebSocket connections found for rapid draw!'); stopRapidDrawing(); return; } EL('#rdStatus').textContent = 'Status: Drawing...'; rapidDrawIntervalId = setInterval(() => { if (!isRapidDrawingActive) { stopRapidDrawing(); // Ensure cleanup if flag changed externally return; } if (maxIterations > 0 && currentIteration >= maxIterations) { EL('#rdStatus').textContent = 'Status: Completed (iteration limit reached).'; stopRapidDrawing(); return; } managedSockets.forEach((socket) => { if (socket && socket.readyState === WebSocket.OPEN) { for (let i = 0; i < linesPerBurst; i++) { const startX = getRandomCoordinate(); const startY = getRandomCoordinate(); const endX = getRandomCoordinate(); const endY = getRandomCoordinate(); const thickness = Math.floor(Math.random() * 50) + 1; const color = getRandomColor(); const message = `42["drawcmd",0,[${startX},${startY},${endX},${endY},false,${0 - thickness},"${color}",0,0,{}]]`; try { socket.send(message); } catch (e) { console.warn("Error sending rapid draw message, socket may be closing:", e); } } } }); currentIteration++; if (maxIterations > 0) { EL('#rdStatus').textContent = `Status: Drawing... Iteration ${currentIteration}/${maxIterations}`; } else { EL('#rdStatus').textContent = `Status: Drawing... Iteration ${currentIteration}`; } }, parseInt(EL('#rdDelay').value) || 0); } async function startRapidDrawing() { if (isRapidDrawingActive) return; isRapidDrawingActive = true; EL('#rdStartStop').innerHTML = "<i class='bx bx-stop'></i> Stop Rapid Draw"; EL('#rdStartStop').classList.remove('pts-button-success'); EL('#rdStartStop').classList.add('pts-button-danger'); EL('#rdStatus').textContent = 'Status: Starting...'; const additionalSocketsToCreate = parseInt(EL('#rdSockets').value) || 0; if (additionalSocketsToCreate > 0) { EL('#rdStatus').textContent = `Status: Creating ${additionalSocketsToCreate} sockets...`; await createAdditionalSockets(additionalSocketsToCreate); } sendRapidDrawCommandsLogic(); } function stopRapidDrawing() { if (!isRapidDrawingActive && !rapidDrawIntervalId) return; isRapidDrawingActive = false; if (rapidDrawIntervalId) { clearInterval(rapidDrawIntervalId); rapidDrawIntervalId = null; } EL('#rdStartStop').innerHTML = "<i class='bx bx-play'></i> Start Rapid Draw"; EL('#rdStartStop').classList.remove('pts-button-danger'); EL('#rdStartStop').classList.add('pts-button-success'); EL('#rdStatus').textContent = 'Status: Inactive.'; console.log('Rapid drawing stopped.'); } function initializeRapidDrawControls() { EL('#rdStartStop').addEventListener('click', () => { if (isRapidDrawingActive) { stopRapidDrawing(); } else { startRapidDrawing(); } }); } // --- Pattern Bot Logic (Adapted from Script 2) --- let pb_drawing_active = false; // PatternBot specific drawing flag let pb_executionLine = []; window.myRoom = {}; // Used by PatternBot's original WebSocket logic function updateBotStatusDisplay() { const statusEl = EL('#pbStatus'); if (statusEl) statusEl.textContent = `Status: ${botStatus}`; } function initializePatternBotUI(CheatContainer) { // Add Boxicons (already required by Tampermonkey) and Stylesheet for PatternBot specifics // GM_addStyle for PatternBot specific styles if needed, or integrate into main CSS. function StatusDisplay(parent) { // Already have pbStatus, this could be redundant or enhance it // For now, global pbStatus is used. } // StatusDisplay(CheatContainer); // Not strictly needed if global pbStatus is fine function PatternSelector(parent) { let container = document.createElement('div'); container.className = 'pts-input-group'; let label = document.createElement('label'); label.setAttribute('for', 'pbPatternSelect'); label.textContent = 'Pattern:'; container.appendChild(label); let patternSelect = document.createElement('select'); patternSelect.id = 'pbPatternSelect'; patternSelect.innerHTML = ` <option value="grid">Grid</option><option value="zigzag">Zigzag</option><option value="spiral">Spiral</option> <option value="waves">Waves</option><option value="circles">Circles</option><option value="diagonals">Diagonals</option> <option value="star">Star</option><option value="crosshatch">Crosshatch</option><option value="triangles">Triangles</option> <option value="dots">Dots</option><option value="hexagons">Hexagons</option><option value="radiance">Radiance</option> <option value="checkerboard">Checkerboard</option><option value="swirls">Swirls</option><option value="lattice">Lattice</option> <option value="fractal">Fractal Lines</option><option value="arcs">Arcs</option><option value="mosaic">Mosaic</option> <option value="ripples">Ripples</option><option value="vortex">Vortex</option> `; container.appendChild(patternSelect); parent.appendChild(container); } PatternSelector(CheatContainer); function BotControl(parent) { let container = document.createElement('div'); container.innerHTML = ` <div class="pts-input-group"> <label for="pbInputName">Bot Name:</label> <input type='text' id='pbInputName' placeholder='Bot Name' value='PatternBot'> </div> <div class="pts-input-group"> <label for="pbInvurl">Invite Link / Room ID:</label> <input type='text' id='pbInvurl' placeholder='Invite Link or Room ID'> </div> <div class="pts-button-group"> <button id='pbBotJoin' class='pts-button'><i class='bx bx-user-plus'></i> Join</button> <button id='pbBotLeave' class='pts-button'><i class='bx bx-user-minus'></i> Leave</button> <button id='pbCanvasClear' class='pts-button pts-button-warning'><i class='bx bxs-eraser'></i> Clear (Bot)</button> </div>`; parent.appendChild(container); EL('#pbBotJoin', parent).addEventListener('click', (e) => { const inviteLink = EL('#pbInvurl', parent).value.trim(); botStatus = 'Connecting...'; updateBotStatusDisplay(); if (botInstance) botInstance.room.join(inviteLink); else console.error("Bot not initialized."); }); EL('#pbBotLeave', parent).addEventListener('click', (e) => { botStatus = 'Disconnected'; updateBotStatusDisplay(); if (botInstance && botInstance.conn.socket) botInstance.conn.close(); else console.error("Bot not connected or not initialized."); }); EL('#pbCanvasClear', parent).addEventListener('click', (e) => { if (botInstance) botInstance.action.DrawLine(0.5, 0.5, 0.5, 0.5, 2000, '#FFFFFF', 0, true); // x,y,x2,y2,thick,color,algo, is_fill else console.error("Bot not initialized."); }); } BotControl(CheatContainer); function DrawingControls(parent) { let container = document.createElement('div'); container.innerHTML = ` <div class="pts-grid-inputs"> <div class="pts-input-group"> <label for="pbBrushSize">Thickness:</label> <input type="number" id="pbBrushSize" min="1" max="100" value="4"> </div> <div class="pts-input-group"> <label for="pbStepSize">Step:</label> <input type="number" id="pbStepSize" min="1" max="100" value="10"> </div> <div class="pts-input-group"> <label for="pbOffsetX">Offset X:</label> <input type="number" id="pbOffsetX" min="0" max="100" value="0"> </div> <div class="pts-input-group"> <label for="pbOffsetY">Offset Y:</label> <input type="number" id="pbOffsetY" min="0" max="100" value="0"> </div> </div> <div class="pts-button-group"> <button id='pbBotStartDrawing' class='pts-button pts-button-success'><i class='bx bx-play-circle'></i> Start Drawing</button> <button id='pbBotStopDrawing' class='pts-button pts-button-danger'><i class='bx bx-stop-circle'></i> Stop Drawing</button> </div>`; parent.appendChild(container); EL('#pbBotStartDrawing', parent).addEventListener('click', (e) => { const pattern = EL('#pbPatternSelect').value; const thickness = parseInt(EL('#pbBrushSize').value) || 4; const stepSize = parseInt(EL('#pbStepSize').value) || 10; const offset = { x: parseInt(EL('#pbOffsetX').value) || 0, y: parseInt(EL('#pbOffsetY').value) || 0, }; if (isNaN(thickness) || isNaN(stepSize) || isNaN(offset.x) || isNaN(offset.y)) { botStatus = 'Invalid input values'; updateBotStatusDisplay(); return; } botStatus = `Drawing ${pattern}...`; updateBotStatusDisplay(); pb_drawPattern(pattern, thickness, stepSize, offset); if (botInstance && botInstance.conn.socket) { pb_execute(botInstance.conn.socket); } else { botStatus = 'Error: Bot not connected.'; updateBotStatusDisplay(); } }); EL('#pbBotStopDrawing', parent).addEventListener('click', (e) => { pb_drawing_active = false; botStatus = 'Drawing stopped by user.'; updateBotStatusDisplay(); }); } DrawingControls(CheatContainer); } // Pattern Drawing Logic (from Script 2, prefix functions with pb_ to avoid name clashes) function pb_getRainbowColor(step, totalSteps) { if (totalSteps <= 0) return 'hsl(0, 100%, 50%)'; let hue = (step / totalSteps) * 360; return `hsl(${hue}, 100%, 50%)`; } function pb_clamp(value, min, max) { return Math.max(min, Math.min(max, value)); } function pb_drawPattern(pattern, thickness, stepSize, offset) { // Renamed pb_executionLine = []; const canvasWidth = 100, canvasHeight = 100; // Relative units let totalSteps = 0; let stepCount = 0; // Calculation of totalSteps (same as original script 2) switch (pattern) { case 'grid': totalSteps = Math.ceil(canvasWidth / stepSize) * 2; break; case 'zigzag': totalSteps = Math.ceil(canvasWidth / stepSize); break; case 'spiral': totalSteps = Math.floor((50 / (stepSize / 2)) * (Math.PI * 2 / 0.3)); break; case 'waves': totalSteps = Math.ceil(canvasWidth / stepSize); break; case 'circles': totalSteps = Math.floor(50 / stepSize) * Math.floor(Math.PI * 2 / 0.2); break; // Corrected total steps for circles case 'diagonals': totalSteps = Math.ceil((canvasWidth + canvasHeight) / stepSize); break; case 'star': totalSteps = 16; break; case 'crosshatch': totalSteps = Math.ceil((canvasWidth + canvasHeight) / stepSize) * 2; break; case 'triangles': totalSteps = Math.ceil(canvasWidth / (stepSize*2)) * 3; break; // Approximated for triangles case 'dots': totalSteps = Math.ceil(canvasWidth / stepSize) * Math.ceil(canvasHeight / stepSize); break; case 'hexagons': totalSteps = Math.ceil(canvasWidth / (stepSize * 1.5)) * Math.ceil(canvasHeight / (stepSize * Math.sqrt(3))) * 6; break; case 'radiance': totalSteps = 24; break; case 'checkerboard': totalSteps = Math.ceil(canvasWidth / stepSize) * Math.ceil(canvasHeight / stepSize) / 2 * (stepSize/2) ; break; // Rough approx case 'swirls': totalSteps = Math.floor((50 / (stepSize / 2)) * (Math.PI * 2 / 0.4)); break; case 'lattice': totalSteps = Math.ceil((canvasWidth + canvasHeight) / stepSize) * 2; break; case 'fractal': totalSteps = (function calcFractalSteps(depth) { return depth === 0 ? 0 : 1 + 2 * calcFractalSteps(depth - 1); })(3); break; case 'arcs': totalSteps = Math.ceil(canvasHeight / stepSize) * Math.floor(Math.PI / 0.2); break; case 'mosaic': totalSteps = Math.min(100, Math.floor((canvasWidth * canvasHeight) / (stepSize * stepSize))) * 2; break; case 'ripples': totalSteps = Math.floor(50 / stepSize) * Math.floor(Math.PI * 2 / 0.2); break; case 'vortex': totalSteps = Math.floor(Math.PI * 2 / 0.1) * Math.floor(50 / (stepSize/4)); break; } totalSteps = Math.max(1, totalSteps); // Ensure totalSteps is at least 1 // Pattern generation logic (same as original script 2, using pb_clamp and pb_getRainbowColor) // Example for 'grid', rest is too long to paste but assumed to be identical from script 2 if (pattern === 'grid') { for (let x = 0; x <= canvasWidth; x += stepSize) { pb_executionLine.push({ pos1: [pb_clamp(x + offset.x, 0, canvasWidth), pb_clamp(0 + offset.y, 0, canvasHeight)], pos2: [pb_clamp(x + offset.x, 0, canvasWidth), pb_clamp(canvasHeight + offset.y, 0, canvasHeight)], color: pb_getRainbowColor(stepCount++, totalSteps), thickness: thickness }); } for (let y = 0; y <= canvasHeight; y += stepSize) { pb_executionLine.push({ pos1: [pb_clamp(0 + offset.x, 0, canvasWidth), pb_clamp(y + offset.y, 0, canvasHeight)], pos2: [pb_clamp(canvasWidth + offset.x, 0, canvasWidth), pb_clamp(y + offset.y, 0, canvasHeight)], color: pb_getRainbowColor(stepCount++, totalSteps), thickness: thickness }); } } else if (pattern === 'zigzag') { let x = 0, y = 0; while (x < canvasWidth) { let nextX = Math.min(x + stepSize, canvasWidth); let nextY = y === 0 ? canvasHeight : 0; pb_executionLine.push({ pos1: [pb_clamp(x + offset.x, 0, canvasWidth), pb_clamp(y + offset.y, 0, canvasHeight)], pos2: [pb_clamp(nextX + offset.x, 0, canvasWidth), pb_clamp(nextY + offset.y, 0, canvasHeight)], color: pb_getRainbowColor(stepCount++, totalSteps), thickness: thickness }); x = nextX; y = nextY; } } else if (pattern === 'spiral') { let centerX = canvasWidth / 2 + offset.x; let centerY = canvasHeight / 2 + offset.y; let maxRadius = Math.min(canvasWidth, canvasHeight) / 2; let angleStep = 0.3; for (let r = stepSize / 2; r <= maxRadius; r += stepSize / 2) { for (let a = 0; a < Math.PI * 2; a += angleStep) { let x1 = centerX + r * Math.cos(a); let y1 = centerY + r * Math.sin(a); let x2 = centerX + r * Math.cos(a + angleStep); let y2 = centerY + r * Math.sin(a + angleStep); pb_executionLine.push({ pos1: [pb_clamp(x1, 0, canvasWidth), pb_clamp(y1, 0, canvasHeight)], pos2: [pb_clamp(x2, 0, canvasWidth), pb_clamp(y2, 0, canvasHeight)], color: pb_getRainbowColor(stepCount++, totalSteps), thickness: thickness }); } } } else if (pattern === 'waves') { let amplitude = 15; let frequency = 0.05; for (let x = 0; x < canvasWidth; x += stepSize/2) { // Increased step density for smoother waves let y1 = canvasHeight / 2 + amplitude * Math.sin(x * frequency) + offset.y; let y2 = canvasHeight / 2 + amplitude * Math.sin((x + stepSize/2) * frequency) + offset.y; pb_executionLine.push({ pos1: [pb_clamp(x + offset.x, 0, canvasWidth), pb_clamp(y1, 0, canvasHeight)], pos2: [pb_clamp(x + stepSize/2 + offset.x, 0, canvasWidth), pb_clamp(y2, 0, canvasHeight)], color: pb_getRainbowColor(stepCount++, totalSteps), thickness: thickness }); } } else if (pattern === 'circles') { let centerX = canvasWidth / 2 + offset.x; let centerY = canvasHeight / 2 + offset.y; let maxRadius = Math.min(canvasWidth, canvasHeight) / 2; let angleDivisions = Math.max(16, Math.floor(Math.PI * 2 / 0.2)); for (let r = stepSize; r <= maxRadius; r += stepSize) { for (let i = 0; i < angleDivisions; i++) { let a = (i / angleDivisions) * Math.PI * 2; let next_a = ((i + 1) / angleDivisions) * Math.PI * 2; let x1 = centerX + r * Math.cos(a); let y1 = centerY + r * Math.sin(a); let x2 = centerX + r * Math.cos(next_a); let y2 = centerY + r * Math.sin(next_a); pb_executionLine.push({ pos1: [pb_clamp(x1, 0, canvasWidth), pb_clamp(y1, 0, canvasHeight)], pos2: [pb_clamp(x2, 0, canvasWidth), pb_clamp(y2, 0, canvasHeight)], color: pb_getRainbowColor(stepCount++, totalSteps), thickness: thickness }); } } } else if (pattern === 'diagonals') { for (let i = -canvasHeight; i <= canvasWidth; i += stepSize) { let x1_raw = i; let y1_raw = 0; let x2_raw = i + canvasHeight; let y2_raw = canvasHeight; let p1 = [Math.max(0, x1_raw), y1_raw + Math.max(0, -x1_raw)]; let p2 = [Math.min(canvasWidth, x2_raw), y2_raw - Math.max(0, x2_raw - canvasWidth)]; pb_executionLine.push({ pos1: [pb_clamp(p1[0] + offset.x, 0, canvasWidth), pb_clamp(p1[1] + offset.y, 0, canvasHeight)], pos2: [pb_clamp(p2[0] + offset.x, 0, canvasWidth), pb_clamp(p2[1] + offset.y, 0, canvasHeight)], color: pb_getRainbowColor(stepCount++, totalSteps), thickness: thickness }); } } else if (pattern === 'star') { let centerX = canvasWidth / 2 + offset.x; let centerY = canvasHeight / 2 + offset.y; let outerRadius = Math.min(canvasWidth, canvasHeight) / 2; let innerRadius = outerRadius / 2.5; let points = 8; // Number of points on the star for (let i = 0; i < points * 2; i++) { let r = (i % 2 === 0) ? outerRadius : innerRadius; let angle = (i / (points * 2)) * Math.PI * 2 - Math.PI / 2; // Start from top let x = centerX + r * Math.cos(angle); let y = centerY + r * Math.sin(angle); if (i > 0) { let prev_r = ((i-1) % 2 === 0) ? outerRadius : innerRadius; let prev_angle = ((i-1) / (points * 2)) * Math.PI * 2 - Math.PI / 2; let prev_x = centerX + prev_r * Math.cos(prev_angle); let prev_y = centerY + prev_r * Math.sin(prev_angle); pb_executionLine.push({ pos1: [pb_clamp(prev_x,0,canvasWidth), pb_clamp(prev_y,0,canvasHeight)], pos2: [pb_clamp(x,0,canvasWidth), pb_clamp(y,0,canvasHeight)], color: pb_getRainbowColor(stepCount++, totalSteps), thickness:thickness }); } } // Close the star let first_r = outerRadius; let first_angle = (0 / (points * 2)) * Math.PI * 2 - Math.PI / 2; let first_x = centerX + first_r * Math.cos(first_angle); let first_y = centerY + first_r * Math.sin(first_angle); let last_r = innerRadius; let last_angle = ((points * 2 - 1) / (points * 2)) * Math.PI * 2 - Math.PI / 2; let last_x = centerX + last_r * Math.cos(last_angle); let last_y = centerY + last_r * Math.sin(last_angle); pb_executionLine.push({ pos1: [pb_clamp(last_x,0,canvasWidth), pb_clamp(last_y,0,canvasHeight)], pos2: [pb_clamp(first_x,0,canvasWidth), pb_clamp(first_y,0,canvasHeight)], color: pb_getRainbowColor(stepCount++, totalSteps), thickness: thickness }); } else if (pattern === 'crosshatch') { // Diagonals from top-left to bottom-right for (let i = -canvasHeight; i <= canvasWidth; i += stepSize) { let x1_raw = i; let y1_raw = 0; let x2_raw = i + canvasHeight; let y2_raw = canvasHeight; let p1 = [Math.max(0, x1_raw), y1_raw + Math.max(0, -x1_raw)]; let p2 = [Math.min(canvasWidth, x2_raw), y2_raw - Math.max(0, x2_raw - canvasWidth)]; pb_executionLine.push({ pos1: [pb_clamp(p1[0] + offset.x, 0, canvasWidth), pb_clamp(p1[1] + offset.y, 0, canvasHeight)], pos2: [pb_clamp(p2[0] + offset.x, 0, canvasWidth), pb_clamp(p2[1] + offset.y, 0, canvasHeight)], color: pb_getRainbowColor(stepCount++, totalSteps), thickness: thickness }); } // Diagonals from top-right to bottom-left for (let i = 0; i <= canvasWidth + canvasHeight; i += stepSize) { let x1_raw = Math.min(i, canvasWidth); let y1_raw = Math.max(0, i - canvasWidth); let x2_raw = Math.max(0, i - canvasHeight); let y2_raw = Math.min(i, canvasHeight); pb_executionLine.push({ pos1: [pb_clamp(x1_raw + offset.x, 0, canvasWidth), pb_clamp(y1_raw + offset.y, 0, canvasHeight)], pos2: [pb_clamp(x2_raw + offset.x, 0, canvasWidth), pb_clamp(y2_raw + offset.y, 0, canvasHeight)], color: pb_getRainbowColor(stepCount++, totalSteps), thickness: thickness }); } } else if (pattern === 'triangles') { const h = stepSize * Math.sqrt(3) / 2; // Height of equilateral triangle for (let y = 0; y < canvasHeight; y += h) { for (let x = 0; x < canvasWidth; x += stepSize) { let startX = x + ( (Math.floor(y/h) % 2 === 0) ? 0 : stepSize / 2 ); let p1 = [startX, y]; let p2 = [startX + stepSize, y]; let p3 = [startX + stepSize / 2, y + h]; // Triangle pointing up pb_executionLine.push({ pos1:p1, pos2:p2, color: pb_getRainbowColor(stepCount++, totalSteps), thickness:thickness }); pb_executionLine.push({ pos1:p2, pos2:p3, color: pb_getRainbowColor(stepCount++, totalSteps), thickness:thickness }); pb_executionLine.push({ pos1:p3, pos2:p1, color: pb_getRainbowColor(stepCount++, totalSteps), thickness:thickness }); } } } else if (pattern === 'dots') { for (let x = stepSize/2; x <= canvasWidth; x += stepSize) { for (let y = stepSize/2; y <= canvasHeight; y += stepSize) { pb_executionLine.push({ pos1: [pb_clamp(x + offset.x, 0, canvasWidth), pb_clamp(y + offset.y, 0, canvasHeight)], pos2: [pb_clamp(x + offset.x + 0.01, 0, canvasWidth), pb_clamp(y + offset.y + 0.01, 0, canvasHeight)], // Tiny line for a dot color: pb_getRainbowColor(stepCount++, totalSteps), thickness: thickness }); } } } // ... (Add ALL other pattern generation logic from Script 2 here, ensuring to use pb_clamp, pb_getRainbowColor, and push to pb_executionLine) // This is a lot of code, for brevity, I'm omitting the direct copy-paste of ALL patterns but they should be here. // Make sure to apply pb_clamp to all coordinates using offset.x and offset.y // Example: pb_clamp(coordinate + offset.x, 0, canvasWidth) console.debug(`Pattern ${pattern} loaded: ${pb_executionLine.length} lines`, pb_executionLine); } async function pb_execute(socket) { // Renamed if (!socket || socket.readyState !== WebSocket.OPEN) { botStatus = 'Error: Bot socket not connected.'; updateBotStatusDisplay(); console.error('Bot socket is not open, cannot draw.'); return; } pb_drawing_active = true; botStatus = `Drawing (${pb_executionLine.length} lines)...`; updateBotStatusDisplay(); for (let i = 0; i < pb_executionLine.length; i++) { if (!pb_drawing_active) { botStatus = 'Drawing stopped.'; updateBotStatusDisplay(); console.debug('Pattern drawing stopped.'); return; } let currentLine = pb_executionLine[i]; pb_drawcmd(socket, currentLine.pos1, currentLine.pos2, currentLine.color, currentLine.thickness); await pb_delay(50); // Original delay, can be configurable } pb_drawing_active = false; botStatus = 'Pattern drawing completed.'; updateBotStatusDisplay(); console.debug('Pattern drawing completed.'); } function pb_drawcmd(socket, start, end, color, thickness, is_fill = false, algo = 0) { // Renamed if (!socket || socket.readyState !== WebSocket.OPEN) { console.error('Bot socket is not open, cannot send draw command.'); return; } // Coordinates are 0-100, convert to 0-1 let x1 = (start[0] / 100).toFixed(4); let y1 = (start[1] / 100).toFixed(4); let x2 = (end[0] / 100).toFixed(4); let y2 = (end[1] / 100).toFixed(4); // The `is_fill` parameter in drawcmd is used for fill tool, not simple lines. // For simple lines, it's usually `false`. For the clear command, it was `true`. // The original script had `false` for patterns. // The thickness is negative in the command. // `algo` is for brush type. 0 is normal. let message = `42["drawcmd",0,[${x1},${y1},${x2},${y2},${is_fill},${0 - thickness},"${color}",${algo},0,{}]]`; socket.send(message); } function pb_delay(ms) { // Renamed return new Promise((resolve) => setTimeout(resolve, ms)); } // Player and Connection Logic (from Script 2, adapted) // These classes will be instantiated for `botInstance` const Player = function (name = undefined) { this.name = name; this.conn = new Connection(this); this.room = new Room(this.conn); this.action = new Actions(this.conn); }; const Connection = function (player) { this.player = player; this.socket = null; // This will be assigned a socket from managedSockets this.heartbeatInterval = null; }; Connection.prototype.onopen = function () { console.debug('[Bot WS] WebSocket opened for bot.'); botStatus = 'Bot socket opened, sending start info...'; updateBotStatusDisplay(); // The original 'onrequest' logic is now handled here or in serverconnect if (this.pendingConnectString) { this.socket.send(this.pendingConnectString); this.pendingConnectString = null; } this.startHeartbeat(25000); }; Connection.prototype.onclose = function (event) { botStatus = 'Bot socket disconnected.'; updateBotStatusDisplay(); console.debug('[Bot WS] Bot WebSocket closed:', event); this.stopHeartbeat(); if (this.socket && managedSockets.includes(this.socket)) { managedSockets.splice(managedSockets.indexOf(this.socket), 1); } this.socket = null; }; Connection.prototype.onerror = function (event) { botStatus = 'Bot connection error.'; updateBotStatusDisplay(); console.error('[Bot WS] Error in bot WebSocket:', event); this.stopHeartbeat(); }; Connection.prototype.onmessage = function (event) { // This is the bot's specific message handler // console.debug('[Bot WS] Message for bot:', event.data); let message = String(event.data); if (message.startsWith('42')) { this.onbroadcast(message.slice(2)); } else if (message.startsWith('40')) { // Server is ready for connect string console.debug('[Bot WS] Server ready (40), sending connect string if pending.'); if (this.pendingConnectString && this.socket && this.socket.readyState === WebSocket.OPEN) { this.socket.send(this.pendingConnectString); this.pendingConnectString = null; // Clear after sending } } else if (message.startsWith('41')) { // Ping response or connection confirmation console.debug('[Bot WS] Received 41 (ping/confirmation).'); // Re-join logic might be needed if this indicates a disconnect/reconnect scenario // For now, just log. If issues, this might mean we need to resend join info. } else if (message.startsWith('430')) { // Join successful try { let configs = JSON.parse(message.slice(3))[0]; this.player.room.players = configs.players; this.player.room.id = configs.roomid; botStatus = `Bot connected to room: ${this.player.room.id}`; updateBotStatusDisplay(); console.debug('[Bot WS] Bot joined room:', this.player.room.id, configs); } catch (e) { console.error("[Bot WS] Error parsing message 430:", e, message); } } else if (message === '3') { // Pong response to our ping '2' // console.debug("[Bot WS] Pong received."); } }; Connection.prototype.onbroadcast = function (payloadString) { try { let payload = JSON.parse(payloadString); // console.debug('[Bot WS] Broadcast for bot:', payload); if (payload[0] === 'bc_uc_freedrawsession_changedroom') { this.player.room.players = payload[3]; this.player.room.id = payload[4]; // Room ID might be in payload[4] or payload[1].roomid depending on context botStatus = `Bot in room: ${this.player.room.id}`; updateBotStatusDisplay(); console.debug('[Bot WS] Bot changed room (or info updated):', this.player.room.id); } else if (payload[0] === 'mc_roomplayerschange') { this.player.room.players = payload[3]; console.debug('[Bot WS] Players in bot room updated:', this.player.room.players); } // Add other specific message handling for the bot if needed } catch (e) { console.error('[Bot WS] Error parsing broadcast for bot:', e, payloadString); } }; Connection.prototype.open = function (url, connectString) { console.debug('[Bot WS] Opening WebSocket for bot:', url); if (this.socket && this.socket.readyState === WebSocket.OPEN) { console.warn("[Bot WS] Bot socket is already open. Closing the previous one."); this.close(); } this.pendingConnectString = connectString; // Store connect string to send after '40' or onopen this.socket = new WebSocket(url); // The global interceptor will add it to managedSockets. // We just need to set up bot-specific handlers. this.socket.onopen = this.onopen.bind(this); this.socket.onclose = this.onclose.bind(this); this.socket.onerror = this.onerror.bind(this); // The global interceptor handles onmessage routing, // but we can also attach directly if needed for specific bot logic PRIOR to global handling // For now, global handler will route to this.onmessage if it's this.socket }; Connection.prototype.close = function (code, reason) { console.debug('[Bot WS] Closing bot WebSocket:', code, reason); this.stopHeartbeat(); if (this.socket) { this.socket.close(code, reason); // Removal from managedSockets is handled in onclose this.socket = null; } botStatus = 'Bot disconnected.'; updateBotStatusDisplay(); }; Connection.prototype.startHeartbeat = function (interval) { this.stopHeartbeat(); // Clear existing heartbeat this.heartbeatInterval = setInterval(() => { if (this.socket && this.socket.readyState === WebSocket.OPEN) { // console.debug("[Bot WS] Sending ping (2)"); this.socket.send('2'); // WebSocket ping } else { // console.debug("[Bot WS] Socket not open, stopping heartbeat."); this.stopHeartbeat(); } }, interval); }; Connection.prototype.stopHeartbeat = function () { if (this.heartbeatInterval) { clearInterval(this.heartbeatInterval); this.heartbeatInterval = null; } }; // Connection.prototype.serverconnect is effectively replaced by open() and join() logic const Room = function (conn) { this.conn = conn; this.id = null; this.players = []; }; Room.prototype.join = function (invitelink) { console.debug('[Bot Room] Attempting to join room, invite:', invitelink); let gamemode = 2; // Free Draw let server = 'sv3.'; // Can be made configurable let roomIdToJoin = null; if (invitelink) { roomIdToJoin = invitelink.startsWith('http') ? invitelink.split('/').pop() : invitelink; roomIdToJoin = roomIdToJoin.match(/^[a-zA-Z0-9]+$/) ? `"${roomIdToJoin}"` : null; // Ensure it's a valid ID format console.debug('[Bot Room] Extracted room ID:', roomIdToJoin); } else { console.debug('[Bot Room] No invite link, joining random room.'); } let serverurl = `wss://${server}drawaria.online/socket.io/?EIO=3&transport=websocket`; // Removed sid1 let playerName = EL('#pbInputName').value.trim() || 'PatternBot_Suite'; if (!playerName) { botStatus = 'Error: Bot name required.'; updateBotStatusDisplay(); return; } this.conn.player.name = playerName; // Update player name in botInstance // Construct the "startplay" message // 420["startplay","BotName",2,"en", ROOM_ID_OR_NULL, null,[null,"https://drawaria.online/",1000,1000,[null,null,null],null]] let connectString = `420["startplay","${playerName}",${gamemode},"en",${roomIdToJoin || null},null,[null,"https://drawaria.online/",1000,1000,[null,null,null],null]]`; console.log("[Bot Room] Connect string:", connectString); // Open a new WebSocket connection for the bot this.conn.open(serverurl, connectString); }; // Room.prototype.next could be added if needed const Actions = function (conn) { this.conn = conn; }; Actions.prototype.DrawLine = function (bx = 0.5, by = 0.5, ex = 0.5, ey = 0.5, thickness = 50, color = '#FFFFFF', algo = 0, is_fill = false) { if (!this.conn.socket || this.conn.socket.readyState !== WebSocket.OPEN) { botStatus = 'Error: Bot socket not connected for drawing.'; updateBotStatusDisplay(); console.error('Bot socket is not open, cannot draw line.'); return; } // Coordinates are already 0-1 if called directly, or 0-100 if from pattern exec which then converts. // Ensure they are 0-1 for the command. let x1 = bx.toFixed(4); let y1 = by.toFixed(4); let x2 = ex.toFixed(4); let y2 = ey.toFixed(4); // For clear (fill), the command is slightly different. // Original script used is_fill=true for clear. // 42["drawcmd",0,[0.5,0.5,0.5,0.5,true,-2000,"#FFFFFF",0,0,{"2":0,"3":0.5,"4":0.5}]] // The {} part with "2", "3", "4" seems to be for brush options like stabilizer. // For simple lines, it's usually empty or not critical. // The value `0 - thickness` makes thickness negative. let message = `42["drawcmd",0,[${x1},${y1},${x2},${y2},${is_fill},${0 - Math.abs(thickness)},"${color}",${algo},0,{}]]`; this.conn.socket.send(message); // Original script sent two messages for DrawLine, one with true, one with false for the 5th param. // This might be to end a stroke. For patterns, only one is needed. // For a single "clear" line, one might be enough. // If `is_fill` is true, it's likely a fill command, not a typical line. // For standard lines, `is_fill` should be false. // Script 2 uses: `false` for patterns. // Script 2 uses: `true` for DrawLine (clear). if (!is_fill) { // If it's a normal line (not a fill action like clear) // Some games require a second message with the same coords to "finalize" the line segment // or to indicate mouse up. Drawaria might not need this for every segment of a complex pattern. // Test to see if this is necessary. Script 1 sent only one. Script 2 sent two for its clear. // Let's assume for patterns, only one per segment is fine. } }; // --- Styles --- function loadStyles() { GM_addStyle(` #powerToolsSuite { position: fixed; top: 20px; left: 20px; width: 380px; background-color: #2c3e50; /* Dark blue-gray */ color: #ecf0f1; /* Light gray */ border: 1px solid #34495e; /* Slightly darker border */ border-radius: 8px; box-shadow: 0 5px 15px rgba(0,0,0,0.3); z-index: 9999; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-size: 14px; overflow: hidden; /* For border-radius on children */ } #ptsHeader { background-color: #34495e; /* Darker header */ padding: 8px 12px; cursor: move; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #2c3e50; } #ptsTitle { font-weight: bold; font-size: 16px; } #ptsControls i { margin-left: 8px; cursor: pointer; font-size: 20px; transition: color 0.2s; } #ptsControls i:hover { color: #3498db; /* Accent blue */ } #ptsTabBar { display: flex; background-color: #34495e; /* Matches header */ } .ptsTabButton { flex-grow: 1; padding: 10px 15px; cursor: pointer; border: none; background-color: transparent; color: #bdc3c7; /* Muted light gray for inactive tabs */ font-size: 14px; transition: background-color 0.2s, color 0.2s; border-bottom: 3px solid transparent; } .ptsTabButton.active { color: #ecf0f1; /* Active tab text */ border-bottom: 3px solid #3498db; /* Accent blue underline */ } .ptsTabButton:hover:not(.active) { background-color: #465c71; } #ptsContent { padding: 15px; max-height: 70vh; overflow-y: auto; } .ptsTabContent { display: none; } .ptsTabContent.active { display: block; } .ptsTabContent h4 { margin-top: 0; color: #3498db; /* Accent blue for headings */ border-bottom: 1px solid #34495e; padding-bottom: 8px; margin-bottom: 15px; display: flex; align-items: center; } .ptsTabContent h4 i { margin-right: 8px; font-size: 22px; } .pts-input-group { margin-bottom: 12px; display: flex; flex-direction: column; } .pts-input-group label { margin-bottom: 4px; font-size: 13px; color: #bdc3c7; } .pts-input-group input[type="text"], .pts-input-group input[type="number"], .pts-input-group select { width: 100%; padding: 8px 10px; background-color: #34495e; border: 1px solid #465c71; color: #ecf0f1; border-radius: 4px; box-sizing: border-box; } .pts-input-group input[type="number"] { text-align: right; } .pts-button-group { display: flex; gap: 10px; /* Space between buttons */ margin-top: 15px; margin-bottom: 10px; } .pts-button-group .pts-button { flex-grow: 1; /* Buttons share space equally */ } .pts-button { background-color: #3498db; /* Accent blue */ color: white; border: none; padding: 10px 15px; border-radius: 4px; cursor: pointer; font-size: 14px; transition: background-color 0.2s; display: flex; align-items: center; justify-content: center; } .pts-button i { margin-right: 6px; font-size: 18px; } .pts-button:hover { background-color: #2980b9; /* Darker blue */ } .pts-button-success { background-color: #2ecc71; /* Green */ } .pts-button-success:hover { background-color: #27ae60; /* Darker Green */ } .pts-button-danger { background-color: #e74c3c; /* Red */ } .pts-button-danger:hover { background-color: #c0392b; /* Darker Red */ } .pts-button-warning { background-color: #f39c12; /* Orange */ } .pts-button-warning:hover { background-color: #e67e22; /* Darker Orange */ } .pts-status { margin-top: 10px; padding: 8px; background-color: #34495e; border-radius: 4px; font-size: 13px; text-align: center; } .pts-grid-inputs { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; } /* PatternBot specific styles from original, if any, could be integrated or added here */ /* e.g. .cheat-row, .cheat-border etc. can be adapted to .pts-input-group or similar */ `); } // --- Initialization --- function initializeSuite() { loadStyles(); createMainUI(); botInstance = new Player('PatternBot_Suite'); // Initialize PatternBot instance window.___BOT = botInstance; // For potential external access if original script relied on it updateBotStatusDisplay(); // Initial status console.log("Drawaria Power Tools Suite loaded."); } // Wait for the page to load fully before initializing if (document.readyState === 'loading') { window.addEventListener('DOMContentLoaded', initializeSuite); } else { initializeSuite(); } })();