// ==UserScript==
// @name Drawaria World Generator
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Drawaria World Generator with improved UI, image fitting, optimization controls, and spiral drawing for ~5s rendering.
// @author YouTubeDrawaria
// @include https://drawaria.online*
// @icon https://www.google.com/s2/favicons?sz=64&domain=drawaria.online/room/
// @grant none
// @license MIT
// ==/UserScript==
(() => {
'use strict';
const EL = (sel) => document.querySelector(sel);
const ELL = (sel) => document.querySelectorAll(sel);
// Drawing variables
let drawing_active = false; // Flag to stop drawing
let previewCanvas = document.createElement('canvas');
let originalCanvas = null; // Will be set when canvas is found
let cw = 0; // Canvas width
let ch = 0; // Canvas height
var data; // Image pixel data from previewCanvas
let executionLine = []; // Array to store drawing commands
// Room & Socket Control (Keep existing logic)
window.myRoom = {};
window.sockets = [];
const originalSend = WebSocket.prototype.send;
WebSocket.prototype.send = function (...args) {
if (window.sockets.indexOf(this) === -1) {
window.sockets.push(this);
// Listen only to the first socket connection established by the game itself
if (window.sockets.indexOf(this) === 0) {
this.addEventListener('message', (event) => {
let message = String(event.data);
if (message.startsWith('42')) {
let payload = JSON.parse(message.slice(2));
// Update room info on relevant messages
if (payload[0] == 'bc_uc_freedrawsession_changedroom') {
if (payload.length > 3) window.myRoom.players = payload[3];
if (payload.length > 4) window.myRoom.id = payload[4]; // Room ID might be here
}
if (payload[0] == 'mc_roomplayerschange') {
if (payload.length > 3) window.myRoom.players = payload[3];
}
// Additional checks for potential room ID updates
if (payload[0] === 'gamestart' && payload[1]?.roomid) {
window.myRoom.id = payload[1].roomid;
if (payload[1].players) window.myRoom.players = payload[1].players;
}
// Listen for the drawing canvas to become available
if (originalCanvas === null) {
originalCanvas = document.getElementById('canvas');
if (originalCanvas) {
cw = originalCanvas.width;
ch = originalCanvas.height;
console.log(`Drawaria canvas found: ${cw}x${ch}`);
}
}
} else if (message.startsWith('41')) {
// Server acknowledges connection upgrade
} else if (message.startsWith('430')) {
// Initial room configuration upon connection
let configs = JSON.parse(message.slice(3))[0];
if (configs) {
window.myRoom.players = configs.players;
window.myRoom.id = configs.roomid;
}
// Check for canvas again after initial connection
if (originalCanvas === null) {
originalCanvas = document.getElementById('canvas');
if (originalCanvas) {
cw = originalCanvas.width;
ch = originalCanvas.height;
console.log(`Drawaria canvas found: ${cw}x${ch}`);
}
}
}
});
}
}
return originalSend.call(this, ...args);
};
// Add Boxicons Stylesheet
function addBoxIcons() {
let boxicons = document.createElement('link');
boxicons.href = 'https://unpkg.com/[email protected]/css/boxicons.min.css';
boxicons.rel = 'stylesheet';
document.head.appendChild(boxicons);
}
// Add Custom Stylesheet
function CreateStylesheet() {
let container = document.createElement('style');
container.innerHTML =
'#world-generator { position: absolute; top: 10px; left: 10px; background-color: #ffffff; border: 2px solid #555; border-radius: 8px; padding: 15px; cursor: grab; z-index: 1000; font-family: sans-serif; box-shadow: 3px 3px 8px rgba(0,0,0,0.3); } ' +
'#world-generator.dragging { cursor: grabbing; }' +
'#world-generator h2 { margin-top: 0; margin-bottom: 15px; color: #333; text-align: center; border-bottom: 1px solid #eee; padding-bottom: 10px; } ' +
'#world-generator .world-list { max-height: 200px; overflow-y: auto; margin BOTTOM: 15px; padding-right: 5px; }' +
'#world-generator .world-item { margin: 5px 0; padding: 10px; background-color: #eef; border: 1px solid #ccf; border-radius: 5px; cursor: pointer; transition: background-color 0.2s ease, transform 0.1s ease; color: #333; } ' +
'#world-generator .world-item:hover { background-color: #ddf; transform: translateY(-1px); } ' +
'#world-generator .controls { margin-bottom: 15px; padding: 10px; background-color: #f9f9f9; border: 1px solid #eee; border-radius: 5px; } ' +
'#world-generator .control-group { display: grid; grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); gap: 10px; margin-bottom: 10px; }' +
'#world-generator .control-item label { display: block; margin-bottom: 3px; font-size: 0.9em; color: #555; } ' +
'#world-generator .control-item input[type="number"] { width: 100%; padding: 5px; border: 1px solid #ccc; border-radius: 3px; box-sizing: border-box; text-align: center; -webkit-appearance: none; -moz-appearance: textfield; } ' +
'#world-generator .control-item input[type="number"]::-webkit-outer-spin-button, #world-generator .control-item input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } ' +
'#start-button, #stop-button { width: 100%; padding: 10px; border: none; border-radius: 5px; cursor: pointer; font-size: 1em; transition: background-color 0.2s ease, transform 0.1s ease; color: white; text-align: center; } ' +
'#start-button { background-color: #5cb85c; } ' +
'#start-button:hover { background-color: #4cae4c; transform: translateY(-1px); } ' +
'#stop-button { background-color: #d9534f; margin-top: 10px; } ' +
'#stop-button:hover { background-color: #c9302c; transform: translateY(-1px); } ' +
'#status { margin-top: 10px; text-align: center; font-size: 0.9em; color: #555; min-height: 1.2em; }';
document.head.appendChild(container);
}
// Add World Generator Menu
function CreateWorldGenerator() {
originalCanvas = document.getElementById('canvas');
if (!originalCanvas) {
console.warn("Drawaria canvas not found. World Generator will wait for it.");
const observer = new MutationObserver((mutations, obs) => {
originalCanvas = document.getElementById('canvas');
if (originalCanvas) {
cw = originalCanvas.width;
ch = originalCanvas.height;
console.log(`Drawaria canvas found: ${cw}x${ch}`);
createUI();
obs.disconnect();
}
});
observer.observe(document.body, { childList: true, subtree: true });
return;
} else {
cw = originalCanvas.width;
ch = originalCanvas.height;
console.log(`Drawaria canvas found immediately: ${cw}x${ch}`);
createUI();
}
}
function createUI() {
if (document.getElementById('world-generator')) {
console.log("World Generator UI already exists.");
return;
}
let worldGenerator = document.createElement('div');
worldGenerator.id = 'world-generator';
worldGenerator.innerHTML = `
<h2>Drawaria World Generator</h2>
<div class="world-list">
<div class="world-item" data-world="Superflat">Superflat</div>
<div class="world-item" data-world="Lava Cave">Lava Cave</div>
<div class="world-item" data-world="Desert Vista">Desert Vista</div>
<div class="world-item" data-world="Night Landscape">Night Landscape</div>
<div class="world-item" data-world="House">House</div>
<div class="world-item" data-world="Classroom">Classroom</div>
<div class="world-item" data-world="Castle">Castle</div>
<div class="world-item" data-world="School">School</div>
</div>
<div class="controls">
<div class="control-group">
<div class="control-item">
<label for="draw-size">Step Size</label>
<input type="number" id="draw-size" value="4" min="1" step="1">
</div>
<div class="control-item">
<label for="draw-modifier">Step Modifier</label>
<input type="number" id="draw-modifier" value="3" min="1" step="1">
</div>
<div class="control-item">
<label for="draw-thickness">Thickness</label>
<input type="number" id="draw-thickness" value="50" min="1" step="1">
</div>
<div class="control-item">
<label for="draw-delay">Delay (ms)</label>
<input type="number" id="draw-delay" value="1" min="0" step="1"> <!-- Reduced to 1ms -->
</div>
<div class="control-item">
<label for="speed-factor">Speed Factor (1-10)</label>
<input type="number" id="speed-factor" value="2" min="1" max="10" step="1"> <!-- New control -->
</div>
</div>
<div class="control-item">
<label for="ignore-colors">Ignore Colors (hex/rgb, comma-separated)</label>
<input type="text" id="ignore-colors" value="">
</div>
</div>
<button id="start-button">Load & Generate</button>
<button id="stop-button" class="hidden">Stop Drawing</button>
<div id="status">Select a world to load.</div>
`;
document.body.appendChild(worldGenerator);
const startButton = document.getElementById('start-button');
const stopButton = document.getElementById('stop-button');
const statusDiv = document.getElementById('status');
// Make the menu draggable
let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
worldGenerator.onmousedown = dragMouseDown;
function dragMouseDown(e) {
e = e || window.event;
if (e.target.tagName === 'INPUT' || e.target.tagName === 'BUTTON' || e.target.classList.contains('world-item') || e.target.closest('.controls')) {
return;
}
e.preventDefault();
worldGenerator.classList.add('dragging');
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;
worldGenerator.style.top = (worldGenerator.offsetTop - pos2) + "px";
worldGenerator.style.left = (worldGenerator.offsetLeft - pos1) + "px";
}
function closeDragElement() {
worldGenerator.classList.remove('dragging');
document.onmouseup = null;
document.onmousemove = null;
}
// Add event listeners to world items
let worldItems = worldGenerator.querySelectorAll('.world-item');
worldItems.forEach(item => {
item.addEventListener('click', async () => {
if (drawing_active) {
alert("Please stop the current drawing first.");
return;
}
const worldName = item.getAttribute('data-world');
const worldUrl = WORLDS[worldName];
if (worldUrl) {
statusDiv.textContent = `Loading "${worldName}"...`;
startButton.disabled = true;
stopButton.classList.add('hidden');
drawing_active = false;
try {
await loadImage(worldUrl);
statusDiv.textContent = `"${worldName}" loaded. Ready to draw (${executionLine.length} commands).`;
startButton.textContent = "Start Drawing";
startButton.disabled = false;
} catch (error) {
console.error("Error loading image:", error);
statusDiv.textContent = `Error loading "${worldName}". See console.`;
startButton.textContent = "Load & Generate";
startButton.disabled = false;
}
} else {
statusDiv.textContent = `Error: World "${worldName}" not found.`;
}
});
});
// Start button
startButton.addEventListener('click', async () => {
if (executionLine.length === 0) {
statusDiv.textContent = 'Please load an image first.';
return;
}
if (window['___BOT'] && window['___BOT'].conn && window['___BOT'].conn.socket && window['___BOT'].conn.socket.readyState === WebSocket.OPEN) {
drawing_active = true;
startButton.classList.add('hidden');
stopButton.classList.remove('hidden');
statusDiv.textContent = `Drawing... (0/${executionLine.length})`;
await execute(window['___BOT'].conn.socket);
drawing_active = false;
startButton.classList.remove('hidden');
stopButton.classList.add('hidden');
startButton.textContent = "Load & Generate";
statusDiv.textContent = `Drawing finished (${executionLine.length}/${executionLine.length}).`;
} else {
statusDiv.textContent = 'Error: Bot socket not available or not open.';
console.error("Bot socket not available or not open.", window['___BOT']);
}
});
// Stop button
stopButton.addEventListener('click', () => {
drawing_active = false;
stopButton.disabled = true;
statusDiv.textContent = 'Stopping drawing...';
});
// Initial status
statusDiv.textContent = originalCanvas ? 'Select a world to load.' : 'Waiting for game canvas...';
if (!originalCanvas) startButton.disabled = true;
}
const WORLDS = {
"Superflat": "https://static.vecteezy.com/system/resources/thumbnails/027/879/859/small/side-view-land-and-cloud-in-pixel-art-style-vector.jpg",
"Lava Cave": "https://pics.craiyon.com/2023-09-23/40aa7363eec448ea836d2fb2cd4cce6a.webp",
"Desert Vista": "https://i.ibb.co/pv9sVTtP/9e14aa6d-2348-4654-b235-c41ebbe678f2.png",
"Night Landscape": "https://images.stockcake.com/public/2/b/c/2bc47920-6b02-49b8-aac8-81d965e2ab70_large/moonlit-pixel-city-stockcake.jpg",
"House": "https://static.vecteezy.com/system/resources/previews/010/963/629/non_2x/house-pixel-icon-free-vector.jpg",
"Classroom": "https://i.ibb.co/hFHRGT3j/download.jpg",
"Castle": "https://i.ibb.co/8gRg8rtZ/cwtdyva7.png",
"School": "https://static.vecteezy.com/system/resources/previews/045/712/354/non_2x/school-building-in-pixel-art-style-vector.jpg"
};
// Convert hex to RGB
function hexToRgb(hex) {
const bigint = parseInt(hex.replace('#', ''), 16);
const r = (bigint >> 16) & 255;
const g = (bigint >> 8) & 255;
const b = bigint & 255;
return `rgb(${r},${g},${b})`;
}
// Parse ignore colors input
function parseIgnoreColors(input) {
const colors = input.split(',').map(c => c.trim()).filter(c => c);
const rgbColors = [];
colors.forEach(color => {
if (color.startsWith('#')) {
try {
rgbColors.push(hexToRgb(color));
} catch (e) {
console.warn(`Invalid hex color: ${color}`);
}
} else if (color.startsWith('rgb(') && color.endsWith(')')) {
const rgbValues = color.substring(4, color.length - 1).split(',').map(Number);
if (rgbValues.length === 3 && rgbValues.every(v => v >= 0 && v <= 255)) {
rgbColors.push(color);
} else {
console.warn(`Invalid rgb color format or values: ${color}`);
}
} else {
console.warn(`Unrecognized color format: ${color}`);
}
});
return rgbColors;
}
// Load and process image
async function loadImage(url) {
return new Promise((resolve, reject) => {
if (!originalCanvas) {
reject("Drawaria canvas not found.");
return;
}
const img = new Image();
img.crossOrigin = 'Anonymous';
img.addEventListener('load', () => {
previewCanvas.width = cw;
previewCanvas.height = ch;
const ctx = previewCanvas.getContext('2d');
// Scale and center image
const scaleW = cw / img.width;
const scaleH = ch / img.height;
const scale = Math.max(scaleW, scaleH);
const scaledWidth = img.width * scale;
const scaledHeight = img.height * scale;
const offsetX = (cw - scaledWidth) / 2;
const offsetY = (ch - scaledHeight) / 2;
ctx.clearRect(0, 0, cw, ch);
ctx.fillStyle = '#FFFFFF';
ctx.fillRect(0, 0, cw, ch);
ctx.drawImage(img, 0, 0, img.width, img.height, offsetX, offsetY, scaledWidth, scaledHeight);
const imgData = ctx.getImageData(0, 0, cw, ch);
data = imgData.data;
ctx.clearRect(0, 0, cw, ch);
// Generate commands with spiral pattern
const size = parseInt(document.getElementById('draw-size').value, 10) || 4;
const modifier = parseInt(document.getElementById('draw-modifier').value, 10) || 4;
const thickness = parseInt(document.getElementById('draw-thickness').value, 10) || 27;
const speedFactor = parseInt(document.getElementById('speed-factor').value, 10) || 2;
const ignoreInput = document.getElementById('ignore-colors').value;
const ignoreColors = parseIgnoreColors(ignoreInput);
generateDrawingCommands(size, modifier, thickness, speedFactor, ignoreColors);
console.log('Image loaded and commands generated.');
resolve();
});
img.addEventListener('error', (e) => {
console.error("Failed to load image:", url, e);
reject(`Failed to load image: ${url}`);
});
img.src = url;
});
}
// Generate drawing commands using rectangular spiral pattern
function generateDrawingCommands(size, modifier, thickness, speedFactor, ignoreColors) {
executionLine = [];
const step = Math.max(1, size * modifier * speedFactor); // Scale step with speedFactor
if (step <= 0) {
console.error("Invalid step size:", step);
alert("Error generating commands: Invalid step size.");
return;
}
// Spiral traversal: outer to inner rectangular spiral
let left = 0, right = cw - 1, top = 0, bottom = ch - 1;
let currentSegmentStart = null; // Start pixel coordinate [x, y]
let currentSegmentColor = null; // Current segment color
// Create reference objects to pass to processPixel
const segmentRefs = {
currentSegmentStart: { value: null },
currentSegmentColor: { value: null }
};
while (left <= right && top <= bottom) {
// Traverse top row (left to right)
for (let x = left; x <= right; x += step) {
processPixel(x, top, step, thickness, ignoreColors, segmentRefs.currentSegmentStart, segmentRefs.currentSegmentColor);
}
top += step;
// Traverse right column (top to bottom)
for (let y = top; y <= bottom && left <= right; y += step) {
processPixel(right, y, step, thickness, ignoreColors, segmentRefs.currentSegmentStart, segmentRefs.currentSegmentColor);
}
right -= step;
// Traverse bottom row (right to left)
for (let x = right; x >= left && top <= bottom; x -= step) {
processPixel(x, bottom, step, thickness, ignoreColors, segmentRefs.currentSegmentStart, segmentRefs.currentSegmentColor);
}
bottom -= step;
// Traverse left column (bottom to top)
for (let y = bottom; y >= top && left <= right; y -= step) {
processPixel(left, y, step, thickness, ignoreColors, segmentRefs.currentSegmentStart, segmentRefs.currentSegmentColor);
}
left += step;
}
// End any active segment
if (segmentRefs.currentSegmentStart.value !== null) {
executionLine.push({
pos1: recalc(segmentRefs.currentSegmentStart.value),
pos2: recalc(segmentRefs.currentSegmentStart.value), // End at same point for single pixel
color: segmentRefs.currentSegmentColor.value,
thickness: thickness,
});
}
console.log(`Generated ${executionLine.length} drawing commands.`);
}
// Helper to process a pixel and manage segments
function processPixel(x, y, step, thickness, ignoreColors, currentSegmentStartRef, currentSegmentColorRef) {
if (x < 0 || x >= cw || y < 0 || y >= ch) return;
const pixelIndex = (y * cw + x) * 4;
const alpha = data[pixelIndex + 3];
if (alpha > 20) {
const r = data[pixelIndex + 0];
const g = data[pixelIndex + 1];
const b = data[pixelIndex + 2];
const color = `rgb(${r},${g},${b})`;
if (!ignoreColors.includes(color)) {
if (currentSegmentStartRef.value === null) {
currentSegmentStartRef.value = [x, y];
currentSegmentColorRef.value = color;
} else if (color !== currentSegmentColorRef.value) {
executionLine.push({
pos1: recalc(currentSegmentStartRef.value),
pos2: recalc([x, y]),
color: currentSegmentColorRef.value,
thickness: thickness,
});
currentSegmentStartRef.value = [x, y];
currentSegmentColorRef.value = color;
}
// Continue segment if color is the same
} else if (currentSegmentStartRef.value !== null) {
executionLine.push({
pos1: recalc(currentSegmentStartRef.value),
pos2: recalc([x, y]),
color: currentSegmentColorRef.value,
thickness: thickness,
});
currentSegmentStartRef.value = null;
currentSegmentColorRef.value = null;
}
} else if (currentSegmentStartRef.value !== null) {
executionLine.push({
pos1: recalc(currentSegmentStartRef.value),
pos2: recalc([x, y]),
color: currentSegmentColorRef.value,
thickness: thickness,
});
currentSegmentStartRef.value = null;
currentSegmentColorRef.value = null;
}
}
// Helper to process a pixel and manage segments
function processPixel(x, y, step, thickness, ignoreColors, currentSegmentStartRef, currentSegmentColorRef) {
if (x < 0 || x >= cw || y < 0 || y >= ch) return;
const pixelIndex = (y * cw + x) * 4;
const alpha = data[pixelIndex + 3];
if (alpha > 20) {
const r = data[pixelIndex + 0];
const g = data[pixelIndex + 1];
const b = data[pixelIndex + 2];
const color = `rgb(${r},${g},${b})`;
if (!ignoreColors.includes(color)) {
if (currentSegmentStartRef.value === null) {
currentSegmentStartRef.value = [x, y];
currentSegmentColorRef.value = color;
} else if (color !== currentSegmentColorRef.value) {
executionLine.push({
pos1: recalc(currentSegmentStartRef.value),
pos2: recalc([x, y]),
color: currentSegmentColorRef.value,
thickness: thickness,
});
currentSegmentStartRef.value = [x, y];
currentSegmentColorRef.value = color;
}
} else if (currentSegmentStartRef.value !== null) {
executionLine.push({
pos1: recalc(currentSegmentStartRef.value),
pos2: recalc([x, y]),
color: currentSegmentColorRef.value,
thickness: thickness,
});
currentSegmentStartRef.value = null;
currentSegmentColorRef.value = null;
}
} else if (currentSegmentStartRef.value !== null) {
executionLine.push({
pos1: recalc(currentSegmentStartRef.value),
pos2: recalc([x, y]),
color: currentSegmentColorRef.value,
thickness: thickness,
});
currentSegmentStartRef.value = null;
currentSegmentColorRef.value = null;
}
}
// Execute drawing commands with optimized timing
async function execute(socket) {
const delayMs = parseInt(document.getElementById('draw-delay').value, 10) || 1;
const statusDiv = document.getElementById('status');
const stopButton = document.getElementById('stop-button');
stopButton.disabled = false;
const maxCommandsPerSecond = 500; // Prevent server overload
const batchSize = Math.floor(1000 / maxCommandsPerSecond / delayMs) || 1;
for (let i = 0; i < executionLine.length; i += batchSize) {
if (!drawing_active) {
console.log("Drawing stopped by user.");
statusDiv.textContent = `Drawing stopped (${i}/${executionLine.length}).`;
break;
}
// Send batch of commands
for (let j = 0; j < batchSize && i + j < executionLine.length; j++) {
let currentLine = executionLine[i + j];
let p1 = currentLine.pos1;
let p2 = currentLine.pos2;
let color = currentLine.color;
let thickness = currentLine.thickness;
if (p1 && p2 && typeof p1[0] === 'number' && typeof p1[1] === 'number' && typeof p2[0] === 'number' && typeof p2[1] === 'number' && !isNaN(p1[0]) && !isNaN(p1[1]) && !isNaN(p2[0]) && !isNaN(p2[1])) {
drawcmd(socket, p1, p2, color, thickness);
} else {
console.warn("Skipping invalid command:", currentLine);
}
}
// Update status every 50 commands or at end
if ((i + batchSize) % 50 === 0 || i + batchSize >= executionLine.length) {
statusDiv.textContent = `Drawing... (${Math.min(i + batchSize, executionLine.length)}/${executionLine.length})`;
}
// Check WebSocket buffer to prevent overload
if (socket.bufferedAmount > 100000) {
console.warn("WebSocket buffer high, pausing...");
await delay(100);
}
await delay(delayMs);
}
stopButton.disabled = true;
}
// Send drawing command
function drawcmd(s, start, end, color, thickness) {
const cmd = `42["drawcmd",0,[${start[0].toFixed(4)},${start[1].toFixed(4)},${end[0].toFixed(4)},${end[1].toFixed(4)},false,${0 - thickness},"${color}",0,0,{}]]`;
s.send(cmd);
}
// Delay function
function delay(ms) {
return new Promise((resolve) => {
if (ms > 0) {
setTimeout(resolve, ms);
} else {
resolve();
}
});
}
// Recalculate coordinates to normalized
function recalc(pixelCoords) {
if (cw === 0 || ch === 0) {
console.error("Canvas dimensions not set!", cw, ch);
return [NaN, NaN];
}
return [
pixelCoords[0] / cw,
pixelCoords[1] / ch
];
}
// Helper from original bot
var nullify = (value = null) => {
return value == null ? null : String().concat('"', value, '"');
};
// Initialize script
function init() {
if (!document.getElementById('world-generator')) {
addBoxIcons();
CreateStylesheet();
CreateWorldGenerator();
if (!window['___BOT']) {
window['___BOT'] = new Player('bot');
}
if (!window['___ENGINE']) {
window['___ENGINE'] = { loadImage: loadImage, drawImage: generateDrawingCommands, execute: execute, recalc: recalc };
}
} else {
console.log("World Generator script already running.");
}
}
window.addEventListener('load', init);
if (document.readyState === 'complete' || document.readyState === 'interactive') {
init();
} else {
document.addEventListener('DOMContentLoaded', init);
}
// Player/Connection/Room/Actions classes
const Player = function (name = undefined) {
this.name = name;
this.sid1 = null;
this.uid = '';
this.wt = '';
this.conn = new Connection(this);
this.room = new Room(this.conn);
this.action = new Actions(this.conn);
};
Player.prototype.annonymize = function (name) {
this.name = name;
this.uid = undefined;
this.wt = undefined;
};
const Connection = function (player) {
this.player = player;
this.socket = null;
};
Connection.prototype.onopen = function (event) {
this.Heartbeat(25000);
};
Connection.prototype.onclose = function (event) {
};
Connection.prototype.onerror = function (event) {
console.error("Socket error:", event);
};
Connection.prototype.onmessage = function (event) {
let message = String(event.data);
if (message.startsWith('42')) {
this.onbroadcast(message.slice(2));
} else if (message.startsWith('40')) {
this.onrequest();
} else if (message.startsWith('41')) {
} else if (message.startsWith('430')) {
let configs = JSON.parse(message.slice(3))[0];
if (configs) {
this.player.room.players = configs.players;
this.player.room.id = configs.roomid;
console.log(`Bot joined room ${this.player.room.id}`);
}
}
};
Connection.prototype.onbroadcast = function (payload) {
try {
payload = JSON.parse(payload);
if (payload[0] == 'bc_uc_freedrawsession_changedroom') {
if (payload.length > 3) this.player.room.players = payload[3];
if (payload.length > 4) this.player.room.id = payload[4];
}
if (payload[0] == 'mc_roomplayerschange') {
if (payload.length > 3) this.player.room.players = payload[3];
}
} catch(e) {
console.error("Error parsing broadcast payload:", payload, e);
}
};
Connection.prototype.onrequest = function () {
};
Connection.prototype.open = function (url) {
if (this.socket && this.socket.readyState !== WebSocket.CLOSED) {
console.log("Socket already open or connecting.");
return;
}
this.socket = new WebSocket(url);
this.socket.onopen = this.onopen.bind(this);
this.socket.onclose = this.onclose.bind(this);
this.socket.onerror = this.onerror.bind(this);
this.socket.onmessage = this.onmessage.bind(this);
console.log("Attempting to open WebSocket:", url);
};
Connection.prototype.close = function (code, reason) {
if (this.socket) {
console.log("Closing WebSocket:", code, reason);
this.socket.close(code, reason);
this.socket = null;
}
};
Connection.prototype.Heartbeat = function (interval) {
this.heartbeatTimeout = setTimeout(() => {
if (this.socket && this.socket.readyState == this.socket.OPEN) {
this.socket.send(2);
this.Heartbeat(interval);
} else {
console.log("Heartbeat stopped, socket not open.");
}
}, interval);
};
Connection.prototype.stopHeartbeat = function() {
if (this.heartbeatTimeout) {
clearTimeout(this.heartbeatTimeout);
this.heartbeatTimeout = null;
}
};
Connection.prototype.serverconnect = function (server, connectstring) {
this.stopHeartbeat();
this.close();
this.open(server);
this.onrequest = () => {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send(connectstring);
this.onrequest = function() {};
} else {
console.error("Socket not open when onrequest was called.");
}
};
};
const Room = function (conn) {
this.conn = conn;
this.id = null;
this.players = [];
};
Room.prototype.join = function (invitelink) {
let gamemode = 2;
let server = '';
let roomIdToSend = null;
if (invitelink != null) {
if (invitelink.startsWith('http')) {
const urlParts = invitelink.split('/');
this.id = urlParts.pop();
} else {
this.id = invitelink;
}
roomIdToSend = nullify(this.id);
if (this.id && typeof this.id === 'string') {
if (this.id.endsWith('.3')) {
server = 'sv3.';
gamemode = 2;
} else if (this.id.endsWith('.2')) {
server = 'sv2.';
gamemode = 2;
} else {
server = '';
gamemode = 2;
}
} else {
console.warn("Could not parse room ID from invitelink:", invitelink);
roomIdToSend = null;
server = 'sv3.';
gamemode = 2;
this.id = null;
}
} else {
this.id = null;
server = 'sv3.';
gamemode = 2;
roomIdToSend = null;
}
let serverurl = `wss://${server}drawaria.online/socket.io/?sid1=undefined&hostname=drawaria.online&EIO=3&transport=websocket`;
let player = this.conn.player;
let connectstring = `420["startplay",${nullify(player.name)},${gamemode},"en",${roomIdToSend},null,[${nullify(player.sid1)},${nullify(player.uid)},${nullify(player.wt)}],null]]`;
console.log("Attempting to connect to server:", serverurl, "with command:", connectstring);
this.conn.serverconnect(serverurl, connectstring);
};
Room.prototype.next = function () {
if (this.conn.socket && this.conn.socket.readyState === this.conn.socket.OPEN) {
this.conn.socket.send('42["pgswtichroom"]');
} else {
console.warn("Socket not open, cannot switch room.");
this.join(null);
}
};
const Actions = function (conn) {
this.conn = conn;
};
Actions.prototype.DrawLine = function (bx = 50, by = 50, ex = 50, ey = 50, thickness = 50, color = '#FFFFFF', algo = 0) {
bx = bx / 100;
by = by / 100;
ex = ex / 100;
ey = ey / 100;
if (this.conn.socket && this.conn.socket.readyState === this.conn.socket.OPEN) {
this.conn.socket.send(`42["drawcmd",0,[${bx},${by},${ex},${ey},true,${0 - thickness},"${color}",0,0,{"2":${algo},"3":0.5,"4":0.5}]]`);
this.conn.socket.send(`42["drawcmd",0,[${bx},${by},${ex},${ey},false,${0 - thickness},"${color}",0,0,{"2":${algo},"3":0.5,"4":0.5}]]`);
} else {
console.warn("Socket not open, cannot draw line.");
}
};
})();