您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Pixel companions with player selector, very optimized, monster enemies, and DOM-based image mode
// ==UserScript== // @name Drawaria.online RPG Mode🗡️ // @namespace http://tampermonkey.net/ // @version 3.9 // @description Pixel companions with player selector, very optimized, monster enemies, and DOM-based image mode // @author YouTubeDrawaria // @match https://drawaria.online/* // @match https://*.drawaria.online/* // @grant none // @license MIT // @icon https://www.google.com/s2/favicons?sz=64&domain=drawaria.online // ==/UserScript== (function() { 'use strict'; /* ---------- SISTEMA BASE OPTIMIZADO ---------- */ let drawariaSocket = null; let drawariaCanvas = null; let drawariaCtx = null; // Queue optimizado para comandos con batching inteligente const commandQueue = []; let batchProcessor = null; const BATCH_SIZE = 8; const BATCH_INTERVAL = 60; // Variables de control del sistema let systemActive = false; let selectedPlayerId = null; let selectedPlayerName = null; let renderMode = 'drawings'; // 'drawings' o 'images' let activeComponents = { hearts: true, sword: true, shield: true, monsters: true }; // Función auxiliar para delays function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } // Intercept WebSocket optimizado const originalWebSocketSend = WebSocket.prototype.send; WebSocket.prototype.send = function (...args) { if (!drawariaSocket && this.url && this.url.includes('drawaria')) { drawariaSocket = this; console.log('🔗 [PIXEL COMPANIONS] Socket capturado para Pixel Companions'); startBatchProcessor(); } return originalWebSocketSend.apply(this, args); }; function startBatchProcessor() { if (batchProcessor) return; batchProcessor = setInterval(() => { if (!drawariaSocket || drawariaSocket.readyState !== WebSocket.OPEN || commandQueue.length === 0) { return; } const batch = commandQueue.splice(0, BATCH_SIZE); batch.forEach(cmd => { try { drawariaSocket.send(cmd); } catch (e) { console.warn('⚠️ [PIXEL COMPANIONS] Error enviando comando:', e); } }); }, BATCH_INTERVAL); } /* ---------- SISTEMA DE IMÁGENES ---------- */ class ImageAssetManager { constructor() { this.loadedImages = new Map(); this.imageUrls = { sword: 'https://i.ibb.co/KpD33DZV/espada.png', shield: 'https://i.ibb.co/XxbQXt5g/escudo.png', hearts: 'https://i.ibb.co/b5HT0JDQ/hearts-fw.png', monster: 'https://media.tenor.com/dPsOXgYjb30AAAAj/pixel-pixelart.gif' }; this.loadingPromises = new Map(); this.preloadImages(); } preloadImages() { console.log('🖼️ [IMAGE MODE] Precargando imágenes...'); Object.entries(this.imageUrls).forEach(([key, url]) => { this.loadImage(key, url); }); } loadImage(key, url) { if (this.loadingPromises.has(key)) { return this.loadingPromises.get(key); } const promise = new Promise((resolve, reject) => { const img = new Image(); img.crossOrigin = 'anonymous'; img.onload = () => { this.loadedImages.set(key, img); console.log(`✅ [IMAGE MODE] Imagen cargada: ${key} (${img.width}x${img.height})`); resolve(img); }; img.onerror = (error) => { console.error(`❌ [IMAGE MODE] Error cargando imagen ${key}:`, error); reject(error); }; img.src = url; }); this.loadingPromises.set(key, promise); return promise; } getImage(key) { return this.loadedImages.get(key) || null; } isImageLoaded(key) { return this.loadedImages.has(key); } async waitForImage(key) { if (this.isImageLoaded(key)) { return this.getImage(key); } if (this.loadingPromises.has(key)) { return await this.loadingPromises.get(key); } return null; } getLoadedImagesCount() { return this.loadedImages.size; } } /* ---------- PATRONES PIXEL ART OPTIMIZADOS + MONSTER ---------- */ const pixelPatterns = { 'heart': [ " RR RR ", "RRRR RRRR", "RRRRRRRRR", " RRRRRRR ", " RRRRR ", " RRR ", " R " ], 'sword': [ " W ", " W ", " W ", " W ", " W ", " W ", " W ", " BBBBB ", " B ", " B " ], 'shield': [ " BBBBB ", " BWWWWWB ", "BWWWWWWB", "BWWWWWWB", "BWWWWWWB", "BWWWWWWB", " BWWWWB ", " BWWB ", " BB " ], 'monster': [ " RRRRR ", " RRRRRRR ", "RRR G RRR", "RRR RRR", "RRR G RRR", "RRRRRRRRR", "RRR RRR RR", "RR R RR", " R R ", "RR RR" ] }; const specificColorSchemes = { heart: { classic: { 'R': '#FF0000', 'W': '#FFFFFF', 'B': '#000000', ' ': null }, dark: { 'R': '#330000', 'W': '#333333', 'B': '#111111', ' ': null } }, sword: { classic: { 'W': '#00FFFF', 'B': '#0000FF', ' ': null }, dark: { 'W': '#006666', 'B': '#000066', ' ': null } }, shield: { classic: { 'W': '#FFFF00', 'B': '#000000', ' ': null }, dark: { 'W': '#666600', 'B': '#111111', ' ': null } }, monster: { classic: { 'R': '#CC0000', 'G': '#00FF00', ' ': null }, angry: { 'R': '#FF0000', 'G': '#FFFF00', ' ': null } } }; /* ---------- SISTEMA DE MONSTRUOS ---------- */ class MonsterSystem { constructor(companionSystem) { this.monsters = []; this.companionSystem = companionSystem; this.spawnTimer = 0; this.SPAWN_INTERVAL = 8000; this.MONSTER_SPEED = 0.8; this.COLLISION_DISTANCE = 25; this.MAX_MONSTERS = 3; this.lastSpawnTime = 0; } getRandomSpawnPosition() { if (!drawariaCanvas) return null; const margin = 50; const sides = ['top', 'bottom', 'left', 'right']; const side = sides[Math.floor(Math.random() * sides.length)]; let x, y; switch(side) { case 'top': x = Math.random() * (drawariaCanvas.width - 2 * margin) + margin; y = margin; break; case 'bottom': x = Math.random() * (drawariaCanvas.width - 2 * margin) + margin; y = drawariaCanvas.height - margin; break; case 'left': x = margin; y = Math.random() * (drawariaCanvas.height - 2 * margin) + margin; break; case 'right': x = drawariaCanvas.width - margin; y = Math.random() * (drawariaCanvas.height - 2 * margin) + margin; break; } return { x, y, side }; } spawnMonster() { if (this.monsters.length >= this.MAX_MONSTERS) return; const position = this.getRandomSpawnPosition(); if (!position) return; const monster = { id: `monster_${Date.now()}_${Math.random()}`, x: position.x, y: position.y, spawnSide: position.side, lastX: -9999, lastY: -9999, targetX: position.x, targetY: position.y, speed: this.MONSTER_SPEED, active: true, chaseMode: false, needsRedraw: true, colorScheme: 'classic', domElement: null // Para modo images }; this.monsters.push(monster); // Crear elemento DOM si estamos en modo images if (renderMode === 'images') { this.createMonsterDOMElement(monster); } console.log(`👹 [MONSTERS] Nuevo monstruo spawneado en ${position.side}: ${monster.id}`); } createMonsterDOMElement(monster) { const element = document.createElement('div'); element.style.cssText = ` position: fixed; width: 120px; height: 120px; background-image: url('${this.companionSystem.imageAssetManager.imageUrls.monster}'); background-size: contain; background-repeat: no-repeat; background-position: center; pointer-events: none; z-index: 1001; display: block; will-change: transform, left, top; `; monster.domElement = element; document.body.appendChild(element); } updateMonsters() { if (!activeComponents.monsters || !systemActive) return; const now = Date.now(); if (now - this.lastSpawnTime > this.SPAWN_INTERVAL) { this.spawnMonster(); this.lastSpawnTime = now; } this.monsters.forEach(monster => { this.updateSingleMonster(monster); }); this.monsters = this.monsters.filter(monster => monster.active); } updateSingleMonster(monster) { if (!monster.active) return; const targetPlayers = this.companionSystem.getTargetPlayerCoords(selectedPlayerId); if (!targetPlayers || targetPlayers.length === 0) return; let closestPlayer = targetPlayers; let minDistance = Infinity; targetPlayers.forEach(player => { const distance = Math.sqrt( Math.pow(player.x - monster.x, 2) + Math.pow(player.y - monster.y, 2) ); if (distance < minDistance) { minDistance = distance; closestPlayer = player; } }); if (minDistance < this.COLLISION_DISTANCE) { console.log(`💥 [MONSTERS] ¡Monstruo ${monster.id} colisionó con jugador!`); // Limpiar según el modo if (renderMode === 'drawings' && monster.lastX !== -9999) { this.companionSystem.drawOptimizedPixelArt('monster', 'classic', 4, monster.lastX, monster.lastY, 0, true); } else if (renderMode === 'images' && monster.domElement) { if (monster.domElement.parentNode) { monster.domElement.parentNode.removeChild(monster.domElement); } } monster.active = false; if (!this.companionSystem.animationState.damageSequence) { this.companionSystem.executeDamageSequence(); this.companionSystem.executeSwordAttack(); } return; } const deltaX = closestPlayer.x - monster.x; const deltaY = closestPlayer.y - monster.y; const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); if (distance > 0) { const moveX = (deltaX / distance) * monster.speed; const moveY = (deltaY / distance) * monster.speed; monster.targetX = monster.x + moveX; monster.targetY = monster.y + moveY; monster.x = monster.targetX; monster.y = monster.targetY; monster.chaseMode = distance < 150; monster.colorScheme = monster.chaseMode ? 'angry' : 'classic'; } this.drawMonster(monster); } drawMonster(monster) { const newX = Math.round(monster.x); const newY = Math.round(monster.y); const deltaX = Math.abs(newX - monster.lastX); const deltaY = Math.abs(newY - monster.lastY); if (deltaX > 2 || deltaY > 2 || monster.needsRedraw) { if (renderMode === 'drawings') { if (monster.lastX !== -9999) { this.companionSystem.drawOptimizedPixelArt('monster', 'classic', 4, monster.lastX, monster.lastY, 0, true); } this.companionSystem.drawOptimizedPixelArt('monster', monster.colorScheme, 4, newX, newY, 0, false); } else if (renderMode === 'images' && monster.domElement) { // Obtener canvas para posición relativa const canvas = document.getElementById('canvas'); if (canvas) { const canvasRect = canvas.getBoundingClientRect(); const finalX = canvasRect.left + newX - 30; const finalY = canvasRect.top + newY - 30; monster.domElement.style.left = `${finalX}px`; monster.domElement.style.top = `${finalY}px`; } } monster.lastX = newX; monster.lastY = newY; monster.needsRedraw = false; } } clearAllMonsters() { console.log('🧹 [MONSTERS] Limpiando todos los monstruos...'); this.monsters.forEach(monster => { if (renderMode === 'drawings' && monster.lastX !== -9999) { this.companionSystem.drawOptimizedPixelArt('monster', 'classic', 4, monster.lastX, monster.lastY, 0, true); } else if (renderMode === 'images' && monster.domElement) { if (monster.domElement.parentNode) { monster.domElement.parentNode.removeChild(monster.domElement); } } }); this.monsters = []; this.lastSpawnTime = 0; console.log('🧹 [MONSTERS] Todos los monstruos eliminados'); } getStats() { return { active: this.monsters.length, chasing: this.monsters.filter(m => m.chaseMode).length }; } } /* ---------- GESTIÓN DE JUGADORES SIMPLIFICADA ---------- */ class PlayerManager { constructor() { this.validPlayers = []; this.updateInterval = null; } updatePlayerOptions() { const playerSelect = document.getElementById('player-selector'); if (!playerSelect) return; const currentSelection = playerSelect.value; playerSelect.innerHTML = ''; const playerElements = document.querySelectorAll('.spawnedavatar[data-playerid], .playerlist-row[data-playerid]'); const validPlayers = []; playerElements.forEach(el => { const playerId = el.dataset.playerid; if (!playerId || playerId === '0' || el.dataset.self === 'true') { return; } let playerName = ''; const nicknameEl = el.querySelector('.nickname, .playerlist-name a, .player-name'); if (nicknameEl) { playerName = nicknameEl.textContent.trim(); } if (!playerName) { const parentRow = el.closest('.playerlist-row'); if (parentRow) { const nameEl = parentRow.querySelector('.playerlist-name a, .player-name'); if (nameEl) { playerName = nameEl.textContent.trim(); } } } if (!playerName) { playerName = `Player ${playerId}`; } if (!validPlayers.some(p => p.id === playerId)) { validPlayers.push({ id: playerId, name: playerName }); } }); this.validPlayers = validPlayers; if (validPlayers.length === 0) { const opt = document.createElement('option'); opt.value = ''; opt.textContent = '❌ No players available'; opt.disabled = true; playerSelect.appendChild(opt); const startBtn = document.getElementById('start-system-btn'); if (startBtn) startBtn.disabled = true; } else { const allOpt = document.createElement('option'); allOpt.value = 'all'; allOpt.textContent = `🌍 All Players (${validPlayers.length})`; playerSelect.appendChild(allOpt); validPlayers.forEach(player => { const opt = document.createElement('option'); opt.value = player.id; opt.textContent = `🎯 ${player.name}`; opt.dataset.playerName = player.name; playerSelect.appendChild(opt); }); const stillExists = currentSelection === 'all' || validPlayers.some(p => p.id === currentSelection); if (currentSelection && stillExists) { playerSelect.value = currentSelection; if (currentSelection === 'all') { selectedPlayerName = 'Todos los jugadores'; } else { const selectedPlayer = validPlayers.find(p => p.id === currentSelection); selectedPlayerName = selectedPlayer ? selectedPlayer.name : null; } } else if (currentSelection && !stillExists) { playerSelect.value = 'all'; selectedPlayerId = 'all'; selectedPlayerName = 'Todos los jugadores'; } else { playerSelect.value = 'all'; selectedPlayerId = 'all'; selectedPlayerName = 'Todos los jugadores'; } const startBtn = document.getElementById('start-system-btn'); if (startBtn) startBtn.disabled = false; } selectedPlayerId = playerSelect.value; } startAutoUpdate() { if (this.updateInterval) return; this.updateInterval = setInterval(() => { this.updatePlayerOptions(); }, 2000); this.updatePlayerOptions(); } stopAutoUpdate() { if (this.updateInterval) { clearInterval(this.updateInterval); this.updateInterval = null; } } } /* ---------- SISTEMA DE DIBUJO OPTIMIZADO ---------- */ function enqueueDrawCommand(x1, y1, x2, y2, color, thickness) { if (!drawariaCanvas || !drawariaSocket || !systemActive) return; const normX1 = (x1 / drawariaCanvas.width).toFixed(4); const normY1 = (y1 / drawariaCanvas.height).toFixed(4); const normX2 = (x2 / drawariaCanvas.width).toFixed(4); const normY2 = (y2 / drawariaCanvas.height).toFixed(4); const cmd = `42["drawcmd",0,[${normX1},${normY1},${normX2},${normY2},false,${-Math.abs(thickness)},"${color}",0,0,{}]]`; commandQueue.push(cmd); if (drawariaCtx) { drawariaCtx.strokeStyle = color; drawariaCtx.lineWidth = thickness; drawariaCtx.lineCap = 'round'; drawariaCtx.beginPath(); drawariaCtx.moveTo(x1, y1); drawariaCtx.lineTo(x2, y2); drawariaCtx.stroke(); } } /* ---------- SISTEMA PRINCIPAL MEJORADO ---------- */ class EnhancedPixelCompanionSystem { constructor() { this.initialized = false; this.lives = 3; this.lastPlayerPositions = new Map(); this.playerManager = new PlayerManager(); this.imageAssetManager = new ImageAssetManager(); this.monsterSystem = new MonsterSystem(this); // Elementos DOM para modo images this.imageElements = null; this.MOVEMENT_THRESHOLD = 4; this.UPDATE_INTERVAL = 80; this.lastUpdateTime = 0; this.COLLISION_COOLDOWN = 3000; this.lastCollisionTime = 0; this.companionStates = new Map(); this.animationState = { damageSequence: false, swordAttack: false, shieldDefend: false }; this.cachedPatterns = this.preOptimizePatterns(); this.init(); } init() { const checkGameReady = () => { const gameCanvas = document.getElementById('canvas'); if (gameCanvas) { this.canvasElement = gameCanvas; drawariaCanvas = gameCanvas; this.canvasContext = gameCanvas.getContext('2d'); drawariaCtx = gameCanvas.getContext('2d'); this.initialized = true; this.createControlInterface(); console.log('✅ [PIXEL COMPANIONS] Sistema inicializado correctamente'); } else { setTimeout(checkGameReady, 100); } }; checkGameReady(); } preOptimizePatterns() { const optimized = {}; Object.keys(pixelPatterns).forEach(patternName => { optimized[patternName] = this.optimizePatternToLines(pixelPatterns[patternName]); }); return optimized; } optimizePatternToLines(pattern) { const lines = []; for (let row = 0; row < pattern.length; row++) { let currentLine = null; for (let col = 0; col < pattern[row].length; col++) { const char = pattern[row][col]; if (char !== ' ') { if (!currentLine || currentLine.char !== char) { if (currentLine) lines.push(currentLine); currentLine = { char, startCol: col, endCol: col, row }; } else { currentLine.endCol = col; } } else { if (currentLine) { lines.push(currentLine); currentLine = null; } } } if (currentLine) lines.push(currentLine); } return lines; } createImageElements() { console.log('🖼️ [DOM SIMPLE] Creando elementos DOM para companions...'); // Limpiar elementos previos if (this.imageElements) { Object.values(this.imageElements).forEach(element => { if (element && element.parentNode) { element.parentNode.removeChild(element); } }); } this.imageElements = {}; const configs = { hearts: { width: '80px', height: '30px', url: this.imageAssetManager.imageUrls.hearts }, sword: { width: '50px', height: '50px', url: this.imageAssetManager.imageUrls.sword }, shield: { width: '50px', height: '50px', url: this.imageAssetManager.imageUrls.shield } }; // Usar exactamente el mismo método que los monstruos Object.entries(configs).forEach(([type, config]) => { const element = document.createElement('div'); element.id = `companion-${type}`; element.style.cssText = ` position: fixed; width: ${config.width}; height: ${config.height}; background-image: url('${config.url}'); background-size: contain; background-repeat: no-repeat; background-position: center; pointer-events: none; z-index: 1001; display: block; will-change: transform, left, top; left: 200px; top: 200px; `; this.imageElements[type] = element; document.body.appendChild(element); console.log(`✅ [DOM SIMPLE] ${type} creado exitosamente`); }); } getTargetPlayerCoords(playerId = null) { if (!playerId || playerId === 'all') { const allPlayers = []; const avatars = document.querySelectorAll('.spawnedavatar[data-playerid]:not([data-playerid="0"]):not(.self)'); avatars.forEach(avatar => { if (!this.canvasElement) return; const cRect = this.canvasElement.getBoundingClientRect(); const aRect = avatar.getBoundingClientRect(); if (aRect.width > 0 && aRect.height > 0) { allPlayers.push({ id: avatar.dataset.playerid || `player_${allPlayers.length}`, x: Math.round((aRect.left - cRect.left) + (aRect.width / 2)), y: Math.round((aRect.top - cRect.top) + (aRect.height / 2)), width: aRect.width, height: aRect.height }); } }); return allPlayers.length > 0 ? allPlayers : null; } const targetPlayers = []; const avatars = document.querySelectorAll(`.spawnedavatar[data-playerid="${playerId}"]`); avatars.forEach(avatar => { if (!this.canvasElement) return; const cRect = this.canvasElement.getBoundingClientRect(); const aRect = avatar.getBoundingClientRect(); if (aRect.width > 0 && aRect.height > 0) { targetPlayers.push({ id: playerId, x: Math.round((aRect.left - cRect.left) + (aRect.width / 2)), y: Math.round((aRect.top - cRect.top) + (aRect.height / 2)), width: aRect.width, height: aRect.height }); } }); return targetPlayers.length > 0 ? targetPlayers : null; } detectCollisionWithOtherPlayers() { if (selectedPlayerId === 'all') return false; const myPos = this.getTargetPlayerCoords(selectedPlayerId); if (!myPos || myPos.length === 0) return false; const now = Date.now(); if (now - this.lastCollisionTime < this.COLLISION_COOLDOWN) { return false; } const otherPlayers = document.querySelectorAll('.spawnedavatar.spawnedavatar-otherplayer'); const collisionDistance = 45; for (let otherPlayer of otherPlayers) { if (!this.canvasElement) continue; const cRect = this.canvasElement.getBoundingClientRect(); const otherRect = otherPlayer.getBoundingClientRect(); const otherX = (otherRect.left - cRect.left) + (otherRect.width / 2); const otherY = (otherRect.top - cRect.top) + (otherRect.height / 2); const distance = Math.sqrt( Math.pow(myPos.x - otherX, 2) + Math.pow(myPos.y - otherY, 2) ); if (distance < collisionDistance) { this.lastCollisionTime = now; return true; } } return false; } drawOptimizedPixelArt(patternName, colorScheme, pixelSize, centerX, centerY, rotation = 0, eraseMode = false) { const lines = this.cachedPatterns[patternName]; if (!lines) return; let colors; if (eraseMode) { colors = Object.keys(specificColorSchemes[patternName][colorScheme] || {}) .reduce((acc, key) => { acc[key] = key === ' ' ? null : '#FFFFFF'; return acc; }, {}); } else { colors = specificColorSchemes[patternName] ? specificColorSchemes[patternName][colorScheme] : specificColorSchemes.heart[colorScheme]; } const patternCenter = Math.floor(pixelPatterns[patternName].length / 2); lines.forEach(line => { if (!colors[line.char]) return; const startX = centerX + (line.startCol - patternCenter) * pixelSize; const endX = centerX + (line.endCol - patternCenter) * pixelSize + pixelSize; const y = centerY + (line.row - patternCenter) * pixelSize; enqueueDrawCommand(startX, y, endX, y, colors[line.char], pixelSize * 1.2); }); } async executeDamageSequence() { if (this.animationState.damageSequence || this.lives <= 0) return; this.animationState.damageSequence = true; console.log(`💔 [PIXEL COMPANIONS] ¡Daño recibido! Vidas restantes: ${this.lives - 1}`); this.lives--; if (this.lives <= 0) { console.log('💀 [PIXEL COMPANIONS] ¡Sin vidas! Reiniciando sistema...'); await new Promise(r => setTimeout(r, 2000)); this.lives = 3; console.log('🔄 [PIXEL COMPANIONS] Sistema reiniciado con 3 vidas'); } this.animationState.damageSequence = false; } async executeSwordAttack() { if (this.animationState.swordAttack) return; this.animationState.swordAttack = true; console.log('⚔️ [PIXEL COMPANIONS] ¡Ejecutando ataque con espada!'); await new Promise(r => setTimeout(r, 1000)); this.animationState.swordAttack = false; } async executeShieldDefense() { if (this.animationState.shieldDefend) return; this.animationState.shieldDefend = true; console.log('🛡️ [PIXEL COMPANIONS] ¡Activando defensa con escudo!'); await new Promise(r => setTimeout(r, 800)); this.animationState.shieldDefend = false; } async cleanCanvas() { if (!drawariaCanvas) return; console.log('🧹 [PIXEL COMPANIONS] Limpiando canvas...'); const canvasWidth = drawariaCanvas.width; const canvasHeight = drawariaCanvas.height; for (let y = 0; y < canvasHeight; y += 100) { for (let x = 0; x < canvasWidth; x += 100) { const width = Math.min(100, canvasWidth - x); const height = Math.min(100, canvasHeight - y); enqueueDrawCommand(x, y, x + width, y + height, '#FFFFFF', Math.max(width, height)); await sleep(5); } } if (drawariaCtx) { drawariaCtx.clearRect(0, 0, canvasWidth, canvasHeight); } console.log('🧹 [PIXEL COMPANIONS] Canvas limpiado completamente!'); } updateCompanions() { if (!systemActive || !this.initialized) return; const now = Date.now(); if (now - this.lastUpdateTime < this.UPDATE_INTERVAL) return; const targetPlayers = this.getTargetPlayerCoords(selectedPlayerId); if (!targetPlayers) return; this.monsterSystem.updateMonsters(); if (selectedPlayerId !== 'all') { const collision = this.detectCollisionWithOtherPlayers(); if (collision && !this.animationState.damageSequence) { this.executeDamageSequence(); this.executeSwordAttack(); this.executeShieldDefense(); } } targetPlayers.forEach(player => { this.updateCompanionForPlayer(player); }); this.lastUpdateTime = now; } updateCompanionForPlayer(player) { const playerId = player.id; if (!this.companionStates.has(playerId)) { this.companionStates.set(playerId, { hearts: Array(3).fill().map(() => ({ active: true, lastX: -9999, lastY: -9999, needsRedraw: true })), sword: { rotation: 0, lastX: -9999, lastY: -9999, needsRedraw: true }, shield: { rotation: 0, lastX: -9999, lastY: -9999, needsRedraw: true } }); } const state = this.companionStates.get(playerId); const lastPos = this.lastPlayerPositions.get(playerId) || { x: 0, y: 0 }; const deltaX = Math.abs(player.x - lastPos.x); const deltaY = Math.abs(player.y - lastPos.y); const significantMovement = deltaX > this.MOVEMENT_THRESHOLD || deltaY > this.MOVEMENT_THRESHOLD; // Renderizado según el modo if (renderMode === 'drawings') { this.updateCompanionDrawings(player, state, significantMovement); } else if (renderMode === 'images') { this.updateCompanionImages(player, state, significantMovement); } if (significantMovement) { this.lastPlayerPositions.set(playerId, { x: player.x, y: player.y }); } } updateCompanionDrawings(player, state, significantMovement) { if (activeComponents.hearts) { for (let i = 0; i < 3; i++) { const heartX = player.x + (i - 1) * 32; const heartY = player.y - player.height / 2 - 25; if (significantMovement || state.hearts[i].needsRedraw) { if (state.hearts[i].lastX !== -9999) { this.drawOptimizedPixelArt('heart', 'classic', 3, state.hearts[i].lastX, state.hearts[i].lastY, 0, true); } const scheme = state.hearts[i].active ? 'classic' : 'dark'; this.drawOptimizedPixelArt('heart', scheme, 3, heartX, heartY, 0, false); state.hearts[i].lastX = heartX; state.hearts[i].lastY = heartY; state.hearts[i].needsRedraw = false; } } } if (activeComponents.sword) { const swordX = player.x + player.width / 2 + 30; const swordY = player.y; if (significantMovement || state.sword.needsRedraw) { if (state.sword.lastX !== -9999) { this.drawOptimizedPixelArt('sword', 'classic', 5, state.sword.lastX, state.sword.lastY, 0, true); } this.drawOptimizedPixelArt('sword', 'classic', 5, swordX, swordY, state.sword.rotation, false); state.sword.lastX = swordX; state.sword.lastY = swordY; state.sword.needsRedraw = false; } } if (activeComponents.shield) { const shieldX = player.x - player.width / 2 - 30; const shieldY = player.y; if (significantMovement || state.shield.needsRedraw) { if (state.shield.lastX !== -9999) { this.drawOptimizedPixelArt('shield', 'classic', 5, state.shield.lastX, state.shield.lastY, 0, true); } this.drawOptimizedPixelArt('shield', 'classic', 5, shieldX, shieldY, 0, false); state.shield.lastX = shieldX; state.shield.lastY = shieldY; state.shield.needsRedraw = false; } } } // ✅ FUNCIÓN COMPLETA: updateCompanionImages updateCompanionImages(player, state, significantMovement) { // Crear elementos si no existen if (!this.imageElements) { this.createImageElements(); return; } // Obtener canvas para posición relativa (igual que los monstruos) const canvas = document.getElementById('canvas'); if (!canvas) return; const canvasRect = canvas.getBoundingClientRect(); // ✅ ACTUALIZAR HEARTS (usando método exacto de monstruos) if (activeComponents.hearts && this.imageElements.hearts) { const heartsElement = this.imageElements.hearts; // Posicionar arriba del jugador, centrado const heartsX = canvasRect.left + player.x - 40; // Centrar (80px width / 2) const heartsY = canvasRect.top + player.y - player.height / 2 - 40; heartsElement.style.left = `${heartsX}px`; heartsElement.style.top = `${heartsY}px`; heartsElement.style.display = 'block'; } else if (this.imageElements?.hearts) { this.imageElements.hearts.style.display = 'none'; } // ✅ ACTUALIZAR SWORD (usando método exacto de monstruos) if (activeComponents.sword && this.imageElements.sword) { const swordElement = this.imageElements.sword; // Posicionar a la derecha del jugador const swordX = canvasRect.left + player.x + player.width / 2 + 30 - 25; // 30 offset - 25 center const swordY = canvasRect.top + player.y - 25; // Centrar verticalmente swordElement.style.left = `${swordX}px`; swordElement.style.top = `${swordY}px`; swordElement.style.display = 'block'; } else if (this.imageElements?.sword) { this.imageElements.sword.style.display = 'none'; } // ✅ ACTUALIZAR SHIELD (usando método exacto de monstruos) if (activeComponents.shield && this.imageElements.shield) { const shieldElement = this.imageElements.shield; // Posicionar a la izquierda del jugador const shieldX = canvasRect.left + player.x - player.width / 2 - 30 - 25; // 30 offset - 25 center const shieldY = canvasRect.top + player.y - 25; // Centrar verticalmente shieldElement.style.left = `${shieldX}px`; shieldElement.style.top = `${shieldY}px`; shieldElement.style.display = 'block'; } else if (this.imageElements?.shield) { this.imageElements.shield.style.display = 'none'; } } // ✅ FUNCIÓN COMPLETA: debugCompanionSystem debugCompanionSystem() { console.log('🐛 [SYSTEM DEBUG] === ESTADO DEL SISTEMA ==='); console.log('System active:', systemActive); console.log('Render mode:', renderMode); console.log('Selected player:', selectedPlayerId); console.log('Active components:', activeComponents); console.log('Image elements exists:', !!this.imageElements); console.log('Canvas element:', !!this.canvasElement); // Debug de jugadores target const targetPlayers = this.getTargetPlayerCoords(selectedPlayerId); console.log('Target players found:', targetPlayers?.length || 0); if (targetPlayers && targetPlayers.length > 0) { console.log('First player coords:', targetPlayers); } if (this.imageElements) { Object.keys(this.imageElements).forEach(type => { const element = this.imageElements[type]; console.log(`${type}:`, { exists: !!element, display: element?.style.display, left: element?.style.left, top: element?.style.top, inDOM: document.body.contains(element) }); }); } console.log('🐛 [SYSTEM DEBUG] === FIN DEBUG ==='); } // ✅ FUNCIÓN COMPLETA: verifyElementsInDOM verifyElementsInDOM() { console.log('🔍 [DOM VERIFY] === VERIFICANDO ELEMENTOS ==='); if (!this.imageElements) { console.error('❌ [DOM VERIFY] imageElements no existe'); return; } Object.keys(this.imageElements).forEach(type => { const element = document.getElementById(`companion-${type}`); const inMemory = this.imageElements[type]; const inDOM = document.body.contains(inMemory); console.log(`${type}:`, { 'En DOM por ID': !!element, 'En memoria': !!inMemory, 'Conectado al DOM': inDOM, 'Style ready': !!(inMemory && inMemory.style), 'Background set': !!(inMemory && inMemory.style.backgroundImage), 'Position set': !!(inMemory && inMemory.style.left && inMemory.style.top) }); if (inMemory && inDOM) { const rect = inMemory.getBoundingClientRect(); console.log(`${type} rect:`, rect); } }); console.log('🔍 [DOM VERIFY] === FIN VERIFICACIÓN ==='); } startSystem() { if (!this.initialized) { console.log('❌ [PIXEL COMPANIONS] Sistema no inicializado. Esperando canvas...'); return; } if (!selectedPlayerId) { console.log('❌ [PIXEL COMPANIONS] No hay jugador seleccionado'); return; } systemActive = true; this.playerManager.startAutoUpdate(); this.companions = { hearts: Array(3).fill().map(() => ({ active: true, lastX: -9999, lastY: -9999, needsRedraw: true })), sword: { rotation: 0, lastX: -9999, lastY: -9999, needsRedraw: true }, shield: { rotation: 0, lastX: -9999, lastY: -9999, needsRedraw: true } }; // Crear elementos DOM para modo imágenes if (renderMode === 'images') { this.createImageElements(); } const updateLoop = () => { if (systemActive && this.initialized) { this.updateCompanions(); this.updateStatusDisplay(); } if (systemActive) { requestAnimationFrame(updateLoop); } }; updateLoop(); console.log('🎮 [PIXEL COMPANIONS] Sistema iniciado exitosamente'); console.log(`🎯 [PIXEL COMPANIONS] Target: ${selectedPlayerName || 'Jugador desconocido'}`); console.log(`📱 [PIXEL COMPANIONS] Modo de renderizado: ${renderMode}`); console.log(`⚙️ [PIXEL COMPANIONS] Componentes activos: ${Object.entries(activeComponents).filter(([,v]) => v).map(([k]) => k).join(', ')}`); if (renderMode === 'images') { console.log(`🖼️ [IMAGE MODE] Imágenes cargadas: ${this.imageAssetManager.getLoadedImagesCount()}/4`); console.log(`🖼️ [IMAGE MODE] Usando elementos DOM como monstruos - Sin rastros!`); } } async stopSystem() { console.log('⏹️ [PIXEL COMPANIONS] Deteniendo sistema...'); systemActive = false; this.playerManager.stopAutoUpdate(); this.monsterSystem.clearAllMonsters(); // Limpiar elementos DOM individuales (no container) if (this.imageElements) { Object.values(this.imageElements).forEach(element => { if (element && element.parentNode) { element.parentNode.removeChild(element); } }); this.imageElements = null; console.log('🖼️ [IMAGE MODE] Elementos DOM eliminados'); } // Solo limpiar canvas en modo drawings if (renderMode === 'drawings') { await this.cleanCanvas(); } commandQueue.length = 0; this.companionStates.clear(); this.lastPlayerPositions.clear(); this.animationState = { damageSequence: false, swordAttack: false, shieldDefend: false }; this.lives = 3; console.log('⏹️ [PIXEL COMPANIONS] Sistema detenido completamente'); } createControlInterface() { const existingPanel = document.getElementById('pixel-companions-control'); if (existingPanel) existingPanel.remove(); const controlPanel = document.createElement('div'); controlPanel.id = 'pixel-companions-control'; controlPanel.style.cssText = ` position: fixed; top: 20px; right: 20px; z-index: 9999; background: linear-gradient(135deg, #1a1a2e, #16213e); color: white; padding: 20px; border-radius: 15px; font-family: 'Segoe UI', Arial, sans-serif; border: 2px solid #4fd1c7; min-width: 320px; box-shadow: 0 0 30px rgba(79,209,199,0.4); font-size: 14px; `; controlPanel.innerHTML = ` <div style="text-align: center; font-weight: bold; color: #4fd1c7; margin-bottom: 15px;"> 🎮 DRAWARIA RPG Mode🗡️ </div> <div style="margin-bottom: 15px;"> <label style="display: block; margin-bottom: 8px; color: #FFD700;">🎨 Render Mode:</label> <div style="display: flex; gap: 10px;"> <label style="display: flex; align-items: center; gap: 5px; cursor: pointer;"> <input type="radio" name="render-mode" value="drawings" checked style="accent-color: #4fd1c7;"> <span>🖌️ Drawings</span> </label> <label style="display: flex; align-items: center; gap: 5px; cursor: pointer;"> <input type="radio" name="render-mode" value="images" style="accent-color: #4fd1c7;"> <span>🖼️ Images (DOM)</span> </label> </div> </div> <div style="margin-bottom: 15px;"> <label style="display: block; margin-bottom: 5px; color: #FFD700;">🎯 Target Player:</label> <select id="player-selector" style="width: 100%; padding: 8px; border-radius: 5px; background: #2d2d2d; color: white; border: 1px solid #4fd1c7;"> <option value="">🔍 Scanning players...</option> </select> </div> <div style="margin-bottom: 15px;"> <label style="display: block; margin-bottom: 8px; color: #FFD700;">⚙️ Active Components:</label> <div style="display: flex; gap: 10px; flex-wrap: wrap;"> <label style="display: flex; align-items: center; gap: 5px; cursor: pointer;"> <input type="checkbox" id="hearts-toggle" checked style="accent-color: #FF0000;"> <span>💖 Hearts</span> </label> <label style="display: flex; align-items: center; gap: 5px; cursor: pointer;"> <input type="checkbox" id="sword-toggle" checked style="accent-color: #00FFFF;"> <span>⚔️ Sword</span> </label> <label style="display: flex; align-items: center; gap: 5px; cursor: pointer;"> <input type="checkbox" id="shield-toggle" checked style="accent-color: #FFFF00;"> <span>🛡️ Shield</span> </label> <label style="display: flex; align-items: center; gap: 5px; cursor: pointer;"> <input type="checkbox" id="monsters-toggle" checked style="accent-color: #CC0000;"> <span>👹 Monsters</span> </label> </div> </div> <div style="display: flex; gap: 10px; margin-bottom: 15px;"> <button id="start-system-btn" style="flex: 1; padding: 10px; background: linear-gradient(135deg, #00FF00, #32CD32); color: white; border: none; border-radius: 8px; font-weight: bold; cursor: pointer;" disabled> ▶️ START </button> <button id="stop-system-btn" style="flex: 1; padding: 10px; background: linear-gradient(135deg, #FF4444, #CC0000); color: white; border: none; border-radius: 8px; font-weight: bold; cursor: pointer;" disabled> ⏹️ STOP </button> </div> <div style="display: flex; gap: 10px; margin-bottom: 15px;"> <button id="debug-system-btn" style="flex: 1; padding: 8px; background: linear-gradient(135deg, #FF8C00, #FF6347); color: white; border: none; border-radius: 6px; font-size: 12px; cursor: pointer;"> 🐛 DEBUG </button> <button id="verify-dom-btn" style="flex: 1; padding: 8px; background: linear-gradient(135deg, #9370DB, #8A2BE2); color: white; border: none; border-radius: 6px; font-size: 12px; cursor: pointer;"> 🔍 VERIFY DOM </button> </div> <div id="system-status" style="text-align: center; padding: 10px; background: #333; border-radius: 5px; margin-bottom: 10px;"> ⏸️ System: <span style="color: #FF6B6B;">STOPPED</span> | Ready to start </div> <div id="companion-stats" style="font-size: 12px; color: #888;"> 📊 Players: 0 | Images: 0/4 | Monsters: 0/0 chasing </div> <div style="font-size: 11px; color: #666; margin-top: 10px; text-align: center;"> 🖼️ DOM Mode: Images without trails using monster method! </div> `; document.body.appendChild(controlPanel); this.setupEventListeners(); } setupEventListeners() { const playerSelect = document.getElementById('player-selector'); const heartsToggle = document.getElementById('hearts-toggle'); const swordToggle = document.getElementById('sword-toggle'); const shieldToggle = document.getElementById('shield-toggle'); const monstersToggle = document.getElementById('monsters-toggle'); const startBtn = document.getElementById('start-system-btn'); const stopBtn = document.getElementById('stop-system-btn'); const debugBtn = document.getElementById('debug-system-btn'); const verifyBtn = document.getElementById('verify-dom-btn'); const renderModeRadios = document.querySelectorAll('input[name="render-mode"]'); // Guardar referencia a 'this' const self = this; // Event listener para modo de renderizado renderModeRadios.forEach(radio => { radio.addEventListener('change', (e) => { const oldMode = renderMode; renderMode = e.target.value; console.log(`🎨 [PIXEL COMPANIONS] Modo de renderizado cambiado a: ${renderMode}`); if (renderMode === 'images') { console.log(`🖼️ [IMAGE MODE] Usando elementos DOM como monstruos - Sin rastros!`); } // Si el sistema está activo y cambiamos el modo, reiniciar companions if (systemActive && oldMode !== renderMode) { console.log('🔄 [PIXEL COMPANIONS] Reiniciando por cambio de modo...'); if (oldMode === 'images' && self.imageElements) { Object.values(self.imageElements).forEach(element => { if (element && element.parentNode) { element.parentNode.removeChild(element); } }); self.imageElements = null; } if (self.companionStates) { self.companionStates.forEach(state => { state.hearts.forEach(heart => heart.needsRedraw = true); state.sword.needsRedraw = true; state.shield.needsRedraw = true; }); } } }); }); // Event listeners para botones de debug debugBtn?.addEventListener('click', () => { self.debugCompanionSystem(); }); verifyBtn?.addEventListener('click', () => { self.verifyElementsInDOM(); }); playerSelect?.addEventListener('change', (e) => { selectedPlayerId = e.target.value; if (selectedPlayerId === 'all') { selectedPlayerName = 'Todos los jugadores'; } else if (selectedPlayerId) { const selectedOption = e.target.options[e.target.selectedIndex]; selectedPlayerName = selectedOption.dataset.playerName || selectedOption.textContent.replace('🎯 ', '') || `Jugador ${selectedPlayerId}`; } else { selectedPlayerName = null; } if (startBtn) startBtn.disabled = !selectedPlayerId; }); heartsToggle?.addEventListener('change', (e) => { activeComponents.hearts = e.target.checked; console.log(`💖 [PIXEL COMPANIONS] Corazones: ${e.target.checked ? 'Activados' : 'Desactivados'}`); }); swordToggle?.addEventListener('change', (e) => { activeComponents.sword = e.target.checked; console.log(`⚔️ [PIXEL COMPANIONS] Espada: ${e.target.checked ? 'Activada' : 'Desactivada'}`); }); shieldToggle?.addEventListener('change', (e) => { activeComponents.shield = e.target.checked; console.log(`🛡️ [PIXEL COMPANIONS] Escudo: ${e.target.checked ? 'Activado' : 'Desactivado'}`); }); monstersToggle?.addEventListener('change', (e) => { activeComponents.monsters = e.target.checked; console.log(`👹 [MONSTERS] Monstruos: ${e.target.checked ? 'Activados' : 'Desactivados'}`); if (!e.target.checked && self.monsterSystem) { self.monsterSystem.clearAllMonsters(); } }); startBtn?.addEventListener('click', () => { if (!selectedPlayerId) { console.log('🚫 [PIXEL COMPANIONS] Error: No hay jugador seleccionado'); return; } self.startSystem(); startBtn.disabled = true; if (stopBtn) stopBtn.disabled = false; }); stopBtn?.addEventListener('click', async () => { stopBtn.disabled = true; stopBtn.textContent = '🧹 CLEANING...'; await self.stopSystem(); stopBtn.textContent = '⏹️ STOP'; if (startBtn) startBtn.disabled = false; stopBtn.disabled = true; }); setTimeout(() => { self.playerManager.startAutoUpdate(); }, 1000); } updateStatusDisplay() { const statusDiv = document.getElementById('system-status'); const statsDiv = document.getElementById('companion-stats'); if (statusDiv) { const status = systemActive ? `▶️ System: <span style="color: #00FF00;">ACTIVE</span> | Mode: ${renderMode} | Target: ${selectedPlayerName || 'Unknown'}` : `⏸️ System: <span style="color: #FF6B6B;">STOPPED</span> | Mode: ${renderMode} | Ready to start`; statusDiv.innerHTML = status; } if (statsDiv) { const monsterStats = this.monsterSystem.getStats(); const imagesLoaded = this.imageAssetManager.getLoadedImagesCount(); statsDiv.textContent = `📊 Players: ${this.playerManager.validPlayers.length} | Images: ${imagesLoaded}/4 | Monsters: ${monsterStats.active}/${monsterStats.chasing} chasing`; } } } /* ---------- INICIALIZACIÓN ---------- */ let enhancedCompanionSystem = null; const initEnhancedSystem = () => { if (!enhancedCompanionSystem) { console.log('🚀 [PIXEL COMPANIONS] Inicializando Enhanced Pixel Companions System v3.9 DOM MODE...'); enhancedCompanionSystem = new EnhancedPixelCompanionSystem(); setTimeout(() => { console.log(''); console.log('✅ [PIXEL COMPANIONS] ENHANCED PIXEL COMPANIONS v3.9 LOADED! (DOM MODE)'); console.log('🖼️ [DOM MODE] NEW: Images using DOM elements like monsters!'); console.log('🚫 [DOM MODE] NO TRAILS: Elements move cleanly without canvas interference'); console.log('📱 [DOM MODE] position: fixed + background-image like monster system'); console.log('🎨 [RENDER MODE] Toggle: Drawings (canvas) vs Images (DOM)'); console.log('👹 [MONSTERS] Compatible with both render modes'); console.log('🧹 [CLEANUP] Smart cleanup: Canvas for drawings, DOM removal for images'); console.log('⚡ [PERFORMANCE] Smooth following without rastros/estelas'); console.log('🐛 [DEBUG] Added debug buttons for troubleshooting'); console.log(''); console.log('🖼️ [IMAGES] Hearts, Sword, Shield, Monster GIF as DOM elements'); console.log('🎯 [USAGE] Select "Images (DOM)" mode for clean sprite following!'); console.log('🔧 [MONSTER METHOD] Using exact same technique as monsters for companions'); console.log(''); }, 1000); } }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initEnhancedSystem); } else { setTimeout(initEnhancedSystem, 500); } setTimeout(initEnhancedSystem, 2000); console.log('🌟 [PIXEL COMPANIONS] Enhanced Drawaria Pixel Companions v3.9 DOM MODE loaded! 🌟'); console.log('🖼️ [DOM MODE] Images follow using monster method - Clean and smooth, no trails!'); })();