您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Animates characters on Drawaria.online canvas with optimized drawing commands, fetched from JSON, and precisely centered on the canvas. Supports multiple animations and a delay between loops.
// ==UserScript== // @name Drawaria Animator // @namespace http://tampermonkey.net/ // @version 1.0 // @description Animates characters on Drawaria.online canvas with optimized drawing commands, fetched from JSON, and precisely centered on the canvas. Supports multiple animations and a delay between loops. // @author YouTubeDrawaria // @match https://drawaria.online/* // @grant none // @license MIT // @icon https://www.google.com/s2/favicons?sz=64&domain=drawaria.online // ==/UserScript== (function() { 'use strict'; // --- Configuration --- const SPACE_INVADER_ANIMATION_JSON_URL = "https://raw.githubusercontent.com/NuevoMundoOficial/DrawariaASCIIPacks/main/spaceinvaders-videogames_drawaria_animation.json"; const MARIO_ANIMATION_JSON_URL = "https://raw.githubusercontent.com/NuevoMundoOficial/DrawariaASCIIPacks/main/mario_drawaria_animation.json"; const SONIC_ANIMATION_JSON_URL = "https://raw.githubusercontent.com/NuevoMundoOficial/DrawariaASCIIPacks/main/sonic_drawaria_animation.json"; const DEFAULT_ANIMATION_DISPLAY_SIZE_PX = 30; const LOOPS_BEFORE_PAUSE = 1; const PAUSE_DURATION_MS = 10000; const COMMANDS_PER_CHUNK = 50; const CHUNK_DELAY_MS = 10; // --- GLOBAL SHARED WEBSOCKET HOOKING SYSTEM --- const _activeSockets = window._drawariaActiveSockets || []; if (!window._drawariaActiveSockets) { window._drawariaActiveSockets = _activeSockets; const _originalWebSocketSend = WebSocket.prototype.send; WebSocket.prototype.send = function (...args) { if (_activeSockets.indexOf(this) === -1) { _activeSockets.push(this); this.addEventListener('close', () => { const index = _activeSockets.indexOf(this); if (index > -1) { _activeSockets.splice(index, 1); } }); } return _originalWebSocketSend.apply(this, args); }; } function _getGameSocket() { return _activeSockets.find(s => s.url.includes("drawaria.online/socket.io") && s.readyState === WebSocket.OPEN); } function _delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } function _clamp(value, min, max) { return Math.max(min, Math.min(max, value)); } function _sendAndRenderDrawCmd(canvas, ctx, start_norm, end_norm, color, thickness_game_units, isEraser = false) { const gameSocket = _getGameSocket(); if (!gameSocket || gameSocket.readyState !== WebSocket.OPEN) { console.warn("WebSocket no conectado. No se puede enviar ni renderizar el comando de dibujo."); return false; } const p1x_norm = _clamp(start_norm[0], 0, 1); const p1y_norm = _clamp(start_norm[1], 0, 1); const p2x_norm = _clamp(end_norm[0], 0, 1); const p2y_norm = _clamp(end_norm[1], 0, 1); let numThickness = parseFloat(thickness_game_units); if (isNaN(numThickness)) { numThickness = 5; } ctx.strokeStyle = color; ctx.lineWidth = numThickness * (canvas.width / 100); ctx.lineCap = 'round'; ctx.lineJoin = 'round'; if (isEraser) { ctx.globalCompositeOperation = 'destination-out'; } ctx.beginPath(); ctx.moveTo(p1x_norm * canvas.width, p1y_norm * canvas.height); ctx.lineTo(p2x_norm * canvas.width, p2y_norm * canvas.height); ctx.stroke(); if (isEraser) { ctx.globalCompositeOperation = 'source-over'; } const gT = isEraser ? numThickness : 0 - numThickness; gameSocket.send(`42["drawcmd",0,[${p1x_norm.toFixed(4)},${p1y_norm.toFixed(4)},${p2x_norm.toFixed(4)},${p2y_norm.toFixed(4)},${isEraser},${gT},"${color}",0,0,{}]]`); return true; } async function _clearCanvas(canvas, ctx, messageCallback) { const gameSocket = _getGameSocket(); if (!gameSocket || gameSocket.readyState !== WebSocket.OPEN) { messageCallback("warning", "No hay conexión al juego para limpiar el lienzo."); return; } if (ctx && canvas) { ctx.clearRect(0, 0, canvas.width, canvas.height); } gameSocket.send(`42["drawcmd",0,[0.5,0.5,0.5,0.5,true,-2000,"#FFFFFF",0,0,{}]]`); messageCallback("success", "Lienzo limpiado para todos."); await _delay(100); } function makeDraggable(element, header) { let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; const dragHandle = header || element; dragHandle.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; element.style.top = (element.offsetTop - pos2) + "px"; element.style.left = (element.offsetLeft - pos1) + "px"; } function closeDragElement() { document.onmouseup = null; document.onmousemove = null; } } class DrawariaAnimator { constructor() { this._currentAnimationType = 'spaceInvader'; this._animationJsonUrl = this._getAnimationUrlByType(this._currentAnimationType); this._defaultAnimationDisplaySizePx = DEFAULT_ANIMATION_DISPLAY_SIZE_PX; this._mainCanvas = null; this._mainCtx = null; this._animationFrames = []; this._animationMetadata = {}; this._currentFrameIndex = 0; this._animationTimeoutId = null; this._loopCount = 0; this._isActive = false; this._isAnimating = false; this._ui = {}; this._notificationTimeout = null; this._init(); } _getAnimationUrlByType(type) { switch (type) { case 'spaceInvader': return SPACE_INVADER_ANIMATION_JSON_URL; case 'mario': return MARIO_ANIMATION_JSON_URL; case 'sonic': return SONIC_ANIMATION_JSON_URL; default: return SPACE_INVADER_ANIMATION_JSON_URL; } } _init() { const checkCanvas = () => { this._mainCanvas = document.getElementById('canvas'); if (this._mainCanvas) { this._mainCtx = this._mainCanvas.getContext('2d'); this._setupUI(); this._loadAnimationData(); this._updateUIState(); this._notify("info", "Image Animator cargado."); } else { setTimeout(checkCanvas, 500); } }; checkCanvas(); } _setupUI() { this._ui.mainContainer = document.createElement('div'); this._ui.mainContainer.id = 'image-animator-menu'; this._ui.mainContainer.style.cssText = ` position: fixed; top: 20px; left: 20px; width: 250px; background: #2b2b2b; border: 1px solid #444; border-radius: 8px; box-shadow: 0 4px 10px rgba(0,0,0,0.5); font-family: Arial, sans-serif; color: #f0f0f0; z-index: 10000; display: flex; flex-direction: column; user-select: none; `; document.body.appendChild(this._ui.mainContainer); this._ui.header = document.createElement('div'); this._ui.header.style.cssText = ` padding: 10px; background: #3c3c3c; border-bottom: 1px solid #555; border-top-left-radius: 8px; border-top-right-radius: 8px; font-weight: bold; cursor: grab; text-align: center; `; this._ui.header.textContent = "Drawaria Animator"; this._ui.mainContainer.appendChild(this._ui.header); makeDraggable(this._ui.mainContainer, this._ui.header); this._ui.content = document.createElement('div'); this._ui.content.style.cssText = ` padding: 10px; display: flex; flex-direction: column; gap: 8px; `; this._ui.mainContainer.appendChild(this._ui.content); this._ui.moduleToggleButton = this._createButton('toggle-module-btn-invader', '<i class="fas fa-power-off"></i> Activar Animador', 'info'); this._ui.moduleToggleButton.addEventListener('click', () => this._toggleModuleActive()); this._ui.content.appendChild(this._ui.moduleToggleButton); this._ui.imagePreview = document.createElement('img'); this._ui.imagePreview.id = 'imagePreview'; this._ui.imagePreview.style.cssText = "width:60px;height:60px; margin: 10px auto; display: block;"; this._ui.content.appendChild(this._ui.imagePreview); this._ui.prevAnimButton = this._createButton('prev-anim-btn', '<i class="fas fa-step-backward"></i> Anterior', 'info'); this._ui.prevAnimButton.addEventListener('click', () => this._prevAnimation()); this._ui.content.appendChild(this._ui.prevAnimButton); this._ui.nextAnimButton = this._createButton('next-anim-btn', '<i class="fas fa-step-forward"></i> Siguiente', 'info'); this._ui.nextAnimButton.addEventListener('click', () => this._nextAnimation()); this._ui.content.appendChild(this._ui.nextAnimButton); this._ui.speedInput = this._createInput('number', 50, 2500, 2500); this._ui.content.appendChild(this._createFormGroup("Velocidad (ms/frame):", this._ui.speedInput)); this._ui.brushSizeInput = this._createInput('number', 1, 100, 40); this._ui.content.appendChild(this._createFormGroup("Grosor del Pincel (1-100):", this._ui.brushSizeInput)); const btnGroup1 = document.createElement('div'); btnGroup1.style.cssText = "display: flex; gap: 5px;"; this._ui.startButton = this._createButton('start-anim-btn', '<i class="fas fa-play"></i> Iniciar Animación', 'success'); this._ui.startButton.addEventListener('click', () => this._startAnimation()); this._ui.stopButton = this._createButton('stop-anim-btn', '<i class="fas fa-stop"></i> Detener Animación', 'danger'); this._ui.stopButton.addEventListener('click', () => this._stopAnimation()); btnGroup1.append(this._ui.startButton, this._ui.stopButton); this._ui.content.appendChild(btnGroup1); this._ui.clearCanvasButton = this._createButton('clear-canvas-btn', 'Limpiar Lienzo', 'warning'); this._ui.clearCanvasButton.title = "Limpia el lienzo con una línea blanca muy grande."; this._ui.clearCanvasButton.addEventListener('click', () => { _clearCanvas(this._mainCanvas, this._mainCtx, this._notify.bind(this)); }); this._ui.content.appendChild(this._ui.clearCanvasButton); this._ui.statusDisplay = document.createElement('div'); this._ui.statusDisplay.style.cssText = "text-align:center; margin-top:10px; font-size:0.9em; color:#aaffaa;"; this._ui.mainContainer.appendChild(this._ui.statusDisplay); } _createButton(id, html, type) { const button = document.createElement('button'); button.id = id; button.innerHTML = html; button.style.cssText = ` flex: 1; padding: 8px; border: none; border-radius: 4px; color: white; font-weight: bold; cursor: pointer; transition: background-color 0.2s; background: ${type === 'success' ? '#28a745' : type === 'danger' ? '#dc3545' : type === 'warning' ? '#ffc107' : type === 'info' ? '#17a2b8' : '#6c757d'}; `; button.onmouseover = () => button.style.backgroundColor = (type === 'success' ? '#218838' : type === 'danger' ? '#c82333' : type === 'warning' ? '#e0a800' : type === 'info' ? '#138496' : '#5a6268'); button.onmouseout = () => button.style.backgroundColor = (type === 'success' ? '#28a745' : type === 'danger' ? '#dc3545' : type === 'warning' ? '#e0a800' : type === 'info' ? '#138496' : '#5a6268'); return button; } _createInput(type, min, max, value) { const input = document.createElement('input'); input.type = type; input.min = min; input.max = max; input.value = value; input.style.cssText = ` width: 100%; padding: 6px; border: 1px solid #555; border-radius: 4px; background: #3c3c3c; color: #f0f0f0; box-sizing: border-box; `; return input; } _createFormGroup(labelText, inputElement) { const div = document.createElement('div'); div.style.cssText = "display: flex; flex-direction: column; gap: 4px;"; const label = document.createElement('label'); label.textContent = labelText; label.style.cssText = "font-size:0.9em; color:#bbb;"; div.append(label, inputElement); return div; } _toggleModuleActive() { this._isActive = !this._isActive; this._updateUIState(); if (this._isActive) { this._notify("info", "Image Animator ACTIVADO."); } else { this._notify("info", "Image Animator DESACTIVADO."); if (this._isAnimating) { this._stopAnimation(); } } } _updateUIState() { const isConnected = _getGameSocket() !== null; const hasFrames = this._animationFrames.length > 0; this._ui.moduleToggleButton.innerHTML = this._isActive ? '<i class="fas fa-power-off"></i> Desactivar Animador' : '<i class="fas fa-power-off"></i> Activar Animador'; this._ui.moduleToggleButton.classList.toggle('active', this._isActive); this._ui.startButton.disabled = !this._isActive || this._isAnimating || !isConnected || !hasFrames; this._ui.stopButton.disabled = !this._isAnimating; this._ui.speedInput.disabled = !this._isActive || this._isAnimating; this._ui.brushSizeInput.disabled = !this._isActive || this._isAnimating; this._ui.clearCanvasButton.disabled = !this._isActive || !isConnected || this._isAnimating; this._ui.prevAnimButton.disabled = this._isAnimating || !this._isActive; this._ui.nextAnimButton.disabled = this._isAnimating || !this._isActive; this._ui.statusDisplay.textContent = isConnected ? (this._isActive ? (this._isAnimating ? "Estado: Animando..." : "Estado: Listo.") : "Estado: Módulo Inactivo.") : "Estado: No conectado al juego. Conéctate a una sala primero."; } _notify(type, message) { if (this._notificationTimeout) { clearTimeout(this._notificationTimeout); } const statusColor = type === 'success' ? '#aaffaa' : type === 'info' ? '#aaddff' : type === 'warning' ? '#ffccaa' : '#ffaaaa'; this._ui.statusDisplay.style.color = statusColor; this._ui.statusDisplay.textContent = `Estado: ${message}`; this._notificationTimeout = setTimeout(() => { this._ui.statusDisplay.style.color = '#aaffaa'; this._ui.statusDisplay.textContent = _getGameSocket() ? (this._isActive ? (this._isAnimating ? "Animando..." : "Listo.") : "Módulo Inactivo.") : "No conectado al juego. Conéctate a una sala primero."; }, 3000); } _prevAnimation() { if (this._isAnimating) { this._notify("warning", "Detén la animación antes de cambiar de fuente."); return; } const animations = ['spaceInvader', 'mario', 'sonic']; const currentIndex = animations.indexOf(this._currentAnimationType); const prevIndex = (currentIndex - 1 + animations.length) % animations.length; this._currentAnimationType = animations[prevIndex]; this._animationJsonUrl = this._getAnimationUrlByType(this._currentAnimationType); this._updateImagePreview(); this._updateUIState(); this._loadAnimationData(); this._notify("info", `Cargada animación: ${this._currentAnimationType}`); } _nextAnimation() { if (this._isAnimating) { this._notify("warning", "Detén la animación antes de cambiar de fuente."); return; } const animations = ['spaceInvader', 'mario', 'sonic']; const currentIndex = animations.indexOf(this._currentAnimationType); const nextIndex = (currentIndex + 1) % animations.length; this._currentAnimationType = animations[nextIndex]; this._animationJsonUrl = this._getAnimationUrlByType(this._currentAnimationType); this._updateImagePreview(); this._updateUIState(); this._loadAnimationData(); this._notify("info", `Cargada animación: ${this._currentAnimationType}`); } _updateImagePreview() { switch (this._currentAnimationType) { case 'spaceInvader': this._ui.imagePreview.src = "https://www.space-invaders.com/static/img/icons/icon.png"; break; case 'mario': this._ui.imagePreview.src = "https://static.wikia.nocookie.net/fantendo/images/0/0e/NES_Mario.jpg"; break; case 'sonic': this._ui.imagePreview.src = "https://www.sonicthehedgehog.com/wp-content/uploads/2021/08/sonic-animated.gif"; break; } } async _loadAnimationData() { this._notify("info", `Cargando datos de animación (${this._currentAnimationType})...`); try { const response = await fetch(this._animationJsonUrl); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); if (!data || !data.frames || !Array.isArray(data.frames) || data.frames.length === 0) { throw new Error("Formato de datos de animación JSON inválido o vacío."); } this._animationFrames = data.frames; this._animationMetadata = data.metadata || {}; this._notify("success", `Datos de animación cargados: ${this._animationFrames.length} frames.`); this._updateUIState(); } catch (error) { this._notify("error", `Error al cargar datos de animación: ${error.message}.`); console.error("Error loading animation data:", error); this._animationFrames = []; this._animationMetadata = {}; this._updateUIState(); } } _startAnimation() { if (this._isAnimating) return; if (this._animationFrames.length === 0) { this._notify("warning", "No hay frames cargados para animar."); return; } if (!_getGameSocket()) { this._notify("error", "No conectado al juego. Conéctate a una sala primero."); return; } this._isAnimating = true; this._currentFrameIndex = 0; this._loopCount = 0; this._updateUIState(); this._notify("info", `Iniciando animación ${this._currentAnimationType}...`); _clearCanvas(this._mainCanvas, this._mainCtx, this._notify.bind(this)).then(() => { this._animateLoop(); }); } _stopAnimation() { this._isAnimating = false; if (this._animationTimeoutId) { clearTimeout(this._animationTimeoutId); this._animationTimeoutId = null; } this._updateUIState(); this._notify("info", "Animación detenida."); } async _animateLoop() { if (!this._isAnimating) return; const gameSocket = _getGameSocket(); if (!gameSocket || gameSocket.readyState !== WebSocket.OPEN) { console.warn("WebSocket no conectado. Esperando reconexión para la animación..."); this._animationTimeoutId = setTimeout(() => this._animateLoop(), 500); return; } const frameDelay = parseInt(this._ui.speedInput.value); const brushThickness = parseInt(this._ui.brushSizeInput.value); const currentFrameCommands = this._animationFrames[this._currentFrameIndex]; const animWidthGameUnits = this._animationMetadata.width || this._defaultAnimationDisplaySizePx; const animHeightGameUnits = this._animationMetadata.height || this._defaultAnimationDisplaySizePx; const animWidthFraction = animWidthGameUnits / 100; const animHeightFraction = animHeightGameUnits / 100; const offsetXForCentering = (1 - animWidthFraction) / 2; const offsetYForCentering = (1 - animHeightFraction) / 2; const centerXOfAnimArea = offsetXForCentering + (animWidthFraction / 2); const centerYOfAnimArea = offsetYForCentering + (animHeightFraction / 2); _sendAndRenderDrawCmd( this._mainCanvas, this._mainCtx, [centerXOfAnimArea, centerYOfAnimArea], [centerXOfAnimArea, centerYOfAnimArea], '#FFFFFF', Math.max(animWidthGameUnits, animHeightGameUnits) * 1.2, true ); await _delay(30); for (let i = 0; i < currentFrameCommands.length; i++) { if (!this._isAnimating) break; const cmd = currentFrameCommands[i]; const p1x_final = (cmd.start_norm[0] * animWidthFraction) + offsetXForCentering; const p1y_final = (cmd.start_norm[1] * animHeightFraction) + offsetYForCentering; const p2x_final = (cmd.end_norm[0] * animWidthFraction) + offsetXForCentering; const p2y_final = (cmd.end_norm[1] * animHeightFraction) + offsetYForCentering; _sendAndRenderDrawCmd( this._mainCanvas, this._mainCtx, [p1x_final, p1y_final], [p2x_final, p2y_final], cmd.color, brushThickness ); if ((i + 1) % COMMANDS_PER_CHUNK === 0) { await _delay(CHUNK_DELAY_MS); if (!this._isAnimating) break; } } this._currentFrameIndex = (this._currentFrameIndex + 1) % this._animationFrames.length; if (this._currentFrameIndex === 0) { this._loopCount++; if (this._loopCount >= LOOPS_BEFORE_PAUSE) { this._notify("info", `Pausa de ${PAUSE_DURATION_MS / 1000} segundos para aliviar el servidor.`); this._stopAnimation(); setTimeout(() => { this._startAnimation(); }, PAUSE_DURATION_MS); return; } } this._animationTimeoutId = setTimeout(() => this._animateLoop(), frameDelay); } } window.addEventListener('load', () => { new DrawariaAnimator(); }); })();