您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
The ultimate enhancement for your Drawaria.online experience. Redefining possibilities!
当前为
// ==UserScript== // @name Cube Engine Uptaded New Options // @version 9.0.2 // @description The ultimate enhancement for your Drawaria.online experience. Redefining possibilities! // @namespace drawaria.modded.fullspec // @homepage https://drawaria.online/profile/?uid=63196790-c7da-11ec-8266-c399f90709b7 // @author ≺ᴄᴜʙᴇ³≻ And YouTubeDrawaria // @match https://drawaria.online/ // @match https://drawaria.online/test // @match https://drawaria.online/room/* // @icon https://drawaria.online/avatar/cache/e53693c0-18b1-11ec-b633-b7649fa52d3f.jpg // @grant none // @license GNU GPLv3 // @run-at document-end // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js // ==/UserScript== (function () { (function CodeMaid(callback) { class TypeChecker { constructor() {} isArray(value) { return this.isA("Array", value); } isObject(value) { return !this.isUndefined(value) && value !== null && this.isA("Object", value); } isString(value) { return this.isA("String", value); } isNumber(value) { return this.isA("Number", value); } isFunction(value) { return this.isA("Function", value); } isAsyncFunction(value) { return this.isA("AsyncFunction", value); } isGeneratorFunction(value) { return this.isA("GeneratorFunction", value); } isTypedArray(value) { return ( this.isA("Float32Array", value) || this.isA("Float64Array", value) || this.isA("Int16Array", value) || this.isA("Int32Array", value) || this.isA("Int8Array", value) || this.isA("Uint16Array", value) || this.isA("Uint32Array", value) || this.isA("Uint8Array", value) || this.isA("Uint8ClampedArray", value) ); } isA(typeName, value) { return this.getType(value) === "[object " + typeName + "]"; } isError(value) { if (!value) { return false; } if (value instanceof Error) { return true; } return typeof value.stack === "string" && typeof value.message === "string"; } isUndefined(obj) { return obj === void 0; } getType(value) { return Object.prototype.toString.apply(value); } } class DOMCreate { #validate; constructor() { this.#validate = new TypeChecker(); } exportNodeTree(node = document.createElement("div")) { let referenceTolocalThis = this; let json = { nodeName: node.nodeName, attributes: {}, children: [], }; Array.from(node.attributes).forEach(function (attribute) { json.attributes[attribute.name] = attribute.value; }); if (node.children.length <= 0) { json.children.push(node.textContent.replaceAll("\t", "")); return json; } Array.from(node.children).forEach(function (childNode) { json.children.push(referenceTolocalThis.exportNodeTree(childNode)); }); return json; } importNodeTree(json = { nodeName: "", attributes: {}, children: [] }) { let referenceTolocalThis = this; if (referenceTolocalThis.#validate.isString(json)) { return this.TextNode(json); } let node = this.Tree(json.nodeName, json.attributes); json.children.forEach(function (child) { node.appendChild(referenceTolocalThis.importNodeTree(child)); }); return node; } Element() { return document.createElement.apply(document, arguments); } TextNode() { return document.createTextNode.apply(document, arguments); } Tree(type, attrs, childrenArrayOrVarArgs) { const el = this.Element(type); let children; if (this.#validate.isArray(childrenArrayOrVarArgs)) { children = childrenArrayOrVarArgs; } else { children = []; for (let i = 2; i < arguments.length; i++) { children.push(arguments[i]); } } for (let i = 0; i < children.length; i++) { const child = children[i]; if (typeof child === "string") { el.appendChild(this.TextNode(child)); } else { if (child) { el.appendChild(child); } } } for (const attr in attrs) { if (attr == "className") { el[attr] = attrs[attr]; } else { el.setAttribute(attr, attrs[attr]); } } el.appendAll = function (...nodes) { nodes.forEach((node) => { el.appendChild(node); }); }; return el; } } class CookieManager { constructor() {} set(name, value = "") { document.cookie = name + "=" + value + "; expires=" + new Date("01/01/2024").toUTCString().replace("GMT", "UTC") + "; path=/"; } get(name) { var nameEQ = name + "="; var ca = document.cookie.split(";"); for (var i = 0; i < ca.length; i++) { var c = ca[i]; while (c.charAt(0) == " ") c = c.substring(1, c.length); if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length); } return null; } clear(name) { document.cookie = name + "=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;"; } } class DocumentCleaner { document; constructor() { this.document = new DOMCreate(); } scripts(remove = true) { try { let array = document.querySelectorAll('script[src]:not([data-codemaid="ignore"])'); array.forEach((script) => { if (script.src != "") document.head.appendChild(script); }); } catch (error) { console.error(error); } try { let unifiedScript = this.document.Tree("script"); let scripts = document.querySelectorAll('script:not([src]):not([data-codemaid="ignore"])'); let unifiedScriptContent = ""; scripts.forEach((script) => { let content = script.textContent; //.replaceAll(/\s/g, ''); unifiedScriptContent += `try{${content}}catch(e){console.warn(e);}`; script.remove(); }); unifiedScript.textContent = unifiedScriptContent; if (!remove) document.head.appendChild(unifiedScript); } catch (error) { console.error(error); } } styles(remove = false) { try { let unifiedStyles = this.document.Tree("style"); unifiedStyles.textContet = ""; let styles = document.querySelectorAll('style:not([data-codemaid="ignore"])'); styles.forEach((style) => { unifiedStyles.textContent += style.textContent; style.remove(); }); if (!remove) document.head.appendChild(unifiedStyles); } catch (error) { console.error(error); } } embeds() { try { let array = document.querySelectorAll("iframe"); array.forEach((iframe) => { iframe.remove(); }); } catch (error) { console.error(error); } } } class CustomGenerator { constructor() {} uuidv4() { return crypto.randomUUID(); } } globalThis.typecheck = new TypeChecker(); globalThis.cookies = new CookieManager(); globalThis.domMake = new DOMCreate(); globalThis.domClear = new DocumentCleaner(); globalThis.generate = new CustomGenerator(); if (window.location.pathname === "/") window.location.assign("/test"); })(); (function CubicEngine() { domMake.Button = function (content) { let btn = domMake.Tree("button", { class: "btn btn-outline-secondary" }); btn.innerHTML = content; return btn; }; domMake.Row = function () { return domMake.Tree("div", { class: "_row" }); }; domMake.IconList = function () { return domMake.Tree("div", { class: "icon-list" }); }; const sockets = []; const originalSend = WebSocket.prototype.send; WebSocket.prototype.send = function (...args) { let socket = this; if (sockets.indexOf(socket) === -1) { sockets.push(socket); } socket.addEventListener("close", function () { const pos = sockets.indexOf(socket); if (~pos) sockets.splice(pos, 1); }); return originalSend.call(socket, ...args); }; const identifier = "🧊"; class Stylizer { constructor() { this.element = domMake.Tree("style", { "data-codemaid": "ignore" }, []); document.head.appendChild(this.element); this.initialize(); } initialize() { this.addRules([ `body * {margin: 0; padding: 0; box-sizing: border-box; line-height: normal;}`, `#${identifier} {--CE-bg_color: var(--light); --CE-color: var(--dark); line-height: 2rem; font-size: 1rem;}`, `#${identifier}>details {position:relative; overflow:visible; z-index: 999; background-color: var(--CE-bg_color); border: var(--CE-color) 1px solid; border-radius: .25rem;}`, `#${identifier} details>summary::marker {content:"📘";}`, `#${identifier} details[open]>summary::marker {content:"📖";}`, `#${identifier} details details {margin: 1px 0; border-top: var(--CE-color) 1px solid;}`, `#${identifier} input.toggle[name][hidden]:not(:checked) + * {display: none !important;}`, `#${identifier} header>.icon {margin: 1px;}`, `#${identifier} header>.icon.active {color: var(--success);}`, `#${identifier} header>.icon:not(.active) {color:var(--danger); opacity:.6;}`, `#${identifier} header:not(:has([title='Unselect'] + *)) > [title='Unselect'] {display:none;}`, `#${identifier} .btn {padding: 0;}`, `#${identifier} .icon-list {display: flex; flex-flow: wrap;}`, `#${identifier} .nowrap {overflow-x: scroll; padding-bottom: 12px; flex-flow: nowrap;}`, `#${identifier} .icon {display: flex; flex: 0 0 auto; max-width: 1.6rem; min-width: 1.6rem; height: 1.6rem; border-radius: .25rem; border: 1px solid var(--CE-color); aspect-ratio: 1/1;}`, `#${identifier} .icon > * {margin: auto; text-align: center; max-height: 100%; max-width: 100%;}`, `#${identifier} .itext {text-align: center; -webkit-appearance: none; -moz-appearance: textfield;}`, `#${identifier} ._row {display: flex; width: 100%;}`, `#${identifier} ._row > * {width: 100%;}`, `hr {margin: 5px 0;}`, `.playerlist-row::after {content: attr(data-playerid); position: relative; float: right; top: -20px;}`, `[hidden] {display: none !important;}`, `.noselect {-webkit-touch-callout: none; -webkit-user-select: none; -moz-user-select: none; user-select: none;}`, ]); } addRules(rules = []) { let reference = this; rules.forEach(function (rule) { reference.addRule(rule); }); } addRule(rule) { let TextNode = domMake.TextNode(rule); this.element.appendChild(TextNode); } } class ModBase { static globalListOfExtensions = []; static localListOfExtensions = []; static Styles = new Stylizer(); static register = function (extension) { extension.localListOfExtensions = []; ModBase.globalListOfExtensions.push(extension); return ModBase; }; static bind = function (extension, target) { let parent; if (typecheck.isFunction(target)) parent = target; else if (typecheck.isString(target)) parent = ModBase.globalListOfExtensions.find((entry) => entry.name === target); else if (typecheck.isObject(target)) parent = target.constructor; else { console.log(typecheck.getType(target)); } if (!parent) return new Error(`${parent}`); parent.localListOfExtensions.push(extension); parent.autostart = true; return parent; }; static findGlobal = function (extensionName) { return ModBase.globalListOfExtensions.find((entry) => entry.name === extensionName); }; #id; #name; #icon; htmlElements; children; parent; constructor(name, icon) { this.#id = generate.uuidv4(); this.#name = this.constructor.name; this.#icon = "📦"; this.children = []; this.htmlElements = {}; this.#onStartup(); this.setName(name || this.#name); this.setIcon(icon || this.#icon); } #onStartup() { this.#loadInterface(); if (this.constructor.autostart) this.constructor.localListOfExtensions.forEach((extension) => { this.loadExtension(extension); }); } #loadInterface() { this.htmlElements.details = domMake.Tree("details", { class: "noselect", open: false, // Changed from true to false to make it closed by default "data-reference": this.constructor.name, }); this.htmlElements.summary = domMake.Tree("summary"); this.htmlElements.header = domMake.Tree("header", { class: "icon-list" }); this.htmlElements.section = domMake.Tree("section"); this.htmlElements.children = domMake.Tree("section"); this.htmlElements.details.appendChild(this.htmlElements.summary); this.htmlElements.details.appendChild(this.htmlElements.header); this.htmlElements.details.appendChild(this.htmlElements.section); this.htmlElements.details.appendChild(this.htmlElements.children); this.htmlElements.input = domMake.Tree( "input", { type: "radio", id: this.#id, name: "QBit", class: "toggle", hidden: true, title: this.#name }, [this.#name] ); this.htmlElements.label = domMake.Tree("label", { for: this.#id, class: "icon" }); { const input = this.htmlElements.input; const label = this.htmlElements.label; input.addEventListener("change", (event) => { this.parent?.children.forEach((child) => { child.htmlElements.label.classList.remove("active"); }); label.classList[input.checked ? "add" : "remove"]("active"); }); label.classList[input.checked ? "add" : "remove"]("active"); } { const resetImageSelectionLabel = domMake.Tree("div", { class: "icon", title: "Unselect" }, [ domMake.Tree("i", { class: "fas fa-chevron-left" }), ]); resetImageSelectionLabel.addEventListener("click", () => { this.children.forEach((child) => { child.htmlElements.label.classList.remove("active"); child.htmlElements.input.checked = !1; }); }); this.htmlElements.header.appendChild(resetImageSelectionLabel); } } loadExtension(extension, referenceHandler) { let activeExtension = new extension(); activeExtension.parent = this; activeExtension.htmlElements.input.name = this.getName(); if (referenceHandler) referenceHandler(activeExtension); else this.children.push(activeExtension); if (!extension.siblings) extension.siblings = []; extension.siblings.push(activeExtension); if (extension.isFavorite) { activeExtension.htmlElements.input.click(); if (activeExtension.enable) activeExtension.enable(); } this.htmlElements.header.appendChild(activeExtension.htmlElements.label); this.htmlElements.children.appendChild(activeExtension.htmlElements.input); this.htmlElements.children.appendChild(activeExtension.htmlElements.details); return activeExtension; } notify(level, message) { if (typeof message != "string") { try { message = JSON.stringify(message); } catch (error) { throw error; } } let color = ""; if ([5, "error"].includes(level)) { color = "#dc3545"; } else if ([4, "warning"].includes(level)) { color = "#ffc107"; } else if ([3, "info"].includes(level)) { color = "#17a2b8"; } else if ([2, "success"].includes(level)) { color = "#28a745"; } else if ([1, "log"].includes(level)) { color = "#6c757d"; } else if ([0, "debug"].includes(level)) { color = "purple"; } console.log(`%c${this.#name}: ${message}`, `color: ${color}`); let chatmessage = domMake.Tree( "div", { class: `chatmessage systemchatmessage5`, "data-ts": Date.now(), style: `color: ${color}` }, [`${this.#name}: ${message}`] ); let loggingContainer = document.getElementById("chatbox_messages"); if (!loggingContainer) loggingContainer = document.body; loggingContainer.appendChild(chatmessage); } findGlobal(extensionName) { return this.referenceToBase.findGlobal(extensionName); } findLocal(extensionName) { return this.children.filter((child) => child.constructor.name === extensionName); } setName(name) { if (!name) return; this.#name = name; this.htmlElements.label.title = name; this.htmlElements.summary.childNodes.forEach((child) => child.remove()); if (typecheck.isString(name)) { if (name.startsWith("<")) return (this.htmlElements.summary.innerHTML = name); name = domMake.TextNode(name); } this.htmlElements.summary.appendChild(name); } getName() { return this.#name; } setIcon(icon) { if (!icon) return; this.#icon = icon; this.htmlElements.label.childNodes.forEach((child) => child.remove()); if (typecheck.isString(icon)) { if (icon.startsWith("<")) return (this.htmlElements.label.innerHTML = icon); icon = domMake.TextNode(icon); } this.htmlElements.label.appendChild(icon); } getIcon() { return this.#icon; } get referenceToBase() { return this.constructor.dummy1; } get referenceToMaster() { return this.constructor.dummy2; } _EXP_destroy(youSure = false) { if (!youSure) return; this.children.forEach((child) => { child._EXP_destroy(youSure); delete [child]; }); this.children = null; let pos = this.parent.children.indexOf(this); if (~pos) { this.parent.children.splice(pos, 1); } this.htmlElements.children.remove(); this.htmlElements.section.remove(); this.htmlElements.header.remove(); this.htmlElements.summary.remove(); this.htmlElements.details.remove(); this.htmlElements.input.remove(); this.htmlElements.label.remove(); this.htmlElements = null; let pos2 = this.constructor.siblings.indexOf(this); if (~pos2) { this.constructor.siblings.splice(pos2, 1); } } } class CubeEngine extends ModBase { static dummy1 = ModBase.register(this); constructor() { super("CubeEngine"); } } class Await { static dummy1 = ModBase.register(this); #interval; #handler; #callback; constructor(callback, interval) { this.#interval = interval; this.#callback = callback; } call() { const localThis = this; clearTimeout(this.#handler); this.#handler = setTimeout(function () { localThis.#callback(); }, this.#interval); } } globalThis[arguments[0]] = ModBase; return function (when = "load") { setTimeout(() => { const ModMenu = new CubeEngine(); ModMenu.htmlElements.details.open = false; // This line is now redundant as it's set in ModBase const target = document.getElementById("accountbox"); const container = domMake.Tree("div", { id: identifier, style: "height: 1.6rem; flex: 0 0 auto;" }); container.appendChild(ModMenu.htmlElements.details); target.after(container); target.after(domMake.Tree("hr")); globalThis["CubeEngine"] = ModMenu; globalThis["sockets"] = sockets; domClear.embeds(); domClear.scripts(); domClear.styles(); }, 200); }; })("QBit")(); // ... (final del módulo CubicEngine, p.ej. })("QBit")(); ) // --- Global readiness promises for external libraries --- // Estas promesas se resolverán cuando la librería respectiva esté completamente cargada e inicializada. // Su colocación es CRÍTICA: Deben estar en el scope más externo de tu userscript // pero después de la inicialización de CubicEngine para que 'QBit' y 'globalThis' estén disponibles. // NOTA: image-js ha sido ELIMINADO de aquí y del código del módulo ImageAnalyzer. let _cvReadyPromise = new Promise(resolve => { // OpenCV.js requiere `cv.onRuntimeInitialized` para asegurar que WebAssembly se ha cargado. if (typeof cv !== 'undefined') { if (cv.Mat) { // Si ya está listo (poco probable tan temprano), resolvemos. resolve(); console.log("Cube Engine: OpenCV.js ya estaba listo al inicio."); } else { cv.onRuntimeInitialized = () => { resolve(); console.log("Cube Engine: OpenCV.js onRuntimeInitialized disparado. Librería lista."); }; } } }); // --- End Global readiness promises --- // --- START NEW MODULE: ImageAnalyzer (CORREGIDO) --- // (Pega el código completo del módulo ImageAnalyzer aquí) // --- END NEW MODULE: ImageAnalyzer --- // --- START NEW MODULE: ShapeDetector (CORREGIDO) --- // (Pega el código completo del módulo ShapeDetector aquí) // --- END NEW MODULE: ShapeDetector --- (function BotClient() { const QBit = globalThis[arguments[0]]; function parseServerUrl(any) { var prefix = String(any).length == 1 ? `sv${any}.` : ""; return `wss://${prefix}drawaria.online/socket.io/?sid1=undefined&hostname=drawaria.online&EIO=3&transport=websocket`; } function parseRoomId(any) { return String(any).match(/([a-f0-9.-]+?)$/gi)[0]; } function parseSocketIOEvent(prefix_length, event_data) { try { return JSON.parse(event_data.slice(prefix_length)); } catch (error) {} } function parseAvatarURL(arr = []) { return `https://drawaria.online/avatar/cache/${arr.length > 0 ? arr.join(".") : "default"}.jpg`; } // CLASE BOTCLIENT MODIFICADA PARA HEREDAR DE QBit class BotClient extends QBit { // <--- ESTA LÍNEA FUE MODIFICADA/DESCOMENTADA static dummy1 = QBit.register(this); // constructor(name = '', avatar = []) { constructor(name = "JavaScript", avatar = ["86e33830-86ea-11ec-8553-bff27824cf71"]) { super(name, `<img src="${parseAvatarURL(avatar)}">`); // Asegura que se llama al constructor de la clase padre this.name = name; // Asegura que el nombre del bot se almacene para 'notify' this.avatar = avatar; this.attributes = { spawned: false, rounded: false, status: false }; this.url = ""; this.socket = null; this.interval_id = 0; this.interval_ms = 25000; this.room = { id: null, config: null, type: 2, players: [], }; this.customObservers = [ { event: "mc_roomplayerschange", callback: (data) => { this.room.players = data[2]; }, }, ]; } getReadyState() { const localThis = this; if (!localThis.socket) return false; return localThis.socket.readyState == localThis.socket.OPEN; } connect(url) { const localThis = this; // if (localThis.getReadyState()) localThis.disconnect(); if (localThis.getReadyState()) return; if (!url) return localThis.enterRoom(document.querySelector("#invurl").value); localThis.socket = new WebSocket(parseServerUrl(url)); localThis.socket.addEventListener("open", function (event) { localThis.interval_id = setInterval(function () { if (!localThis.getReadyState()) return clearInterval(localThis.interval_id); localThis.send(2); }, localThis.interval_ms); }); localThis.socket.addEventListener("message", function (message_event) { var prefix = String(message_event.data).match(/(^\d+)/gi)[0] || ""; if (prefix == "40") { localThis.send(emits.startplay(localThis.room, localThis.name, localThis.avatar)); } var data = parseSocketIOEvent(prefix.length, message_event.data) || []; if (data && data.length == 1) { if (data[0].players) localThis.room.players = data[0].players; } if (data && data.length > 1) { var event = data.shift(); localThis.customObservers.forEach((listener) => { if (listener.event === event) if (listener.callback) listener.callback(data); }); } }); } disconnect() { if (!this.getReadyState()) return; this.socket.close(); } reconnect() { this.send(41); this.send(40); } enterRoom(roomid) { this.room.id = parseRoomId(roomid); if (!this.getReadyState()) this.connect(this.room.id.includes(".") ? this.room.id.slice(-1) : ""); this.reconnect(); } addEventListener(eventname, callback) { this.customObservers.push({ event: eventname, callback }); } send(data) { if (!this.getReadyState()) return /*console.warn(data)*/; this.socket.send(data); } emit(event, ...data) { // data = data.length > 0 ? data : null; var emitter = emits[event]; if (emitter) this.send(emitter(...data)); } } const emits = { chatmsg: function (message) { // 42["chatmsg","a"] let data = ["chatmsg", message]; return `${42}${JSON.stringify(data)}`; }, passturn: function () { // 42["passturn"] let data = ["passturn"]; return `${42}${JSON.stringify(data)}`; }, pgdrawvote: function (playerid) { // 42["pgdrawvote",2,0] let data = ["pgdrawvote", playerid, 0]; return `${42}${JSON.stringify(data)}`; }, pgswtichroom: function () { // 42["pgswtichroom"] let data = ["pgswtichroom"]; return `${42}${JSON.stringify(data)}`; }, playerafk: function () { // 42["playerafk"] let data = ["playerafk"]; return `${42}${JSON.stringify(data)}`; }, playerrated: function () { // 42["playerrated"] let data = ["playerrated"]; return `${42}${JSON.stringify(data)}`; }, sendgesture: function (gestureid) { // 42["sendgesture",16] let data = ["sendgesture", gestureid]; return `${42}${JSON.stringify(data)}`; }, sendvote: function () { // 42["sendvote"] let data = ["sendvote"]; return `${42}${JSON.stringify(data)}`; }, sendvotekick: function (playerid) { // 42["sendvotekick",93] let data = ["sendvotekick", playerid]; return `${42}${JSON.stringify(data)}`; }, wordselected: function (wordid) { // 42["wordselected",0] let data = ["sendvotekick", wordid]; return `${42}${JSON.stringify(data)}`; }, activateitem: function (itemid, isactive) { let data = ["clientcmd", 12, [itemid, isactive]]; return `${42}${JSON.stringify(data)}`; }, buyitem: function (itemid) { let data = ["clientcmd", 11, [itemid]]; return `${42}${JSON.stringify(data)}`; }, canvasobj_changeattr: function (itemid, target, value) { // target = zindex || shared let data = ["clientcmd", 234, [itemid, target, value]]; return `${42}${JSON.stringify(data)}`; }, canvasobj_getobjects: function () { let data = ["clientcmd", 233]; return `${42}${JSON.stringify(data)}`; }, canvasobj_remove: function (itemid) { let data = ["clientcmd", 232, [itemid]]; return `${42}${JSON.stringify(data)}`; }, canvasobj_setposition: function (itemid, positionX, positionY, speed) { let data = ["clientcmd", 230, [itemid, 100 / positionX, 100 / positionY, { movespeed: speed }]]; return `${42}${JSON.stringify(data)}`; }, canvasobj_setrotation: function (itemid, rotation) { let data = ["clientcmd", 231, [itemid, rotation]]; return `${42}${JSON.stringify(data)}`; }, customvoting_setvote: function (value) { let data = ["clientcmd", 301, [value]]; return `${42}${JSON.stringify(data)}`; }, getfpid: function (value) { let data = ["clientcmd", 901, [value]]; return `${42}${JSON.stringify(data)}`; }, getinventory: function () { let data = ["clientcmd", 10, [true]]; return `${42}${JSON.stringify(data)}`; }, getspawnsstate: function () { let data = ["clientcmd", 102]; return `${42}${JSON.stringify(data)}`; }, moveavatar: function (positionX, positionY) { let data = [ "clientcmd", 103, [1e4 * Math.floor((positionX / 100) * 1e4) + Math.floor((positionY / 100) * 1e4), false], ]; return `${42}${JSON.stringify(data)}`; }, setavatarprop: function () { let data = ["clientcmd", 115]; return `${42}${JSON.stringify(data)}`; }, setstatusflag: function (flagid, isactive) { let data = ["clientcmd", 3, [flagid, isactive]]; return `${42}${JSON.stringify(data)}`; }, settoken: function (playerid, tokenid) { let data = ["clientcmd", 2, [playerid, tokenid]]; return `${42}${JSON.stringify(data)}`; }, snapchatmessage: function (playerid, value) { let data = ["clientcmd", 330, [playerid, value]]; return `${42}${JSON.stringify(data)}`; }, spawnavatar: function () { let data = ["clientcmd", 101]; return `${42}${JSON.stringify(data)}`; }, startrollbackvoting: function () { let data = ["clientcmd", 320]; return `${42}${JSON.stringify(data)}`; }, trackforwardvoting: function () { let data = ["clientcmd", 321]; return `${42}${JSON.stringify(data)}`; }, startplay: function (room, name, avatar) { let data = `${420}${JSON.stringify([ "startplay", name, room.type, "en", room.id, null, [null, "https://drawaria.online/", 1000, 1000, [null, avatar[0], avatar[1]], null], ])}`; return data; }, votetrack: function (trackid) { let data = ["clientcmd", 1, [trackid]]; return `${42}${JSON.stringify(data)}`; }, requestcanvas: function (playerid) { let data = ["clientnotify", playerid, 10001]; return `${42}${JSON.stringify(data)}`; }, respondcanvas: function (playerid, base64) { let data = ["clientnotify", playerid, 10002, [base64]]; return `${42}${JSON.stringify(data)}`; }, galleryupload: function (playerid, imageid) { let data = ["clientnotify", playerid, 11, [imageid]]; return `${42}${JSON.stringify(data)}`; }, warning: function (playerid, type) { let data = ["clientnotify", playerid, 100, [type]]; return `${42}${JSON.stringify(data)}`; }, mute: function (playerid, targetname, mute = 0) { let data = ["clientnotify", playerid, 1, [mute, targetname]]; return `${42}${JSON.stringify(data)}`; }, hide: function (playerid, targetname, hide = 0) { let data = ["clientnotify", playerid, 3, [hide, targetname]]; return `${42}${JSON.stringify(data)}`; }, report: function (playerid, reason, targetname) { let data = ["clientnotify", playerid, 2, [targetname, reason]]; return `${42}${JSON.stringify(data)}`; }, line: function (playerid, lastx, lasty, x, y, isactive, size, color, ispixel) { let data = [ "drawcmd", 0, [lastx / 100, lasty / 100, x / 100, y / 100, isactive, -size, color, playerid, ispixel], ]; return `${42}${JSON.stringify(data)}`; }, erase: function (playerid, lastx, lasty, x, y, isactive, size, color) { let data = ["drawcmd", 1, [lastx / 100, lasty / 100, x / 100, y / 100, isactive, -size, color, playerid]]; return `${42}${JSON.stringify(data)}`; }, flood: function (x, y, color, size, r, g, b, a) { // 42["drawcmd",2,[x, y,color,{"0":r,"1":g,"2":b,"3":a},size]] let data = ["drawcmd", 2, [x / 100, y / 100, color, { 0: r, 1: g, 2: b, 3: a }, size]]; return `${42}${JSON.stringify(data)}`; }, undo: function (playerid) { // 42["drawcmd",3,[playerid]] let data = ["drawcmd", 3, [playerid]]; return `${42}${JSON.stringify(data)}`; }, clear: function () { // 42["drawcmd",4,[]] let data = ["drawcmd", 4, []]; return `${42}${JSON.stringify(data)}`; }, noop: function () { // 42["drawcmd",5,[0.44882022129015975,0.3157894736842105,0.44882022129015975,0.3157894736842105,true,-12,"#000000",playerid]] }, }; const events = { bc_announcement: function (data) { // }, bc_chatmessage: function (data) { // 42["bc_chatmessage",3,"playername","a"] }, bc_clearcanvasobj: function (data) { // }, bc_clientnotify: function (data) { // 42["bc_clientnotify",playerid,"playername",code,null] }, bc_createcanvasobj: function (data) { // 42["bc_createcanvasobj","1",[3,63001,0.5,0.5,0,1,null,"1",true]] }, bc_customvoting_abort: function (data) { // }, bc_customvoting_error: function (data) { // 42["bc_customvoting_error","rollbackcanvas"] }, bc_customvoting_results: function (data) { // 42["bc_customvoting_results",[2],true,0] }, bc_customvoting_start: function (data) { // 42["bc_customvoting_start",{"type":321,"secs":20,"acceptratios":[0.51],"pgdrawallow":true,"voteoptions":["YES","NO"]},1] }, bc_customvoting_vote: function (data) { // 42["bc_customvoting_vote",1,0,[2,1,[1]]] }, bc_exp: function (data) { // 42["bc_exp",29,4357] }, bc_extannouncement: function (data) { // }, bc_freedrawsession_reset: function (data) { // 42["bc_freedrawsession_reset",-1,{"votingtype":2,"currentvotes":0,"neededvotes":2,"votingtimeout":null}null] }, bc_gesture: function (data) { // 42["bc_gesture",3,31] }, bc_musicbox_play: function (data) { // 42["bc_musicbox_play",[30394,1,"37678185",252386,1661295694733,"Sony Masterworks - Smooth Criminal","2cellos/smooth-criminal"]] }, bc_musicbox_vote: function (data) { // 42["bc_musicbox_vote",[[30394,1]],3,30394] }, bc_pgdrawallow_results: function (data) { // 42["bc_pgdrawallow_results",2,true,true] }, bc_pgdrawallow_startvoting: function (data) { // 42["bc_pgdrawallow_startvoting",2,1,false] }, bc_pgdrawallow_vote: function (data) { // 42["bc_pgdrawallow_vote",2,1,0,false,[1,0]] }, bc_playerafk: function (data) { // 42["bc_playerafk",28,"Jinx"] }, bc_playerrated: function (data) { // 42["bc_playerrated",1,29,"lil cute girl",28,"Jinx",[1]] }, bc_removecanvasobj: function (data) { // 42["bc_removecanvasobj",3,"1",null] }, bc_resetplayername: function (data) { // }, bc_round_results: function (data) { // 42["bc_round_results",[[5,"Jinx",15,61937,3,"63196790-c7da-11ec-8266-c399f90709b7",0],[4,"ツ♡thick mojo ♡ツ",15,65464,3,"018cdc20-47a4-11ec-b5b5-6bdacecdd51e",1]]] }, bc_setavatarprop: function (data) { // 42["bc_setavatarprop",3] }, bc_setobjattr: function (data) { // 42["bc_setobjattr","1","shared",false] }, bc_setstatusflag: function (data) { // 42["bc_setstatusflag",3,3,true] }, bc_spawnavatar: function (data) { // 42["bc_spawnavatar",3,true] }, bc_startinground: function (data) { // 42["bc_startinground",200000,[],{"votingtype":0,"currentvotes":0,"neededvotes":2,"votingtimeout":null}] }, bc_token: function (data) { // 42["bc_token",1,3,0] }, bc_turn_abort: function (data) { // 42["bc_turn_abort","pass","lil cute girl","2c276aa0-dc5e-11ec-9fd3-c3a00b129da4","hammer",null] }, bc_turn_fastout: function (data) { // 42["bc_turn_fastout",15000] }, bc_turn_results: function (data) { // 42["bc_turn_results",[[1,"Jinx",2,2,"63196790-c7da-11ec-8266-c399f90709b7",0,0],[2,"vale",3,3,"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx.xxxxxxxxxxxxx",9248]],"cavern"] }, bc_turn_waitplayers: function (data) { // 42["bc_turn_waitplayers",true,-1,6] }, bc_uc_freedrawsession_changedroom: function (data) { // console.log(data[2], data[3]) // 42["bc_uc_freedrawsession_changedroom",[list of drawlines not !important]] }, bc_uc_freedrawsession_start: function (data) { // }, bc_votekick: function (data) { // 42["bc_votekick","Jinx",22,true] }, bc_votingtimeout: function (data) { // 42["bc_votingtimeout",{"votingtype":2,"currentvotes":0,"neededvotes":2,"votingtimeout":null}] }, bcmc_playervote: function (data) { // 42["bcmc_playervote","playername",{"votingtype":3,"currentvotes":1,"neededvotes":2,"votingtimeout":1661296731309}] }, bcuc_getfpid: function (data) { // 42["bcuc_getfpid"] // 42["clientcmd",901,[{"visitorId":"a8923f0870050d4a4e771cd26679ab6e"}]] }, bcuc_itemactivated: function (data) { // 42["bcuc_itemactivated",3,63001,[2,[0.5,0.5],0,1,null],1] }, bcuc_itemactivationabort: function (data) { // }, bcuc_moderatormsg: function (data) { // 42["bcuc_moderatormsg","Kick Player",true] }, bcuc_snapchatmessage: function (data) { // 42["uc_snapshot","1671740010120.1.28028"] // https://drawaria.online/snapshot/save }, uc_avatarspawninfo: function (data) { // 42["uc_avatarspawninfo","9a2ab5b2-b81e-4690-9af7-475d870d6e20",[[38,75059625,0]]] }, uc_buyitemerror: function (data) { // }, uc_canvasobjs: function (data) { // 42["uc_canvasobjs","9a2ab5b2-b81e-4690-9af7-475d870d6e20",{}] }, uc_chatmuted: function (data) { // 42["uc_chatmuted"] }, uc_coins: function (data) { // 42["uc_coins",-50,43] }, uc_inventoryitems: function (data) { // 42["uc_inventoryitems",[[100,99,null],[63000,null,null],[86000,null,null]],false,false] list }, uc_resetavatar: function (data) { // }, uc_serverserstart: function (data) { // }, uc_snapshot: function (data) { // }, uc_tokenerror: function (data) { // 42["uc_tokenerror",2] }, uc_turn_begindraw: function (data) { // 42["uc_turn_begindraw",90000,"arrow"] }, uc_turn_selectword: function (data) { // 42["uc_turn_selectword",11000,["vase","cellar","rain"],1,7,false] }, uc_turn_selectword_refreshlist: function (data) { // 42["uc_turn_selectword_refreshlist",["crayons","trade","team"]] }, uc_turn_wordguessedlocalThis: function (data) { // 42["uc_turn_wordguessedlocalThis","stage",3,[[2,3,3,53938],[1,2,2,0]]] }, }; globalThis["_io"] = { events, emits }; })("QBit"); // YOUTUBEDRAWARIA // ... (Tu código existente hasta aquí, incluyendo las promesas globales y otros módulos) ... // --- START NEW MODULE: MagicTools (COMBINADO - SOLUCIÓN DEFINITIVA BOTÓN "COLOR RÁPIDO") --- (function MagicToolsModule() { const QBit = globalThis[arguments[0]]; // All Known Elements for Hide/Show Menus (copied from HideShowMenusModule) const allKnownElements = [ { selector: '#canvas', label: 'Canvas' }, { selector: '#leftbar', label: 'Left Sidebar' }, { selector: '#rightbar', label: 'Right Sidebar' }, { selector: '#playerlist', label: 'Player List' }, { selector: '#cyclestatus', label: 'Cycle Status' }, { selector: '#votingbox', label: 'Voting Box' }, { selector: '#passturnbutton', label: 'Pass Turn Button' }, { selector: '.timer', label: 'Round Timer' }, { selector: '#roomcontrols', label: 'Room Controls' }, { selector: '#infotext', label: 'Info Text' }, { selector: '#gesturespickerselector', label: 'Gestures Picker' }, { selector: '#chatbox_messages', label: 'Chat Messages' }, { selector: '#drawcontrols', label: 'Draw Controls' }, { selector: '#turnresults', label: 'Turn Results' }, { selector: '#roundresults', label: 'Round Results' }, { selector: '#snapmessage_container', label: 'Snap Message Container' }, { selector: '#accountbox', label: 'Account Box' }, { selector: '#customvotingbox', label: 'Custom Voting Box' }, { selector: '#showextmenu', label: 'Show Ext Menu Button' }, { selector: '#playgroundroom_next', label: 'Playground Next Button' }, { selector: '#homebutton', label: 'Home Button' }, { selector: '.invbox', label: 'Invitation Box' }, { selector: '#howtoplaydialog', label: 'How to Play' }, { selector: '#newroomdialog', label: 'New Room Options' }, { selector: '#musictracks', label: 'Music Tracks' }, { selector: '#inventorydlg', label: 'Inventory' }, { selector: '#extmenu', label: 'Extra Menu' }, { selector: '#pressureconfigdlg', label: 'Pressure Settings' }, { selector: '#palettechooser', label: 'Palette Chooser' }, { selector: '#wordchooser', label: 'Word Chooser' }, { selector: '#targetword', label: 'Target Word Info' }, { selector: '#invitedlg', label: 'Invite Dialog' }, { selector: '#reportdlg', label: 'Report Dialog' }, { selector: '#turtabortedmsg', label: 'Turn Aborted Msg' }, { selector: '#drawsettings', label: 'Draw Settings' }, { selector: '.modal-header', label: 'Header (Any)' }, { selector: '.modal-body', label: 'Body (Any)' }, { selector: '.modal-footer', label: 'Footer (Any)' }, { selector: '.form-group', label: 'Form Group (Any)' }, { selector: '.table', label: 'Table (Any)' }, { selector: '.spinner-border', label: 'Spinner/Loading Icon (Any)' }, ]; QBit.Styles.addRules([ `#${QBit.identifier} .magic-tools-section { border: 1px solid var(--CE-color); border-radius: .25rem; padding: 5px; margin-bottom: 10px; background-color: var(--CE-bg_color); }`, `#${QBit.identifier} .magic-tools-section-title { font-weight: bold; margin-bottom: 5px; color: var(--dark-blue-title); text-align: center; }`, `#${QBit.identifier} .magic-tools-toggle-button { /* Base style for all toggle buttons */ background-color: var(--secondary); color: var(--dark); }`, `#${QBit.identifier} .magic-tools-toggle-button.active { /* Active state for toggle buttons */ background-color: var(--info); color: white; }`, `#${QBit.identifier} .magic-tools-controls-row > * { flex: 1; }`, `#${QBit.identifier} .magic-tools-selector { border: 1px solid var(--input-border-blue); background-color: var(--input-bg); color: var(--dark-text); padding: 5px; width: 100%; box-sizing: border-box; margin-bottom: 5px; }` // OLD CSS related to .magic-tool-icon-toggle and .custom-checkbox-indicator has been removed ]); class MagicTools extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); // BiggerBrush / BetterBrush #biggerBrushActive = false; #betterBrushActive = false; #drawwidthrangeSlider; // For BiggerBrush #betterBrushVisibilityObserver; // For BetterBrush popuplist #rapidColorChangeButton; // Reference to the new button for Rapid Color Change // BiggerStencil #biggerStencilActive = false; #biggerStencilAccessibilityObserver; // Hide/Show Menus #hideShowElementSelector; #hideShowMenusObserver; constructor() { super("Herramientas Mágicas", '<i class="fas fa-magic"></i>'); this.#onStartup(); } #onStartup() { this.#loadInterface(); this.#setupDrawControlsObservers(); // For BiggerBrush, BetterBrush, BiggerStencil this.#setupHideShowMenusObserver(); // For Hide/Show Menus this.#populateHideShowSelector(); // Initial population } #loadInterface() { const container = domMake.Tree("div"); // --- Section: Herramientas de Pincel --- const brushToolsSection = domMake.Tree("div", { class: "magic-tools-section" }); brushToolsSection.appendChild(domMake.Tree("div", { class: "magic-tools-section-title" }, ["Herramientas de Pincel"])); // BiggerBrush Toggle const biggerBrushRow = domMake.Row(); const biggerBrushButton = domMake.Button('<i class="fas fa-brush"></i> Pincel<br>Grande'); biggerBrushButton.classList.add("magic-tools-toggle-button"); biggerBrushButton.addEventListener("click", () => this.#toggleBiggerBrush(biggerBrushButton)); biggerBrushRow.appendChild(biggerBrushButton); brushToolsSection.appendChild(biggerBrushRow); // BetterBrush Toggle const betterBrushRow = domMake.Row(); const betterBrushButton = domMake.Button('<i class="fas fa-magic"></i> Pincel<br>Mejorado'); betterBrushButton.classList.add("magic-tools-toggle-button"); betterBrushButton.addEventListener("click", () => this.#toggleBetterBrush(betterBrushButton)); betterBrushRow.appendChild(betterBrushButton); brushToolsSection.appendChild(betterBrushRow); // NEW: Rapid Color Change button in its own row, directly below BetterBrush const rapidColorChangeRow = domMake.Row(); this.#rapidColorChangeButton = domMake.Button('<i class="fas fa-adjust"></i> Color<br>Rápido'); // Use <br> for newline this.#rapidColorChangeButton.classList.add("magic-tools-toggle-button"); this.#rapidColorChangeButton.addEventListener("click", () => this.#toggleRapidColorChange(this.#rapidColorChangeButton)); rapidColorChangeRow.appendChild(this.#rapidColorChangeButton); brushToolsSection.appendChild(rapidColorChangeRow); // BiggerStencil Toggle const biggerStencilRow = domMake.Row(); const biggerStencilButton = domMake.Button('<i class="fas fa-object-group"></i> Plantillas<br>Grandes'); biggerStencilButton.classList.add("magic-tools-toggle-button"); biggerStencilButton.addEventListener("click", () => this.#toggleBiggerStencil(biggerStencilButton)); biggerStencilRow.appendChild(biggerStencilButton); brushToolsSection.appendChild(biggerStencilRow); container.appendChild(brushToolsSection); // --- Section: Exportar Chat --- const exportChatSection = domMake.Tree("div", { class: "magic-tools-section" }); exportChatSection.appendChild(domMake.Tree("div", { class: "magic-tools-section-title" }, ["Exportar Chat"])); const exportChatRow = domMake.Row(); const exportChatButton = domMake.Button('<i class="fas fa-file-export"></i> Exportar<br>Chat (TXT)'); exportChatButton.title = "Exporta todos los mensajes del chat a un archivo de texto."; exportChatButton.addEventListener("click", () => this.#exportChatMessages()); exportChatRow.appendChild(exportChatButton); exportChatSection.appendChild(exportChatRow); container.appendChild(exportChatSection); // --- Section: Ocultar/Mostrar Menús --- const hideShowMenusSection = domMake.Tree("div", { class: "magic-tools-section" }); hideShowMenusSection.appendChild(domMake.Tree("div", { class: "magic-tools-section-title" }, ["Ocultar/Mostrar Menús"])); this.#hideShowElementSelector = domMake.Tree("select", { id: "magic-tools-element-selector", class: "magic-tools-selector" }); hideShowMenusSection.appendChild(this.#hideShowElementSelector); const hideShowButtonRow = domMake.Row({ class: "action-buttons" }); const showButton = domMake.Button("Mostrar"); showButton.addEventListener('click', () => this.#toggleElementVisibility(false)); const hideButton = domMake.Button("Ocultar"); hideButton.addEventListener('click', () => this.#toggleElementVisibility(true)); hideShowButtonRow.appendAll(showButton, hideButton); hideShowMenusSection.appendChild(hideShowButtonRow); container.appendChild(hideShowMenusSection); this.htmlElements.section.appendChild(container); } // --- BiggerBrush Logic --- #toggleBiggerBrush(button) { this.#biggerBrushActive = !this.#biggerBrushActive; button.classList.toggle("active", this.#biggerBrushActive); if (!this.drawwidthrangeSlider) { this.drawwidthrangeSlider = document.querySelector("#drawwidthrange"); if (!this.drawwidthrangeSlider) { this.notify("error", "Slider de ancho de dibujo no encontrado."); this.#biggerBrushActive = false; button.classList.remove("active"); return; } } if (this.#biggerBrushActive) { document.querySelectorAll(".drawcontrols-button").forEach((n) => { n.classList.remove("drawcontrols-disabled"); }); button.innerHTML = '<i class="fas fa-paint-brush"></i> Pincel Grande<br>Activo'; this.drawwidthrangeSlider.parentElement.previousElementSibling.lastElementChild.click(); this.drawwidthrangeSlider.parentElement.style.display = "flex"; this.drawwidthrangeSlider.max = 48; this.drawwidthrangeSlider.min = -2000; this.notify("success", "Pincel Grande activado."); } else { button.innerHTML = '<i class="fas fa-paint-brush"></i> Pincel<br>Grande'; this.drawwidthrangeSlider.max = 45; this.drawwidthrangeSlider.min = -100; this.notify("warning", "Pincel Grande desactivado."); } } // --- BetterBrush Logic --- #toggleBetterBrush(button) { this.#betterBrushActive = !this.#betterBrushActive; button.classList.toggle("active", this.#betterBrushActive); button.innerHTML = this.#betterBrushActive ? '<i class="fas fa-magic"></i> Pincel Mejorado<br>Activo' : '<i class="fas fa-magic"></i> Pincel<br>Mejorado'; this.notify("info", `Pincel Mejorado ${this.#betterBrushActive ? 'activado' : 'desactivado'}.`); } // --- Rapid Color Change Logic --- #toggleRapidColorChange(button) { const colorflowSpeedInput = document.querySelector('input[data-localprop="colorflow"]'); const colorflowTypeSelect = document.querySelector('select[data-localprop="colorautochange"]'); const settingsContainer = document.querySelector(".drawcontrols-settingscontainer:has([data-localprop='colorautochange'])"); if (!colorflowSpeedInput || !colorflowTypeSelect || !settingsContainer) { this.notify("warning", "Controles de flujo de color no encontrados. Asegúrate de que los controles de dibujo están visibles."); return; } const isActive = button.classList.contains("active"); const newActiveState = !isActive; button.classList.toggle("active", newActiveState); button.innerHTML = newActiveState ? '<i class="fas fa-adjust"></i> Color Rápido<br>Activo' : '<i class="fas fa-adjust"></i> Color<br>Rápido'; if (newActiveState) { colorflowTypeSelect.value = "2"; colorflowSpeedInput.max = 10; colorflowSpeedInput.value = 10; this.notify("info", "Cambio Rápido de Color activado."); } else { colorflowTypeSelect.value = "0"; colorflowSpeedInput.max = 1; colorflowSpeedInput.value = 0; this.notify("info", "Cambio Rápido de Color desactivado."); } settingsContainer.dispatchEvent(new CustomEvent("change")); } // --- BiggerStencil Logic --- #toggleBiggerStencil(button) { this.#biggerStencilActive = !this.#biggerStencilActive; button.classList.toggle("active", this.#biggerStencilActive); button.innerHTML = this.#biggerStencilActive ? '<i class="fas fa-object-group"></i> Plantillas Grandes<br>Activo' : '<i class="fas fa-object-group"></i> Plantillas<br>Grandes'; this.notify("info", `Plantillas Grandes ${this.#biggerStencilActive ? 'activado' : 'desactivado'}.`); const targetStencilButton = document.querySelector(".fa-parachute-box")?.parentElement; if (!targetStencilButton) { this.notify("warning", "Botón de Plantillas no encontrado."); return; } if (this.#biggerStencilActive) { if (targetStencilButton.disabled) { targetStencilButton.disabled = ""; } } } #setupDrawControlsObservers() { const betterBrushTarget = document.querySelector(".drawcontrols-popuplist"); if (betterBrushTarget) { this.#betterBrushVisibilityObserver = new MutationObserver((mutations) => { if (this.#betterBrushActive) { if (mutations[0].target.style.display !== "none") { mutations[0].target.querySelectorAll("div").forEach((n) => { n.removeAttribute("style"); }); } } }); this.#betterBrushVisibilityObserver.observe(betterBrushTarget, { attributes: true, attributeFilter: ['style'] }); } else { this.notify("warning", "Contenedor de pop-up de pincel no encontrado para BetterBrush."); } const biggerStencilTarget = document.querySelector(".fa-parachute-box")?.parentElement; if (biggerStencilTarget) { this.#biggerStencilAccessibilityObserver = new MutationObserver((mutations) => { if (this.#biggerStencilActive) { if (mutations[0].target.disabled) { mutations[0].target.disabled = ""; } } }); this.#biggerStencilAccessibilityObserver.observe(biggerStencilTarget, { attributes: true, attributeFilter: ['disabled'] }); } else { this.notify("warning", "Botón de esténcil no encontrado para BiggerStencil."); } this.drawwidthrangeSlider = document.querySelector("#drawwidthrange"); if (!this.drawwidthrangeSlider) { this.notify("warning", "Slider de ancho de dibujo no encontrado para BiggerBrush."); } } // --- Export Chat Logic --- #exportChatMessages() { const chatbox = document.getElementById('chatbox_messages'); if (!chatbox) { this.notify("warning", "Contenedor de chat no encontrado."); return; } const messages = chatbox.querySelectorAll('div.chatmessage'); let exportedMessages = []; messages.forEach(message => { let timestamp = message.dataset.ts ? new Date(parseInt(message.dataset.ts)).toLocaleTimeString() : 'N/A'; if (message.classList.contains('systemchatmessage') || message.classList.contains('systemchatmessage5')) { exportedMessages.push(`[${timestamp}] [Sistema] ${message.textContent.trim().replace(/^System: /, '')}`); } else if (message.classList.contains('playerchatmessage-highlightable') || message.classList.contains('chatmessage')) { const playerName = message.querySelector('.playerchatmessage-name')?.textContent?.trim() || 'Desconocido'; const playerMessage = message.querySelector('.playerchatmessage-text')?.textContent?.trim() || ''; exportedMessages.push(`[${timestamp}] ${playerName}: ${playerMessage}`); } }); if (exportedMessages.length === 0) { this.notify("info", "No hay mensajes en el chat para exportar."); return; } const blob = new Blob([exportedMessages.join('\n')], { type: 'text/plain;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `drawaria_chat_${new Date().toISOString().slice(0, 10)}.txt`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); this.notify("success", "Chat exportado exitosamente."); } // --- Hide/Show Menus Logic --- #populateHideShowSelector() { const currentSelectedValue = this.#hideShowElementSelector.value; this.#hideShowElementSelector.innerHTML = ''; const addedSelectors = new Set(); const placeholderOption = domMake.Tree('option', { value: '' }, ['-- Selecciona un elemento --']); this.#hideShowElementSelector.appendChild(placeholderOption); allKnownElements.forEach(item => { try { if (document.querySelector(item.selector) && !addedSelectors.has(item.selector)) { const option = domMake.Tree('option', { value: item.selector }, [item.label]); this.#hideShowElementSelector.appendChild(option); addedSelectors.add(item.selector); } } catch (e) { console.warn(`[MagicTools - HideShow] Selector inválido: ${item.selector}. Error: ${e.message}`); } }); if (currentSelectedValue && Array.from(this.#hideShowElementSelector.options).some(opt => opt.value === currentSelectedValue)) { this.#hideShowElementSelector.value = currentSelectedValue; } else { this.#hideShowElementSelector.value = ''; } } #toggleElementVisibility(hide) { const selectedValue = this.#hideShowElementSelector.value; if (!selectedValue) { this.notify("warning", "No hay elemento seleccionado."); return; } try { document.querySelectorAll(selectedValue).forEach(el => { if (hide) { if (el.style.display && el.style.display !== 'none') { el.dataset.originalDisplay = el.style.display; } el.style.display = 'none'; el.style.visibility = 'hidden'; if (selectedValue.includes('.modal-backdrop')) { el.remove(); } this.notify("info", `Ocultando: ${selectedValue}`); } else { if (el.dataset.originalDisplay) { el.style.display = el.dataset.originalDisplay; delete el.dataset.originalDisplay; } else { el.style.display = ''; } el.style.visibility = ''; this.notify("info", `Mostrando: ${selectedValue}`); } }); } catch (e) { this.notify("error", `Error al ${hide ? 'ocultar' : 'mostrar'} el elemento ${selectedValue}: ${e.message}`); } } #setupHideShowMenusObserver() { if (this.#hideShowMenusObserver) { this.#hideShowMenusObserver.disconnect(); } this.#hideShowMenusObserver = new MutationObserver(() => { clearTimeout(this.#hideShowMenusObserver._timer); this.#hideShowMenusObserver._timer = setTimeout(() => { this.#populateHideShowSelector(); }, 500); }); this.#hideShowMenusObserver.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); } } })("QBit"); // --- END NEW MODULE: MagicTools --- // ... (Tu código existente continúa con los demás módulos) ... (function GhostCanvas() { const QBit = globalThis[arguments[0]]; const Await = QBit.findGlobal("Await"); QBit.Styles.addRule( ".ghostimage { position:fixed; top:50%; left:50%; opacity:.6; box-shadow: 0 0 1px 1px cornflowerblue inset; }" ); function getBoundingClientRect(htmlElement) { let { top, right, bottom, left, width, height, x, y } = htmlElement.getBoundingClientRect(); top = Number(top).toFixed(); right = Number(right).toFixed(); bottom = Number(bottom).toFixed(); left = Number(left).toFixed(); width = Number(width).toFixed(); height = Number(height).toFixed(); x = Number(x).toFixed(); y = Number(y).toFixed(); return { top, right, bottom, left, width, height, x, y }; } function makeDragable(draggableElement, update) { var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; draggableElement.onmousedown = dragMouseDown; function dragMouseDown(e) { e = e || window.event; e.preventDefault(); // get the mouse cursor position at startup: pos3 = e.clientX; pos4 = e.clientY; document.onmouseup = closeDragElement; // call a function whenever the cursor moves: document.onmousemove = elementDrag; } function elementDrag(e) { e = e || window.event; e.preventDefault(); // calculate the new cursor position: pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; // set the element's new position: draggableElement.style.top = Number(draggableElement.offsetTop - pos2).toFixed() + "px"; draggableElement.style.left = Number(draggableElement.offsetLeft - pos1).toFixed() + "px"; update(); } function closeDragElement() { /* stop moving when mouse button is released:*/ document.onmouseup = null; document.onmousemove = null; } } const radios = []; class GhostCanvas extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); static isFavorite = true; GameCanvas; DrawCanvas; DrawCanvasContext; DrawCanvasRect; loadedImages; drawingManager; constructor() { super("GhostCanvas", '<i class="fas fa-images"></i>'); this.GameCanvas = document.body.querySelector("canvas#canvas"); this.DrawCanvas = document.createElement("canvas"); this.DrawCanvasRect = {}; this.loadedImages = []; this.DrawCanvasContext = this.DrawCanvas.getContext("2d"); this.drawingManager = new TaskManager(this); this.#onStartup(); this.resetAllSettings(); } #onStartup() { this.#loadInterface(); this.DrawCanvas.width = this.GameCanvas.width; this.DrawCanvas.height = this.GameCanvas.height; this.DrawCanvas.style = "position:fixed; opacity:.6; box-shadow: 0 0 1px 1px firebrick inset; pointer-events: none;"; const onResize = new Await(this.alignDrawCanvas.bind(this), 500); window.addEventListener("resize", (event) => { onResize.call(); }); this.htmlElements.input.addEventListener("change", (event) => { if (this.htmlElements.input.checked) this.alignDrawCanvas(); }); } #loadInterface() { this.#row1(); this.#row2(); this.#row3(); this.#row4(); this.#row5(); } #row1() { const row = domMake.Row(); { const resetSettingsButton = domMake.Button("Reset"); const showCanvasInput = domMake.Tree("input", { type: "checkbox", title: "Toggle Canvas", class: "icon" }); const clearCanvasButton = domMake.Button("Clear"); resetSettingsButton.title = "Reset Settings"; clearCanvasButton.title = "Clear GameCanvas"; resetSettingsButton.addEventListener("click", (event) => { this.resetAllSettings(); }); showCanvasInput.addEventListener("change", () => { this.DrawCanvas.style.display = showCanvasInput.checked ? "block" : "none"; }); clearCanvasButton.addEventListener("click", (event) => { let data = ["drawcmd", 0, [0.5, 0.5, 0.5, 0.5, !0, -2000, "#FFFFFF", -1, !1]]; this.findGlobal("BotClientInterface").siblings[0].bot.send(`${42}${JSON.stringify(data)}`); }); document.body.appendChild(this.DrawCanvas); row.appendAll(resetSettingsButton, showCanvasInput, clearCanvasButton); } this.htmlElements.section.appendChild(row); } #row2() { const row = domMake.Row(); { const loadPixelDataButton = domMake.Button("Load"); const pixelsLeftToDraw = domMake.Tree("input", { type: "text", readonly: true, style: "text-align: center;", value: "0", }); const clearPixelListButton = domMake.Button("Clear"); this.htmlElements.pixelsLeftToDraw = pixelsLeftToDraw; loadPixelDataButton.title = "Load Pixels to draw"; clearPixelListButton.title = "Clear Pixels to draw"; loadPixelDataButton.addEventListener("click", (event) => { this.saveCanvas(); }); clearPixelListButton.addEventListener("click", (event) => { this.setPixelList([]); }); row.appendAll(loadPixelDataButton, pixelsLeftToDraw, clearPixelListButton); } this.htmlElements.section.appendChild(row); } #row3() { const row = domMake.Row(); { const startDrawingButton = domMake.Button("Start"); const stopDrawingButton = domMake.Button("Stop"); startDrawingButton.addEventListener("click", (event) => { this.drawingManager.startDrawing(); }); stopDrawingButton.addEventListener("click", (event) => { this.drawingManager.stopDrawing(); }); row.appendAll(startDrawingButton, stopDrawingButton); } this.htmlElements.section.appendChild(row); } #row4() { const row = domMake.Row(); { const brushSizeInput = domMake.Tree("input", { type: "number", min: 2, value: 2, max: 200, step: 1 }); const singleColorModeInput = domMake.Tree("input", { type: "checkbox", class: "icon" }); const brushColorInput = domMake.Tree("input", { type: "text", value: "blue" }); brushSizeInput.addEventListener("change", (event) => { this.drawingManager.brushSize = Number(brushSizeInput.value); }); singleColorModeInput.addEventListener("change", (event) => { this.drawingManager.singleColor = Boolean(singleColorModeInput.checked); }); brushColorInput.addEventListener("change", (event) => { this.drawingManager.brushColor = brushColorInput; }); row.appendAll(brushSizeInput, singleColorModeInput, brushColorInput); } this.htmlElements.section.appendChild(row); } #row5() { const row = domMake.IconList(); { const id = generate.uuidv4(); const chooseGhostlyImageInput = domMake.Tree("input", { type: "file", id: id, hidden: true }); const chooseGhostlyImageLabel = domMake.Tree("label", { for: id, class: "icon", title: "Add Image" }, [ domMake.Tree("i", { class: "fas fa-plus" }), ]); const localThis = this; function onChange() { if (!this.files || !this.files[0]) return; const myFileReader = new FileReader(); myFileReader.addEventListener("load", (event) => { const base64 = event.target.result.replace("image/gif", "image/png"); localThis.createGhostImage(base64, row); }); myFileReader.readAsDataURL(this.files[0]); } chooseGhostlyImageInput.addEventListener("change", onChange); row.appendAll(chooseGhostlyImageLabel, chooseGhostlyImageInput); } { const resetImageSelectionLabel = domMake.Tree("div", { class: "icon", title: "Unselect" }, [ domMake.Tree("i", { class: "fas fa-chevron-left" }), ]); resetImageSelectionLabel.addEventListener("click", () => { document.body.querySelectorAll('input[name="ghostimage"]').forEach((node) => { node.checked = false; }); }); row.appendChild(resetImageSelectionLabel); } this.htmlElements.section.appendChild(row); } createGhostImage(imageSource, row) { this.alignDrawCanvas(); const image = this.loadExtension(GhostImage, (reference) => { this.loadedImages.push(reference); }); row.appendChild(image.htmlElements.label); image.setImageSource(imageSource); } clearCanvas() { this.DrawCanvasContext.clearRect(0, 0, this.DrawCanvas.width, this.DrawCanvas.height); } saveCanvas() { this.getAllPixels(); } resetAllSettings() { this.clearCanvas(); this.loadedImages.forEach((image, index) => { setTimeout(() => { image.reduceToAtoms(); }, 10 * index); }); this.drawingManager.stopDrawing(); this.setPixelList([]); this.alignDrawCanvas(true); } alignDrawCanvas() { if (arguments[0]) { this.DrawCanvas.width = this.GameCanvas.width; this.DrawCanvas.height = this.GameCanvas.height; } const GameCanvasRect = getBoundingClientRect(this.GameCanvas); this.DrawCanvas.style.top = `${GameCanvasRect.top}px`; this.DrawCanvas.style.left = `${GameCanvasRect.left}px`; this.DrawCanvas.style.width = `${GameCanvasRect.width}px`; this.DrawCanvas.style.height = `${GameCanvasRect.height}px`; const DrawCanvasRect = getBoundingClientRect(this.DrawCanvas); if (DrawCanvasRect.width <= 0 || DrawCanvasRect.height <= 0) return Object.assign(this.DrawCanvasRect, DrawCanvasRect); // DrawCanvasRect.alignModifierX = Number(this.DrawCanvas.width / DrawCanvasRect.width).toFixed(2); // DrawCanvasRect.alignModifierY = Number(this.DrawCanvas.height / DrawCanvasRect.height).toFixed(2); DrawCanvasRect.drawModifierX = 100 / DrawCanvasRect.width; DrawCanvasRect.drawModifierY = 100 / DrawCanvasRect.height; Object.assign(this.DrawCanvasRect, DrawCanvasRect); } getAllPixels() { const image = this.DrawCanvasContext.getImageData( 0, 0, this.DrawCanvasContext.canvas.width, this.DrawCanvasContext.canvas.height ); const pixels = []; for (let index = 0; index < image.data.length; index += 4) { // const x = (index * 0.25) % image.width; // const y = Math.floor((index * 0.25) / image.width); const x = (index * 0.25) % image.width; const y = Math.floor((index * 0.25) / image.width); const r = image.data[index + 0]; const g = image.data[index + 1]; const b = image.data[index + 2]; const a = image.data[index + 3]; // const color = rgbaArrayToHex([r, g, b, a]); const color = [r, g, b, a]; pixels.push({ x1: x, y1: y, x2: x, y2: y, color }); } this.setPixelList(pixels); } getNoneTransparentPixels() { this.getAllPixels(); const newPixelArray = this.drawingManager.pixelList.filter((pixel) => { return pixel.color !== "#000000"; // return /^#0[0-8]0[0-8]0[0-8]$/g.test(pixel.color); }); this.setPixelList(newPixelArray); } setPixelList(pixelArray) { this.drawingManager.pixelList = pixelArray; this.htmlElements.pixelsLeftToDraw.value = pixelArray.length; } } class GhostImage extends QBit { image; rect; constructor() { super("GhostImage", '<i class="fas fa-image-polaroid"></i>'); this.#onStartup(); } #onStartup() { this.#loadInterface(); this.image = domMake.Tree("img", { class: "ghostimage" }); this.image.addEventListener("mousedown", (event) => { this.htmlElements.label.click(); }); this.htmlElements.input.type = "radio"; this.htmlElements.input.name = "ghostimage"; radios.push(this.htmlElements.input); this.htmlElements.input.addEventListener("change", (event) => { radios.forEach(function (radio) { document.body.querySelector(`label[for="${radio.id}"]`).classList.remove("active"); }); this.htmlElements.label.classList.add("active"); }); document.body.appendChild(this.image); makeDragable(this.image, this.updatePosition.bind(this)); this.updatePosition(); } #loadInterface() { this.#row1(); this.#row2(); } #row1() { const row = domMake.Row(); { const paintCanvasButton = domMake.Button("Place"); paintCanvasButton.addEventListener("click", (event) => { this.drawImage(); }); row.appendAll(paintCanvasButton); } { const enableButton = domMake.Button("Delete"); enableButton.addEventListener("click", (event) => { this.reduceToAtoms(); }); row.appendChild(enableButton); this.htmlElements.toggleStatusButton = enableButton; } this.htmlElements.section.appendChild(row); } #row2() { const row = domMake.Row(); { const scaleInput = domMake.Tree("input", { type: "number", title: "rotation", min: 0.1, max: 10, value: 1, step: 0.02, }); scaleInput.addEventListener("change", () => { this.image.style.scale = scaleInput.value; }); this.htmlElements.scaleInput = scaleInput; row.appendAll(scaleInput); } { const rotationInput = domMake.Tree("input", { type: "number", title: "rotation", value: 0, step: 1 }); rotationInput.addEventListener("change", () => { this.image.style.rotate = `${rotationInput.value}deg`; }); this.htmlElements.rotationInput = rotationInput; row.appendChild(rotationInput); } this.htmlElements.section.appendChild(row); } drawImage() { this.updatePosition(); const ctx = this.parent.DrawCanvasContext; const offsetTop = Number(this.rect.top) - Number(this.parent.DrawCanvasRect.top); const offsetLeft = Number(this.rect.left) - Number(this.parent.DrawCanvasRect.left); // const multiX = Number(this.parent.DrawCanvasRect.alignModifierX); // const multiY = Number(this.parent.DrawCanvasRect.alignModifierY); const angle = (Math.PI / 180) * Number(this.htmlElements.rotationInput.value); const scale = Number(this.htmlElements.scaleInput.value); const imageWidth = this.image.width * scale; const imageHeight = this.image.height * scale; const imgHalfWidth = imageWidth * 0.5; const imgHalfHeight = imageHeight * 0.5; ctx.save(); ctx.translate(offsetLeft + imgHalfWidth, offsetTop + imgHalfHeight); ctx.rotate(angle); ctx.translate(-imgHalfWidth, -imgHalfHeight); ctx.drawImage(this.image, 0, 0, imageWidth, imageHeight); ctx.restore(); } setImageSource(imageSource) { this.image.src = imageSource; this.setIcon(`<img src="${this.image.src}">`); } updatePosition() { this.rect = getBoundingClientRect(this.image); } reduceToAtoms() { this.image.remove(); const pos = radios.indexOf(this.htmlElements.input); if (~pos) radios.splice(pos, 1); let pos2 = this.parent.loadedImages.indexOf(this); if (~pos2) { this.parent.loadedImages.splice(pos2, 1); } this._EXP_destroy(!0); } } class TaskManager { isRunning; pixelList; parent; BotClientManager; singleColor; brushColor; brushSize; constructor(parent) { this.pixelList = []; this.singleColor = !1; this.brushColor = "blue"; this.brushSize = 2; this.parent = parent; } startDrawing() { this.BotClientManager = this.parent.findGlobal("BotClientManager")?.siblings[0]; this.isRunning = true; this.doTasks(); this.parent.notify("info", "Started"); } stopDrawing() { this.isRunning = false; } doTasks() { if (!this.BotClientManager || this.BotClientManager.children.length <= 0) this.stopDrawing(); if (!this.isRunning) return this.parent.notify("info", "Stopped"); this.BotClientManager.children.forEach((botClientInterface, index) => { this.parseAndSendPixel(botClientInterface, index); }); setTimeout(() => { this.doTasks(); }, 1); } parseAndSendPixel(botClientInterface, index) { if (this.pixelList.length <= 0) return this.stopDrawing(); if (!botClientInterface.bot || !botClientInterface.bot.getReadyState()) return; const task = index % 2 == 0 ? this.pixelList.shift() : this.pixelList.pop(); botClientInterface.bot.send(this.convertTasks(task)); this.parent.htmlElements.pixelsLeftToDraw.value = this.pixelList.length; } convertTasks(pixel) { const playerid = -1; const lastx = pixel.x1 * this.parent.DrawCanvasRect.drawModifierX; const lasty = pixel.y1 * this.parent.DrawCanvasRect.drawModifierY; const x = pixel.x2 * this.parent.DrawCanvasRect.drawModifierX; const y = pixel.y2 * this.parent.DrawCanvasRect.drawModifierY; const isactive = !0; const size = pixel.size ?? this.brushSize; const pxColor = pixel.color; const color = this.singleColor ? this.brushColor : `rgba(${pxColor[0]},${pxColor[1]},${pxColor[2]},${parseFloat(pxColor[3] * 0.390625).toFixed(2)})`; const ispixel = !1; let data = [ "drawcmd", 0, [lastx * 0.01, lasty * 0.01, x * 0.01, y * 0.01, isactive, -size, color, playerid, ispixel], ]; return `${42}${JSON.stringify(data)}`; } } })("QBit"); (function GhostCanvasAlgorithms() { const QBit = globalThis[arguments[0]]; function sortByColor(pixel1, pixel2) { const color1 = rgbaArrayToHex(pixel1.color); const color2 = rgbaArrayToHex(pixel2.color); if (color1 < color2) { return -1; } if (color1 > color2) { return 1; } return 0; } function intToHex(number) { return number.toString(16).padStart(2, "0"); } function rgbaArrayToHex(rgbaArray) { const r = intToHex(rgbaArray[0]); const g = intToHex(rgbaArray[1]); const b = intToHex(rgbaArray[2]); const a = intToHex(rgbaArray[3]); return "#" + r + g + b + a; } function areSameColor(colorArray1, colorArray2, allowedDifference = 8) { var red = colorArray1[0] - colorArray2[0]; var green = colorArray1[1] - colorArray2[1]; var blue = colorArray1[2] - colorArray2[2]; if (red < 0) red = red * -1; if (green < 0) green = green * -1; if (blue < 0) blue = blue * -1; if (blue > allowedDifference || green > allowedDifference || red > allowedDifference) return false; return true; } class GhostCanvasMinify extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "GhostCanvas"); constructor() { super("Minify", '<i class="fas fa-compress-arrows-alt"></i>'); this.minOpacity = 20; this.maxFuzzyness = 32; this.#onStartup(); } #onStartup() { this.#loadInterface(); } #loadInterface() { this.#row1(); this.#row2(); this.#row3(); this.#row4(); } #row1() { const row = domMake.Row(); { const fuzzynessInput = domMake.Tree("input", { type: "number", title: "Fuzzyness", step: 1, min: 0, max: 255, value: 1, }); const opacityInput = domMake.Tree("input", { type: "number", title: "Opacity", step: 1, min: 0, max: 255, value: 0, }); fuzzynessInput.addEventListener("change", () => { this.maxFuzzyness = Number(fuzzynessInput.value); }); opacityInput.addEventListener("change", () => { this.minOpacity = Number(opacityInput.value); }); row.appendAll(fuzzynessInput, opacityInput); } this.htmlElements.section.appendChild(row); } #row2() { const row = domMake.Row(); { const minifyPixelsArrayButton = domMake.Button("Minify"); minifyPixelsArrayButton.addEventListener("click", (event) => { this.minifyPixelsArray(); }); row.appendAll(minifyPixelsArrayButton); } this.htmlElements.section.appendChild(row); } #row3() {} #row4() {} minifyPixelsArray() { const pixelArray = this.parent.drawingManager.pixelList; const newPixelArray = []; let currentPixel = pixelArray[0]; let lastPixel = currentPixel; let currentLine = currentPixel; for (let index = 0; index < pixelArray.length; index++) { currentPixel = pixelArray[index]; if (lastPixel.color[3] < 10 && currentPixel.color[3] >= 10) { // From Transparent To Solid currentLine = currentPixel; } else if (lastPixel.color[3] >= 10 && currentPixel.color[3] < 10) { // From Solid To Transparent currentLine.x2 = lastPixel.x2; newPixelArray.push(currentLine); currentLine = currentPixel; } else if (currentPixel.color[3] >= 10 && lastPixel.color[3] >= 10) { // From Solid To Solid if ( currentLine.y1 !== currentPixel.y1 || lastPixel.x2 !== currentPixel.x1 - 1 || !areSameColor(lastPixel.color, currentPixel.color, this.maxFuzzyness) ) { currentLine.x2 = lastPixel.x2; newPixelArray.push(currentLine); currentLine = currentPixel; } } else { // From Transparent To Transparent } lastPixel = currentPixel; } // if (currentLine.color[3] >= 10) newPixelArray.push(currentLine); this.parent.setPixelList(newPixelArray); } minifyPixelsArray_alt() { const pixelArray = this.parent.drawingManager.pixelList; const newPixelArray = []; var lastPixel = pixelArray[0]; var currentLine = lastPixel; const stepsize = this.parent.stepsize ?? 1; for (let i = 0; i < pixelArray.length; i += stepsize) { const currentPixel = pixelArray[i]; if (currentPixel.y1 !== currentLine.y1 || currentPixel.color !== lastPixel.color) { currentLine.x2 = lastPixel.x2; if (!/^#[0-9a-fA-F]{6}[0-4]{2}$/.test(lastPixel.color)) newPixelArray.push(currentLine); currentLine = currentPixel; } lastPixel = currentPixel; } newPixelArray.push(currentLine); this.parent.setPixelList(newPixelArray); } } class GhostCanvasSort extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "GhostCanvas"); constructor() { super("Sort", '<i class="fas fa-sort-numeric-down"></i>'); this.#onStartup(); } #onStartup() { this.#loadInterface(); } #loadInterface() { this.#row1(); this.#row2(); this.#row3(); this.#row4(); } #row1() { const row = domMake.Row(); { const sortPixelsArrayButton = domMake.Button("Sort"); sortPixelsArrayButton.addEventListener("click", (event) => { this.sortPixelsArray(); }); row.appendAll(sortPixelsArrayButton); } this.htmlElements.section.appendChild(row); } #row2() {} #row3() {} #row4() {} sortPixelsArray() { const pixelArray = this.parent.drawingManager.pixelList; const newPixelArray = [...pixelArray].sort(sortByColor); this.parent.setPixelList(newPixelArray); } } })("QBit"); (function BotClientInterface() { const QBit = globalThis[arguments[0]]; const BotClient = QBit.findGlobal("BotClient"); let botcount = 0; const radios = []; function getMasterId() { return document.querySelector(".playerlist-name-self")?.parentElement.dataset.playerid || 0; } function parseAvatarURL(arr = []) { return `https://drawaria.online/avatar/cache/${arr.length > 0 ? arr.join(".") : "default"}.jpg`; } class BotClientManager extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); constructor() { super(`BotClientManager`, '<i class="fas fa-robot"></i>'); this.#onStartup(); } #onStartup() { this.#loadInterface(); } #loadInterface() { this.#row1(); } #row1() { const row = domMake.IconList(); { const id = generate.uuidv4(); const createBotClientInterfaceInput = domMake.Tree("input", { type: "button", id: id, hidden: true }); const createBotClientInterfaceLabel = domMake.Tree("label", { for: id, class: "icon", title: "Add Image" }, [ domMake.Tree("i", { class: "fas fa-plus" }), ]); createBotClientInterfaceInput.addEventListener("click", () => { this.createBotClientInterface(); }); row.appendAll(createBotClientInterfaceLabel); row.appendAll(createBotClientInterfaceInput); } { const id = generate.uuidv4(); const removeBotClientInterfaceInput = domMake.Tree("input", { type: "button", id: id, hidden: true }); const removeBotClientInterfaceLabel = domMake.Tree("label", { for: id, class: "icon", title: "Add Image" }, [ domMake.Tree("i", { class: "fas fa-minus" }), ]); removeBotClientInterfaceInput.addEventListener("click", () => { this.deleteBotClientInterface(); }); row.appendAll(removeBotClientInterfaceLabel); row.appendAll(removeBotClientInterfaceInput); } this.htmlElements.header.before(row); } createBotClientInterface() { const instance = this.loadExtension(BotClientInterface); instance.htmlElements.input.type = "radio"; instance.htmlElements.input.name = "botClient"; radios.push(instance.htmlElements.input); instance.htmlElements.input.addEventListener("change", (event) => { radios.forEach(function (radio) { document.body.querySelector(`label[for="${radio.id}"]`).classList.remove("active"); }); instance.htmlElements.label.classList.add("active"); }); return instance; } deleteBotClientInterface() { const input = document.body.querySelector(`input[name="botClient"]:checked`); const matches = this.children.filter((child) => { return child.htmlElements.input === input; }); if (matches.length <= 0) return; const instance = matches[0]; instance.bot.disconnect(); const labelPos = radios.indexOf(instance.htmlElements.input); if (~labelPos) radios.splice(labelPos, 1); instance._EXP_destroy(!0); } } class BotClientInterface extends QBit { static dummy1 = QBit.register(this); // static dummy2 = QBit.bind(this, 'CubeEngine'); // static dummy3 = QBit.bind(this, 'CubeEngine'); constructor() { super(`Bot${botcount}`, '<i class="fas fa-robot"></i>'); this.bot = new BotClient(); this.#onStartup(); } #onStartup() { this.#loadInterface(); this.setClientName(this.bot.name); this.setClientIcon(this.bot.avatar); } #loadInterface() { this.#row1(); } #row1() { const row = domMake.Row(); { let join_button = domMake.Button("Enter"); let leave_button = domMake.Button("Leave"); join_button.addEventListener("click", (event) => { this.bot.enterRoom(document.querySelector("#invurl").value); }); leave_button.addEventListener("click", (event) => { this.bot.disconnect(); }); row.appendAll(join_button, leave_button); } this.htmlElements.section.appendChild(row); } setClientName(name) { this.setName(name); this.bot.name = name; } setClientIcon(icon) { this.setIcon(`<img src="${parseAvatarURL(this.bot.avatar)}">`); this.bot.avatar = icon; } } })("QBit"); (function BotClientInteractions() { const QBit = globalThis[arguments[0]]; class BotPersonality extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "BotClientInterface"); constructor() { super("Personality", '<i class="fas fa-user-cog"></i>'); this.#onStartup(); } #onStartup() { this.#loadInterface(); } #loadInterface() { this.#row1(); this.#row2(); } #row1() { const row = domMake.Row(); { let botName = domMake.Tree("input", { type: "text", placeholder: "Your Bots name" }); let botNameAccept = domMake.Tree("button", { class: "icon" }, [domMake.Tree("i", { class: "fas fa-check" })]); botNameAccept.addEventListener("click", (event) => { this.parent.setClientName(botName.value); }); row.appendAll(botName, botNameAccept); } this.htmlElements.section.appendChild(row); } #row2() { let id = generate.uuidv4(); const row = domMake.Row(); { let botAvatarUpload = domMake.Tree("input", { type: "file", id: id, hidden: true }); let botAvatarAccept = domMake.Tree("label", { for: id, class: "btn btn-outline-secondary" }, [ "Upload BotAvatar", ]); const localThis = this; function onChange() { if (!this.files || !this.files[0]) return; let myFileReader = new FileReader(); myFileReader.addEventListener("load", (e) => { let a = e.target.result.replace("image/gif", "image/png"); fetch("https://drawaria.online/uploadavatarimage", { method: "POST", body: "imagedata=" + encodeURIComponent(a) + "&fromeditor=true", headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" }, }).then((res) => res.text().then((body) => { localThis.parent.setClientIcon(body.split(".")); }) ); }); myFileReader.readAsDataURL(this.files[0]); } botAvatarUpload.addEventListener("change", onChange); row.appendAll(botAvatarUpload, botAvatarAccept); } this.htmlElements.section.appendChild(row); } } class BotSozials extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "BotClientInterface"); constructor() { super("Socialize", '<i class="fas fa-comment-dots"></i>'); this.#onStartup(); } #onStartup() { this.#loadInterface(); } #loadInterface() { this.#row1(); this.#row2(); } #row1() { const row = domMake.Row(); { let messageClear_button = domMake.Button('<i class="fas fa-strikethrough"></i>'); let messageSend_button = domMake.Button('<i class="fas fa-paper-plane"></i>'); let message_input = domMake.Tree("input", { type: "text", placeholder: "message..." }); messageClear_button.classList.add("icon"); messageSend_button.classList.add("icon"); messageClear_button.addEventListener("click", (event) => { message_input.value = ""; }); messageSend_button.addEventListener("click", (event) => { this.parent.bot.emit("chatmsg", message_input.value); }); message_input.addEventListener("keypress", (event) => { if (event.keyCode != 13) return; this.parent.bot.emit("chatmsg", message_input.value); }); row.appendAll(messageClear_button, message_input, messageSend_button); } this.htmlElements.section.appendChild(row); } #row2() { const row = domMake.IconList(); // row.classList.add('nowrap'); { document .querySelectorAll("#gesturespickerselector .gesturespicker-container .gesturespicker-item") .forEach((node, index) => { let clone = node.cloneNode(true); clone.classList.add("icon"); clone.addEventListener("click", (event) => { this.parent.bot.emit("sendgesture", index); }); row.appendChild(clone); }); } this.htmlElements.section.appendChild(row); } } class BotTokenGiver extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "BotClientInterface"); constructor() { super("Tokken", '<i class="fas fa-thumbs-up"></i>'); this.#onStartup(); } #onStartup() { this.#loadInterface(); } #loadInterface() { this.#row1(); this.#row2(); } #row1() { const row = domMake.IconList(); // row.classList.add('nowrap'); { let listOfTokens = [ '<i class="fas fa-thumbs-up"></i>', '<i class="fas fa-heart"></i>', '<i class="fas fa-paint-brush"></i>', '<i class="fas fa-cocktail"></i>', '<i class="fas fa-hand-peace"></i>', '<i class="fas fa-feather-alt"></i>', '<i class="fas fa-trophy"></i>', '<i class="fas fa-mug-hot"></i>', '<i class="fas fa-gift"></i>', ]; listOfTokens.forEach((token, index) => { let tokenSend_button = domMake.Button(token); tokenSend_button.classList.add("icon"); tokenSend_button.addEventListener("click", () => { this.parent.bot.room.players.forEach((player) => { this.parent.bot.emit("settoken", player.id, index); }); }); row.appendChild(tokenSend_button); }); } this.htmlElements.section.appendChild(row); } #row2() { const row = domMake.Row(); { let toggleStatus_button = domMake.Button("Toggle Status"); toggleStatus_button.addEventListener("click", () => { this.parent.bot.attributes.status = !this.parent.bot.attributes.status; let status = this.parent.bot.attributes.status; toggleStatus_button.classList[status ? "add" : "remove"]("active"); this.parent.bot.emit("setstatusflag", 0, status); this.parent.bot.emit("setstatusflag", 1, status); this.parent.bot.emit("setstatusflag", 2, status); this.parent.bot.emit("setstatusflag", 3, status); this.parent.bot.emit("setstatusflag", 4, status); }); row.appendChild(toggleStatus_button); } this.htmlElements.section.appendChild(row); } } class BotCanvasAvatar extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "BotClientInterface"); constructor() { super("SpawnAvatar", '<i class="fas fa-user-circle"></i>'); this.#onStartup(); } #onStartup() { this.#loadInterface(); } #loadInterface() { this.#row1(); } #row1() { const row = domMake.Row(); { // Spawn && Move Avatar let avatarPosition = { x: 0, y: 0 }; let avatarSpawn_button = domMake.Button('<i class="fas fa-exchange-alt"></i>'); let avatarChange_button = domMake.Button('<i class="fas fa-retweet"></i>'); let avatarPositionX_button = domMake.Tree("input", { type: "number", value: 2, min: 2, max: 98, title: "Left", }); let avatarPositionY_button = domMake.Tree("input", { type: "number", value: 2, min: 2, max: 98, title: "Top", }); avatarSpawn_button.addEventListener("click", (event) => { this.parent.bot.emit("spawnavatar"); this.parent.bot.attributes.spawned = !this.parent.bot.attributes.spawned; }); avatarChange_button.addEventListener("click", (event) => { this.parent.bot.emit("setavatarprop"); this.parent.bot.attributes.rounded = !this.parent.bot.attributes.rounded; }); avatarPositionX_button.addEventListener("change", (event) => { avatarPosition.x = avatarPositionX_button.value; this.parent.bot.emit("moveavatar", avatarPosition.x, avatarPosition.y); }); avatarPositionY_button.addEventListener("change", (event) => { avatarPosition.y = avatarPositionY_button.value; this.parent.bot.emit("moveavatar", avatarPosition.x, avatarPosition.y); }); avatarSpawn_button.title = "Spawn Avatar"; avatarChange_button.title = "Toggle Round Avatar"; row.appendAll(avatarSpawn_button, avatarPositionX_button, avatarPositionY_button, avatarChange_button); } this.htmlElements.section.appendChild(row); } } function getMyId() { return document.querySelector(".playerlist-name-self")?.parentElement.dataset.playerid || 0; } function parseAvatarURL(arr = []) { return `https://drawaria.online/avatar/cache/${arr.length > 0 ? arr.join(".") : "default"}.jpg`; } })("QBit"); // --- NEW FUNCTIONALITIES START HERE --- // --- START NEW MODULE: PlayerKickTools (COMBINED MODULE) --- (function PlayerKickToolsModule() { const QBit = globalThis[arguments[0]]; QBit.Styles.addRules([ `#${QBit.identifier} .kick-tools-section { border: 1px solid var(--CE-color); border-radius: .25rem; padding: 5px; margin-bottom: 10px; background-color: var(--CE-bg_color); }`, `#${QBit.identifier} .kick-tools-section-title { font-weight: bold; margin-bottom: 5px; color: var(--dark-blue-title); text-align: center; }`, `#${QBit.identifier} .kick-tools-player-list { display: flex; flex-wrap: wrap; gap: 5px; max-height: 150px; /* Limit height to show scrollbar if many players */ overflow-y: auto; padding: 5px; border: 1px dashed var(--CE-color); border-radius: .25rem; }`, `#${QBit.identifier} .kick-tools-player-list .btn { flex: 0 0 auto; /* Prevent stretching */ margin: 0; /* Remove default margin from btn class */ padding: 3px 8px; /* Compact padding */ font-size: 0.8em; }` ]); class PlayerKickTools extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); #humanKickPlayerListContainer; #botKickPlayerListContainer; #cachedPlayers = []; // To store player IDs and names for both sections constructor() { super("Herramientas de Expulsión", '<i class="fas fa-gavel"></i>'); // Original icon this.#onStartup(); } #onStartup() { this.#loadInterface(); this.#setupObservers(); // Setup MutationObserver for player list } #loadInterface() { const container = domMake.Tree("div"); // --- Section: Voto para Expulsar (Tú) --- const humanKickSection = domMake.Tree("div", { class: "kick-tools-section" }); humanKickSection.appendChild(domMake.Tree("div", { class: "kick-tools-section-title" }, ["Voto para Expulsar (Tú)"])); this.#humanKickPlayerListContainer = domMake.IconList({ class: "kick-tools-player-list" }); // Use IconList for flex layout humanKickSection.appendChild(this.#humanKickPlayerListContainer); container.appendChild(humanKickSection); // --- Section: Expulsar con Bot --- const botKickSection = domMake.Tree("div", { class: "kick-tools-section" }); botKickSection.appendChild(domMake.Tree("div", { class: "kick-tools-section-title" }, ["Expulsar con Bot"])); this.#botKickPlayerListContainer = domMake.IconList({ class: "kick-tools-player-list" }); // Use IconList for flex layout botKickSection.appendChild(this.#botKickPlayerListContainer); container.appendChild(botKickSection); this.htmlElements.section.appendChild(container); } #setupObservers() { const playerListElement = document.getElementById("playerlist"); if (playerListElement) { const observer = new MutationObserver(() => this.#updatePlayerLists()); observer.observe(playerListElement, { childList: true, subtree: true }); this.#updatePlayerLists(); // Initial update } } #updatePlayerLists() { this.#humanKickPlayerListContainer.innerHTML = ''; // Clear existing buttons this.#botKickPlayerListContainer.innerHTML = ''; // Clear existing buttons this.#cachedPlayers = []; // Clear cached players const playerRows = document.querySelectorAll("#playerlist .playerlist-row"); const myPlayerIdElement = document.querySelector(".playerlist-name-self")?.parentElement; const myPlayerId = myPlayerIdElement ? myPlayerIdElement.dataset.playerid : null; if (playerRows.length <= 1) { // Only self or no other players const noPlayersMessage = domMake.Tree("span", {}, ["No hay otros jugadores para expulsar."]); this.#humanKickPlayerListContainer.appendChild(noPlayersMessage.cloneNode(true)); this.#botKickPlayerListContainer.appendChild(noPlayersMessage); return; } playerRows.forEach(playerRow => { const playerId = playerRow.dataset.playerid; const playerName = playerRow.querySelector(".playerlist-name a")?.textContent || `Jugador ${playerId}`; if (playerId === myPlayerId) { // Don't allow kicking self return; } this.#cachedPlayers.push({ id: parseInt(playerId), name: playerName }); }); this.#renderHumanKickButtons(); this.#renderBotKickButtons(); } #renderHumanKickButtons() { this.#humanKickPlayerListContainer.innerHTML = ''; // Clear before rendering if (this.#cachedPlayers.length === 0) { this.#humanKickPlayerListContainer.appendChild(domMake.Tree("span", {}, ["No hay jugadores."])); return; } this.#cachedPlayers.forEach(player => { const playerButton = domMake.Button(player.name); playerButton.title = `Votar para expulsar a ${player.name} (ID: ${player.id}).`; playerButton.addEventListener("click", () => this.#sendHumanVoteKick(player.id, player.name)); this.#humanKickPlayerListContainer.appendChild(playerButton); }); } #renderBotKickButtons() { this.#botKickPlayerListContainer.innerHTML = ''; // Clear before rendering if (this.#cachedPlayers.length === 0) { this.#botKickPlayerListContainer.appendChild(domMake.Tree("span", {}, ["No hay jugadores."])); return; } this.#cachedPlayers.forEach(player => { const playerButton = domMake.Button(player.name); playerButton.title = `Expulsar a ${player.name} (ID: ${player.id}) con el bot seleccionado.`; playerButton.addEventListener("click", () => this.#sendBotKick(player.id, player.name)); this.#botKickPlayerListContainer.appendChild(playerButton); }); } // --- Human Vote Kick Method --- #sendHumanVoteKick(targetPlayerId, targetPlayerName) { if (globalThis.sockets && globalThis.sockets.length > 0) { const data = _io.emits.sendvotekick(targetPlayerId); globalThis.sockets[0].send(data); this.notify("info", `Voto para expulsar enviado para ${targetPlayerName} (ID: ${targetPlayerId}).`); } else { this.notify("warning", "No hay conexión WebSocket activa para enviar el voto de expulsión."); } } // --- Bot Kick Methods --- #getBot() { const botManagerClass = this.findGlobal("BotClientManager"); if (!botManagerClass || !botManagerClass.siblings || botManagerClass.siblings.length === 0) { this.notify("warning", "No hay instancias activas de 'BotClientManager'. Por favor, crea uno desde 'CubeEngine'."); return null; } const botManagerInstance = botManagerClass.siblings[0]; const botClientInterfaces = botManagerInstance.children; let activeBotClientInterface = null; const selectedBotInput = document.querySelector('input[name="botClient"]:checked'); if (selectedBotInput) { activeBotClientInterface = botClientInterfaces.find(bci => bci.htmlElements.input === selectedBotInput); } if (!activeBotClientInterface && botClientInterfaces.length > 0) { activeBotClientInterface = botClientInterfaces[0]; this.notify("info", `No se seleccionó un bot. Usando el primer bot disponible: ${activeBotClientInterface.getName()}.`); } if (!activeBotClientInterface || !activeBotClientInterface.bot || !activeBotClientInterface.bot.getReadyState()) { this.notify("warning", `El bot "${activeBotClientInterface ? activeBotClientInterface.getName() : 'desconocido'}" no está conectado y listo para enviar comandos.`); return null; } return activeBotClientInterface.bot; } #sendBotKick(targetPlayerId, targetPlayerName) { const bot = this.#getBot(); if (!bot) return; // #getBot already handles notifications const data = _io.emits.sendvotekick(targetPlayerId); bot.send(data); this.notify("success", `Bot "${bot.name}" envió solicitud de expulsión para ${targetPlayerName}.`); } } })("QBit"); // --- END NEW MODULE: PlayerKickTools --- // --- START NEW MODULE: PlayerSocials (COMBINED MODULE) --- (function PlayerSocialsModule() { const QBit = globalThis[arguments[0]]; // Define token names here to be self-contained const TOKEN_NAMES = { 0: "Thumbs Up", 1: "Heart", 2: "Paint Brush", 3: "Cocktail", 4: "Peace Sign", 5: "Feather", 6: "Trophy", 7: "Mug", 8: "Gift" }; QBit.Styles.addRules([ `#${QBit.identifier} .player-socials-section { border: 1px solid var(--CE-color); border-radius: .25rem; padding: 5px; margin-bottom: 10px; background-color: var(--CE-bg_color); }`, `#${QBit.identifier} .player-socials-section-title { font-weight: bold; margin-bottom: 5px; color: var(--dark-blue-title); /* Use existing style var */ text-align: center; }`, `#${QBit.identifier} .player-socials-afk-toggle, #${QBit.identifier} .player-socials-status-flags .status-flag-button { background-color: var(--secondary); color: var(--dark); width: 100%; padding: 5px 10px; box-sizing: border-box; }`, `#${QBit.identifier} .player-socials-afk-toggle.active, #${QBit.identifier} .player-socials-status-flags .status-flag-button.active { background-color: var(--info); /* Consistent active color */ color: white; }`, `#${QBit.identifier} .player-socials-token-list .icon { width: 38px; /* Slightly larger icons */ height: 38px; min-width: 38px; min-height: 38px; font-size: 1.2em; /* Larger icon itself */ }` ]); class PlayerSocials extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); // Custom Chat #messageInput; // Toggle AFK #isAfkActive = false; #afkToggleButton; // Global Token Giver #playerList = []; // #tokenButtons will be local to render method // Set Status Flag #statusFlagsState = {}; // To keep track of active flags constructor() { super("Sociales del Jugador", '<i class="fas fa-comments-dollar"></i>'); // New icon this.#onStartup(); } #onStartup() { this.#loadInterface(); this.#setupObservers(); // Setup MutationObserver for player list } #loadInterface() { const container = domMake.Tree("div"); // --- Section: Custom Chat Message --- const chatSection = domMake.Tree("div", { class: "player-socials-section" }); chatSection.appendChild(domMake.Tree("div", { class: "player-socials-section-title" }, ["Enviar Mensaje"])); const chatRow = domMake.Row(); this.#messageInput = domMake.Tree("input", { type: "text", placeholder: "Tu mensaje..." }); const sendButton = domMake.Button('<i class="fas fa-paper-plane"></i>'); sendButton.classList.add("icon"); sendButton.addEventListener("click", () => { this.#sendMessage(this.#messageInput.value); this.#messageInput.value = ''; }); this.#messageInput.addEventListener("keypress", (event) => { if (event.keyCode === 13) { this.#sendMessage(this.#messageInput.value); this.#messageInput.value = ''; } }); chatRow.appendAll(this.#messageInput, sendButton); chatSection.appendChild(chatRow); container.appendChild(chatSection); // --- Section: Toggle AFK --- const afkSection = domMake.Tree("div", { class: "player-socials-section" }); afkSection.appendChild(domMake.Tree("div", { class: "player-socials-section-title" }, ["Estado AFK"])); const afkRow = domMake.Row(); this.#afkToggleButton = domMake.Button("Toggle AFK"); this.#afkToggleButton.classList.add("player-socials-afk-toggle"); this.#afkToggleButton.addEventListener("click", () => this.#toggleAfkStatus()); afkRow.appendChild(this.#afkToggleButton); afkSection.appendChild(afkRow); container.appendChild(afkSection); // --- Section: Global Token Giver --- const tokensSection = domMake.Tree("div", { class: "player-socials-section" }); tokensSection.appendChild(domMake.Tree("div", { class: "player-socials-section-title" }, ["Dar Emblemas"])); const tokensRow = domMake.IconList({ class: "player-socials-token-list" }); const tokens = [ { id: 0, icon: '<i class="fas fa-thumbs-up"></i>' }, { id: 1, icon: '<i class="fas fa-heart"></i>' }, { id: 2, icon: '<i class="fas fa-paint-brush"></i>' }, { id: 3, icon: '<i class="fas fa-cocktail"></i>' }, { id: 4, icon: '<i class="fas fa-hand-peace"></i>' }, { id: 5, icon: '<i class="fas fa-feather-alt"></i>' }, { id: 6, icon: '<i class="fas fa-trophy"></i>' }, { id: 7, icon: '<i class="fas fa-mug-hot"></i>' }, { id: 8, icon: '<i class="fas fa-gift"></i>' } ]; tokens.forEach(token => { const tokenButton = domMake.Button(token.icon); tokenButton.classList.add("icon"); tokenButton.title = `Dar Emblema: ${TOKEN_NAMES[token.id]}`; tokenButton.addEventListener("click", () => this.#giveToken(token.id)); tokensRow.appendChild(tokenButton); }); tokensSection.appendChild(tokensRow); container.appendChild(tokensSection); // --- Section: Set Status Flag --- const statusSection = domMake.Tree("div", { class: "player-socials-section" }); statusSection.appendChild(domMake.Tree("div", { class: "player-socials-section-title" }, ["Establecer Estado"])); const flags = [ { id: 0, name: "Música", icon: '<i class="fas fa-music"></i>' }, { id: 1, name: "AFK 1", icon: '<i class="fas fa-bed"></i>' }, { id: 2, name: "AFK 2", icon: '<i class="fas fa-couch"></i>' }, { id: 3, name: "Inventario Abierto", icon: '<i class="fas fa-box-open"></i>' }, { id: 4, name: "Lista Amigos Abierta", icon: '<i class="fas fa-user-friends"></i>' } ]; flags.forEach(flag => { const flagRow = domMake.Row(); const toggleButton = domMake.Button(flag.icon + " " + flag.name); toggleButton.classList.add("status-flag-button"); toggleButton.dataset.flagId = flag.id; toggleButton.dataset.isActive = "false"; // Initial state this.#statusFlagsState[flag.id] = false; // Initialize internal state toggleButton.addEventListener("click", () => this.#toggleStatusFlag(flag.id, toggleButton)); flagRow.appendChild(toggleButton); statusSection.appendChild(flagRow); }); container.appendChild(statusSection); this.htmlElements.section.appendChild(container); } #setupObservers() { // Observer for player list changes (for Token Giver) const playerListElement = document.getElementById("playerlist"); if (playerListElement) { const observer = new MutationObserver(() => this.#updatePlayerList()); observer.observe(playerListElement, { childList: true, subtree: true }); this.#updatePlayerList(); // Initial update } } // --- Custom Chat Message Methods --- #sendMessage(message) { if (!message.trim()) return; if (globalThis.sockets && globalThis.sockets.length > 0) { const data = _io.emits.chatmsg(message); globalThis.sockets[0].send(data); this.notify("info", `Mensaje enviado: "${message}"`); } else { this.notify("warning", "No hay conexión WebSocket activa."); } } // --- Toggle AFK Methods --- #toggleAfkStatus() { this.#isAfkActive = !this.#isAfkActive; if (globalThis.sockets && globalThis.sockets.length > 0) { const data = _io.emits.playerafk(); globalThis.sockets[0].send(data); this.#afkToggleButton.classList[this.#isAfkActive ? "add" : "remove"]("active"); this.#afkToggleButton.textContent = this.#isAfkActive ? "AFK Activo" : "AFK Inactivo"; this.notify("info", `Estado AFK cambiado a: ${this.#isAfkActive}`); } else { this.notify("warning", "No hay conexión WebSocket activa."); } } // --- Global Token Giver Methods --- #updatePlayerList() { this.#playerList = []; const playerRows = document.querySelectorAll("#playerlist .playerlist-row"); playerRows.forEach(playerRow => { const playerId = playerRow.dataset.playerid; const playerName = playerRow.querySelector(".playerlist-name a")?.textContent || `Jugador ${playerId}`; this.#playerList.push({ id: parseInt(playerId), name: playerName }); }); } #giveToken(tokenId) { if (globalThis.sockets && globalThis.sockets.length > 0) { if (this.#playerList.length > 0) { this.#playerList.forEach(player => { const data = _io.emits.settoken(player.id, tokenId); globalThis.sockets[0].send(data); this.notify("info", `Emblema '${TOKEN_NAMES[tokenId]}' enviado a ${player.name} (ID: ${player.id}).`); }); } else { this.notify("warning", "No se encontraron jugadores en la sala para dar emblemas."); } } else { this.notify("warning", "No hay conexión WebSocket activa."); } } // --- Set Status Flag Methods --- #toggleStatusFlag(flagId, buttonElement) { const newActiveState = !this.#statusFlagsState[flagId]; this.#statusFlagsState[flagId] = newActiveState; buttonElement.classList[newActiveState ? "add" : "remove"]("active"); buttonElement.dataset.isActive = newActiveState; // Update dataset if (globalThis.sockets && globalThis.sockets.length > 0) { const data = _io.emits.setstatusflag(flagId, newActiveState); globalThis.sockets[0].send(data); this.notify("info", `Estado '${buttonElement.textContent.trim()}' cambiado a: ${newActiveState}`); } else { this.notify("warning", "No hay conexión WebSocket activa."); } } } })("QBit"); // --- END NEW MODULE: PlayerSocials --- (function BotClientModifications() { const QBit = globalThis[arguments[0]]; // Re-declare parseServerUrl and parseRoomId if they are not globally accessible outside BotClient scope function parseServerUrl(any) { var prefix = String(any).length == 1 ? `sv${any}.` : ""; // Expects a single digit server ID return `wss://${prefix}drawaria.online/socket.io/?sid1=undefined&hostname=drawaria.online&EIO=3&transport=websocket`; } function parseRoomId(any) { // Extract the main room ID part, before any optional .server_id const match = String(any).match(/^([a-f0-9-]+)(?:\.\d+)?$/i); if (match && match[1]) { return match[1]; } // If it's a direct room ID like "12345", then that's the ID. // If it's a full URL, attempt to get last segment as ID. const urlMatch = String(any).match(/([a-f0-9.-]+?)$/gi); if (urlMatch && urlMatch[0]) { return urlMatch[0]; } return String(any); // Fallback to original if no specific ID extracted } // AÑADIDO: RE-DECLARACIÓN DE parseSocketIOEvent para su accesibilidad function parseSocketIOEvent(prefix_length, event_data) { try { return JSON.parse(event_data.slice(prefix_length)); } catch (error) { // Puedes añadir un console.error aquí para depuración si es necesario // console.error("Error parsing socket event data:", error, "Data:", event_data); return null; // Retorna null o un array vacío si falla el parseo } } // FIN AÑADIDO // Reference to original emits object (if it's not global) const emits = _io.emits; // Assuming _io is global as per original script // Redefine BotClient class to inject into QBit's context // This is necessary because we are modifying its methods for room joining. // Ensure the original BotClient is available for modification. const OriginalBotClient = QBit.findGlobal("BotClient"); // Get the original class reference if (OriginalBotClient) { // Override/extend methods of the original BotClient prototype // This is safer than re-declaring the whole class if other modules rely on its specific constructor // or static properties. // Store original methods to call them if needed or as a fallback const originalBotClientConnect = OriginalBotClient.prototype.connect; const originalBotClientEnterRoom = OriginalBotClient.prototype.enterRoom; const originalBotClientDisconnect = OriginalBotClient.prototype.disconnect; const originalBotClientReconnect = OriginalBotClient.prototype.reconnect; Object.assign(OriginalBotClient.prototype, { // New or modified internal method to handle socket opening and initial commands // Renamed to avoid conflicts and clearly separate concerns. _onSocketOpenHandler(event) { const localThis = this; clearInterval(localThis.interval_id); // Clear any old interval localThis.interval_id = setInterval(function () { if (!localThis.getReadyState()) { clearInterval(localThis.interval_id); localThis.notify("info", `Bot ${localThis.name} desconectado (ping fallido).`); return; } localThis.send(2); // Keep-alive ping }, localThis.interval_ms); // ONLY send startplay AFTER the socket is confirmed open if (localThis.room.id) { // Ensure there's a room ID set before attempting to start play localThis.send(emits.startplay(localThis.room, localThis.name, localThis.avatar)); localThis.notify("success", `Bot ${localThis.name} conectado y en sala ${localThis.room.id}.`); } else { localThis.notify("warning", `Bot ${localThis.name} conectado, pero sin ID de sala para iniciar juego.`); } }, _onSocketMessageHandler(message_event) { var prefix = String(message_event.data).match(/(^\d+)/gi)?.[0] || ""; var data = parseSocketIOEvent(prefix.length, message_event.data) || []; if (data && data.length === 1) { if (data[0].players) this.room.players = data[0].players; } if (data && data.length > 1) { var event = data.shift(); // Special handling for raw 'drawcmd' to dispatch a custom event if (event === "drawcmd") { this.customObservers.forEach((listener) => { if (listener.event === "bc_drawcommand") { // Dispatch as bc_drawcommand for DrawingReplay etc. if (listener.callback) listener.callback(data); } }); } // Dispatch any other event normally this.customObservers.forEach((listener) => { if (listener.event === event) if (listener.callback) listener.callback(data); }); } }, _onSocketCloseHandler(event) { clearInterval(this.interval_id); this.socket = null; // Ensure socket reference is cleared this.notify("info", `Bot ${this.name} socket cerrado. Código: ${event.code}, Razón: ${event.reason}`); // The global `sockets` array is managed by WebSocket.prototype.send override, no need to manually splice here. }, _onSocketErrorHandler(event) { this.notify("error", `Error de socket para bot ${this.name}.`); console.error(`WebSocket Error for ${this.name}:`, event); clearInterval(this.interval_id); this.socket = null; }, // Overriding connect method connect(serverUrlSegment = "") { if (this.getReadyState()) { // Already connected. Disconnect first to ensure a clean new connection. this.notify("info", `Bot ${this.name} ya está conectado. Desconectando para reconectar.`); this.disconnect(); } const fullServerUrl = parseServerUrl(serverUrlSegment); this.socket = new WebSocket(fullServerUrl); // Bind handlers to 'this' context of the BotClient instance this.socket.addEventListener("open", this._onSocketOpenHandler.bind(this)); this.socket.addEventListener("message", this._onSocketMessageHandler.bind(this)); this.socket.addEventListener("close", this._onSocketCloseHandler.bind(this)); this.socket.addEventListener("error", this._onSocketErrorHandler.bind(this)); this.notify("info", `Bot ${this.name} intentando conectar a: ${fullServerUrl}`); }, // Overriding enterRoom method enterRoom(fullRoomIdOrUrl) { this.room.id = parseRoomId(fullRoomIdOrUrl); // Gets "uuid" part let serverIdSegment = ""; const parts = String(fullRoomIdOrUrl).split('.'); // Check if the last part is a number and indicates a server ID if (parts.length > 1 && !isNaN(parseInt(parts[parts.length - 1]))) { serverIdSegment = parts[parts.length - 1]; } this.connect(serverIdSegment); // Connect using only the server ID part // The `startplay` command will be sent automatically once the socket opens (in _onSocketOpenHandler) }, // Overriding disconnect method disconnect() { if (!this.getReadyState()) { this.notify("info", `Bot ${this.name} ya está desconectado.`); return; } clearInterval(this.interval_id); // Clear ping interval this.send(41); // Explicitly send disconnect message this.socket.close(); // Close the WebSocket connection this.socket = null; // Clear the socket reference this.notify("info", `Bot ${this.name} se ha desconectado de la sala.`); }, // Overriding reconnect method - now primarily re-enters the current room reconnect() { if (this.room.id) { this.notify("info", `Bot ${this.name} intentando reconectar a la sala ${this.room.id}.`); this.enterRoom(this.room.id); // Re-use enterRoom logic } else { this.notify("warning", `Bot ${this.name} no tiene una sala establecida para reconectar.`); } } }); } else { console.error("BotClient class not found for modification. Join room functionality may not work."); } })("QBit"); // --- START UPDATED MODULE: AutodrawV2 (ACTUALIZADO con Modo Formas) --- (function AutodrawV2() { const QBit = globalThis[arguments[0]]; QBit.Styles.addRules([ `#${QBit.identifier} .autodraw-controls .cheat-row { display: flex; width: 100%; margin-bottom: 5px; }`, `#${QBit.identifier} .autodraw-controls .cheat-row > * { flex: 1; margin: 0 2px; text-align: center; }`, `#${QBit.identifier} .autodraw-controls input[type="number"], #${QBit.identifier} .autodraw-controls input[type="file"] { width: 100%; padding: 5px; box-sizing: border-box; border: 1px solid var(--CE-color); border-radius: .25rem; background-color: var(--CE-bg_color); color: var(--CE-color); }`, `#${QBit.identifier} .autodraw-controls .btn { width: 100%; padding: 5px; box-sizing: border-box; background-color: var(--secondary); /* Grey button for controls */ }`, `#${QBit.identifier} .autodraw-controls .btn.active { background-color: var(--success); /* Active state green */ }`, `#${QBit.identifier} .autodraw-controls .btn i { margin-right: 5px; }`, `#${QBit.identifier} .autodraw-controls .effect-toggle.active { background-color: var(--info); /* Blue for active effect */ color: white; }`, `#${QBit.identifier} .autodraw-controls .effect-toggle { background-color: var(--secondary); color: var(--dark); }`, `#${QBit.identifier} .autodraw-controls label { font-size: 0.85em; margin-bottom: 3px; display: block; }` ]); class AutodrawV2 extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); #previewCanvas; // This internal canvas is now crucial for image processing #originalCanvas; // The game's canvas (not used for image loading, but for dimensions) #imageData = null; // Raw pixel data from the loaded image #executionLine = []; // Array of drawing commands (lines/dots) #drawingActive = false; // Control drawing loop // Drawing Modes #currentDrawingMode = 'rainMode'; // Default mode #rainColumns = []; // For rain mode #spiralAngle = 0; // For spiral mode // UI Element References for settings #imageSizeInput; #brushSizeInput; #pixelSizeInput; #offsetXInput; #offsetYInput; #drawingSpeedInput; #modeButtons = {}; // To manage active state of mode buttons #imageFileInput; // Reference to the file input // Default Settings #defaultSettings = { imageSize: 4, brushSize: 32, pixelSize: 1, offsetX: 10, offsetY: 0, drawingSpeed: 10 }; constructor() { super("Autodraw", '<i class="fas fa-magic"></i>'); this.#onStartup(); } #onStartup() { this.#loadInterface(); this.#setupCanvas(); this.#setInitialSettings(); } #loadInterface() { const container = domMake.Tree("div", { class: "autodraw-controls" }); // Image Loader const imageLoadRow = domMake.Row(); this.#imageFileInput = domMake.Tree("input", { type: "file", id: "autodraw-image-input", title: "Cargar Imagen (PNG/JPG)" }); this.#imageFileInput.addEventListener("change", (e) => this.#readImage(e.target)); imageLoadRow.appendChild(this.#imageFileInput); container.appendChild(imageLoadRow); // Drawing Settings - Row 1 (Image Size, Brush Size, Pixel Size) const settingsRow1 = domMake.Row(); this.#imageSizeInput = domMake.Tree("input", { type: "number", min: "1", max: "20", value: this.#defaultSettings.imageSize, title: "Tamaño de Imagen (1=grande, 20=pequeña)" }); this.#brushSizeInput = domMake.Tree("input", { type: "number", min: "1", max: "100", value: this.#defaultSettings.brushSize, title: "Tamaño del Pincel" }); this.#pixelSizeInput = domMake.Tree("input", { type: "number", min: "1", max: "50", value: this.#defaultSettings.pixelSize, title: "Espacio entre Píxeles" }); settingsRow1.appendAll( domMake.Tree("div", {}, [domMake.Tree("label", {}, ["Tamaño Img:"]), this.#imageSizeInput]), domMake.Tree("div", {}, [domMake.Tree("label", {}, ["Tamaño Pincel:"]), this.#brushSizeInput]), domMake.Tree("div", {}, [domMake.Tree("label", {}, ["Espacio Px:"]), this.#pixelSizeInput]) ); container.appendChild(settingsRow1); // Drawing Settings - Row 2 (Offset X, Offset Y, Drawing Speed) const settingsRow2 = domMake.Row(); this.#offsetXInput = domMake.Tree("input", { type: "number", min: "-50", max: "150", value: this.#defaultSettings.offsetX, title: "Desplazamiento X (0-100)" }); this.#offsetYInput = domMake.Tree("input", { type: "number", min: "-50", max: "150", value: this.#defaultSettings.offsetY, title: "Desplazamiento Y (0-100)" }); this.#drawingSpeedInput = domMake.Tree("input", { type: "number", min: "1", max: "100", value: this.#defaultSettings.drawingSpeed, title: "Velocidad de Dibujo (ms/línea)" }); settingsRow2.appendAll( domMake.Tree("div", {}, [domMake.Tree("label", {}, ["Offset X:"]), this.#offsetXInput]), domMake.Tree("div", {}, [domMake.Tree("label", {}, ["Offset Y:"]), this.#offsetYInput]), domMake.Tree("div", {}, [domMake.Tree("label", {}, ["Vel. Dibujo (ms):"]), this.#drawingSpeedInput]) ); container.appendChild(settingsRow2); // Control Buttons (Start/Stop) const controlButtonsRow = domMake.Row(); const startButton = domMake.Button('<i class="fas fa-play-circle"></i> Iniciar Dibujo'); startButton.addEventListener("click", () => this.#startDrawing()); const stopButton = domMake.Button('<i class="fas fa-stop-circle"></i> Detener Dibujo'); stopButton.addEventListener("click", () => this.#stopDrawing()); controlButtonsRow.appendAll(startButton, stopButton); container.appendChild(controlButtonsRow); // Drawing Modes const modesRow = domMake.Row(); modesRow.style.marginTop = "10px"; modesRow.style.flexWrap = "wrap"; modesRow.style.gap = "5px"; const addModeButton = (label, modeKey, iconClass) => { const button = domMake.Button(`<i class="${iconClass}"></i> ${label}`); button.classList.add("effect-toggle"); button.addEventListener("click", () => this.#setDrawingMode(modeKey)); this.#modeButtons[modeKey] = button; modesRow.appendChild(button); }; addModeButton("Modo Lluvia", "rainMode", "fas fa-cloud-rain"); addModeButton("Onda", "waveDraw", "fas fa-wave-square"); addModeButton("Espiral", "spiralDraw", "fas fa-circle-notch"); addModeButton("Píxel Aleatorio", "randomPixelDraw", "fas fa-dice"); addModeButton("Modo Formas", "shapeDrawMode", "fas fa-bezier-curve"); // Nuevo botón para el modo formas container.appendChild(modesRow); this.htmlElements.section.appendChild(container); // AÑADIDO: Asegura que el contenedor principal esté añadido } #setInitialSettings() { this.#imageSizeInput.value = this.#defaultSettings.imageSize; this.#brushSizeInput.value = this.#defaultSettings.brushSize; this.#pixelSizeInput.value = this.#defaultSettings.pixelSize; this.#offsetXInput.value = this.#defaultSettings.offsetX; this.#offsetYInput.value = this.#defaultSettings.offsetY; this.#drawingSpeedInput.value = this.#defaultSettings.drawingSpeed; this.#setDrawingMode('rainMode'); // Activate rainMode by default } #setupCanvas() { this.#previewCanvas = document.createElement('canvas'); this.#originalCanvas = document.getElementById('canvas'); // The game's drawing canvas if (this.#originalCanvas) { this.#previewCanvas.width = this.#originalCanvas.width; this.#previewCanvas.height = this.#originalCanvas.height; } else { this.#previewCanvas.width = 1000; this.#previewCanvas.height = 1000; this.notify("warning", "Canvas del juego no encontrado. Usando dimensiones por defecto para el lienzo interno."); } } #readImage(fileInput) { if (!fileInput.files || !fileInput.files[0]) { this.notify("warning", "No se seleccionó ninguna imagen."); this.#imageData = null; // Clear any previous image data return; } const FR = new FileReader(); FR.addEventListener('load', (evt) => { const img = new Image(); img.addEventListener('load', () => { this.notify("info", "Imagen cargada. Procesando pixeles..."); const ctx = this.#previewCanvas.getContext('2d'); ctx.clearRect(0, 0, this.#previewCanvas.width, this.#previewCanvas.height); ctx.drawImage(img, 0, 0, this.#previewCanvas.width, this.#previewCanvas.height); this.#imageData = ctx.getImageData(0, 0, this.#previewCanvas.width, this.#previewCanvas.height); this.notify("success", "Imagen lista para dibujar."); if (this.#currentDrawingMode) { this.#prepareDrawingCommands(); } }); img.crossOrigin = 'anonymous'; img.src = evt.target.result; }); FR.readAsDataURL(fileInput.files[0]); } async #prepareDrawingCommands() { if (!this.#imageData) { this.notify("warning", "Carga una imagen primero."); return false; } this.#executionLine = []; const { width, height } = this.#imageData; const size = parseFloat(this.#imageSizeInput.value); const modifier = parseFloat(this.#pixelSizeInput.value); const thickness = parseFloat(this.#brushSizeInput.value); const offsetX = parseFloat(this.#offsetXInput.value); const offsetY = parseFloat(this.#offsetYInput.value); const toGameCoords = (x_pixel, y_pixel, originalImgWidth, originalImgHeight) => { const finalDrawWidth = 100 / size; const finalDrawHeight = 100 / size; const gameX = (x_pixel / originalImgWidth) * finalDrawWidth + offsetX; const gameY = (y_pixel / originalImgHeight) * finalDrawHeight + offsetY; return [gameX, gameY]; }; const getPixelColor = (x, y) => { const originalPxX = Math.floor(x * (this.#imageData.width / this.#previewCanvas.width)); const originalPxY = Math.floor(y * (this.#imageData.height / this.#previewCanvas.height)); if (originalPxX < 0 || originalPxX >= this.#imageData.width || originalPxY < 0 || originalPxY >= this.#imageData.height) return null; const index = (originalPxY * this.#imageData.width + originalPxX) * 4; const a = this.#imageData.data[index + 3]; if (a < 20) return null; const r = this.#imageData.data[index + 0]; const g = this.#imageData.data[index + 1]; const b = this.#imageData.data[index + 2]; return `rgb(${r},${g},${b})`; }; if (this.#currentDrawingMode === 'rainMode') { for (let x = 0; x < width; x += modifier) { let columnPixels = []; for (let y = 0; y < height; y += modifier) { const color = getPixelColor(x, y); if (color) { columnPixels.push({ x, y, color }); } } if (columnPixels.length > 0) { this.#rainColumns.push({ x, pixels: columnPixels }); } } this.#shuffleArray(this.#rainColumns); for (let col of this.#rainColumns) { let pixels = col.pixels; if (pixels.length === 0) continue; pixels.sort((a, b) => a.y - b.y); let startPixel = pixels[0]; let prevPixel = pixels[0]; for (let i = 1; i < pixels.length; i++) { let currentPixel = pixels[i]; if (currentPixel.y !== prevPixel.y + modifier || currentPixel.color !== prevPixel.color) { this.#executionLine.push({ pos1: toGameCoords(startPixel.x, startPixel.y, width, height), pos2: toGameCoords(prevPixel.x, prevPixel.y, width, height), color: startPixel.color, thickness }); startPixel = currentPixel; } prevPixel = currentPixel; } this.#executionLine.push({ pos1: toGameCoords(startPixel.x, startPixel.y, width, height), pos2: toGameCoords(prevPixel.x, prevPixel.y, width, height), color: startPixel.color, thickness }); } } else if (this.#currentDrawingMode === 'waveDraw') { const waveAmplitude = 15; const waveFrequency = 0.05; for (let y = 0; y < height; y += modifier) { let startPixel = null; let lastColor = null; for (let x = 0; x < width; x += modifier) { let currentX = x; let currentY = y + waveAmplitude * Math.sin(x * waveFrequency); const actualColor = getPixelColor(currentX, currentY); if (actualColor) { if (!startPixel) { startPixel = { x: currentX, y: currentY, color: actualColor }; lastColor = actualColor; } else if (actualColor !== lastColor) { this.#executionLine.push({ pos1: toGameCoords(startPixel.x, startPixel.y, width, height), pos2: toGameCoords(currentX, currentY, width, height), color: lastColor, thickness }); startPixel = { x: currentX, y: currentY, color: actualColor }; lastColor = actualColor; } } else if (startPixel) { this.#executionLine.push({ pos1: toGameCoords(startPixel.x, startPixel.y, width, height), pos2: toGameCoords(currentX, currentY, width, height), color: lastColor, thickness }); startPixel = null; lastColor = null; } } if (startPixel) { this.#executionLine.push({ pos1: toGameCoords(startPixel.x, startPixel.y, width, height), pos2: toGameCoords(width, y + waveAmplitude * Math.sin(width * waveFrequency), width, height), color: lastColor, thickness }); } } } else if (this.#currentDrawingMode === 'spiralDraw') { const centerX = width / 2; const centerY = height / 2; const maxRadius = Math.min(width, height) / 2; const density = 0.5; for (let r = 0; r < maxRadius; r += modifier * density) { const numPoints = Math.floor(2 * Math.PI * r / modifier); if (numPoints === 0) continue; let prevX = -1, prevY = -1; let startPoint = null; let lastColor = null; for (let i = 0; i < numPoints; i++) { const angle = (i / numPoints) * 2 * Math.PI; const spiralX = centerX + r * Math.cos(angle); const spiralY = centerY + r * Math.sin(angle); const color = getPixelColor(spiralX, spiralY); if (color) { if (startPoint === null) { startPoint = { x: spiralX, y: spiralY, color: color }; lastColor = color; } else if (color !== lastColor) { this.#executionLine.push({ pos1: toGameCoords(startPoint.x, startPoint.y, width, height), pos2: toGameCoords(prevX, prevY, width, height), color: lastColor, thickness }); startPoint = { x: spiralX, y: spiralY, color: color }; lastColor = color; } prevX = spiralX; prevY = spiralY; } else if (startPoint !== null) { this.#executionLine.push({ pos1: toGameCoords(startPoint.x, startPoint.y, width, height), pos2: toGameCoords(prevX, prevY, width, height), color: lastColor, thickness }); startPoint = null; } } if (startPoint) { this.#executionLine.push({ pos1: toGameCoords(startPoint.x, startPoint.y, width, height), pos2: toGameCoords(prevX, prevY, width, height), color: lastColor, thickness }); } } } else if (this.#currentDrawingMode === 'randomPixelDraw') { let allPixels = []; for (let y = 0; y < height; y += modifier) { for (let x = 0; x < width; x += modifier) { const color = getPixelColor(x, y); if (color) { allPixels.push({ x, y, color }); } } } this.#shuffleArray(allPixels); allPixels.forEach(p => { this.#executionLine.push({ pos1: toGameCoords(p.x, p.y, width, height), pos2: toGameCoords(p.x + modifier / 2, p.y + modifier / 2, width, height), color: p.color, thickness }); }); } else if (this.#currentDrawingMode === 'shapeDrawMode') { const shapeDetectorClass = this.findGlobal("ShapeDetector"); if (!shapeDetectorClass || !shapeDetectorClass.siblings || shapeDetectorClass.siblings.length === 0) { this.notify("error", "El módulo 'Detector de Formas' no está activo. Asegúrate de que está cargado."); return false; } const shapeDetectorInstance = shapeDetectorClass.siblings[0]; if (!shapeDetectorInstance || typeof shapeDetectorInstance.analyzeImageDataForShapes !== 'function') { this.notify("error", "El módulo 'Detector de Formas' no está listo o le falta el método 'analyzeImageDataForShapes'."); return false; } this.notify("info", "Analizando imagen para formas con Detector de Formas..."); const { drawingCommands: shapeCommands } = await shapeDetectorInstance.analyzeImageDataForShapes( this.#imageData, this.#imageData.width, this.#imageData.height, false ); if (shapeCommands.length === 0) { this.notify("warning", "No se detectaron formas significativas en la imagen para dibujar."); return false; } shapeCommands.forEach(cmd => { this.#executionLine.push({ pos1: [cmd.x1, cmd.y1], // Ya vienen en coords 0-100 del juego pos2: [cmd.x2, cmd.y2], color: this.htmlElements.colorInput?.value || "#000000", thickness: this.#brushSizeInput.value }); }); this.notify("success", `Modo Formas: Se prepararon ${this.#executionLine.length} líneas desde formas detectadas.`); } this.notify("info", `Comandos de dibujo preparados para el modo '${this.#currentDrawingMode}': ${this.#executionLine.length} líneas.`); return true; } #shuffleArray(array) { for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } } async #startDrawing() { const commandsPrepared = await this.#prepareDrawingCommands(); if (!commandsPrepared) { return; } const botManager = this.findGlobal("BotClientManager")?.siblings[0]; if (!botManager || botManager.children.length === 0) { this.notify("warning", "Necesitas al menos 1 bot activo en 'BotClientManager' para dibujar."); return; } const activeBots = botManager.children.filter(b => b.bot && b.bot.getReadyState()); if (activeBots.length === 0) { this.notify("warning", "Ningún bot está conectado y listo para dibujar. Conecta un bot primero."); return; } this.#drawingActive = true; this.notify("info", `Iniciando dibujo con ${activeBots.length} bots...`); const delayMs = parseInt(this.#drawingSpeedInput.value); let currentLineIndex = 0; while (this.#drawingActive && currentLineIndex < this.#executionLine.length) { const line = this.#executionLine[currentLineIndex]; const botIndex = currentLineIndex % activeBots.length; const botInterface = activeBots[botIndex]; if (botInterface && botInterface.bot && botInterface.bot.getReadyState()) { botInterface.bot.emit( "line", -1, line.pos1[0], line.pos1[1], line.pos2[0], line.pos2[1], true, line.thickness, line.color, false ); currentLineIndex++; } else { this.notify("warning", `Bot ${botInterface ? botInterface.getName() : 'desconocido'} no está listo. Saltando...`); currentLineIndex++; } await new Promise(resolve => setTimeout(resolve, delayMs)); } if (!this.#drawingActive) { this.notify("info", `Dibujo detenido manualmente. Completado ${currentLineIndex} de ${this.#executionLine.length} líneas.`); } else { this.notify("success", "Dibujo completado!"); } this.#drawingActive = false; } #stopDrawing() { this.#drawingActive = false; } #setDrawingMode(mode) { this.#currentDrawingMode = mode; for (const key in this.#modeButtons) { if (this.#modeButtons.hasOwnProperty(key)) { this.#modeButtons[key].classList.toggle("active", key === mode); } } this.notify("info", `Modo de dibujo cambiado a: '${mode}'`); if (this.#imageData && this.#imageData.data && this.#imageData.data.length > 0) { this.#prepareDrawingCommands(); } } } })("QBit"); // --- END UPDATED MODULE: AutodrawV2 --- (function IntelligentArtist() { const QBit = globalThis[arguments[0]]; // --- BASE DE DATOS DE BOCETOS DETALLADOS (50 PALABRAS) --- // (X, Y son porcentajes del 0 al 100) // Cada boceto está diseñado para ser reconocible y compuesto por múltiples trazos. // Helper para bocetos simples por defecto (genera círculos o cuadrados) function generateDefaultSimpleSketch(word) { const hash = word.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0); const isCircle = hash % 2 === 0; // Alternar entre círculo y cuadrado if (isCircle) { const centerX = 50, centerY = 50, radius = 15; const segments = 16; // Más segmentos para un círculo más suave const sketch = []; for (let i = 0; i < segments; i++) { const angle1 = (i / segments) * Math.PI * 2; const angle2 = ((i + 1) / segments) * Math.PI * 2; sketch.push({ x1: centerX + radius * Math.cos(angle1), y1: centerY + radius * Math.sin(angle1), x2: centerX + radius * Math.cos(angle2), y2: centerY + radius * Math.sin(angle2) }); } return sketch; } else { // Generar un cuadrado o un rectángulo simple const xOffset = 25 + (hash % 10); // Ligeramente variable const yOffset = 25 + (hash % 10); const size = 50 - (hash % 10); return [ { x1: xOffset, y1: yOffset, x2: xOffset + size, y2: yOffset }, { x1: xOffset + size, y1: yOffset, x2: xOffset + size, y2: yOffset + size }, { x1: xOffset + size, y1: yOffset + size, x2: xOffset, y2: yOffset + size }, { x1: xOffset, y1: yOffset + size, x2: xOffset, y2: yOffset } ]; } } const SKETCH_DATABASE = { // --- BOCETOS DETALLADOS MEJORADOS (Simulando curvas y texturas) --- "ARBOL": [ // Tronco (más orgánico y con textura) { x1: 45, y1: 80, x2: 43, y2: 60 }, { x1: 43, y1: 60, x2: 45, y2: 40 }, { x1: 55, y1: 80, x2: 57, y2: 60 }, { x1: 57, y1: 60, x2: 55, y2: 40 }, // Textura del tronco (Bark) { x1: 48, y1: 75, x2: 48, y2: 70 }, { x1: 51, y1: 65, x2: 51, y2: 60 }, { x1: 46, y1: 55, x2: 46, y2: 50 }, // Raíces más definidas { x1: 45, y1: 80, x2: 35, y2: 85 }, { x1: 55, y1: 80, x2: 65, y2: 85 }, // Follaje (Contorno más irregular y segmentado) { x1: 45, y1: 40, x2: 30, y2: 45 }, { x1: 30, y1: 45, x2: 25, y2: 35 }, { x1: 25, y1: 35, x2: 35, y2: 25 }, { x1: 35, y1: 25, x2: 40, y2: 20 }, { x1: 40, y1: 20, x2: 50, y2: 15 }, { x1: 50, y1: 15, x2: 60, y2: 20 }, { x1: 60, y1: 20, x2: 70, y2: 25 }, { x1: 70, y1: 25, x2: 75, y2: 35 }, { x1: 75, y1: 35, x2: 70, y2: 45 }, { x1: 70, y1: 45, x2: 55, y2: 40 }, // Detalles internos del follaje (Clumps of leaves) { x1: 45, y1: 35, x2: 50, y2: 30 }, { x1: 50, y1: 30, x2: 55, y2: 35 }, { x1: 35, y1: 40, x2: 30, y2: 35 }, { x1: 65, y1: 40, x2: 70, y2: 35 }, { x1: 50, y1: 25, x2: 45, y2: 22 }, { x1: 50, y1: 25, x2: 55, y2: 22 } ], "CASA": [ // Estructura principal { x1: 30, y1: 80, x2: 70, y2: 80 }, { x1: 30, y1: 80, x2: 30, y2: 50 }, { x1: 70, y1: 80, x2: 70, y2: 50 }, // Tejado (Con alero) { x1: 25, y1: 50, x2: 50, y2: 30 }, { x1: 50, y1: 30, x2: 75, y2: 50 }, { x1: 25, y1: 50, x2: 75, y2: 50 }, // Textura del tejado (Shingles) { x1: 28, y1: 45, x2: 72, y2: 45 }, { x1: 32, y1: 40, x2: 68, y2: 40 }, { x1: 38, y1: 35, x2: 62, y2: 35 }, // Chimenea con ladrillos { x1: 60, y1: 40, x2: 60, y2: 25 }, { x1: 65, y1: 40, x2: 65, y2: 25 }, { x1: 60, y1: 25, x2: 65, y2: 25 }, { x1: 60, y1: 30, x2: 65, y2: 30 }, { x1: 60, y1: 35, x2: 65, y2: 35 }, // Ladrillos // Puerta con pomo y marco { x1: 45, y1: 80, x2: 45, y2: 60 }, { x1: 55, y1: 80, x2: 55, y2: 60 }, { x1: 45, y1: 60, x2: 55, y2: 60 }, { type: "circle", x1: 53, y1: 70, radius: 1 }, // Pomo // Ventana con cruz y marco { x1: 32, y1: 55, x2: 42, y2: 55 }, { x1: 42, y1: 55, x2: 42, y2: 65 }, { x1: 42, y1: 65, x2: 32, y2: 65 }, { x1: 32, y1: 65, x2: 32, y2: 55 }, { x1: 37, y1: 55, x2: 37, y2: 65 }, { x1: 32, y1: 60, x2: 42, y2: 60 } // Cruz ventana ], "SOL": [ { type: "circle", x1: 50, y1: 50, radius: 15 }, // Círculo central // Rayos (más variados y con un ligero patrón) { x1: 50, y1: 35, x2: 50, y2: 25 }, { x1: 50, y1: 35, x2: 48, y2: 28 }, { x1: 50, y1: 35, x2: 52, y2: 28 }, // Arriba { x1: 60, y1: 40, x2: 68, y2: 32 }, { x1: 60, y1: 40, x2: 65, y2: 35 }, // Arriba-derecha { x1: 65, y1: 50, x2: 75, y2: 50 }, { x1: 65, y1: 50, x2: 70, y2: 48 }, // Derecha { x1: 60, y1: 60, x2: 68, y2: 68 }, { x1: 60, y1: 60, x2: 65, y2: 65 }, // Abajo-derecha { x1: 50, y1: 65, x2: 50, y2: 75 }, { x1: 50, y1: 65, x2: 48, y2: 72 }, { x1: 50, y1: 65, x2: 52, y2: 72 }, // Abajo { x1: 40, y1: 60, x2: 32, y2: 68 }, { x1: 40, y1: 60, x2: 35, y2: 65 }, // Abajo-izquierda { x1: 35, y1: 50, x2: 25, y2: 50 }, { x1: 35, y1: 50, x2: 30, y2: 48 }, // Izquierda { x1: 40, y1: 40, x2: 32, y2: 32 }, { x1: 40, y1: 40, x2: 35, y2: 35 }, // Arriba-izquierda // Ojos y Boca (con más expresión) { type: "circle", x1: 45, y1: 45, radius: 1.5 }, { type: "circle", x1: 55, y1: 45, radius: 1.5 }, // Ojos más grandes { x1: 45, y1: 55, x2: 47, y2: 58 }, { x1: 47, y1: 58, x2: 53, y2: 58 }, { x1: 53, y1: 58, x2: 55, y2: 55 } // Boca más curvada ], "FLOR": [ { x1: 50, y1: 80, x2: 50, y2: 55 }, // Tallo // Hojas (Más orgánicas y con venas) { x1: 50, y1: 70, x2: 40, y2: 65 }, { x1: 40, y1: 65, x2: 45, y2: 58 }, { x1: 42, y1: 66, x2: 48, y2: 62 }, // Vena hoja 1 { x1: 50, y1: 70, x2: 60, y2: 65 }, { x1: 60, y1: 65, x2: 55, y2: 58 }, { x1: 58, y1: 66, x2: 52, y2: 62 }, // Vena hoja 2 // Centro de la flor { type: "circle", x1: 50, y1: 50, radius: 5 }, // Círculo central para estambres { type: "circle", x1: 50, y1: 50, radius: 2 }, // Punto central del estambre // Pétalos (Más suaves y superpuestos, usando más segmentos) { x1: 50, y1: 45, x2: 40, y2: 38 }, { x1: 40, y1: 38, x2: 35, y2: 45 }, { x1: 35, y1: 45, x2: 38, y2: 55 }, { x1: 38, y1: 55, x2: 45, y2: 55 }, { x1: 45, y1: 55, x2: 50, y2: 45 }, // Pétalo superior-izquierda { x1: 50, y1: 45, x2: 60, y2: 38 }, { x1: 60, y1: 38, x2: 65, y2: 45 }, { x1: 65, y1: 45, x2: 62, y2: 55 }, { x1: 62, y1: 55, x2: 55, y2: 55 }, { x1: 55, y1: 55, x2: 50, y2: 45 }, // Pétalo superior-derecha { x1: 45, y1: 50, x2: 38, y2: 58 }, { x1: 38, y1: 58, x2: 40, y2: 65 }, { x1: 40, y1: 65, x2: 48, y2: 65 }, { x1: 48, y1: 65, x2: 50, y2: 55 }, { x1: 50, y1: 55, x2: 45, y2: 50 }, // Pétalo inferior-izquierda { x1: 55, y1: 50, x2: 62, y2: 58 }, { x1: 62, y1: 58, x2: 60, y2: 65 }, { x1: 60, y1: 65, x2: 52, y2: 65 }, { x1: 52, y1: 65, x2: 50, y2: 55 }, { x1: 50, y1: 55, x2: 55, y2: 50 } // Pétalo inferior-derecha ], "PERRO": [ // Cuerpo (Más curvo y orgánico) { x1: 40, y1: 60, x2: 50, y2: 55 }, { x1: 50, y1: 55, x2: 70, y2: 58 }, // Lomo { x1: 40, y1: 70, x2: 50, y2: 68 }, { x1: 50, y1: 68, x2: 70, y2: 65 }, // Vientre // Cabeza y Hocico { x1: 30, y1: 65, x2: 40, y2: 60 }, { x1: 40, y1: 60, x2: 35, y2: 50 }, // Cuello y cabeza { x1: 35, y1: 50, x2: 25, y2: 55 }, { x1: 25, y1: 55, x2: 30, y2: 65 }, // Hocico // Oreja { x1: 35, y1: 50, x2: 30, y2: 45 }, { x1: 30, y1: 45, x2: 40, y2: 50 }, // Ojo y Nariz { type: "circle", x1: 32, y1: 55, radius: 1 }, // Ojo { x1: 25, y1: 55, x2: 26, y2: 56 }, // Nariz (punto) // Patas (Con grosor y articulaciones) // Delantera { x1: 40, y1: 70, x2: 40, y2: 80 }, { x1: 43, y1: 70, x2: 43, y2: 80 }, // Trasera { x1: 70, y1: 65, x2: 65, y2: 75 }, { x1: 65, y1: 75, x2: 65, y2: 80 }, { x1: 73, y1: 65, x2: 68, y2: 80 }, // Cola (Curva) { x1: 70, y1: 58, x2: 75, y2: 50 }, { x1: 75, y1: 50, x2: 80, y2: 45 }, // Textura de pelaje (líneas cortas) { x1: 50, y1: 55, x2: 50, y2: 53 }, { x1: 60, y1: 56, x2: 60, y2: 54 }, { x1: 45, y1: 68, x2: 45, y2: 70 }, { x1: 55, y1: 68, x2: 55, y2: 70 } ], "GATO": [ // Cuerpo (sentado, más curvo) { x1: 40, y1: 80, x2: 60, y2: 80 }, // Base { x1: 40, y1: 80, x2: 35, y2: 60 }, { x1: 35, y1: 60, x2: 40, y2: 50 }, // Lado Izq { x1: 60, y1: 80, x2: 65, y2: 60 }, { x1: 65, y1: 60, x2: 60, y2: 50 }, // Lado Der // Cabeza (más redonda) { type: "circle", x1: 50, y1: 45, radius: 10 }, // Orejas puntiagudas detalladas { x1: 42, y1: 40, x2: 40, y2: 30 }, { x1: 40, y1: 30, x2: 48, y2: 38 }, // Oreja Izq { x1: 58, y1: 40, x2: 60, y2: 30 }, { x1: 60, y1: 30, x2: 52, y2: 38 }, // Oreja Der // Ojos y Nariz { type: "circle", x1: 45, y1: 45, radius: 1 }, { type: "circle", x1: 55, y1: 45, radius: 1 }, { x1: 50, y1: 48, x2: 50, y2: 50 }, // Nariz y boca (línea Y) { x1: 50, y1: 50, x2: 47, y2: 52 }, { x1: 50, y1: 50, x2: 53, y2: 52 }, // Bigotes (Whiskers) { x1: 45, y1: 50, x2: 30, y2: 48 }, { x1: 45, y1: 51, x2: 30, y2: 51 }, { x1: 55, y1: 50, x2: 70, y2: 48 }, { x1: 55, y1: 51, x2: 70, y2: 51 }, // Cola (enroscada alrededor del cuerpo) { x1: 65, y1: 75, x2: 75, y2: 75 }, { x1: 75, y1: 75, x2: 70, y2: 80 }, // Textura de pelaje (Hatching ligero) { x1: 50, y1: 60, x2: 50, y2: 58 }, { x1: 45, y1: 70, x2: 45, y2: 68 }, { x1: 55, y1: 70, x2: 55, y2: 68 } ], "MESA": [ // Superficie (con grosor y perspectiva mejorada) { x1: 25, y1: 40, x2: 75, y2: 40 }, // Borde frontal { x1: 25, y1: 40, x2: 20, y2: 35 }, // Lado izquierdo superior { x1: 75, y1: 40, x2: 80, y2: 35 }, // Lado derecho superior { x1: 20, y1: 35, x2: 80, y2: 35 }, // Borde trasero { x1: 25, y1: 45, x2: 75, y2: 45 }, // Borde frontal inferior (grosor) { x1: 20, y1: 40, x2: 20, y2: 35 }, // Lateral inferior izq { x1: 80, y1: 40, x2: 80, y2: 35 }, // Lateral inferior der // Patas delanteras (con grosor) { x1: 30, y1: 45, x2: 30, y2: 70 }, { x1: 32, y1: 45, x2: 32, y2: 70 }, { x1: 68, y1: 45, x2: 68, y2: 70 }, { x1: 70, y1: 45, x2: 70, y2: 70 }, // Patas traseras en perspectiva (con grosor) { x1: 20, y1: 35, x2: 20, y2: 60 }, { x1: 22, y1: 35, x2: 22, y2: 60 }, // Pata trasera izq { x1: 78, y1: 35, x2: 78, y2: 60 }, { x1: 80, y1: 35, x2: 80, y2: 60 }, // Pata trasera der // Líneas de conexión para perspectiva { x1: 30, y1: 70, x2: 20, y2: 60 }, // Diagonal inferior izq { x1: 70, y1: 70, x2: 80, y2: 60 } // Diagonal inferior der ], "SILLA": [ // Respaldo (con detalles de listones) { x1: 35, y1: 40, x2: 35, y2: 60 }, { x1: 65, y1: 40, x2: 65, y2: 60 }, // Postes verticales { x1: 35, y1: 40, x2: 65, y2: 40 }, // Borde superior { x1: 38, y1: 45, x2: 62, y2: 45 }, { x1: 38, y1: 50, x2: 62, y2: 50 }, // Listones horizontales { x1: 38, y1: 55, x2: 62, y2: 55 }, // Asiento (con perspectiva) { x1: 35, y1: 60, x2: 65, y2: 60 }, // Borde frontal { x1: 35, y1: 60, x2: 32, y2: 63 }, // Lado izquierdo { x1: 65, y1: 60, x2: 68, y2: 63 }, // Lado derecho { x1: 32, y1: 63, x2: 68, y2: 63 }, // Borde trasero // Patas delanteras (con grosor) { x1: 38, y1: 60, x2: 38, y2: 75 }, { x1: 40, y1: 60, x2: 40, y2: 75 }, { x1: 60, y1: 60, x2: 60, y2: 75 }, { x1: 62, y1: 60, x2: 62, y2: 75 }, // Patas traseras en perspectiva (con grosor y curvatura) { x1: 32, y1: 63, x2: 32, y2: 75 }, { x1: 30, y1: 63, x2: 30, y2: 75 }, // Pata trasera izq { x1: 68, y1: 63, x2: 68, y2: 75 }, { x1: 70, y1: 63, x2: 70, y2: 75 }, // Pata trasera der // Conexiones de las patas al asiento { x1: 38, y1: 60, x2: 32, y2: 63 }, { x1: 62, y1: 60, x2: 68, y2: 63 } ], "LIBRO": [ // Lado izquierdo (con grosor de página) { x1: 30, y1: 40, x2: 50, y2: 30 }, { x1: 50, y1: 30, x2: 50, y2: 70 }, { x1: 50, y1: 70, x2: 30, y2: 60 }, { x1: 30, y1: 60, x2: 30, y2: 40 }, { x1: 32, y1: 42, x2: 48, y2: 32 }, { x1: 48, y1: 32, x2: 48, y2: 68 }, // Página izquierda interior // Lado derecho (con grosor de página) { x1: 50, y1: 30, x2: 70, y2: 40 }, { x1: 70, y1: 40, x2: 70, y2: 60 }, { x1: 70, y1: 60, x2: 50, y2: 70 }, { x1: 52, y1: 32, x2: 68, y2: 42 }, { x1: 68, y1: 42, x2: 68, y2: 68 }, // Página derecha interior // Lomo del libro { x1: 50, y1: 30, x2: 50, y2: 70 }, // Páginas (líneas que simulan texto) { x1: 35, y1: 45, x2: 45, y2: 40 }, { x1: 35, y1: 50, x2: 45, y2: 45 }, { x1: 55, y1: 45, x2: 65, y2: 50 }, { x1: 55, y1: 50, x2: 65, y2: 55 } ], "TAZA": [ // Cuerpo de la taza (más curvo y con base más ancha) { x1: 40, y1: 40, x2: 60, y2: 40 }, // Borde superior { x1: 38, y1: 40, x2: 35, y2: 65 }, // Lado izquierdo { x1: 62, y1: 40, x2: 65, y2: 65 }, // Lado derecho { x1: 35, y1: 65, x2: 65, y2: 65 }, // Base (curvada) // Asa (con grosor y curvatura) { x1: 65, y1: 45, x2: 70, y2: 45 }, { x1: 70, y1: 45, x2: 70, y2: 55 }, { x1: 70, y1: 55, x2: 65, y2: 55 }, { x1: 65, y1: 47, x2: 68, y2: 47 }, { x1: 68, y1: 47, x2: 68, y2: 53 }, { x1: 68, y1: 53, x2: 65, y2: 53 }, // Grosor del asa // Interior de la taza (para dar profundidad) { x1: 42, y1: 42, x2: 58, y2: 42 } ], "VENTANA": [ // Marco exterior (con grosor) { x1: 28, y1: 28, x2: 72, y2: 28 }, { x1: 72, y1: 28, x2: 72, y2: 72 }, { x1: 72, y1: 72, x2: 28, y2: 72 }, { x1: 28, y1: 72, x2: 28, y2: 28 }, { x1: 30, y1: 30, x2: 70, y2: 30 }, { x1: 70, y1: 30, x2: 70, y2: 70 }, { x1: 70, y1: 70, x2: 30, y2: 70 }, { x1: 30, y1: 70, x2: 30, y2: 30 }, // Marco interior (con grosor y divisiones) { x1: 35, y1: 35, x2: 65, y2: 35 }, { x1: 65, y1: 35, x2: 65, y2: 65 }, { x1: 65, y1: 65, x2: 35, y2: 65 }, { x1: 35, y1: 65, x2: 35, y2: 35 }, { x1: 40, y1: 35, x2: 40, y2: 65 }, { x1: 50, y1: 35, x2: 50, y2: 65 }, { x1: 60, y1: 35, x2: 60, y2: 65 }, { x1: 35, y1: 40, x2: 65, y2: 40 }, { x1: 35, y1: 50, x2: 65, y2: 50 }, { x1: 35, y1: 60, x2: 65, y2: 60 } ], "PUERTA": [ // Puerta principal (con paneles y marco) { x1: 40, y1: 30, x2: 60, y2: 30 }, { x1: 60, y1: 30, x2: 60, y2: 70 }, { x1: 60, y1: 70, x2: 40, y2: 70 }, { x1: 40, y1: 70, x2: 40, y2: 30 }, // Marco exterior (con grosor) { x1: 38, y1: 28, x2: 38, y2: 72 }, { x1: 62, y1: 28, x2: 62, y2: 72 }, { x1: 38, y1: 28, x2: 62, y2: 28 }, { x1: 38, y1: 72, x2: 62, y2: 72 }, // Paneles de la puerta { x1: 43, y1: 35, x2: 57, y2: 35 }, { x1: 57, y1: 35, x2: 57, y2: 45 }, { x1: 57, y1: 45, x2: 43, y2: 45 }, { x1: 43, y1: 45, x2: 43, y2: 35 }, { x1: 43, y1: 50, x2: 57, y2: 50 }, { x1: 57, y1: 50, x2: 57, y2: 65 }, { x1: 57, y1: 65, x2: 43, y2: 65 }, { x1: 43, y1: 65, x2: 43, y2: 50 }, // Pomo (círculo) { type: "circle", x1: 55, y1: 50, radius: 2 } ], "CAJA": [ // Frente de la caja (con grosor de las líneas) { x1: 30, y1: 40, x2: 70, y2: 40 }, { x1: 70, y1: 40, x2: 70, y2: 60 }, { x1: 70, y1: 60, x2: 30, y2: 60 }, { x1: 30, y1: 60, x2: 30, y2: 40 }, // Tapa (con grosor y ligera elevación) { x1: 30, y1: 40, x2: 35, y2: 35 }, { x1: 70, y1: 40, x2: 75, y2: 35 }, { x1: 35, y1: 35, x2: 75, y2: 35 }, // Borde frontal de la tapa { x1: 32, y1: 38, x2: 72, y2: 38 }, // Borde trasero de la tapa (grosor) { x1: 35, y1: 35, x2: 32, y2: 38 }, { x1: 75, y1: 35, x2: 72, y2: 38 }, // Lado derecho (con grosor) { x1: 70, y1: 40, x2: 75, y2: 35 }, { x1: 70, y1: 60, x2: 75, y2: 55 }, // Perspectiva { x1: 75, y1: 35, x2: 75, y2: 55 }, // Sombreado interior (opcional, para dar profundidad) { x1: 32, y1: 60, x2: 32, y2: 58 }, { x1: 32, y1: 58, x2: 68, y2: 58 }, { x1: 68, y1: 58, x2: 68, y2: 60 } ], "BOTELLA": [ // Cuello (más largo y definido) { x1: 48, y1: 20, x2: 52, y2: 20 }, // Boca { x1: 48, y1: 20, x2: 45, y2: 30 }, { x1: 52, y1: 20, x2: 55, y2: 30 }, // Parte superior del cuello { x1: 45, y1: 30, x2: 45, y2: 35 }, { x1: 55, y1: 30, x2: 55, y2: 35 }, // Parte inferior del cuello // Hombros de la botella (curva) { x1: 45, y1: 35, x2: 40, y2: 45 }, { x1: 55, y1: 35, x2: 60, y2: 45 }, // Cuerpo (más curvado) { x1: 40, y1: 45, x2: 38, y2: 70 }, { x1: 60, y1: 45, x2: 62, y2: 70 }, // Base (con grosor y curvatura) { x1: 38, y1: 70, x2: 62, y2: 70 }, { x1: 39, y1: 72, x2: 61, y2: 72 } // Sombra de la base ], "CAMISA": [ // Cuello { x1: 45, y1: 35, x2: 55, y2: 35 }, { x1: 45, y1: 35, x2: 40, y2: 40 }, // Lado izquierdo del cuello { x1: 55, y1: 35, x2: 60, y2: 40 }, // Lado derecho del cuello // Hombros y mangas (con pliegues) { x1: 40, y1: 40, x2: 25, y2: 50 }, { x1: 25, y1: 50, x2: 20, y2: 60 }, // Manga izquierda { x1: 60, y1: 40, x2: 75, y2: 50 }, { x1: 75, y1: 50, x2: 80, y2: 60 }, // Manga derecha // Cuerpo (con pliegues y dobladillo) { x1: 20, y1: 60, x2: 20, y2: 75 }, { x1: 80, y1: 60, x2: 80, y2: 75 }, { x1: 20, y1: 75, x2: 80, y2: 75 }, // Línea central y botones { x1: 50, y1: 40, x2: 50, y2: 75 }, { type: "circle", x1: 50, y1: 48, radius: 1 }, { type: "circle", x1: 50, y1: 55, radius: 1 }, { type: "circle", x1: 50, y1: 62, radius: 1 }, { type: "circle", x1: 50, y1: 69, radius: 1 }, // Pliegues sutiles en la tela { x1: 30, y1: 65, x2: 35, y2: 68 }, { x1: 70, y1: 65, x2: 65, y2: 68 } ], "ZAPATO": [ // Suela (más gruesa y con relieve) { x1: 20, y1: 70, x2: 80, y2: 70 }, { x1: 20, y1: 70, x2: 25, y2: 75 }, { x1: 80, y1: 70, x2: 75, y2: 75 }, { x1: 25, y1: 75, x2: 75, y2: 75 }, { x1: 30, y1: 72, x2: 35, y2: 72 }, { x1: 40, y1: 72, x2: 45, y2: 72 }, // Relieve suela // Cuerpo del zapato (más orgánico y con punta) { x1: 20, y1: 70, x2: 25, y2: 60 }, { x1: 25, y1: 60, x2: 35, y2: 55 }, { x1: 35, y1: 55, x2: 65, y2: 55 }, { x1: 65, y1: 55, x2: 75, y2: 60 }, { x1: 75, y1: 60, x2: 80, y2: 70 }, // Lengüeta del zapato { x1: 40, y1: 55, x2: 45, y2: 50 }, { x1: 45, y1: 50, x2: 55, y2: 50 }, { x1: 55, y1: 50, x2: 60, y2: 55 }, // Cordones (con más detalle) { x1: 42, y1: 56, x2: 58, y2: 56 }, // Línea base { x1: 40, y1: 57, x2: 45, y2: 53 }, { x1: 45, y1: 53, x2: 50, y2: 57 }, // Cruce 1 { x1: 50, y1: 57, x2: 55, y2: 53 }, { x1: 55, y1: 53, x2: 60, y2: 57 } // Cruce 2 ], "AGUA": [ // Superficie del agua (ondas y reflejos) { x1: 20, y1: 70, x2: 80, y2: 70 }, { x1: 25, y1: 65, x2: 75, y2: 65 }, { x1: 30, y1: 60, x2: 70, y2: 60 }, // Ondas concéntricas { x1: 35, y1: 55, x2: 65, y2: 55 }, // Gotas de agua (con brillo) { type: "circle", x1: 50, y1: 40, radius: 5 }, { type: "circle", x1: 48, y1: 38, radius: 1 }, // Gota 1 con brillo { type: "circle", x1: 40, y1: 50, radius: 4 }, { type: "circle", x1: 39, y1: 49, radius: 0.8 }, // Gota 2 con brillo { type: "circle", x1: 60, y1: 55, radius: 3 }, { type: "circle", x1: 59, y1: 54, radius: 0.6 } // Gota 3 con brillo ], "FUEGO": [ // Llama (más dinámica y con múltiples picos) { x1: 50, y1: 75, x2: 40, y2: 65 }, { x1: 40, y1: 65, x2: 45, y2: 50 }, { x1: 45, y1: 50, x2: 50, y2: 40 }, { x1: 50, y1: 40, x2: 55, y2: 50 }, { x1: 55, y1: 50, x2: 60, y2: 65 }, { x1: 60, y1: 65, x2: 50, y2: 75 }, { x1: 48, y1: 55, x2: 45, y2: 48 }, { x1: 45, y1: 48, x2: 50, y2: 42 }, // Llama interior 1 { x1: 52, y1: 55, x2: 55, y2: 48 }, { x1: 55, y1: 48, x2: 50, y2: 42 }, // Llama interior 2 // Base del fuego (simulando troncos o brasas) { x1: 40, y1: 75, x2: 60, y2: 75 }, { x1: 42, y1: 77, x2: 50, y2: 77 }, { x1: 50, y1: 77, x2: 58, y2: 77 } // Líneas internas de la base ], "VIENTO": [ // Líneas de viento con mayor dinamismo y remolinos definidos { x1: 15, y1: 50, x2: 85, y2: 50 }, { x1: 20, y1: 55, x2: 90, y2: 55 }, { x1: 25, y1: 60, x2: 95, y2: 60 }, // Remolino 1 (espiral simulada con segmentos) { x1: 30, y1: 45, x2: 35, y2: 40 }, { x1: 35, y1: 40, x2: 40, y2: 45 }, { x1: 40, y1: 45, x2: 35, y2: 50 }, { x1: 35, y1: 50, x2: 30, y2: 45 }, // Remolino 2 { x1: 70, y1: 45, x2: 75, y2: 40 }, { x1: 75, y1: 40, x2: 80, y2: 45 }, { x1: 80, y1: 45, x2: 75, y2: 50 }, { x1: 75, y1: 50, x2: 70, y2: 45 }, // Pequeñas ráfagas adicionales { x1: 45, y1: 40, x2: 40, y2: 38 }, { x1: 55, y1: 40, x2: 60, y2: 38 } ], "TIERRA": [ // Horizonte montañoso (más picos y valles) { x1: 20, y1: 70, x2: 30, y2: 65 }, { x1: 30, y1: 65, x2: 35, y2: 70 }, { x1: 35, y1: 70, x2: 45, y2: 60 }, { x1: 45, y1: 60, x2: 50, y2: 65 }, { x1: 50, y1: 65, x2: 60, y2: 55 }, { x1: 60, y1: 55, x2: 65, y2: 60 }, { x1: 65, y1: 60, x2: 75, y2: 65 }, { x1: 75, y1: 65, x2: 80, y2: 70 }, // Terreno frontal (rocas y textura) { x1: 25, y1: 75, x2: 35, y2: 78 }, { x1: 40, y1: 75, x2: 45, y2: 78 }, { x1: 55, y1: 75, x2: 60, y2: 78 }, // Árboles distantes (más pequeños y esquemáticos) { x1: 30, y1: 62, x2: 30, y2: 58 }, { x1: 28, y1: 58, x2: 32, y2: 58 }, // Árbol 1 { x1: 70, y1: 62, x2: 70, y2: 58 }, { x1: 68, y1: 58, x2: 72, y2: 58 } // Árbol 2 ], "NUBE": [ // Forma de nube (más orgánica e irregular) { x1: 30, y1: 40, x2: 40, y2: 35 }, { x1: 40, y1: 35, x2: 48, y2: 32 }, { x1: 48, y1: 32, x2: 55, y2: 35 }, { x1: 55, y1: 35, x2: 62, y2: 32 }, { x1: 62, y1: 32, x2: 70, y2: 35 }, { x1: 70, y1: 35, x2: 75, y2: 40 }, { x1: 75, y1: 40, x2: 70, y2: 50 }, { x1: 70, y1: 50, x2: 65, y2: 55 }, { x1: 65, y1: 55, x2: 55, y2: 58 }, { x1: 55, y1: 58, x2: 45, y2: 55 }, { x1: 45, y1: 55, x2: 35, y2: 50 }, { x1: 35, y1: 50, x2: 30, y2: 40 }, // Sombreado interno (líneas curvas paralelas para volumen) { x1: 35, y1: 45, x2: 45, y2: 43 }, { x1: 45, y1: 43, x2: 55, y2: 45 }, { x1: 40, y1: 50, x2: 50, y2: 52 }, { x1: 50, y1: 52, x2: 60, y2: 50 } ], "LUNA": [ // Luna creciente (con un corte más suave) { type: "circle", x1: 50, y1: 50, radius: 20 }, // Círculo exterior { type: "circle", x1: 42, y1: 50, radius: 18 }, // Círculo interior para la forma creciente // Cráteres (varios tamaños y posiciones) { type: "circle", x1: 55, y1: 40, radius: 3 }, { type: "circle", x1: 60, y1: 50, radius: 2.5 }, { type: "circle", x1: 52, y1: 58, radius: 4 }, { type: "circle", x1: 48, y1: 45, radius: 1.5 } ], "ESTRELLA": [ // Estrella de 5 puntas (proporciones más precisas) { x1: 50, y1: 20, x2: 55, y2: 40 }, { x1: 55, y1: 40, x2: 75, y2: 45 }, { x1: 75, y1: 45, x2: 60, y2: 60 }, { x1: 60, y1: 60, x2: 65, y2: 80 }, { x1: 65, y1: 80, x2: 50, y2: 70 }, { x1: 50, y1: 70, x2: 35, y2: 80 }, { x1: 35, y1: 80, x2: 40, y2: 60 }, { x1: 40, y1: 60, x2: 25, y2: 45 }, { x1: 25, y1: 45, x2: 45, y2: 40 }, { x1: 45, y1: 40, x2: 50, y2: 20 }, // Brillo (múltiples líneas finas saliendo del centro) { x1: 50, y1: 50, x2: 50, y2: 30 }, { x1: 50, y1: 50, x2: 65, y2: 40 }, { x1: 50, y1: 50, x2: 60, y2: 65 }, { x1: 50, y1: 50, x2: 35, y2: 65 }, { x1: 50, y1: 50, x2: 35, y2: 40 } ], "AUTO": [ // Chasis inferior y parachoques { x1: 15, y1: 75, x2: 85, y2: 75 }, { x1: 15, y1: 75, x2: 10, y2: 70 }, { x1: 85, y1: 75, x2: 90, y2: 70 }, // Carrocería (más aerodinámica) { x1: 10, y1: 70, x2: 10, y2: 60 }, // Frente { x1: 10, y1: 60, x2: 25, y2: 50 }, // Capó { x1: 25, y1: 50, x2: 35, y2: 40 }, // Parabrisas { x1: 35, y1: 40, x2: 65, y2: 40 }, // Techo { x1: 65, y1: 40, x2: 80, y2: 50 }, // Ventana trasera { x1: 80, y1: 50, x2: 90, y2: 60 }, // Maletero { x1: 90, y1: 60, x2: 90, y2: 70 }, // Trasera // Ruedas (Con detalle interior/Hubcap) { type: "circle", x1: 25, y1: 75, radius: 8 }, { type: "circle", x1: 25, y1: 75, radius: 3 }, // Hubcap { type: "circle", x1: 75, y1: 75, radius: 8 }, { type: "circle", x1: 75, y1: 75, radius: 3 }, // Hubcap // Ventanas y Puertas { x1: 35, y1: 40, x2: 35, y2: 55 }, { x1: 65, y1: 40, x2: 65, y2: 55 }, { x1: 50, y1: 40, x2: 50, y2: 75 }, // División de puertas // Detalles (Manija y Faro) { x1: 40, y1: 60, x2: 45, y2: 60 }, // Manija delantera { x1: 10, y1: 65, x2: 15, y2: 65 }, { x1: 15, y1: 65, x2: 15, y2: 70 } // Faro ], "BICICLETA": [ // Ruedas (con rayos y válvula) { type: "circle", x1: 35, y1: 65, radius: 10 }, { type: "circle", x1: 35, y1: 65, radius: 1 }, // Válvula { x1: 35, y1: 55, x2: 35, y2: 75 }, { x1: 25, y1: 65, x2: 45, y2: 65 }, // Rayos { type: "circle", x1: 65, y1: 65, radius: 10 }, { type: "circle", x1: 65, y1: 65, radius: 1 }, // Válvula { x1: 65, y1: 55, x2: 65, y2: 75 }, { x1: 55, y1: 65, x2: 75, y2: 65 }, // Rayos // Cuadro principal (más triángulos y estructura) { x1: 35, y1: 65, x2: 50, y2: 55 }, { x1: 50, y1: 55, x2: 65, y2: 65 }, { x1: 50, y1: 55, x2: 40, y2: 45 }, { x1: 40, y1: 45, x2: 35, y2: 65 }, // Tubo diagonal { x1: 50, y1: 55, x2: 50, y2: 50 }, // Tubo de asiento // Asiento (con forma de sillín) { x1: 48, y1: 48, x2: 52, y2: 48 }, { x1: 48, y1: 48, x2: 45, y2: 45 }, { x1: 52, y1: 48, x2: 55, y2: 45 }, // Sillín // Manillar (con puños) { x1: 40, y1: 45, x2: 45, y2: 40 }, { x1: 45, y1: 40, x2: 55, y2: 40 }, // Manillar { x1: 40, y1: 45, x2: 38, y2: 45 }, { x1: 55, y1: 40, x2: 57, y2: 40 }, // Puños // Pedal y cadena (detalles) { x1: 50, y1: 65, x2: 52, y2: 68 }, { x1: 52, y1: 68, x2: 55, y2: 65 }, // Pedal { x1: 50, y1: 65, x2: 60, y2: 65 } // Cadena (línea) ], "BARCO": [ // Casco (más curvado y con profundidad) { x1: 20, y1: 70, x2: 80, y2: 70 }, // Base { x1: 20, y1: 70, x2: 25, y2: 60 }, { x1: 80, y1: 70, x2: 75, y2: 60 }, // Lados del casco { x1: 25, y1: 60, x2: 75, y2: 60 }, { x1: 28, y1: 62, x2: 72, y2: 62 }, // Borde interior del casco // Mástil (más grueso y con uniones) { x1: 50, y1: 60, x2: 50, y2: 30 }, { x1: 48, y1: 60, x2: 48, y2: 30 }, // Grosor del mástil { x1: 48, y1: 30, x2: 52, y2: 30 }, // Parte superior del mástil // Vela (más voluminosa y con líneas de viento) { x1: 50, y1: 30, x2: 65, y2: 40 }, { x1: 65, y1: 40, x2: 50, y2: 55 }, // Contorno de la vela { x1: 52, y1: 35, x2: 60, y2: 40 }, { x1: 52, y1: 40, x2: 60, y2: 45 }, // Líneas de viento en la vela // Bandera (ondeando) { x1: 50, y1: 30, x2: 55, y2: 25 }, { x1: 55, y1: 25, x2: 50, y2: 20 }, { x1: 50, y1: 20, x2: 50, y2: 30 } ], "PESCADO": [ // Cuerpo (más hidrodinámico y con escamas) { x1: 30, y1: 50, x2: 70, y2: 45 }, { x1: 70, y1: 45, x2: 75, y2: 50 }, // Lomo { x1: 30, y1: 50, x2: 70, y2: 55 }, { x1: 70, y1: 55, x2: 75, y2: 50 }, // Vientre { x1: 75, y1: 50, x2: 80, y2: 48 }, // Transición a la cola // Cola (más bifurcada y detallada) { x1: 80, y1: 48, x2: 85, y2: 40 }, { x1: 85, y1: 40, x2: 85, y2: 60 }, { x1: 85, y1: 60, x2: 80, y2: 52 }, // Aletas (con rayos) { x1: 40, y1: 45, x2: 45, y2: 40 }, { x1: 45, y1: 40, x2: 40, y2: 50 }, // Aleta dorsal { x1: 40, y1: 55, x2: 45, y2: 60 }, { x1: 45, y1: 60, x2: 40, y2: 50 }, // Aleta ventral // Ojo y Boca (con detalle) { type: "circle", x1: 35, y1: 48, radius: 1.5 }, { type: "circle", x1: 34.5, y1: 47.5, radius: 0.5 }, // Ojo con brillo { x1: 30, y1: 50, x2: 32, y2: 53 }, { x1: 32, y1: 53, x2: 30, y2: 52 } // Boca ], "MANZANA": [ { type: "circle", x1: 50, y1: 50, radius: 20 }, // Contorno // Tallito (más realista) { x1: 48, y1: 30, x2: 52, y2: 30 }, { x1: 50, y1: 30, x2: 50, y2: 25 }, // Grosor del tallo // Hoja (con vena) { x1: 50, y1: 25, x2: 55, y2: 28 }, { x1: 55, y1: 28, x2: 52, y2: 32 }, { x1: 52, y1: 32, x2: 50, y2: 25 }, { x1: 52, y1: 27, x2: 54, y2: 30 } // Vena de la hoja ], "PLATO": [ { type: "circle", x1: 50, y1: 50, radius: 25 }, // Borde exterior { type: "circle", x1: 50, y1: 50, radius: 20 }, // Borde interior // Detalles para dar profundidad y relieve { x1: 30, y1: 50, x2: 32, y2: 52 }, { x1: 70, y1: 50, x2: 68, y2: 52 }, // Curvas sutiles { x1: 50, y1: 25, x2: 52, y2: 27 }, { x1: 50, y1: 75, x2: 48, y2: 73 } ], "CEREBRO": [ // Hemisferios con surcos más detallados y orgánicos { x1: 30, y1: 40, x2: 40, y2: 30 }, { x1: 40, y1: 30, x2: 60, y2: 30 }, { x1: 60, y1: 30, x2: 70, y2: 40 }, { x1: 70, y1: 40, x2: 65, y2: 50 }, { x1: 65, y1: 50, x2: 55, y2: 55 }, { x1: 55, y1: 55, x2: 45, y2: 55 }, { x1: 45, y1: 55, x2: 35, y2: 50 }, { x1: 35, y1: 50, x2: 30, y2: 40 }, // Contorno exterior { x1: 50, y1: 30, x2: 50, y2: 55 }, // Surco central (más profundo) // Surcos secundarios { x1: 35, y1: 35, x2: 45, y2: 40 }, { x1: 45, y1: 40, x2: 40, y2: 48 }, // Surco 1 { x1: 55, y1: 35, x2: 65, y2: 40 }, { x1: 65, y1: 40, x2: 60, y2: 48 }, // Surco 2 { x1: 40, y1: 32, x2: 35, y2: 38 }, { x1: 60, y1: 32, x2: 65, y2: 38 } // Surcos superiores ], "CORAZON": [ // Corazón con curvas más suaves y volumen { x1: 50, y1: 75, x2: 30, y2: 50 }, { x1: 30, y1: 50, x2: 30, y2: 40 }, { x1: 30, y1: 40, x2: 40, y2: 30 }, { x1: 40, y1: 30, x2: 50, y2: 35 }, // Curva superior izquierda { x1: 50, y1: 35, x2: 60, y2: 30 }, { x1: 60, y1: 30, x2: 70, y2: 40 }, { x1: 70, y1: 40, x2: 70, y2: 50 }, { x1: 70, y1: 50, x2: 50, y2: 75 }, // Curva superior derecha y base // Línea central (para dar forma al lóbulo) { x1: 50, y1: 35, x2: 50, y2: 65 } ], "MANO": [ // Palma y muñeca (base más orgánica) { x1: 50, y1: 80, x2: 40, y2: 75 }, { x1: 40, y1: 75, x2: 40, y2: 60 }, { x1: 50, y1: 80, x2: 70, y2: 75 }, { x1: 70, y1: 75, x2: 70, y2: 60 }, { x1: 40, y1: 60, x2: 70, y2: 60 }, // Pulgar { x1: 40, y1: 65, x2: 35, y2: 55 }, { x1: 35, y1: 55, x2: 38, y2: 50 }, { x1: 38, y1: 50, x2: 45, y2: 58 }, // Índice { x1: 45, y1: 60, x2: 45, y2: 40 }, { x1: 45, y1: 40, x2: 48, y2: 35 }, { x1: 48, y1: 35, x2: 52, y2: 35 }, { x1: 52, y1: 35, x2: 52, y2: 60 }, // Medio { x1: 52, y1: 60, x2: 52, y2: 32 }, { x1: 52, y1: 32, x2: 55, y2: 30 }, { x1: 55, y1: 30, x2: 58, y2: 32 }, { x1: 58, y1: 32, x2: 58, y2: 60 }, // Anular { x1: 58, y1: 60, x2: 58, y2: 35 }, { x1: 58, y1: 35, x2: 61, y2: 33 }, { x1: 61, y1: 33, x2: 64, y2: 35 }, { x1: 64, y1: 35, x2: 64, y2: 60 }, // Meñique { x1: 64, y1: 60, x2: 64, y2: 45 }, { x1: 64, y1: 45, x2: 67, y2: 43 }, { x1: 67, y1: 43, x2: 70, y2: 45 }, { x1: 70, y1: 45, x2: 70, y2: 60 }, // Detalles (Nudillos/Líneas de la palma) { x1: 48, y1: 55, x2: 55, y2: 55 }, { x1: 60, y1: 55, x2: 65, y2: 55 } ], "OJO": [ // Forma almendrada del ojo (simulada con segmentos) { x1: 30, y1: 50, x2: 40, y2: 42 }, { x1: 40, y1: 42, x2: 60, y2: 42 }, { x1: 60, y1: 42, x2: 70, y2: 50 }, // Párpado superior { x1: 30, y1: 50, x2: 40, y2: 58 }, { x1: 40, y1: 58, x2: 60, y2: 58 }, { x1: 60, y1: 58, x2: 70, y2: 50 }, // Párpado inferior // Iris y Pupila { type: "circle", x1: 50, y1: 50, radius: 10 }, // Iris { type: "circle", x1: 50, y1: 50, radius: 4 }, // Pupila // Brillo (Highlight) - da vida al ojo { type: "circle", x1: 47, y1: 47, radius: 1 }, // Pestañas (varias líneas finas) { x1: 40, y1: 42, x2: 38, y2: 38 }, { x1: 45, y1: 42, x2: 45, y2: 37 }, { x1: 50, y1: 42, x2: 52, y2: 37 }, { x1: 55, y1: 42, x2: 57, y2: 38 }, { x1: 60, y1: 42, x2: 62, y2: 39 }, // Pliegue del párpado superior { x1: 35, y1: 40, x2: 50, y2: 38 }, { x1: 50, y1: 38, x2: 65, y2: 40 } ], "BOCA": [ // Contorno de labios más detallado y curvo { x1: 35, y1: 50, x2: 45, y2: 45 }, { x1: 45, y1: 45, x2: 55, y2: 45 }, { x1: 55, y1: 45, x2: 65, y2: 50 }, // Labio superior { x1: 35, y1: 50, x2: 45, y2: 55 }, { x1: 45, y1: 55, x2: 55, y2: 55 }, { x1: 55, y1: 55, x2: 65, y2: 50 }, // Labio inferior // Comisuras y pliegues { x1: 35, y1: 50, x2: 33, y2: 52 }, { x1: 65, y1: 50, x2: 67, y2: 52 }, { x1: 40, y1: 50, x2: 40, y2: 52 }, { x1: 60, y1: 50, x2: 60, y2: 52 } // Líneas internas ], "NARIZ": [ // Puente de la nariz (con detalle) { x1: 50, y1: 40, x2: 45, y2: 50 }, { x1: 50, y1: 40, x2: 55, y2: 50 }, // Base de la nariz y fosas nasales (más detalladas) { x1: 45, y1: 50, x2: 45, y2: 55 }, { x1: 55, y1: 50, x2: 55, y2: 55 }, { x1: 45, y1: 55, x2: 55, y2: 55 }, { type: "circle", x1: 47, y1: 57, radius: 1 }, { type: "circle", x1: 53, y1: 57, radius: 1 }, // Fosas nasales // Sombreado bajo la nariz { x1: 46, y1: 58, x2: 54, y2: 58 } ], "OREJA": [ // Contorno exterior (más orgánico y con lóbulo) { x1: 50, y1: 40, x2: 40, y2: 45 }, { x1: 40, y1: 45, x2: 38, y2: 55 }, { x1: 38, y1: 55, x2: 45, y2: 65 }, { x1: 45, y1: 65, x2: 50, y2: 68 }, // Lóbulo { x1: 50, y1: 68, x2: 55, y2: 65 }, { x1: 55, y1: 65, x2: 62, y2: 55 }, { x1: 62, y1: 55, x2: 60, y2: 45 }, { x1: 60, y1: 45, x2: 50, y1: 40 }, // Detalles internos (conchas de la oreja) { x1: 45, y1: 48, x2: 42, y2: 52 }, { x1: 42, y1: 52, x2: 45, y2: 58 }, { x1: 55, y1: 48, x2: 58, y2: 52 }, { x1: 58, y1: 52, x2: 55, y2: 58 }, { x1: 50, y1: 50, x2: 50, y2: 55 } // Conector ], "DIENTE": [ // Cuerpo del diente (más natural y redondeado) { x1: 45, y1: 30, x2: 55, y2: 30 }, // Parte superior { x1: 45, y1: 30, x2: 43, y2: 50 }, { x1: 55, y1: 30, x2: 57, y2: 50 }, // Curvas laterales { x1: 43, y1: 50, x2: 45, y2: 60 }, { x1: 57, y1: 50, x2: 55, y2: 60 }, // Transición a la encía { x1: 45, y1: 60, x2: 55, y2: 60 }, // Borde inferior (encía) // Raíces (más definidas y con separación) { x1: 46, y1: 60, x2: 44, y2: 68 }, { x1: 44, y1: 68, x2: 46, y2: 70 }, { x1: 54, y1: 60, x2: 56, y2: 68 }, { x1: 56, y1: 68, x2: 54, y2: 70 }, // Encía (curvada) { x1: 40, y1: 65, x2: 50, y2: 68 }, { x1: 50, y1: 68, x2: 60, y2: 65 } ], "PAN": [ // Barra de pan (más redondeada y con volumen) { x1: 20, y1: 60, x2: 80, y2: 60 }, // Base { x1: 20, y1: 60, x2: 22, y2: 50 }, { x1: 80, y1: 60, x2: 78, y2: 50 }, // Laterales { x1: 22, y1: 50, x2: 78, y2: 50 }, // Parte superior { x1: 20, y1: 60, x2: 25, y2: 70 }, { x1: 80, y1: 60, x2: 75, y2: 70 }, // Sombras de base { x1: 25, y1: 70, x2: 75, y2: 70 }, // Cortes (más profundos y definidos) { x1: 30, y1: 50, x2: 35, y2: 60 }, { x1: 35, y1: 60, x2: 40, y2: 50 }, { x1: 45, y1: 50, x2: 50, y2: 60 }, { x1: 50, y1: 60, x2: 55, y2: 50 }, { x1: 60, y1: 50, x2: 65, y2: 60 }, { x1: 65, y1: 60, x2: 70, y2: 50 } ], "QUESO": [ // Trozo de queso triangular (con grosor y textura) { x1: 30, y1: 70, x2: 70, y2: 70 }, // Base { x1: 30, y1: 70, x2: 50, y2: 30 }, // Lado izquierdo { x1: 70, y1: 70, x2: 50, y2: 30 }, // Lado derecho { x1: 35, y1: 68, x2: 50, y2: 35 }, { x1: 65, y1: 68, x2: 50, y2: 35 }, // Grosor y cara superior // Agujeros (más irregulares) { type: "circle", x1: 45, y1: 55, radius: 5 }, { type: "circle", x1: 58, y1: 60, radius: 4 }, { type: "circle", x1: 40, y1: 65, radius: 3 }, { type: "circle", x1: 50, y1: 45, radius: 2 } // Agujero pequeño ], "HUEVO": [ // Contorno de huevo (más ovalado en la parte superior) { type: "circle", x1: 50, y1: 55, radius: 20 }, // Base redonda { x1: 50, y1: 35, x2: 45, y2: 30 }, { x1: 50, y1: 35, x2: 55, y2: 30 }, // Parte superior ovalada { x1: 45, y1: 30, x2: 42, y2: 35 }, { x1: 55, y1: 30, x2: 58, y2: 35 }, { x1: 42, y1: 35, x2: 30, y2: 55 }, { x1: 58, y1: 35, x2: 70, y2: 55 }, // Yema (con brillo) { type: "circle", x1: 50, y1: 50, radius: 8 }, { type: "circle", x1: 48, y1: 48, radius: 1 } // Brillo en la yema ], "CARNE": [ // Trozo de carne (forma más irregular y orgánica) { x1: 30, y1: 50, x2: 70, y2: 45 }, { x1: 70, y1: 45, x2: 75, y2: 60 }, { x1: 75, y1: 60, x2: 60, y2: 70 }, { x1: 60, y1: 70, x2: 35, y2: 65 }, { x1: 35, y1: 65, x2: 30, y2: 50 }, // Hueso (más detallado) { x1: 50, y1: 50, x2: 50, y2: 35 }, { x1: 45, y1: 35, x2: 55, y2: 35 }, // Hueso principal { type: "circle", x1: 45, y1: 35, radius: 3 }, { type: "circle", x1: 55, y1: 35, radius: 3 }, // Nudillos del hueso // Líneas de grasa/textura { x1: 40, y1: 55, x2: 50, y2: 58 }, { x1: 60, y1: 50, x2: 65, y2: 55 } ], "POLLO": [ // Pollo asado (más volumen y piernas definidas) { x1: 30, y1: 60, x2: 70, y2: 60 }, { x1: 70, y1: 60, x2: 70, y2: 50 }, { x1: 70, y1: 50, x2: 60, y2: 45 }, { x1: 60, y1: 45, x2: 40, y2: 45 }, { x1: 40, y1: 45, x2: 30, y2: 50 }, { x1: 30, y1: 50, x2: 30, y2: 60 }, // Cuerpo // Piernas (muslos y contramuslos) { x1: 35, y1: 58, x2: 25, y2: 55 }, { x1: 25, y1: 55, x2: 20, y2: 60 }, // Pierna izq { x1: 65, y1: 58, x2: 75, y2: 55 }, { x1: 75, y1: 55, x2: 80, y2: 60 }, // Pierna der // Textura de piel/dorado { x1: 40, y1: 50, x2: 50, y2: 48 }, { x1: 50, y1: 48, x2: 60, y2: 50 } // Líneas para sombreado ], "ARROZ": [ // Bol (con grosor y apertura) { type: "circle", x1: 50, y1: 70, radius: 20 }, // Base del bol { x1: 30, y1: 70, x2: 35, y2: 60 }, { x1: 70, y1: 70, x2: 65, y2: 60 }, // Lados inclinados { x1: 35, y1: 60, x2: 65, y2: 60 }, // Borde superior del bol // Arroz (montón de puntos o pequeñas líneas) { type: "circle", x1: 50, y1: 55, radius: 15 }, // Simular el montón { x1: 45, y1: 50, x2: 46, y2: 50 }, { x1: 55, y1: 52, x2: 56, y2: 52 }, // Granos de arroz (puntos) { x1: 48, y1: 58, x2: 49, y2: 58 }, { x1: 52, y1: 47, x2: 53, y2: 47 }, // Palillos (con detalle y en ángulo) { x1: 40, y1: 55, x2: 40, y2: 35 }, { x1: 40, y1: 35, x2: 42, y2: 30 }, // Palillo 1 { x1: 42, y1: 30, x2: 42, y2: 50 }, { x1: 45, y1: 55, x2: 45, y2: 35 }, { x1: 45, y1: 35, x2: 47, y2: 30 }, // Palillo 2 { x1: 47, y1: 30, x2: 47, y2: 50 } ], "PASTA": [ // Plato (con borde y sombreado para profundidad) { type: "circle", x1: 50, y1: 65, radius: 20 }, // Borde exterior { type: "circle", x1: 50, y1: 65, radius: 18 }, // Borde interior { x1: 32, y1: 65, x2: 35, y2: 68 }, { x1: 68, y1: 65, x2: 65, y2: 68 }, // Sombreado del plato // Pasta (espagueti en un montón) { type: "circle", x1: 50, y1: 50, radius: 10 }, // Base del montón { x1: 40, y1: 50, x2: 45, y2: 45 }, { x1: 45, y1: 45, x2: 50, y2: 50 }, // Hilos de pasta { x1: 50, y1: 50, x2: 55, y2: 45 }, { x1: 55, y1: 45, x2: 60, y2: 50 }, { x1: 45, y1: 55, x2: 50, y2: 50 }, { x1: 55, y1: 55, x2: 50, y2: 50 }, // Tenedor (con dientes) { x1: 48, y1: 50, x2: 48, y2: 40 }, { x1: 52, y1: 50, x2: 52, y2: 40 }, // Mangos { x1: 47, y1: 40, x2: 47, y2: 35 }, { x1: 49, y1: 40, x2: 49, y2: 35 }, // Dientes { x1: 51, y1: 40, x2: 51, y2: 35 }, { x1: 53, y1: 40, x2: 53, y2: 35 } ], "FRUTA": [ // Canasta (con textura de mimbre y asa) { x1: 30, y1: 70, x2: 70, y2: 70 }, // Base { x1: 30, y1: 70, x2: 35, y2: 60 }, { x1: 70, y1: 70, x2: 65, y2: 60 }, // Lados inclinados { x1: 35, y1: 60, x2: 65, y2: 60 }, // Borde superior de la canasta { x1: 37, y1: 62, x2: 63, y2: 62 }, { x1: 39, y1: 64, x2: 61, y2: 64 }, // Textura de mimbre { x1: 45, y1: 60, x2: 45, y2: 50 }, { x1: 55, y1: 60, x2: 55, y2: 50 }, // Asa { x1: 45, y1: 50, x2: 55, y2: 50 }, // Frutas (varias formas y tamaños) { type: "circle", x1: 40, y1: 55, radius: 5 }, // Manzana { type: "circle", x1: 60, y1: 55, radius: 4 }, // Naranja { x1: 50, y1: 48, x2: 45, y2: 40 }, { x1: 45, y1: 40, x2: 55, y2: 40 }, { x1: 55, y1: 40, x2: 50, y2: 48 } // Plátano ], "UVA": [ // Racimo de uvas (más densas y con superposición) { type: "circle", x1: 50, y1: 40, radius: 5 }, // Uva superior { type: "circle", x1: 45, y1: 48, radius: 5 }, { type: "circle", x1: 55, y1: 48, radius: 5 }, { type: "circle", x1: 40, y1: 56, radius: 5 }, { type: "circle", x1: 50, y1: 56, radius: 5 }, { type: "circle", x1: 60, y1: 56, radius: 5 }, { type: "circle", x1: 45, y1: 64, radius: 5 }, { type: "circle", x1: 55, y1: 64, radius: 5 }, { type: "circle", x1: 50, y1: 72, radius: 5 }, // Uva inferior // Tallo (más orgánico y con rama pequeña) { x1: 50, y1: 35, x2: 50, y2: 25 }, { x1: 50, y1: 30, x2: 55, y2: 28 }, { x1: 55, y1: 28, x2: 52, y2: 32 } // Rama ], "PIÑA": [ // Cuerpo ovalado (con textura de rombos) { x1: 40, y1: 40, x2: 60, y2: 40 }, { x1: 40, y1: 40, x2: 35, y2: 60 }, { x1: 60, y1: 40, x2: 65, y2: 60 }, { x1: 35, y1: 60, x2: 65, y2: 60 }, // Textura de rombos { x1: 40, y1: 45, x2: 45, y2: 40 }, { x1: 45, y1: 40, x2: 50, y2: 45 }, // Fila 1 { x1: 50, y1: 45, x2: 55, y2: 40 }, { x1: 55, y1: 40, x2: 60, y2: 45 }, { x1: 37, y1: 50, x2: 42, y2: 45 }, { x1: 42, y1: 45, x2: 47, y2: 50 }, // Fila 2 { x1: 47, y1: 50, x2: 52, y2: 45 }, { x1: 52, y1: 45, x2: 57, y2: 50 }, { x1: 57, y1: 50, x2: 62, y2: 45 }, // Hojas superiores (más puntiagudas y variadas) { x1: 45, y1: 38, x2: 40, y2: 25 }, { x1: 50, y1: 38, x2: 45, y2: 22 }, { x1: 55, y1: 38, x2: 50, y2: 25 }, { x1: 60, y1: 38, x2: 55, y2: 22 } ], "KIWI": [ { type: "circle", x1: 50, y1: 50, radius: 20 }, // Contorno exterior { type: "circle", x1: 50, y1: 50, radius: 5 }, // Centro // Líneas internas (más para simular las semillas y la pulpa) { x1: 50, y1: 30, x2: 50, y2: 70 }, { x1: 30, y1: 50, x2: 70, y2: 50 }, { x1: 35, y1: 35, x2: 65, y2: 65 }, { x1: 35, y1: 65, x2: 65, y2: 35 }, { x1: 40, y1: 30, x2: 40, y2: 70 }, { x1: 60, y1: 30, x2: 60, y2: 70 }, // Líneas de la pulpa { x1: 30, y1: 40, x2: 70, y2: 40 }, { x1: 30, y1: 60, x2: 70, y2: 60 }, // Semillas (puntos pequeños alrededor del centro) { type: "circle", x1: 42, y1: 48, radius: 0.5 }, { type: "circle", x1: 42, y1: 52, radius: 0.5 }, { type: "circle", x1: 48, y1: 42, radius: 0.5 }, { type: "circle", x1: 52, y1: 42, radius: 0.5 }, { type: "circle", x1: 58, y1: 48, radius: 0.5 }, { type: "circle", x1: 58, y1: 52, radius: 0.5 }, { type: "circle", x1: 48, y1: 58, radius: 0.5 }, { type: "circle", x1: 52, y1: 58, radius: 0.5 } ], "CHOCOLATE": [ // Barra de chocolate (con grosor y divisiones en 3D) { x1: 30, y1: 45, x2: 70, y2: 45 }, { x1: 70, y1: 45, x2: 70, y2: 65 }, { x1: 70, y1: 65, x2: 30, y2: 65 }, { x1: 30, y1: 65, x2: 30, y2: 45 }, // Cara frontal { x1: 30, y1: 45, x2: 25, y2: 40 }, { x1: 70, y1: 45, x2: 65, y2: 40 }, // Borde superior en perspectiva { x1: 25, y1: 40, x2: 65, y2: 40 }, { x1: 25, y1: 40, x2: 25, y2: 60 }, { x1: 65, y1: 40, x2: 65, y2: 60 }, // Lados en perspectiva { x1: 25, y1: 60, x2: 30, y2: 65 }, { x1: 65, y1: 60, x2: 70, y2: 65 }, // Divisiones (con líneas que simulan los cuadrados) { x1: 35, y1: 45, x2: 35, y2: 65 }, { x1: 45, y1: 45, x2: 45, y2: 65 }, { x1: 55, y1: 45, x2: 55, y2: 65 }, { x1: 65, y1: 45, x2: 65, y2: 65 }, { x1: 30, y1: 55, x2: 70, y2: 55 } ], "HELADO": [ // Cono (con textura de waffle) { x1: 40, y1: 70, x2: 50, y2: 30 }, { x1: 60, y1: 70, x2: 50, y2: 30 }, { x1: 40, y1: 70, x2: 60, y2: 70 }, { x1: 45, y1: 65, x2: 55, y2: 65 }, { x1: 42, y1: 60, x2: 58, y2: 60 }, // Líneas horizontales del waffle { x1: 47, y1: 70, x2: 50, y2: 30 }, { x1: 53, y1: 70, x2: 50, y2: 30 }, // Líneas verticales del waffle // Bola de helado (con derretimiento) { type: "circle", x1: 50, y1: 30, radius: 15 }, { x1: 40, y1: 35, x2: 42, y2: 40 }, { x1: 42, y1: 40, x2: 40, y2: 42 }, // Gotas simuladas { x1: 60, y1: 35, x2: 58, y2: 40 }, { x1: 58, y1: 40, x2: 60, y2: 42 } ], "GALLETA": [ { type: "circle", x1: 50, y1: 50, radius: 20 }, // Contorno (más irregular para un aspecto casero) { x1: 35, y1: 45, x2: 38, y2: 42 }, { x1: 42, y1: 38, x2: 45, y2: 35 }, { x1: 65, y1: 45, x2: 62, y2: 42 }, { x1: 58, y1: 38, x2: 55, y2: 35 }, // Chispas de chocolate (con un punto central para dar volumen) { type: "circle", x1: 40, y1: 45, radius: 2 }, { type: "circle", x1: 40, y1: 45, radius: 0.5 }, { type: "circle", x1: 60, y1: 45, radius: 2 }, { type: "circle", x1: 60, y1: 45, radius: 0.5 }, { type: "circle", x1: 50, y1: 58, radius: 2 }, { type: "circle", x1: 50, y1: 58, radius: 0.5 }, { type: "circle", x1: 45, y1: 52, radius: 1.5 }, { type: "circle", x1: 55, y1: 52, radius: 1.5 } ], "CARAMELO": [ // Cuerpo central (más grueso y con reflejo) { x1: 30, y1: 50, x2: 70, y2: 50 }, { x1: 30, y1: 52, x2: 70, y2: 52 }, // Grosor { x1: 35, y1: 50, x2: 65, y2: 50 }, // Línea de reflejo // Envoltorio (más plisado y con puntas definidas) { x1: 25, y1: 45, x2: 30, y2: 50 }, { x1: 30, y1: 50, x2: 25, y2: 55 }, { x1: 20, y1: 48, x2: 25, y2: 45 }, { x1: 20, y1: 52, x2: 25, y2: 55 }, // Pliegues izq { x1: 70, y1: 50, x2: 75, y2: 45 }, { x1: 75, y1: 45, x2: 70, y2: 50 }, { x1: 75, y1: 50, x2: 75, y2: 55 }, { x1: 70, y1: 50, x2: 80, y2: 48 }, // Pliegues der { x1: 80, y1: 48, x2: 75, y2: 45 }, { x1: 80, y1: 52, x2: 75, y2: 55 } ], "TARTA": [ // Rebanada de tarta (con capas y textura de crema) { x1: 50, y1: 30, x2: 30, y2: 70 }, { x1: 30, y1: 70, x2: 70, y2: 70 }, { x1: 70, y1: 70, x2: 50, y2: 30 }, // Triángulo principal // Capas internas { x1: 40, y1: 45, x2: 60, y2: 45 }, // Capa 1 { x1: 35, y1: 55, x2: 65, y2: 55 }, // Capa 2 // Textura de crema/glaseado { x1: 50, y1: 30, x2: 50, y2: 28 }, { x1: 48, y1: 28, x2: 52, y2: 28 }, // Topping { x1: 30, y1: 70, x2: 32, y2: 68 }, { x1: 70, y1: 70, x2: 68, y2: 68 } // Borde inferior ], "PIZZA": [ // Rebanada de pizza (con borde y ingredientes) { x1: 50, y1: 30, x2: 30, y2: 70 }, { x1: 30, y1: 70, x2: 70, y2: 70 }, { x1: 70, y1: 70, x2: 50, y2: 30 }, // Triángulo de la rebanada // Borde de la corteza { x1: 50, y1: 30, x2: 40, y2: 35 }, { x1: 40, y1: 35, x2: 32, y2: 65 }, { x1: 32, y1: 65, x2: 30, y2: 70 }, // Borde izquierdo { x1: 50, y1: 30, x2: 60, y2: 35 }, { x1: 60, y1: 35, x2: 68, y2: 65 }, { x1: 68, y1: 65, x2: 70, y2: 70 }, // Borde derecho // Pepperoni (con detalle interior) { type: "circle", x1: 40, y1: 50, radius: 3 }, { type: "circle", x1: 40, y1: 50, radius: 1 }, { type: "circle", x1: 60, y1: 55, radius: 3 }, { type: "circle", x1: 60, y1: 55, radius: 1 }, { type: "circle", x1: 50, y1: 40, radius: 2.5 } // Otro pepperoni ], "HAMBURGUESA": [ // Pan superior (curvo y con semillas) { x1: 30, y1: 40, x2: 70, y2: 40 }, { x1: 30, y1: 40, x2: 25, y2: 45 }, { x1: 70, y1: 40, x2: 75, y2: 45 }, { x1: 25, y1: 45, x2: 75, y2: 45 }, // Curva superior { type: "circle", x1: 40, y1: 42, radius: 0.5 }, { type: "circle", x1: 50, y1: 42, radius: 0.5 }, { type: "circle", x1: 60, y1: 42, radius: 0.5 }, // Semillas // Lechuga/Tomate (línea ondulada) { x1: 25, y1: 45, x2: 30, y2: 48 }, { x1: 30, y1: 48, x2: 40, y2: 46 }, { x1: 40, y1: 46, x2: 50, y2: 48 }, { x1: 50, y1: 48, x2: 60, y2: 46 }, { x1: 60, y1: 46, x2: 70, y2: 48 }, { x1: 70, y1: 48, x2: 75, y2: 45 }, // Carne (con textura) { x1: 28, y1: 50, x2: 72, y2: 50 }, { x1: 28, y1: 50, x2: 28, y2: 55 }, { x1: 72, y1: 50, x2: 72, y2: 55 }, { x1: 28, y1: 55, x2: 72, y2: 55 }, { x1: 35, y1: 52, x2: 38, y2: 52 }, { x1: 45, y1: 53, x2: 48, y2: 53 }, // Textura // Pan inferior (curvo) { x1: 25, y1: 60, x2: 75, y2: 60 }, { x1: 25, y1: 60, x2: 30, y2: 55 }, { x1: 75, y1: 60, x2: 70, y2: 55 } ], "PERRITO": [ // Salchicha (con textura y ligeramente curva) { x1: 30, y1: 50, x2: 70, y2: 50 }, { x1: 30, y1: 50, x2: 30, y2: 53 }, { x1: 70, y1: 50, x2: 70, y2: 53 }, { x1: 30, y1: 53, x2: 70, y2: 53 }, { x1: 40, y1: 51, x2: 40, y2: 52 }, { x1: 50, y1: 51, x2: 50, y2: 52 }, { x1: 60, y1: 51, x2: 60, y2: 52 }, // Rayas de la salchicha // Bollo (más abierto y con textura) { x1: 25, y1: 55, x2: 75, y2: 55 }, // Borde superior del bollo { x1: 25, y1: 55, x2: 20, y2: 65 }, { x1: 75, y1: 55, x2: 80, y2: 65 }, // Lados del bollo { x1: 20, y1: 65, x2: 80, y2: 65 }, // Base del bollo { x1: 30, y1: 60, x2: 35, y2: 62 }, { x1: 65, y1: 60, x2: 60, y2: 62 } // Sombreado interno del bollo ], "MAPACHE": [ // Cabeza (más definida) { type: "circle", x1: 50, y1: 50, radius: 20 }, // Orejas (con forma de gota y pelaje) { x1: 40, y1: 35, x2: 35, y2: 40 }, { x1: 35, y1: 40, x2: 40, y2: 45 }, { x1: 40, y1: 45, x2: 45, y2: 40 }, { x1: 60, y1: 35, x2: 65, y2: 40 }, { x1: 65, y1: 40, x2: 60, y2: 45 }, { x1: 60, y1: 45, x2: 55, y2: 40 }, // Manchas de ojos (más oscuras y con forma) { x1: 38, y1: 45, x2: 42, y2: 45 }, { x1: 38, y1: 50, x2: 42, y2: 50 }, // Mancha izquierda { x1: 58, y1: 45, x2: 62, y2: 45 }, { x1: 58, y1: 50, x2: 62, y2: 50 }, // Mancha derecha // Nariz (triángulo invertido) { x1: 50, y1: 55, x2: 48, y2: 58 }, { x1: 48, y1: 58, x2: 52, y2: 58 }, { x1: 52, y1: 58, x2: 50, y2: 55 }, // Hocico (forma de "U" invertida) { x1: 45, y1: 55, x2: 55, y2: 55 }, // Ojos y bigotes (más detallados) { type: "circle", x1: 40, y1: 47, radius: 1 }, { type: "circle", x1: 60, y1: 47, radius: 1 }, { x1: 45, y1: 55, x2: 35, y2: 53 }, { x1: 45, y1: 56, x2: 35, y2: 56 }, { x1: 55, y1: 55, x2: 65, y2: 53 }, { x1: 55, y1: 56, x2: 65, y2: 56 } ], "ZORRO": [ // Cabeza (forma más triangular) { x1: 50, y1: 65, x2: 35, y2: 50 }, { x1: 35, y1: 50, x2: 50, y2: 35 }, { x1: 50, y1: 35, x2: 65, y2: 50 }, { x1: 65, y1: 50, x2: 50, y2: 65 }, // Hocico (más puntiagudo) { x1: 50, y1: 60, x2: 45, y2: 55 }, { x1: 45, y1: 55, x2: 50, y2: 50 }, { x1: 50, y1: 50, x2: 55, y2: 55 }, { x1: 55, y1: 55, x2: 50, y2: 60 }, // Nariz { x1: 50, y1: 52, x2: 50, y2: 54 }, // Orejas puntiagudas (con detalle interior) { x1: 45, y1: 35, x2: 40, y2: 25 }, { x1: 40, y1: 25, x2: 48, y2: 30 }, { x1: 55, y1: 35, x2: 60, y2: 25 }, { x1: 60, y1: 25, x2: 52, y2: 30 }, { x1: 44, y1: 30, x2: 46, y2: 28 }, { x1: 56, y1: 30, x2: 54, y2: 28 }, // Detalle interior // Ojos y bigotes { type: "circle", x1: 42, y1: 45, radius: 1 }, { type: "circle", x1: 58, y1: 45, radius: 1 }, { x1: 45, y1: 55, x2: 35, y2: 53 }, { x1: 45, y1: 56, x2: 35, y2: 56 }, { x1: 55, y1: 55, x2: 65, y2: 53 }, { x1: 55, y1: 56, x2: 65, y2: 56 } ], "LOBO": [ // Cara (más angulosa y con hocico alargado) { x1: 50, y1: 65, x2: 35, y2: 50 }, { x1: 35, y1: 50, x2: 50, y2: 35 }, { x1: 50, y1: 35, x2: 65, y2: 50 }, { x1: 65, y1: 50, x2: 50, y2: 65 }, // Hocico alargado y aullando { x1: 50, y1: 60, x2: 45, y2: 50 }, { x1: 45, y1: 50, x2: 50, y2: 45 }, { x1: 50, y1: 45, x2: 55, y2: 50 }, { x1: 55, y1: 50, x2: 50, y2: 60 }, { x1: 50, y1: 60, x2: 50, y2: 70 }, // Boca abierta { x1: 48, y1: 68, x2: 52, y2: 68 }, // Dientes (simulados) // Orejas (más grandes y puntiagudas) { x1: 45, y1: 35, x2: 40, y2: 25 }, { x1: 40, y1: 25, x2: 48, y2: 30 }, { x1: 55, y1: 35, x2: 60, y2: 25 }, { x1: 60, y1: 25, x2: 52, y2: 30 }, // Ojos { type: "circle", x1: 42, y1: 45, radius: 1.5 }, { type: "circle", x1: 58, y1: 45, radius: 1.5 } ], "OSO": [ // Cabeza (más definida y peluda) { type: "circle", x1: 50, y1: 50, radius: 20 }, { x1: 30, y1: 45, x2: 32, y2: 42 }, { x1: 32, y1: 42, x2: 35, y2: 45 }, // Pelaje en el contorno // Orejas redondas (con detalle interior) { type: "circle", x1: 40, y1: 35, radius: 8 }, { type: "circle", x1: 40, y1: 35, radius: 4 }, // Oreja izq { type: "circle", x1: 60, y1: 35, radius: 8 }, { type: "circle", x1: 60, y1: 35, radius: 4 }, // Oreja der // Hocico (más prominente) { type: "circle", x1: 50, y1: 58, radius: 7 }, { type: "circle", x1: 50, y1: 55, radius: 2 }, // Nariz // Ojos { type: "circle", x1: 45, y1: 48, radius: 1.5 }, { type: "circle", x1: 55, y1: 48, radius: 1.5 } ], "TIGRE": [ // Cabeza (más cuadrada y fuerte) { type: "circle", x1: 50, y1: 50, radius: 25 }, // Hocico (más amplio) { x1: 40, y1: 55, x2: 60, y2: 55 }, { x1: 40, y1: 55, x2: 35, y2: 60 }, { x1: 60, y1: 55, x2: 65, y2: 60 }, { x1: 35, y1: 60, x2: 65, y2: 60 }, // Ojos y Nariz { type: "circle", x1: 42, y1: 45, radius: 2 }, { type: "circle", x1: 58, y1: 45, radius: 2 }, { x1: 50, y1: 58, x2: 50, y2: 60 }, // Rayas (más variadas y angulares) { x1: 35, y1: 35, x2: 40, y2: 30 }, { x1: 40, y1: 30, x2: 45, y2: 35 }, // Raya superior izq { x1: 65, y1: 35, x2: 60, y2: 30 }, { x1: 60, y1: 30, x2: 55, y2: 35 }, // Raya superior der { x1: 30, y1: 50, x2: 35, y2: 50 }, { x1: 30, y1: 52, x2: 35, y2: 52 }, // Rayas laterales { x1: 70, y1: 50, x2: 65, y2: 50 }, { x1: 70, y1: 52, x2: 65, y2: 52 } ], "LEON": [ // Cabeza (más grande para la melena) { type: "circle", x1: 50, y1: 50, radius: 25 }, // Melena (líneas radiales para simular pelaje) { x1: 50, y1: 25, x2: 40, y2: 20 }, { x1: 50, y1: 25, x2: 60, y2: 20 }, { x1: 50, y1: 75, x2: 40, y2: 80 }, { x1: 50, y1: 75, x2: 60, y2: 80 }, { x1: 25, y1: 50, x2: 20, y2: 40 }, { x1: 25, y1: 50, x2: 20, y2: 60 }, { x1: 75, y1: 50, x2: 80, y2: 40 }, { x1: 75, y1: 50, x2: 80, y2: 60 }, // Cara interna (hocico y boca) { type: "circle", x1: 50, y1: 50, radius: 15 }, { x1: 50, y1: 55, x2: 45, y2: 58 }, { x1: 45, y1: 58, x2: 55, y2: 58 }, { x1: 55, y1: 58, x2: 50, y2: 55 }, // Boca { type: "circle", x1: 50, y1: 52, radius: 1.5 }, // Nariz // Ojos { type: "circle", x1: 45, y1: 45, radius: 1.5 }, { type: "circle", x1: 55, y1: 45, radius: 1.5 } ], "ELEFANTE": [ // Cabeza (con más forma) { type: "circle", x1: 50, y1: 50, radius: 20 }, // Trompa (más curva y con arrugas) { x1: 50, y1: 60, x2: 55, y2: 70 }, { x1: 55, y1: 70, x2: 50, y2: 75 }, { x1: 50, y1: 75, x2: 45, y2: 70 }, { x1: 45, y1: 70, x2: 50, y2: 60 }, { x1: 50, y1: 65, x2: 50, y2: 68 }, { x1: 48, y1: 72, x2: 52, y2: 72 }, // Arrugas de la trompa // Orejas (grandes y colgantes) { x1: 35, y1: 40, x2: 25, y2: 55 }, { x1: 25, y2: 55, x2: 35, y2: 65 }, { x1: 35, y1: 65, x2: 40, y2: 55 }, { x1: 40, y2: 55, x2: 35, y2: 40 }, // Oreja 1 { x1: 65, y1: 40, x2: 75, y2: 55 }, { x1: 75, y2: 55, x2: 65, y2: 65 }, { x1: 65, y1: 65, x2: 60, y2: 55 }, { x1: 60, y2: 55, x2: 65, y2: 40 }, // Oreja 2 // Ojo { type: "circle", x1: 45, y1: 45, radius: 1.5 } ], "JIRAFA": [ // Cuello largo (con grosor y manchas) { x1: 48, y1: 80, x2: 48, y2: 30 }, { x1: 52, y1: 80, x2: 52, y2: 30 }, // Grosor del cuello { x1: 48, y1: 70, x2: 52, y2: 70 }, { x1: 48, y1: 55, x2: 52, y2: 55 }, // Líneas de las manchas // Cabeza (más definida y con osiconos) { type: "circle", x1: 50, y1: 25, radius: 8 }, { x1: 48, y1: 20, x2: 48, y2: 15 }, { x1: 52, y1: 20, x2: 52, y2: 15 }, // Osiconos { type: "circle", x1: 50, y1: 15, radius: 1 }, // Hocico y ojos { x1: 45, y1: 30, x2: 55, y2: 30 }, // Hocico { type: "circle", x1: 47, y1: 27, radius: 1 }, { type: "circle", x1: 53, y1: 27, radius: 1 } // Ojos ], "MONO": [ // Cuerpo (más curvado) { type: "circle", x1: 50, y1: 55, radius: 20 }, // Cabeza (más pequeña y con cara) { type: "circle", x1: 50, y1: 30, radius: 10 }, { type: "circle", x1: 50, y1: 32, radius: 5 }, // Cara interna // Orejas (redondas y prominentes) { type: "circle", x1: 40, y1: 25, radius: 5 }, { type: "circle", x1: 60, y1: 25, radius: 5 }, // Cola (más larga y enroscada) { x1: 60, y1: 65, x2: 70, y2: 60 }, { x1: 70, y2: 60, x2: 75, y2: 70 }, { x1: 75, y2: 70, x2: 70, y2: 75 }, // Brazos y piernas (simulados con líneas) { x1: 40, y1: 50, x2: 35, y2: 45 }, { x1: 60, y1: 50, x2: 65, y2: 45 }, { x1: 45, y1: 70, x2: 40, y2: 75 }, { x1: 55, y1: 70, x2: 60, y2: 75 } ], "KOALA": [ // Cuerpo (más peludo) { type: "circle", x1: 50, y1: 50, radius: 25 }, { x1: 30, y1: 45, x2: 32, y2: 42 }, { x1: 32, y1: 42, x2: 35, y2: 45 }, // Pelaje en el contorno // Orejas grandes y peludas { type: "circle", x1: 40, y1: 35, radius: 10 }, { type: "circle", x1: 40, y1: 35, radius: 5 }, // Oreja izq { type: "circle", x1: 60, y1: 35, radius: 10 }, { type: "circle", x1: 60, y1: 35, radius: 5 }, // Oreja der // Nariz grande y aplanada { x1: 50, y1: 55, x2: 45, y2: 58 }, { x1: 45, y1: 58, x2: 55, y2: 58 }, { x1: 55, y1: 58, x2: 50, y2: 55 }, // Ojos { type: "circle", x1: 45, y1: 48, radius: 1.5 }, { type: "circle", x1: 55, y1: 48, radius: 1.5 } ], "PINGUINO": [ // Cuerpo ovalado (con vientre blanco y alas) { x1: 50, y1: 80, x2: 50, y2: 30 }, { x1: 40, y1: 80, x2: 40, y2: 35 }, { x1: 60, y1: 80, x2: 60, y2: 35 }, { x1: 40, y1: 35, x2: 50, y2: 30 }, { x1: 50, y1: 30, x2: 60, y2: 35 }, // Cabeza y cuerpo { x1: 45, y1: 70, x2: 55, y2: 70 }, // Base del vientre { x1: 45, y1: 70, x2: 45, y2: 45 }, { x1: 55, y1: 70, x2: 55, y2: 45 }, // Contorno del vientre { x1: 40, y1: 50, x2: 35, y2: 55 }, { x1: 35, y1: 55, x2: 40, y2: 60 }, // Ala izq { x1: 60, y1: 50, x2: 65, y2: 55 }, { x1: 65, y1: 55, x2: 60, y2: 60 }, // Ala der // Ojos y pico (más detallados) { type: "circle", x1: 47, y1: 40, radius: 1 }, { type: "circle", x1: 53, y1: 40, radius: 1 }, // Ojos { x1: 50, y1: 42, x2: 48, y2: 45 }, { x1: 48, y1: 45, x2: 52, y2: 45 }, { x1: 52, y1: 45, x2: 50, y2: 42 } // Pico ], "BUHO": [ // Cuerpo (más plumoso y forma de corazón invertido en la cara) { type: "circle", x1: 50, y1: 50, radius: 25 }, { x1: 40, y1: 40, x2: 60, y2: 40 }, { x1: 40, y1: 40, x2: 35, y2: 50 }, // Cara en forma de corazón { x1: 60, y1: 40, x2: 65, y2: 50 }, { x1: 35, y1: 50, x2: 65, y2: 50 }, // Ojos grandes (con brillo y párpados) { type: "circle", x1: 42, y1: 45, radius: 8 }, { type: "circle", x1: 42, y1: 45, radius: 2 }, // Ojo izq con brillo { type: "circle", x1: 58, y1: 45, radius: 8 }, { type: "circle", x1: 58, y1: 45, radius: 2 }, // Ojo der con brillo { x1: 38, y1: 42, x2: 46, y2: 42 }, { x1: 54, y1: 42, x2: 62, y2: 42 }, // Párpados // Pico (pequeño y puntiagudo) { x1: 50, y1: 52, x2: 48, y2: 55 }, { x1: 48, y1: 55, x2: 52, y2: 55 }, { x1: 52, y1: 55, x2: 50, y2: 52 } ], "AGUILA": [ // Cuerpo y cabeza (más robustos) { x1: 50, y1: 40, x2: 50, y2: 30 }, { x1: 45, y1: 30, x2: 55, y2: 30 }, // Cabeza { x1: 50, y1: 40, x2: 45, y2: 50 }, { x1: 45, y1: 50, x2: 50, y2: 60 }, // Cuerpo { x1: 50, y1: 60, x2: 55, y2: 50 }, { x1: 55, y1: 50, x2: 50, y2: 40 }, // Alas (más grandes y plumosas) { x1: 45, y1: 45, x2: 20, y2: 40 }, { x1: 20, y1: 40, x2: 25, y2: 50 }, { x1: 25, y1: 50, x2: 30, y2: 55 }, // Ala izq { x1: 55, y1: 45, x2: 80, y2: 40 }, { x1: 80, y1: 40, x2: 75, y2: 50 }, { x1: 75, y1: 50, x2: 70, y2: 55 }, // Ala der // Pico y ojo { x1: 50, y1: 35, x2: 48, y2: 38 }, { x1: 48, y1: 38, x2: 50, y2: 40 }, { x1: 50, y1: 40, x2: 52, y2: 38 }, { x1: 52, y1: 38, x2: 50, y2: 35 }, // Pico { type: "circle", x1: 47, y1: 35, radius: 1 } // Ojo ], "DELFIN": [ // Cuerpo (más elegante y curvado) { x1: 30, y1: 60, x2: 70, y2: 40 }, { x1: 70, y1: 40, x2: 75, y2: 45 }, // Lomo y morro { x1: 30, y1: 60, x2: 70, y2: 70 }, { x1: 70, y1: 70, x2: 75, y2: 65 }, // Vientre { x1: 75, y1: 45, x2: 80, y2: 55 }, { x1: 80, y1: 55, x2: 75, y2: 65 }, // Transición a la cola // Cola (más ancha y bifurcada) { x1: 80, y1: 55, x2: 85, y2: 50 }, { x1: 85, y1: 50, x2: 85, y2: 60 }, { x1: 85, y1: 60, x2: 80, y2: 55 }, // Aleta dorsal (curvada) { x1: 60, y1: 40, x2: 60, y2: 30 }, { x1: 60, y1: 30, x2: 55, y2: 35 }, { x1: 55, y1: 35, x2: 60, y2: 40 }, // Aleta pectoral (sutil) { x1: 40, y1: 60, x2: 42, y2: 65 } ], "BALLENA": [ // Cuerpo (más masivo y redondeado) { x1: 20, y1: 60, x2: 80, y2: 60 }, { x1: 20, y1: 60, x2: 25, y2: 50 }, { x1: 80, y1: 60, x2: 75, y2: 50 }, { x1: 25, y1: 50, x2: 75, y2: 50 }, // Contorno del cuerpo { x1: 30, y1: 55, x2: 70, y2: 55 }, // Línea de la barriga // Cola (grande y bifurcada) { x1: 70, y1: 55, x2: 80, y2: 45 }, { x1: 80, y2: 45, x2: 85, y2: 45 }, { x1: 85, y2: 45, x2: 80, y2: 55 }, // Lado superior de la cola { x1: 70, y1: 55, x2: 80, y2: 65 }, { x1: 80, y2: 65, x2: 85, y2: 65 }, { x1: 85, y2: 65, x2: 80, y2: 55 }, // Lado inferior de la cola // Soplete (líneas hacia arriba) { x1: 40, y1: 45, x2: 40, y2: 35 }, { x1: 42, y1: 45, x2: 42, y2: 35 }, { x1: 38, y1: 45, x2: 38, y2: 35 } ], "TIBURON": [ // Cuerpo (más agresivo y aerodinámico) { x1: 20, y1: 60, x2: 80, y2: 55 }, { x1: 80, y1: 55, x2: 75, y2: 65 }, { x1: 75, y2: 65, x2: 20, y2: 60 }, // Contorno del cuerpo { x1: 30, y1: 60, x2: 35, y2: 62 }, { x1: 60, y1: 58, x2: 65, y2: 60 }, // Branquias simuladas // Aleta dorsal (grande y puntiaguda) { x1: 50, y1: 50, x2: 50, y2: 40 }, { x1: 45, y1: 40, x2: 55, y2: 40 }, { x1: 55, y1: 40, x2: 50, y2: 50 }, // Aleta caudal (cola) { x1: 80, y1: 55, x2: 85, y2: 50 }, { x1: 85, y1: 50, x2: 80, y2: 60 }, { x1: 80, y1: 60, x2: 85, y2: 65 }, // Bifurcación // Ojo y boca con dientes { type: "circle", x1: 30, y1: 55, radius: 1 }, // Ojo { x1: 25, y1: 58, x2: 35, y2: 60 }, { x1: 35, y1: 60, x2: 40, y2: 58 } // Boca con dientes simulados ], "PULPO": [ // Cabeza (más abultada) { type: "circle", x1: 50, y1: 40, radius: 15 }, // Ojos { type: "circle", x1: 45, y1: 38, radius: 1 }, { type: "circle", x1: 55, y1: 38, radius: 1 }, // Tentáculos (más curvos y con ventosas) { x1: 40, y1: 55, x2: 30, y2: 65 }, { x1: 30, y2: 65, x2: 35, y2: 70 }, { x1: 35, y2: 70, x2: 40, y2: 60 }, // Tentáculo 1 { type: "circle", x1: 33, y1: 67, radius: 0.5 }, { type: "circle", x1: 37, y1: 62, radius: 0.5 }, // Ventosas { x1: 50, y1: 55, x2: 45, y2: 65 }, { x1: 45, y2: 65, x2: 50, y2: 70 }, { x1: 50, y2: 70, x2: 55, y2: 65 }, { x1: 55, y2: 65, x2: 50, y2: 55 }, // Tentáculo 2 (frontal) { type: "circle", x1: 47, y1: 67, radius: 0.5 }, { type: "circle", x1: 53, y1: 67, radius: 0.5 }, { x1: 60, y1: 55, x2: 70, y2: 65 }, { x1: 70, y2: 65, x2: 65, y2: 70 }, { x1: 65, y2: 70, x2: 60, y2: 60 }, // Tentáculo 3 { type: "circle", x1: 67, y1: 67, radius: 0.5 }, { type: "circle", x1: 63, y1: 62, radius: 0.5 } ], "CANGREJO": [ // Cuerpo (más ancho y con textura) { x1: 30, y1: 60, x2: 70, y2: 60 }, { x1: 30, y1: 60, x2: 25, y2: 55 }, { x1: 70, y1: 60, x2: 75, y2: 55 }, { x1: 25, y1: 55, x2: 75, y2: 55 }, // Parte superior { x1: 30, y1: 60, x2: 30, y2: 65 }, { x1: 70, y1: 60, x2: 70, y2: 65 }, { x1: 30, y1: 65, x2: 70, y2: 65 }, // Base del cuerpo // Ojos tallo (stalk eyes) { x1: 40, y1: 55, x2: 40, y2: 45 }, { type: "circle", x1: 40, y1: 45, radius: 1 }, { x1: 60, y1: 55, x2: 60, y2: 45 }, { type: "circle", x1: 60, y1: 45, radius: 1 }, // Patas (varias y articuladas) { x1: 25, y1: 60, x2: 20, y2: 65 }, { x1: 20, y2: 65, x2: 25, y2: 70 }, // Pata 1 { x1: 35, y1: 65, x2: 30, y2: 70 }, { x1: 30, y2: 70, x2: 35, y2: 75 }, // Pata 2 { x1: 75, y1: 60, x2: 80, y2: 65 }, { x1: 80, y2: 65, x2: 75, y2: 70 }, // Pata 3 { x1: 65, y1: 65, x2: 70, y2: 70 }, { x1: 70, y2: 70, x2: 65, y2: 75 }, // Pata 4 // Pinzas (más grandes y con detalles) { x1: 25, y1: 55, x2: 15, y2: 50 }, { x1: 15, y1: 50, x2: 20, y2: 45 }, { x1: 20, y1: 45, x2: 25, y2: 50 }, // Pinza izq (cierre) { x1: 18, y1: 50, x2: 23, y2: 50 }, // Detalle de cierre { x1: 75, y1: 55, x2: 85, y2: 50 }, { x1: 85, y1: 50, x2: 80, y2: 45 }, { x1: 80, y1: 45, x2: 75, y2: 50 } // Pinza der (cierre) ], "MEDUSA": [ // Cuerpo superior (forma de campana y con patrón) { x1: 50, y1: 40, x2: 35, y2: 45 }, { x1: 35, y1: 45, x2: 30, y2: 55 }, { x1: 30, y1: 55, x2: 40, y2: 60 }, { x1: 40, y1: 60, x2: 60, y2: 60 }, { x1: 60, y1: 60, x2: 70, y2: 55 }, { x1: 70, y1: 55, x2: 65, y2: 45 }, { x1: 65, y1: 45, x2: 50, y2: 40 }, // Contorno de la campana { x1: 40, y1: 48, x2: 60, y2: 48 }, // Línea interna // Tentáculos (más numerosos y ondulados) { x1: 40, y1: 60, x2: 35, y2: 70 }, { x1: 35, y2: 70, x2: 40, y2: 75 }, // Tentáculo 1 { x1: 50, y1: 60, x2: 45, y2: 70 }, { x1: 45, y2: 70, x2: 50, y2: 75 }, // Tentáculo 2 { x1: 60, y1: 60, x2: 65, y2: 70 }, { x1: 65, y2: 70, x2: 60, y2: 75 }, // Tentáculo 3 { x1: 42, y1: 60, x2: 38, y2: 68 }, { x1: 38, y2: 68, x2: 42, y2: 72 }, // Tentáculo 4 (intermedio) { x1: 58, y1: 60, x2: 62, y2: 68 }, { x1: 62, y2: 68, x2: 58, y2: 72 } // Tentáculo 5 (intermedio) ], "DINOSAURIO": [ // Contorno de dinosaurio (cuello largo, cuerpo y patas robustas) { x1: 20, y1: 70, x2: 30, y2: 65 }, { x1: 30, y1: 65, x2: 40, y2: 68 }, // Pierna delantera { x1: 40, y1: 68, x2: 60, y2: 68 }, // Vientre { x1: 60, y1: 68, x2: 70, y2: 65 }, { x1: 70, y1: 65, x2: 75, y2: 70 }, // Pierna trasera y cola { x1: 75, y1: 70, x2: 80, y2: 65 }, { x1: 80, y1: 65, x2: 75, y2: 60 }, // Cola { x1: 60, y1: 68, x2: 65, y2: 55 }, { x1: 65, y1: 55, x2: 60, y2: 50 }, // Lomo { x1: 60, y1: 50, x2: 50, y2: 40 }, { x1: 50, y1: 40, x2: 45, y2: 35 }, { x1: 45, y1: 35, x2: 40, y2: 40 }, // Cuello { x1: 40, y1: 40, x2: 35, y2: 45 }, { x1: 35, y1: 45, x2: 30, y2: 50 }, { x1: 30, y1: 50, x2: 30, y2: 65 } // Cabeza y cuello ], "DRAGON": [ // Cuerpo (más musculoso y con escamas) { x1: 30, y1: 60, x2: 70, y2: 60 }, { x1: 70, y1: 60, x2: 65, y2: 40 }, { x1: 65, y1: 40, x2: 35, y2: 40 }, { x1: 35, y1: 40, x2: 30, y2: 60 }, { x1: 35, y1: 45, x2: 40, y2: 48 }, { x1: 45, y1: 45, x2: 50, y2: 48 }, // Escamas (simuladas) // Alas (más grandes y membranosas) { x1: 40, y1: 40, x2: 30, y2: 30 }, { x1: 30, y1: 30, x2: 40, y2: 25 }, { x1: 40, y1: 25, x2: 45, y2: 30 }, { x1: 45, y1: 30, x2: 40, y2: 40 }, // Ala izq { x1: 60, y1: 40, x2: 70, y2: 30 }, { x1: 70, y1: 30, x2: 60, y2: 25 }, { x1: 60, y1: 25, x2: 55, y2: 30 }, { x1: 55, y1: 30, x2: 60, y2: 40 }, // Ala der // Cola (larga y puntiaguda) { x1: 70, y1: 60, x2: 80, y2: 55 }, { x1: 80, y1: 55, x2: 85, y2: 60 }, { x1: 85, y1: 60, x2: 80, y2: 65 }, { x1: 80, y1: 65, x2: 75, y2: 60 }, // Cabeza y cuernos { x1: 35, y1: 40, x2: 30, y2: 35 }, { x1: 30, y2: 35, x2: 35, y2: 30 }, // Cuerno izq { x1: 65, y1: 40, x2: 70, y2: 35 }, { x1: 70, y2: 35, x2: 65, y2: 30 } // Cuerno der ], "UNICORNIO": [ // Cuerpo de caballo (elegante y estilizado) { x1: 50, y1: 70, x2: 35, y2: 60 }, { x1: 35, y1: 60, x2: 30, y2: 45 }, // Cuello y pecho { x1: 30, y1: 45, x2: 35, y2: 35 }, { x1: 35, y1: 35, x2: 45, y2: 30 }, // Cabeza { x1: 45, y1: 30, x2: 55, y2: 30 }, { x1: 55, y1: 30, x2: 65, y2: 35 }, { x1: 65, y1: 35, x2: 70, y2: 45 }, { x1: 70, y1: 45, x2: 65, y2: 60 }, // Lomo { x1: 65, y1: 60, x2: 50, y2: 70 }, // Cuerno (espiral y puntiagudo) { x1: 50, y1: 30, x2: 50, y2: 15 }, { x1: 48, y1: 15, x2: 52, y2: 15 }, // Punta { x1: 49, y1: 20, x2: 51, y2: 20 }, { x1: 49, y1: 25, x2: 51, y2: 25 }, // Espiral // Melena (líneas fluidas) { x1: 45, y1: 30, x2: 40, y2: 25 }, { x1: 40, y2: 25, x2: 35, y2: 30 } ], "SIRENA": [ // Cuerpo (más curvado y femenino) { x1: 50, y1: 40, x2: 45, y2: 50 }, { x1: 45, y1: 50, x2: 50, y2: 60 }, // Torso { x1: 50, y1: 40, x2: 55, y2: 50 }, { x1: 55, y1: 50, x2: 50, y2: 60 }, // Cola de pez (escamas y aleta bifurcada) { x1: 50, y1: 60, x2: 45, y2: 70 }, { x1: 45, y1: 70, x2: 55, y2: 70 }, { x1: 55, y1: 70, x2: 50, y2: 60 }, { x1: 45, y1: 70, x2: 40, y2: 75 }, { x1: 40, y1: 75, x2: 45, y2: 80 }, { x1: 45, y1: 80, x2: 50, y2: 70 }, // Aleta inferior { x1: 55, y1: 70, x2: 60, y2: 75 }, { x1: 60, y1: 75, x2: 55, y2: 80 }, { x1: 55, y1: 80, x2: 50, y2: 70 }, // Pelo largo { x1: 45, y1: 40, x2: 40, y2: 30 }, { x1: 40, y2: 30, x2: 35, y2: 45 } ], "HADA": [ // Cuerpo (más delicado) { x1: 50, y1: 40, x2: 50, y2: 60 }, { x1: 45, y1: 40, x2: 55, y2: 40 }, // Hombros // Alas (transparentes, con venas y formas más orgánicas) { x1: 40, y1: 50, x2: 30, y2: 45 }, { x1: 30, y1: 45, x2: 40, y2: 40 }, { x1: 40, y1: 40, x2: 45, y2: 45 }, { x1: 45, y1: 45, x2: 40, y2: 50 }, // Ala superior izq { x1: 35, y1: 50, x2: 25, y2: 55 }, { x1: 25, y1: 55, x2: 35, y2: 60 }, { x1: 35, y1: 60, x2: 40, y2: 55 }, { x1: 40, y2: 55, x2: 35, y2: 50 }, // Ala inferior izq { x1: 60, y1: 50, x2: 70, y2: 45 }, { x1: 70, y1: 45, x2: 60, y2: 40 }, { x1: 60, y1: 40, x2: 55, y2: 45 }, { x1: 55, y1: 45, x2: 60, y2: 50 }, // Ala superior der { x1: 65, y1: 50, x2: 75, y2: 55 }, { x1: 75, y1: 55, x2: 65, y2: 60 }, { x1: 65, y1: 60, x2: 60, y2: 55 }, { x1: 60, y2: 55, x2: 65, y2: 50 }, // Ala inferior der // Varita mágica (con estrella pequeña) { x1: 60, y1: 40, x2: 70, y2: 35 }, { x1: 68, y1: 35, x2: 70, y2: 30 }, { x1: 70, y2: 30, x2: 72, y2: 35 }, { x1: 72, y2: 35, x2: 70, y2: 35 } ], "MAGO": [ // Cuerpo (con túnica) { x1: 50, y1: 40, x2: 50, y2: 60 }, { x1: 40, y1: 60, x2: 60, y2: 60 }, { x1: 40, y1: 60, x2: 35, y2: 75 }, { x1: 60, y1: 60, x2: 65, y2: 75 }, { x1: 35, y1: 75, x2: 65, y2: 75 }, // Sombrero de punta (más detallado) { x1: 40, y1: 60, x2: 60, y2: 60 }, // Base del sombrero { x1: 40, y1: 60, x2: 45, y2: 40 }, { x1: 60, y1: 60, x2: 55, y2: 40 }, // Lados { x1: 45, y1: 40, x2: 50, y2: 25 }, { x1: 50, y1: 25, x2: 55, y2: 40 }, // Parte superior puntiaguda // Varita mágica (con un brillo en la punta) { x1: 65, y1: 50, x2: 75, y2: 45 }, { type: "circle", x1: 75, y1: 45, radius: 1.5 } // Brillo ], "GUERRERO": [ // Armadura básica (torso, hombreras, piernas) { x1: 50, y1: 40, x2: 50, y2: 60 }, { x1: 40, y1: 60, x2: 60, y2: 60 }, { x1: 40, y1: 60, x2: 40, y2: 75 }, { x1: 60, y1: 60, x2: 60, y2: 75 }, { x1: 40, y1: 75, x2: 60, y2: 75 }, // Piernas // Yelmo (con visera) { x1: 45, y1: 40, x2: 55, y2: 40 }, { x1: 45, y1: 40, x2: 40, y2: 35 }, { x1: 55, y1: 40, x2: 60, y2: 35 }, // Base del yelmo { x1: 40, y1: 35, x2: 60, y2: 35 }, { x1: 45, y1: 38, x2: 55, y2: 38 }, // Visera // Hombros (con forma de armadura) { x1: 40, y1: 45, x2: 35, y2: 50 }, { x1: 35, y2: 50, x2: 40, y2: 55 }, { x1: 60, y1: 45, x2: 65, y2: 50 }, { x1: 65, y2: 50, x2: 60, y2: 55 } ], "CABALLERO": [ // Armadura (con más detalles y grebas) { x1: 50, y1: 40, x2: 50, y2: 60 }, { x1: 40, y1: 60, x2: 60, y2: 60 }, { x1: 40, y1: 60, x2: 40, y2: 75 }, { x1: 60, y1: 60, x2: 60, y2: 75 }, { x1: 40, y1: 75, x2: 60, y2: 75 }, // Grebas { x1: 42, y1: 65, x2: 58, y2: 65 }, // Rodillera (línea) // Yelmo (con pluma) { x1: 45, y1: 40, x2: 55, y2: 40 }, { x1: 45, y1: 40, x2: 40, y2: 35 }, { x1: 55, y1: 40, x2: 60, y2: 35 }, { x1: 40, y1: 35, x2: 60, y2: 35 }, { x1: 50, y1: 35, x2: 50, y2: 25 }, { x1: 48, y1: 25, x2: 52, y2: 25 }, // Pluma // Espada (con guarda y pomo) { x1: 65, y1: 50, x2: 75, y2: 40 }, { x1: 75, y1: 40, x2: 78, y2: 43 }, { x1: 78, y2: 43, x2: 68, y2: 53 }, // Hoja { x1: 65, y1: 50, x2: 68, y2: 50 }, { x1: 68, y1: 50, x2: 68, y2: 53 }, { x1: 68, y1: 53, x2: 65, y2: 53 } // Guarda ], "PRINCESA": [ // Cuerpo (más curvado) { x1: 50, y1: 40, x2: 50, y2: 60 }, { x1: 45, y1: 40, x2: 55, y2: 40 }, // Hombros // Vestido (más voluminoso y con pliegues) { x1: 40, y1: 60, x2: 60, y2: 60 }, { x1: 35, y1: 60, x2: 40, y2: 70 }, { x1: 65, y1: 60, x2: 60, y2: 70 }, { x1: 35, y1: 70, x2: 65, y2: 70 }, { x1: 45, y1: 65, x2: 50, y2: 68 }, { x1: 50, y1: 68, x2: 55, y2: 65 }, // Pliegues // Corona (con gemas) { x1: 45, y1: 35, x2: 55, y2: 35 }, { x1: 48, y1: 35, x2: 50, y2: 30 }, { x1: 50, y1: 30, x2: 52, y2: 35 }, { type: "circle", x1: 48, y1: 32, radius: 0.5 }, { type: "circle", x1: 52, y1: 32, radius: 0.5 } // Gemas ], "REINA": [ // Cuerpo (similar a princesa, pero más formal) { x1: 50, y1: 40, x2: 50, y2: 60 }, { x1: 45, y1: 40, x2: 55, y2: 40 }, // Vestido (más regio y amplio) { x1: 40, y1: 60, x2: 60, y2: 60 }, { x1: 35, y1: 60, x2: 40, y2: 70 }, { x1: 65, y1: 60, x2: 60, y2: 70 }, { x1: 35, y1: 70, x2: 65, y2: 70 }, { x1: 40, y1: 65, x2: 45, y2: 68 }, { x1: 55, y1: 68, x2: 60, y2: 65 }, // Pliegues // Corona grande (con más picos y gemas) { x1: 40, y1: 35, x2: 60, y2: 35 }, { x1: 42, y1: 35, x2: 45, y2: 30 }, { x1: 45, y1: 30, x2: 50, y2: 32 }, { x1: 50, y1: 32, x2: 55, y2: 30 }, { x1: 55, y1: 30, x2: 58, y2: 35 }, { type: "circle", x1: 45, y1: 32, radius: 0.8 }, { type: "circle", x1: 50, y1: 30, radius: 0.8 }, { type: "circle", x1: 55, y1: 32, radius: 0.8 } // Gemas ], "REY": [ // Cuerpo (robusto y con capa) { x1: 50, y1: 40, x2: 50, y2: 60 }, { x1: 40, y1: 60, x2: 60, y2: 60 }, { x1: 35, y1: 45, x2: 30, y2: 60 }, { x1: 30, y2: 60, x2: 35, y2: 70 }, // Capa izquierda { x1: 65, y1: 45, x2: 70, y2: 60 }, { x1: 70, y2: 60, x2: 65, y2: 70 }, // Capa derecha { x1: 40, y1: 70, x2: 60, y2: 70 }, // Base // Corona (con cruces pequeñas y gemas) { x1: 45, y1: 35, x2: 55, y2: 35 }, { x1: 48, y1: 35, x2: 50, y2: 30 }, { x1: 50, y1: 30, x2: 52, y2: 35 }, { type: "circle", x1: 48, y1: 32, radius: 0.5 }, { type: "circle", x1: 52, y1: 32, radius: 0.5 }, { x1: 47, y1: 37, x2: 47, y2: 39 }, { x1: 46, y1: 38, x2: 48, y2: 38 }, // Cruz izq { x1: 53, y1: 37, x2: 53, y2: 39 }, { x1: 52, y1: 38, x2: 54, y2: 38 }, // Cruz der // Cetro (más detallado) { x1: 65, y1: 50, x2: 75, y2: 40 }, { x1: 75, y1: 40, x2: 78, y2: 43 }, { x1: 78, y2: 43, x2: 75, y2: 46 }, { x1: 75, y2: 46, x2: 65, y2: 56 } ], "CASTILLO": [ // Base (con textura de piedra) { x1: 20, y1: 70, x2: 80, y2: 70 }, { x1: 20, y1: 70, x2: 20, y2: 40 }, { x1: 80, y1: 70, x2: 80, y2: 40 }, { x1: 20, y1: 40, x2: 80, y2: 40 }, { x1: 25, y1: 65, x2: 30, y2: 68 }, { x1: 35, y1: 65, x2: 40, y2: 68 }, // Textura de piedra // Almenas (más definidas y con grosor) { x1: 20, y1: 40, x2: 20, y2: 35 }, { x1: 25, y1: 35, x2: 25, y2: 40 }, { x1: 20, y1: 35, x2: 25, y2: 35 }, { x1: 30, y1: 40, x2: 30, y2: 35 }, { x1: 35, y1: 35, x2: 35, y2: 40 }, { x1: 30, y1: 35, x2: 35, y2: 35 }, { x1: 40, y1: 40, x2: 40, y2: 35 }, { x1: 45, y1: 35, x2: 45, y2: 40 }, { x1: 40, y1: 35, x2: 45, y2: 35 }, { x1: 75, y1: 40, x2: 75, y2: 35 }, { x1: 80, y1: 35, x2: 80, y2: 40 }, { x1: 75, y1: 35, x2: 80, y2: 35 }, // Torre central (más alta y con almenas) { x1: 50, y1: 40, x2: 50, y2: 20 }, { x1: 60, y1: 40, x2: 60, y2: 20 }, { x1: 50, y1: 20, x2: 60, y2: 20 }, { x1: 50, y1: 20, x2: 50, y2: 15 }, { x1: 55, y1: 15, x2: 55, y2: 20 }, { x1: 50, y1: 15, x2: 55, y2: 15 } // Almenas de torre ], "ESPADA": [ // Hoja (Afilada y con canal central) { x1: 50, y1: 10, x2: 45, y2: 15 }, { x1: 50, y1: 10, x2: 55, y2: 15 }, // Punta { x1: 45, y1: 15, x2: 45, y2: 60 }, { x1: 55, y1: 15, x2: 55, y2: 60 }, { x1: 50, y1: 15, x2: 50, y2: 60 }, // Canal central (sangradera) // Guarda (Cross-guard más elaborada) { x1: 40, y1: 60, x2: 60, y2: 60 }, { x1: 40, y1: 60, x2: 35, y2: 63 }, { x1: 60, y1: 60, x2: 65, y2: 63 }, { x1: 45, y1: 60, x2: 45, y2: 65 }, { x1: 55, y1: 60, x2: 55, y2: 65 }, { x1: 45, y1: 65, x2: 55, y2: 65 }, // Línea horizontal que une la guarda con la empuñadura (added) // Empuñadura (Grip con textura) { x1: 48, y1: 65, x2: 48, y2: 80 }, { x1: 52, y1: 65, x2: 52, y2: 80 }, { x1: 48, y1: 70, x2: 52, y2: 70 }, { x1: 48, y1: 75, x2: 52, y2: 75 }, // Textura // Pomo (Pommel) { type: "circle", x1: 50, y1: 82, radius: 3 } ], "ESCUDO": [ // Escudo (con borde y forma más orgánica) { x1: 30, y1: 40, x2: 70, y2: 40 }, { x1: 70, y1: 40, x2: 70, y2: 60 }, { x1: 70, y1: 60, x2: 50, y2: 70 }, { x1: 50, y1: 70, x2: 30, y2: 60 }, { x1: 30, y1: 60, x2: 30, y2: 40 }, { x1: 32, y1: 42, x2: 68, y2: 42 }, { x1: 68, y1: 42, x2: 68, y2: 58 }, // Borde interior { x1: 68, y1: 58, x2: 50, y2: 68 }, { x1: 50, y1: 68, x2: 32, y2: 58 }, { x1: 32, y1: 58, x2: 32, y2: 42 }, // Emblema (cruz con centro) { x1: 40, y1: 45, x2: 60, y2: 45 }, { x1: 50, y1: 45, x2: 50, y2: 65 }, { type: "circle", x1: 50, y1: 55, radius: 3 } // Centro de la cruz ], "ARCO": [ // Arco (con grosor y curvatura) { x1: 30, y1: 60, x2: 50, y2: 30 }, { x1: 50, y1: 30, x2: 70, y2: 60 }, { x1: 32, y1: 58, x2: 50, y2: 32 }, { x1: 50, y1: 32, x2: 68, y2: 58 }, // Grosor // Cuerda (más fina y tensa) { x1: 30, y1: 60, x2: 70, y2: 60 }, // Flecha (con punta y plumas) { x1: 50, y1: 45, x2: 80, y2: 45 }, // Cuerpo de la flecha { x1: 75, y1: 42, x2: 80, y2: 45 }, { x1: 80, y1: 45, x2: 75, y2: 48 }, // Punta { x1: 25, y1: 45, x2: 20, y2: 42 }, { x1: 20, y2: 42, x2: 20, y2: 48 }, { x1: 20, y2: 48, x2: 25, y2: 45 } // Plumas ], "FLECHA": [ // Cuerpo de la flecha (más detallado) { x1: 20, y1: 50, x2: 80, y2: 50 }, { x1: 20, y1: 50, x2: 22, y2: 48 }, { x1: 20, y1: 50, x2: 22, y2: 52 }, // Base del asta // Punta (más aguda y con base) { x1: 75, y1: 45, x2: 80, y2: 50 }, { x1: 80, y1: 50, x2: 75, y2: 55 }, { x1: 75, y1: 45, x2: 75, y2: 55 }, // Base de la punta // Cola (plumas, más detalladas y en ángulo) { x1: 25, y1: 45, x2: 20, y2: 40 }, { x1: 20, y1: 40, x2: 20, y2: 50 }, { x1: 20, y1: 50, x2: 25, y2: 55 }, { x1: 25, y1: 55, x2: 20, y2: 60 }, { x1: 20, y2: 60, x2: 20, y2: 50 }, { x1: 20, y2: 50, x2: 25, y2: 45 } ], "POCION": [ // Frasco (más curvo y con etiqueta) { x1: 40, y1: 70, x2: 60, y2: 70 }, // Base { x1: 40, y1: 70, x2: 38, y2: 55 }, { x1: 60, y1: 70, x2: 62, y2: 55 }, // Curvas del cuerpo { x1: 38, y1: 55, x2: 38, y2: 45 }, { x1: 62, y1: 55, x2: 62, y2: 45 }, // Lados del cuerpo // Cuello { x1: 45, y1: 45, x2: 45, y2: 35 }, { x1: 55, y1: 45, x2: 55, y2: 35 }, // Boca y tapón { x1: 48, y1: 35, x2: 52, y2: 35 }, { x1: 48, y1: 35, x2: 48, y2: 30 }, { x1: 52, y1: 35, x2: 52, y2: 30 }, // Tapón { x1: 48, y1: 30, x2: 52, y2: 30 }, // Etiqueta (rectángulo en el cuerpo) { x1: 42, y1: 58, x2: 58, y2: 58 }, { x1: 42, y1: 58, x2: 42, y2: 65 }, { x1: 58, y1: 58, x2: 58, y2: 65 }, { x1: 42, y1: 65, x2: 58, y2: 65 } ], "ANILLO": [ // Anillo (con grosor y más forma) { type: "circle", x1: 50, y1: 50, radius: 20 }, { type: "circle", x1: 50, y1: 50, radius: 18 }, // Grosor del anillo // Gema (más facetada y con brillo) { x1: 45, y1: 35, x2: 50, y2: 30 }, { x1: 50, y1: 30, x2: 55, y2: 35 }, { x1: 55, y1: 35, x2: 45, y2: 35 }, // Forma de diamante { x1: 50, y1: 30, x2: 48, y2: 33 }, { x1: 50, y1: 30, x2: 52, y2: 33 } // Facetas ], "COLLAR": [ // Cadena (más fina y con eslabones sutiles) { x1: 30, y1: 40, x2: 70, y2: 40 }, { x1: 32, y1: 40, x2: 32, y2: 42 }, { x1: 35, y1: 40, x2: 35, y2: 42 }, // Eslabones { x1: 65, y1: 40, x2: 65, y2: 42 }, { x1: 68, y1: 40, x2: 68, y2: 42 }, // Pendiente (más detallado) { x1: 45, y1: 40, x2: 50, y2: 50 }, { x1: 50, y1: 50, x2: 55, y2: 40 }, // Forma principal { x1: 50, y1: 50, x2: 50, y2: 60 }, { x1: 48, y1: 60, x2: 52, y2: 60 }, // Base del pendiente { x1: 47, y1: 52, x2: 53, y2: 52 } // Gema simulada ], "CORONA": [ // Base (con grosor) { x1: 30, y1: 60, x2: 70, y2: 60 }, { x1: 30, y1: 60, x2: 32, y2: 63 }, // Grosor de la base { x1: 70, y1: 60, x2: 68, y2: 63 }, { x1: 32, y1: 63, x2: 68, y2: 63 }, // Puntas (más agudas y con detalles) { x1: 30, y1: 60, x2: 35, y2: 45 }, { x1: 35, y1: 45, x2: 40, y2: 60 }, // Punta 1 { x1: 35, y1: 50, x2: 38, y2: 48 }, // Detalle de punta { x1: 45, y1: 60, x2: 50, y2: 45 }, { x1: 50, y1: 45, x2: 55, y2: 60 }, // Punta 2 { x1: 50, y1: 50, x2: 50, y2: 48 }, { x1: 60, y1: 60, x2: 65, y2: 45 }, { x1: 65, y1: 45, x2: 70, y2: 60 } // Punta 3 ], "GEMA": [ // Gema facetada (más compleja) { x1: 50, y1: 30, x2: 30, y2: 50 }, { x1: 30, y1: 50, x2: 50, y2: 70 }, { x1: 50, y1: 70, x2: 70, y2: 50 }, { x1: 70, y1: 50, x2: 50, y2: 30 }, // Contorno exterior // Facetas internas (líneas de división) { x1: 50, y1: 30, x2: 50, y2: 70 }, { x1: 30, y1: 50, x2: 70, y2: 50 }, { x1: 40, y1: 35, x2: 40, y2: 65 }, { x1: 60, y1: 35, x2: 60, y2: 65 }, { x1: 35, y1: 40, x2: 65, y2: 40 }, { x1: 35, y1: 60, x2: 65, y2: 60 }, // Brillo (pequeños puntos) { type: "circle", x1: 45, y1: 45, radius: 0.5 }, { type: "circle", x1: 55, y1: 45, radius: 0.5 } ], "TESORO": [ // Cofre (con grosor y tapa semi-abierta) { x1: 30, y1: 60, x2: 70, y2: 60 }, { x1: 30, y1: 60, x2: 30, y2: 40 }, { x1: 70, y1: 60, x2: 70, y2: 40 }, { x1: 30, y1: 40, x2: 70, y2: 40 }, // Cuerpo { x1: 32, y1: 62, x2: 68, y2: 62 }, { x1: 32, y1: 42, x2: 68, y2: 42 }, // Grosor del cuerpo { x1: 32, y1: 62, x2: 32, y2: 42 }, { x1: 68, y1: 62, x2: 68, y2: 42 }, // Tapa (curva y con bisagras) { x1: 30, y1: 40, x2: 35, y2: 35 }, { x1: 70, y1: 40, x2: 75, y2: 35 }, { x1: 35, y1: 35, x2: 75, y2: 35 }, // Borde de la tapa { x1: 30, y1: 40, x2: 50, y2: 30 }, { x1: 50, y1: 30, x2: 70, y2: 40 }, // Curva de la tapa // Cerradura (más detallada) { x1: 50, y1: 45, x2: 50, y2: 55 }, { x1: 48, y1: 55, x2: 52, y2: 55 }, // Base de la cerradura { type: "circle", x1: 50, y1: 50, radius: 1 } // Agujero de la cerradura ], "MONEDA": [ { type: "circle", x1: 50, y1: 50, radius: 20 }, // Contorno exterior { type: "circle", x1: 50, y1: 50, radius: 18 }, // Borde interior // Signo de dólar (con más detalle y sombreado) { x1: 48, y1: 40, x2: 48, y2: 60 }, { x1: 52, y1: 40, x2: 52, y2: 60 }, // Barras verticales { x1: 45, y1: 45, x2: 55, y2: 45 }, { x1: 45, y1: 55, x2: 55, y2: 55 }, // Barras horizontales { x1: 49, y1: 40, x2: 49, y2: 60 }, { x1: 51, y1: 40, x2: 51, y2: 60 }, // Sombreado del dólar // Textura (pequeños puntos en el fondo) { type: "circle", x1: 40, y1: 40, radius: 0.2 }, { type: "circle", x1: 60, y1: 40, radius: 0.2 }, { type: "circle", x1: 40, y1: 60, radius: 0.2 }, { type: "circle", x1: 60, y1: 60, radius: 0.2 } ], "MAPA": [ // Mapa enrollado (con relieve y textura de papel) { x1: 30, y1: 40, x2: 70, y2: 40 }, { x1: 70, y1: 40, x2: 70, y2: 60 }, { x1: 70, y1: 60, x2: 30, y2: 60 }, { x1: 30, y1: 60, x2: 30, y2: 40 }, // Contorno // Bordes enrollados (más gruesos y con pliegues) { x1: 25, y1: 40, x2: 30, y2: 45 }, { x1: 25, y1: 55, x2: 30, y2: 60 }, // Lado izq { x1: 25, y1: 40, x2: 25, y2: 60 }, { x1: 70, y1: 45, x2: 75, y2: 40 }, { x1: 70, y1: 55, x2: 75, y2: 60 }, // Lado der { x1: 75, y1: 40, x2: 75, y2: 60 }, // Detalles internos (líneas de relieve o continentes) { x1: 40, y1: 45, x2: 50, y2: 50 }, { x1: 50, y1: 50, x2: 60, y2: 45 }, // Contorno de tierra { x1: 40, y1: 55, x2: 50, y2: 52 }, { x1: 50, y1: 52, x2: 60, y2: 55 } ], "BRUJULA": [ { type: "circle", x1: 50, y1: 50, radius: 20 }, // Contorno exterior { type: "circle", x1: 50, y1: 50, radius: 18 }, // Borde interior // Aguja (más detallada, con dos colores simulados) { x1: 50, y1: 35, x2: 48, y2: 45 }, { x1: 48, y1: 45, x2: 50, y2: 50 }, // Punta norte { x1: 50, y1: 65, x2: 52, y2: 55 }, { x1: 52, y1: 55, x2: 50, y2: 50 }, // Punta sur // Marcas de dirección (N, S, E, O) { x1: 50, y1: 30, x2: 50, y2: 32 }, { x1: 50, y1: 68, x2: 50, y2: 70 }, { x1: 30, y1: 50, x2: 32, y2: 50 }, { x1: 68, y1: 50, x2: 70, y2: 50 } ], "PERGAMINO": [ // Pergamino enrollado (con textura de arrugas) { x1: 30, y1: 40, x2: 70, y2: 40 }, { x1: 70, y1: 40, x2: 70, y2: 60 }, { x1: 70, y1: 60, x2: 30, y2: 60 }, { x1: 30, y1: 60, x2: 30, y2: 40 }, // Cuerpo principal // Enrollado (más detallado) { x1: 25, y1: 40, x2: 30, y2: 45 }, { x1: 25, y1: 55, x2: 30, y2: 60 }, // Lado izq { x1: 25, y1: 40, x2: 25, y2: 60 }, { x1: 28, y1: 43, x2: 28, y2: 57 }, // Interior del enrollado { x1: 70, y1: 45, x2: 75, y2: 40 }, { x1: 70, y1: 55, x2: 75, y2: 60 }, // Lado der { x1: 75, y1: 40, x2: 75, y2: 60 }, { x1: 72, y1: 43, x2: 72, y2: 57 }, // Texto simulado (líneas horizontales cortas) { x1: 35, y1: 48, x2: 65, y2: 48 }, { x1: 35, y1: 52, x2: 65, y2: 52 } ], "ANTORCHA": [ // Palo (con textura de madera y más irregular) { x1: 50, y1: 80, x2: 50, y2: 60 }, { x1: 48, y1: 80, x2: 52, y2: 80 }, // Grosor de la base { x1: 49, y1: 75, x2: 49, y2: 70 }, { x1: 51, y1: 75, x2: 51, y2: 70 }, // Nudos de madera { x1: 47, y1: 65, x2: 47, y2: 60 }, { x1: 53, y1: 65, x2: 53, y2: 60 }, // Base de la llama (con tela) { x1: 45, y1: 60, x2: 55, y2: 60 }, { x1: 45, y1: 60, x2: 42, y2: 58 }, { x1: 55, y1: 60, x2: 58, y2: 58 }, // Pliegues de tela // Llama (más dinámica y con múltiples picos, simulando transparencia) { x1: 50, y1: 50, x2: 45, y2: 40 }, { x1: 45, y1: 40, x2: 50, y2: 30 }, { x1: 50, y1: 30, x2: 55, y2: 40 }, { x1: 55, y1: 40, x2: 50, y2: 50 }, // Contorno principal { x1: 48, y1: 45, x2: 50, y2: 40 }, { x1: 50, y1: 40, x2: 52, y2: 45 } // Llama interior ] }; class IntelligentArtist extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); // Valores por defecto #currentBrushSize = 5; #currentSketchColor = "#888888"; constructor() { super("Artista Inteligente (necesita bot)", '<i class="fas fa-brain"></i>'); this.#onStartup(); } #onStartup() { this.#loadInterface(); } #loadInterface() { this.#row1(); // Generación de Bocetos Asistida this.#row2(); // Limpiar Lienzo this.#row3(); // Configuración de Color y Tamaño del Boceto this.#row4(); // Lista de Bocetos Disponibles (con slider) } #row1() { const row = domMake.Row(); { const sketchInput = domMake.Tree("input", { type: "text", placeholder: "Concepto de boceto (ej. 'árbol')" }); const generateSketchButton = domMake.Button("Generar Boceto"); generateSketchButton.title = "Dibuja un boceto predefinido para la palabra ingresada."; generateSketchButton.addEventListener("click", () => { this.simulateAISketch(sketchInput.value.toUpperCase()); // Convertir a mayúsculas }); row.appendAll(sketchInput, generateSketchButton); } this.htmlElements.section.appendChild(row); } #row2() { const row = domMake.Row(); { const clearCanvasButton = domMake.Button("Limpiar Lienzo"); clearCanvasButton.title = "Limpia el lienzo con una línea blanca muy grande."; clearCanvasButton.addEventListener("click", () => { const botManager = this.findGlobal("BotClientManager")?.siblings[0]; if (botManager && botManager.children.length > 0) { const activeBot = botManager.children[0].bot; // Usar el primer bot activo if (activeBot && activeBot.getReadyState()) { // Dibuja dos líneas blancas que cubren todo el canvas con un grosor muy grande activeBot.emit("line", -1, 0, 0, 100, 100, true, 900, "#FFFFFF", false); // Línea diagonal 1 activeBot.emit("line", -1, 100, 0, 0, 100, true, 900, "#FFFFFF", false); // Línea diagonal 2 this.notify("success", "El lienzo ha sido limpiado."); } else { this.notify("warning", "El bot seleccionado no está conectado."); } } else { this.notify("warning", "Se necesita al menos 1 bot activo para limpiar el lienzo."); } }); row.appendChild(clearCanvasButton); } this.htmlElements.section.appendChild(row); } #row3() { const row = domMake.Row(); { const sketchColorLabel = domMake.Tree("label", {}, ["Color Boceto:"]); const sketchColorInput = domMake.Tree("input", { type: "color", value: this.#currentSketchColor }); sketchColorInput.title = "Define el color del boceto."; sketchColorInput.addEventListener("change", (e) => { this.#currentSketchColor = e.target.value; this.notify("info", `Color del boceto cambiado a: ${this.#currentSketchColor}`); }); const brushSizeLabel = domMake.Tree("label", {}, ["Tamaño Pincel:"]); const brushSizeInput = domMake.Tree("input", { type: "number", min: 1, max: 50, value: this.#currentBrushSize }); brushSizeInput.title = "Define el tamaño del pincel para el boceto."; brushSizeInput.addEventListener("change", (e) => { this.#currentBrushSize = parseInt(e.target.value); this.notify("info", `Tamaño del pincel para boceto cambiado a: ${this.#currentBrushSize}`); }); row.appendAll(sketchColorLabel, sketchColorInput, brushSizeLabel, brushSizeInput); } this.htmlElements.section.appendChild(row); } #row4() { const row = domMake.Row(); // Este row contendrá la etiqueta y el contenedor con scroll const sketchListContainer = domMake.IconList(); // IconList es un flex container sketchListContainer.classList.add('nowrap'); // Clase para forzar no-wrap y añadir scroll horizontal // Crear botones para cada palabra en la base de datos Object.keys(SKETCH_DATABASE).forEach(word => { const sketchButton = domMake.Button(word); sketchButton.title = `Generar boceto para: ${word}`; sketchButton.style.flex = '0 0 auto'; // Impide que los botones se estiren y ocupen todo el ancho disponible sketchButton.style.margin = '2px'; sketchButton.style.padding = '2px 5px'; sketchButton.style.fontSize = '0.7em'; sketchButton.addEventListener("click", () => { this.simulateAISketch(word); }); sketchListContainer.appendChild(sketchButton); }); // Añadir la etiqueta y el contenedor de scroll al row principal de esta sección row.appendAll(domMake.Tree("label", {}, ["Bocetos Rápidos (50):"]), sketchListContainer); this.htmlElements.section.appendChild(row); } simulateAISketch(concept) { const sketchData = SKETCH_DATABASE[concept]; if (!sketchData) { this.notify("warning", `Boceto no disponible para: "${concept}".`); return; } this.notify("info", `Generando boceto para: "${concept}" (conceptual).`); const botManager = this.findGlobal("BotClientManager")?.siblings[0]; if (!botManager || botManager.children.length === 0) { this.notify("warning", "Se necesita al menos 1 bot activo para generar bocetos."); return; } const activeBot = botManager.children[0].bot; // Usar el primer bot activo if (!activeBot || !activeBot.getReadyState()) { this.notify("warning", "El bot seleccionado no está conectado."); return; } this.notify("info", "Dibujando el boceto conceptual con el bot..."); sketchData.forEach(line => { if (line.type === "circle") { // Simular un círculo con múltiples líneas pequeñas const centerX = line.x1; const centerY = line.y1; const radius = line.radius; const segments = 24; // Más segmentos para un círculo más suave for (let i = 0; i < segments; i++) { const angle1 = (i / segments) * Math.PI * 2; const angle2 = ((i + 1) / segments) * Math.PI * 2; const x1_circ = centerX + radius * Math.cos(angle1); const y1_circ = centerY + radius * Math.sin(angle1); const x2_circ = centerX + radius * Math.cos(angle2); const y2_circ = centerY + radius * Math.sin(angle2); activeBot.emit("line", -1, x1_circ, y1_circ, x2_circ, y2_circ, true, this.#currentBrushSize, this.#currentSketchColor, false); } } else { activeBot.emit("line", -1, line.x1, line.y1, line.x2, line.y2, true, this.#currentBrushSize, this.#currentSketchColor, false); } }); this.notify("success", `Boceto de "${concept}" dibujado. ¡Ahora puedes calcarlo o mejorarlo!`); } } })("QBit"); // Drawing Assistant Module // This module provides drawing tools like grid, symmetry, and pixel-perfect drawing on an overlay canvas (function DrawingAssistantModule() { const QBit = globalThis[arguments[0]]; // Corrected access to QBit // Styles for the overlay canvas and UI elements QBit.Styles.addRules([ `#drawing-assistant-overlay { position: absolute; top: 0; left: 0; z-index: 1000; /* Above game canvas, below main UI elements */ pointer-events: none; /* Crucial to allow clicks to pass through to game canvas */ }`, `#drawing-assistant-grid-toggle.active { background-color: var(--info); color: white; }`, `#drawing-assistant-symmetry-toggle.active { background-color: var(--info); color: white; }`, `#drawing-assistant-pixel-perfect-toggle.active { background-color: var(--info); color: white; }`, `#drawing-assistant-controls input[type="number"], #drawing-assistant-controls input[type="color"] { width: 100%; padding: 5px; box-sizing: border-box; border: 1px solid var(--CE-color); border-radius: .25rem; background-color: var(--CE-bg_color); color: var(--CE-color); }`, `#drawing-assistant-controls ._row > * { margin: 0 2px; }` ]); class DrawingAssistant extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); #overlayCanvas; #overlayCtx; #gameCanvas; // Reference to the actual game canvas #isSymmetryActive = false; #isPixelPerfectActive = false; #isGridVisible = false; #drawingColor = "#000000"; // Default drawing color #drawingThickness = 5; // Default drawing thickness // Event listeners (need to manage their lifecycle) #mouseMoveListener = null; #mouseDownListener = null; #mouseUpListener = null; constructor() { super("Asistente de Dibujo", '<i class="fas fa-drafting-compass"></i>'); this.#onStartup(); } #onStartup() { this.#findGameCanvas(); this.#setupOverlayCanvas(); this.#loadInterface(); this.#hookGameDrawingEvents(); this.notify("info", "Asistente de Dibujo cargado. Asegúrate de tener un bot activo para usar las herramientas de dibujo."); } #findGameCanvas() { this.#gameCanvas = document.getElementById('canvas'); if (!this.#gameCanvas) { this.notify("error", "Canvas del juego no encontrado. Algunas funciones de dibujo no estarán disponibles."); } } #setupOverlayCanvas() { this.#overlayCanvas = domMake.Tree('canvas', { id: 'drawing-assistant-overlay' }); if (this.#gameCanvas) { this.#overlayCanvas.width = this.#gameCanvas.width; this.#overlayCanvas.height = this.#gameCanvas.height; this.#overlayCanvas.style.width = this.#gameCanvas.style.width; // Match CSS size this.#overlayCanvas.style.height = this.#gameCanvas.style.height; this.#gameCanvas.parentNode.insertBefore(this.#overlayCanvas, this.#gameCanvas.nextSibling); // Place right after game canvas } else { // Fallback dimensions, but core functions won't work well without game canvas this.#overlayCanvas.width = 1000; this.#overlayCanvas.height = 1000; this.#overlayCanvas.style.width = '700px'; this.#overlayCanvas.style.height = '700px'; document.body.appendChild(this.#overlayCanvas); } this.#overlayCtx = this.#overlayCanvas.getContext('2d'); this.#updateOverlaySizeAndPosition(); // Handle window resize to keep overlay aligned window.addEventListener('resize', this.#updateOverlaySizeAndPosition.bind(this)); } #updateOverlaySizeAndPosition() { if (!this.#gameCanvas || !this.#overlayCanvas) return; const gameCanvasRect = this.#gameCanvas.getBoundingClientRect(); this.#overlayCanvas.style.top = `${gameCanvasRect.top}px`; this.#overlayCanvas.style.left = `${gameCanvasRect.left}px`; this.#overlayCanvas.style.width = `${gameCanvasRect.width}px`; this.#overlayCanvas.style.height = `${gameCanvasRect.height}px`; // If game canvas dimensions change (e.g. initial load), update actual canvas resolution if (this.#overlayCanvas.width !== this.#gameCanvas.width) { this.#overlayCanvas.width = this.#gameCanvas.width; } if (this.#overlayCanvas.height !== this.#gameCanvas.height) { this.#overlayCanvas.height = this.#gameCanvas.height; } this.#clearOverlay(); if (this.#isGridVisible) { this.#drawGrid(); } if (this.#isSymmetryActive) { this.#drawSymmetryLines(); } } #clearOverlay() { this.#overlayCtx.clearRect(0, 0, this.#overlayCanvas.width, this.#overlayCanvas.height); } #loadInterface() { const container = domMake.Tree("div", { id: "drawing-assistant-controls" }); // Drawing parameters (Color, Thickness) const paramsRow = domMake.Row(); paramsRow.style.gap = "5px"; const colorInput = domMake.Tree("input", { type: "color", value: this.#drawingColor, title: "Color de Dibujo" }); colorInput.addEventListener("change", (e) => this.#drawingColor = e.target.value); paramsRow.appendAll(domMake.Tree("label", {}, ["Color:"]), colorInput); const thicknessInput = domMake.Tree("input", { type: "number", min: "1", max: "100", value: this.#drawingThickness, title: "Grosor de Dibujo" }); thicknessInput.addEventListener("change", (e) => this.#drawingThickness = parseInt(e.target.value)); paramsRow.appendAll(domMake.Tree("label", {}, ["Grosor:"]), thicknessInput); container.appendChild(paramsRow); // Geometric Shapes const shapesRow = domMake.Row(); shapesRow.style.gap = "5px"; const drawLineButton = domMake.Button('<i class="fas fa-grip-lines"></i> Línea'); drawLineButton.addEventListener("click", () => this.#enableShapeDrawing('line')); shapesRow.appendChild(drawLineButton); const drawRectButton = domMake.Button('<i class="fas fa-vector-square"></i> Rect.'); drawRectButton.addEventListener("click", () => this.#enableShapeDrawing('rect')); shapesRow.appendChild(drawRectButton); const drawCircleButton = domMake.Button('<i class="fas fa-circle"></i> Círculo'); drawCircleButton.addEventListener("click", () => this.#enableShapeDrawing('circle')); shapesRow.appendChild(drawCircleButton); container.appendChild(shapesRow); // Toggles (Grid, Symmetry, Pixel Perfect) const togglesRow = domMake.Row(); togglesRow.style.gap = "5px"; const toggleGridButton = domMake.Button('<i class="fas fa-th"></i> Cuadrícula'); toggleGridButton.id = "drawing-assistant-grid-toggle"; toggleGridButton.addEventListener("click", () => this.#toggleGrid(toggleGridButton)); togglesRow.appendChild(toggleGridButton); const toggleSymmetryButton = domMake.Button('<i class="fas fa-arrows-alt-h"></i> Simetría'); toggleSymmetryButton.id = "drawing-assistant-symmetry-toggle"; toggleSymmetryButton.addEventListener("click", () => this.#toggleSymmetry(toggleSymmetryButton)); togglesRow.appendChild(toggleSymmetryButton); const togglePixelPerfectButton = domMake.Button('<i class="fas fa-compress-arrows-alt"></i> Píxel Perfect'); togglePixelPerfectButton.id = "drawing-assistant-pixel-perfect-toggle"; togglePixelPerfectButton.addEventListener("click", () => this.#togglePixelPerfect(togglePixelPerfectButton)); togglesRow.appendChild(togglePixelPerfectButton); container.appendChild(togglesRow); // Collaborative Drawing Trigger const collabRow = domMake.Row(); const startCollabDrawButton = domMake.Button('<i class="fas fa-users-cog"></i> Iniciar Dibujo Colaborativo'); startCollabDrawButton.title = "Activa el módulo Autodraw V2 para que los bots colaboren en el dibujo (requiere imagen cargada en Autodraw V2)."; startCollabDrawButton.addEventListener("click", () => this.#triggerAutodrawV2Collab()); collabRow.appendChild(startCollabDrawButton); container.appendChild(collabRow); this.htmlElements.section.appendChild(container); } /** * Generic helper to get the active bot. * @returns {object|null} The active bot instance, or null. */ #getBot() { const botManagerClass = this.findGlobal("BotClientManager"); if (!botManagerClass || !botManagerClass.siblings || botManagerClass.siblings.length === 0) { this.notify("warning", "No hay instancias activas de 'BotClientManager'. Por favor, crea uno desde 'CubeEngine'."); return null; } // Access the first (and likely only) instance of BotClientManager const botManagerInstance = botManagerClass.siblings[0]; // if (!botManagerInstance || !botManagerInstance.children || botManagerInstance.children.length === 0) { // this.notify("warning", "No hay bots activos en 'BotClientManager'. Por favor, crea y conecta uno."); // return null; // } const botClientInterfaces = botManagerInstance.children; let activeBotClientInterface = null; const selectedBotInput = document.querySelector('input[name="botClient"]:checked'); if (selectedBotInput) { activeBotClientInterface = botClientInterfaces.find(bci => bci.htmlElements.input === selectedBotInput); } // Fallback to the first available bot if no specific bot is selected or found if (!activeBotClientInterface && botClientInterfaces.length > 0) { activeBotClientInterface = botClientInterfaces[0]; // Corrected to get the first instance this.notify("info", `No se seleccionó un bot. Usando el primer bot disponible: ${activeBotClientInterface.getName()}.`); } // if (!activeBotClientInterface || !activeBotClientInterface.bot || !activeBotClientInterface.bot.getReadyState()) { // this.notify("warning", `El bot "${activeBotClientInterface ? activeBotClientInterface.getName() : 'desconocido'}" no está conectado y listo para enviar comandos.`); // return null; // } return activeBotClientInterface.bot; } #hookGameDrawingEvents() { if (!this.#gameCanvas) return; // Remove existing listeners to prevent duplicates this.#gameCanvas.removeEventListener('mousedown', this.#mouseDownListener); this.#gameCanvas.removeEventListener('mousemove', this.#mouseMoveListener); this.#gameCanvas.removeEventListener('mouseup', this.#mouseUpListener); this.#gameCanvas.removeEventListener('mouseout', this.#mouseUpListener); let isDrawing = false; let lastX = 0, lastY = 0; const sendLineCommand = (startX, startY, endX, endY) => { const bot = this.#getBot(); if (!bot) return; // Scale coordinates from canvas pixels to game's 0-100 range const rect = this.#gameCanvas.getBoundingClientRect(); const scaleX = 100 / rect.width; const scaleY = 100 / rect.height; let gameX1 = startX * scaleX; let gameY1 = startY * scaleY; let gameX2 = endX * scaleX; let gameY2 = endY * scaleY; // Apply Pixel Perfect snapping if active if (this.#isPixelPerfectActive) { const snapSize = 2; // Snap to 2% grid, adjust as needed gameX1 = Math.round(gameX1 / snapSize) * snapSize; gameY1 = Math.round(gameY1 / snapSize) * snapSize; gameX2 = Math.round(gameX2 / snapSize) * snapSize; gameY2 = Math.round(gameY2 / snapSize) * snapSize; } // Ensure coordinates are within 0-100 bounds gameX1 = Math.max(0, Math.min(100, gameX1)); gameY1 = Math.max(0, Math.min(100, gameY1)); gameX2 = Math.max(0, Math.min(100, gameX2)); gameY2 = Math.max(0, Math.min(100, gameY2)); bot.emit("line", -1, gameX1, gameY1, gameX2, gameY2, true, this.#drawingThickness, this.#drawingColor, false); // If symmetry is active, send mirrored commands if (this.#isSymmetryActive) { const midX = 50; // Center of the canvas (game's 0-100 scale) const mirroredGameX1 = midX + (midX - gameX1); const mirroredGameX2 = midX + (midX - gameX2); bot.emit("line", -1, mirroredGameX1, gameY1, mirroredGameX2, gameY2, true, this.#drawingThickness, this.#drawingColor, false); } }; this.#mouseDownListener = (e) => { isDrawing = true; const rect = this.#gameCanvas.getBoundingClientRect(); lastX = e.clientX - rect.left; lastY = e.clientY - rect.top; // Start a new path on overlay if drawing for visualization this.#overlayCtx.beginPath(); this.#overlayCtx.moveTo(lastX, lastY); }; this.#mouseMoveListener = (e) => { if (!isDrawing) return; const rect = this.#gameCanvas.getBoundingClientRect(); const currentX = e.clientX - rect.left; const currentY = e.clientY - rect.top; sendLineCommand(lastX, lastY, currentX, currentY); // Draw on overlay for visual feedback this.#overlayCtx.lineTo(currentX, currentY); this.#overlayCtx.strokeStyle = this.#drawingColor; this.#overlayCtx.lineWidth = this.#drawingThickness; this.#overlayCtx.lineCap = 'round'; this.#overlayCtx.lineJoin = 'round'; this.#overlayCtx.stroke(); this.#overlayCtx.beginPath(); // Start new path to prevent connecting old points this.#overlayCtx.moveTo(currentX, currentY); lastX = currentX; lastY = currentY; }; this.#mouseUpListener = () => { isDrawing = false; this.#clearOverlay(); // Clear temporary drawing from overlay if (this.#isGridVisible) this.#drawGrid(); // Redraw static guides if (this.#isSymmetryActive) this.#drawSymmetryLines(); // Redraw static guides }; this.#gameCanvas.addEventListener('mousedown', this.#mouseDownListener); this.#gameCanvas.addEventListener('mousemove', this.#mouseMoveListener); this.#gameCanvas.addEventListener('mouseup', this.#mouseUpListener); this.#gameCanvas.addEventListener('mouseout', this.#mouseUpListener); // Stop drawing if mouse leaves canvas } // --- Geometric Shape Drawing --- #enableShapeDrawing(shapeType) { this.notify("info", `Modo '${shapeType}' activado. Haz clic en el lienzo para dibujar.`); const bot = this.#getBot(); if (!bot) return; let startCoords = null; let currentShapeOverlayListener = null; // Renamed to avoid confusion with `currentShapeOverlay` variable const drawShapePreview = (x1, y1, x2, y2) => { this.#clearOverlay(); // Clear previous preview if (this.#isGridVisible) this.#drawGrid(); // Redraw static guides if (this.#isSymmetryActive) this.#drawSymmetryLines(); const ctx = this.#overlayCtx; ctx.strokeStyle = this.#drawingColor; ctx.lineWidth = 1; // Thinner for preview ctx.setLineDash([5, 5]); // Dashed line for preview ctx.beginPath(); const width = x2 - x1; const height = y2 - y1; if (shapeType === 'line') { ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); } else if (shapeType === 'rect') { ctx.rect(x1, y1, width, height); } else if (shapeType === 'circle') { // Calculate radius, ensuring it's not negative const dx = x2 - x1; const dy = y2 - y1; const radius = Math.sqrt(dx * dx + dy * dy); ctx.arc(x1, y1, radius, 0, 2 * Math.PI); } ctx.stroke(); ctx.setLineDash([]); // Reset line dash after drawing preview }; const onClick = (e) => { const rect = this.#gameCanvas.getBoundingClientRect(); const currentX = e.clientX - rect.left; const currentY = e.clientY - rect.top; if (!startCoords) { startCoords = { x: currentX, y: currentY }; this.notify("info", "Haz clic de nuevo para definir el final de la forma."); // Start live preview on mousemove currentShapeOverlayListener = (moveEvent) => { const moveRect = this.#gameCanvas.getBoundingClientRect(); const moveX = moveEvent.clientX - moveRect.left; const moveY = moveEvent.clientY - moveRect.top; drawShapePreview(startCoords.x, startCoords.y, moveX, moveY); }; this.#gameCanvas.addEventListener('mousemove', currentShapeOverlayListener); } else { // Second click: draw the shape const bot = this.#getBot(); if (!bot) { // Check if bot is still available this.notify("warning", "Bot no disponible, no se puede dibujar la forma."); this.#gameCanvas.removeEventListener('click', onClick); this.#gameCanvas.removeEventListener('mousemove', currentShapeOverlayListener); this.#clearOverlay(); return; } const scaleX = 100 / rect.width; const scaleY = 100 / rect.height; const x1 = startCoords.x * scaleX; const y1 = startCoords.y * scaleY; const x2 = currentX * scaleX; const y2 = currentY * scaleY; const thickness = this.#drawingThickness; const color = this.#drawingColor; if (shapeType === 'line') { bot.emit("line", -1, x1, y1, x2, y2, true, thickness, color, false); } else if (shapeType === 'rect') { // Draw 4 lines for rectangle bot.emit("line", -1, x1, y1, x2, y1, true, thickness, color, false); // Top bot.emit("line", -1, x2, y1, x2, y2, true, thickness, color, false); // Right bot.emit("line", -1, x2, y2, x1, y2, true, thickness, color, false); // Bottom bot.emit("line", -1, x1, y2, x1, y1, true, thickness, color, false); // Left } else if (shapeType === 'circle') { const centerX = x1; const centerY = y1; const dx = x2 - x1; const dy = y2 - y1; const radius = Math.sqrt(dx * dx + dy * dy); // Radius in game coordinates (0-100) const segments = 48; // More segments for a smoother circle for (let i = 0; i < segments; i++) { const angle1 = (i / segments) * Math.PI * 2; const angle2 = ((i + 1) / segments) * Math.PI * 2; const cx1 = centerX + radius * Math.cos(angle1); const cy1 = centerY + radius * Math.sin(angle1); const cx2 = centerX + radius * Math.cos(angle2); const cy2 = centerY + radius * Math.sin(angle2); bot.emit("line", -1, cx1, cy1, cx2, cy2, true, thickness, color, false); } } // Clean up and disable shape drawing this.#gameCanvas.removeEventListener('click', onClick); this.#gameCanvas.removeEventListener('mousemove', currentShapeOverlayListener); this.#clearOverlay(); // Clear final preview this.notify("success", `${shapeType} dibujado.`); startCoords = null; // Reset for next shape } }; this.#gameCanvas.addEventListener('click', onClick); } // --- Grid Overlay --- #toggleGrid(button) { this.#isGridVisible = !this.#isGridVisible; button.classList.toggle("active", this.#isGridVisible); this.#clearOverlay(); if (this.#isGridVisible) { this.#drawGrid(); this.notify("info", "Cuadrícula visible."); } else { this.notify("info", "Cuadrícula oculta."); } // Redraw symmetry lines if active if (this.#isSymmetryActive) this.#drawSymmetryLines(); } #drawGrid() { if (!this.#gameCanvas) return; const ctx = this.#overlayCtx; const rect = this.#gameCanvas.getBoundingClientRect(); const cellSize = 50; // px, adjust for desired grid density ctx.strokeStyle = "rgba(100, 100, 100, 0.5)"; ctx.lineWidth = 1; ctx.setLineDash([2, 2]); // Dashed grid lines // Vertical lines for (let x = 0; x <= rect.width; x += cellSize) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, rect.height); ctx.stroke(); } // Horizontal lines for (let y = 0; y <= rect.height; y += cellSize) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(rect.width, y); ctx.stroke(); } ctx.setLineDash([]); // Reset line dash } // --- Symmetry Mode --- #toggleSymmetry(button) { this.#isSymmetryActive = !this.#isSymmetryActive; button.classList.toggle("active", this.#isSymmetryActive); this.#clearOverlay(); if (this.#isGridVisible) this.#drawGrid(); // Redraw grid if active if (this.#isSymmetryActive) { this.#drawSymmetryLines(); this.notify("info", "Modo Simetría Activo."); } else { this.notify("info", "Modo Simetría Inactivo."); } } #drawSymmetryLines() { if (!this.#gameCanvas) return; const ctx = this.#overlayCtx; const rect = this.#gameCanvas.getBoundingClientRect(); // Center vertical line ctx.strokeStyle = "rgba(255, 0, 0, 0.7)"; // Red for symmetry line ctx.lineWidth = 2; ctx.setLineDash([5, 5]); // Dashed line ctx.beginPath(); ctx.moveTo(rect.width / 2, 0); ctx.lineTo(rect.width / 2, rect.height); ctx.stroke(); ctx.setLineDash([]); // Reset line dash } // --- Pixel Perfect / Snap to Grid Drawing --- #togglePixelPerfect(button) { this.#isPixelPerfectActive = !this.#isPixelPerfectActive; button.classList.toggle("active", this.#isPixelPerfectActive); this.notify("info", `Modo 'Píxel Perfect' ${this.#isPixelPerfectActive ? 'Activado' : 'Desactivado'}.`); } // --- Collaborative Drawing Trigger --- #triggerAutodrawV2Collab() { const autodrawV2Class = this.findGlobal("AutodrawV2"); if (!autodrawV2Class || !autodrawV2Class.siblings || autodrawV2Class.siblings.length === 0) { this.notify("warning", "El módulo 'Autodraw V2' no está activo o no se encontró. No se puede iniciar el dibujo colaborativo."); return; } const autodrawV2Instance = autodrawV2Class.siblings[0]; if (autodrawV2Instance && typeof autodrawV2Instance.startDrawing === 'function') { // Assuming AutodrawV2.startDrawing handles bot distribution internally autodrawV2Instance.startDrawing(); this.notify("info", "Iniciando dibujo colaborativo a través del módulo Autodraw V2."); } else { this.notify("warning", "La instancia del módulo 'Autodraw V2' no está lista. Asegúrate de que Autodraw V2 se inicializó correctamente."); } } } })("QBit"); // --- START NEW MODULE: PaletteMaster (CORREGIDO: Event Handlers para StrokeMaster) --- (function PaletteMasterModule() { const QBit = globalThis[arguments[0]]; const LOCAL_STORAGE_KEY = 'cubicEngineCustomColors'; const DEFAULT_CUSTOM_COLORS = [ { name: "Teal", hex: "#008080" }, { name: "Lime", hex: "#AAFF00" }, { name: "Cyan", hex: "#00FFFF" }, { name: "Magenta", hex: "#FF00FF" }, { name: "Olive", hex: "#808000" }, { name: "Maroon", hex: "#800000" } ]; QBit.Styles.addRules([ // General section styling `#${QBit.identifier} .palette-master-section { border: 1px solid var(--CE-color); border-radius: .25rem; padding: 5px; margin-bottom: 10px; background-color: var(--CE-bg_color); }`, `#${QBit.identifier} .palette-master-section-title { font-weight: bold; margin-bottom: 5px; color: var(--dark-blue-title); text-align: center; }`, // MoreColorPalettes specific `#${QBit.identifier} .custom-color-button { box-shadow: 0 0 2px rgba(0,0,0,0.3); cursor: pointer; border: 1px solid transparent; }`, `#${QBit.identifier} .custom-color-button.custom-active-color { box-shadow: 0 0 5px 2px var(--info); border: 1px solid var(--info); }`, // StrokeMaster specific `#${QBit.identifier} .stroke-master-toggle-button.active { background-color: var(--info); color: white; }`, `#${QBit.identifier} .stroke-master-control-group { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 5px; padding-top: 5px; border-top: 1px solid rgba(0,0,0,0.1); }`, `#${QBit.identifier} .stroke-master-control-group > div { flex: 1 1 48%; /* For responsiveness */ display: flex; flex-direction: column; align-items: flex-start; }`, `#${QBit.identifier} .stroke-master-control-group input[type="number"], #${QBit.identifier} .stroke-master-control-group input[type="range"] { width: 100%; }` ]); class PaletteMaster extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); // MoreColorPalettes properties #customColors = []; #colorButtonsContainer; #colorInput; #colorPaletteObserver; #gameTriangleElement = null; #proxyGameButton = null; // StrokeMaster properties #isPressureActive = false; #isTextureActive = false; #lastMousePos = { x: 0, y: 0 }; #lastTimestamp = 0; #lastDrawThickness = 5; // MODIFICADO: Declarar los handlers como propiedades de la clase, enlazadas una única vez #boundMouseDownHandler; #boundMouseMoveHandler; #boundMouseUpHandler; // This will also handle mouseout constructor() { super("Maestro de Paletas", '<i class="fas fa-brush"></i>'); // MODIFICADO: Enlazar los handlers una única vez en el constructor this.#boundMouseDownHandler = this.#handleMouseDown.bind(this); this.#boundMouseMoveHandler = this.#handleMouseMove.bind(this); this.#boundMouseUpHandler = this.#handleMouseUp.bind(this); this.#onStartup(); } #onStartup() { this.#loadInterface(); this.#loadCustomColors(); this.#setupColorPaletteObserver(); this.#hookDrawingEvents(); // Attach event listeners } #loadInterface() { const container = domMake.Tree("div"); // --- Section: Gestión de Paletas --- const paletteSection = domMake.Tree("div", { class: "palette-master-section" }); paletteSection.appendChild(domMake.Tree("div", { style: "width: 100%; text-align: center; font-weight: bold; margin-bottom: 5px;" }, ["Gestión de Paletas"])); const addColorRow = domMake.Row(); const addColorLabel = domMake.Tree("label", {}, ["Añadir Color:"]); this.#colorInput = domMake.Tree("input", { type: "color", value: "#FF0000" }); const addColorButton = domMake.Button("Añadir"); addColorButton.addEventListener("click", () => this.#addNewCustomColor(this.#colorInput.value)); addColorRow.appendAll(addColorLabel, this.#colorInput, addColorButton); paletteSection.appendChild(addColorRow); const clearColorsRow = domMake.Row(); const clearAllColorsButton = domMake.Button("Limpiar Todos"); clearAllColorsButton.addEventListener("click", () => this.#clearAllCustomColors()); clearColorsRow.appendChild(clearAllColorsButton); paletteSection.appendChild(clearColorsRow); const customColorsDisplayRow = domMake.Row(); this.#colorButtonsContainer = domMake.IconList(); customColorsDisplayRow.appendChild(this.#colorButtonsContainer); paletteSection.appendChild(customColorsDisplayRow); container.appendChild(paletteSection); // --- Section: Herramientas de Trazo --- const strokeSection = domMake.Tree("div", { class: "palette-master-section" }); strokeSection.appendChild(domMake.Tree("div", { class: "palette-master-section-title" }, [""])); // Pressure Control const pressureRow = domMake.Row(); const pressureButton = domMake.Button("Control de Presión"); pressureButton.classList.add("stroke-master-toggle-button"); pressureButton.addEventListener("click", () => this.#togglePressureControl(pressureButton)); // pressureRow.appendChild(pressureButton); strokeSection.appendChild(pressureRow); // Texture Brush const textureRow = domMake.Row(); const textureButton = domMake.Button("Pincel Texturizado"); textureButton.classList.add("stroke-master-toggle-button"); textureButton.addEventListener("click", () => this.#toggleTextureBrush(textureButton)); //s textureRow.appendChild(textureButton); strokeSection.appendChild(textureRow); // Gradient Fills (conceptual buttons) const gradientGroup = domMake.Tree("div", { class: "stroke-master-control-group" }); gradientGroup.appendChild(domMake.Tree("label", { style: "width: 100%; text-align: center; font-weight: bold; margin-bottom: 5px;" }, ["Rellenos Degradados"])); const diamondGradientButton = domMake.Button('<i class="fas fa-gem"></i> Degradado Diamante'); diamondGradientButton.addEventListener("click", () => this.#simulateGradientFill("diamond")); gradientGroup.appendChild(domMake.Tree("div", {}, [diamondGradientButton])); const radialGradientButton = domMake.Button('<i class="fas fa-bullseye"></i> Degradado Radial'); radialGradientButton.addEventListener("click", () => this.#simulateGradientFill("radial")); gradientGroup.appendChild(domMake.Tree("div", {}, [radialGradientButton])); const linearGradientButton = domMake.Button('<i class="fas fa-grip-lines"></i> Degradado Lineal'); linearGradientButton.addEventListener("click", () => this.#simulateGradientFill("linear")); gradientGroup.appendChild(domMake.Tree("div", {}, [linearGradientButton])); const verticalGradientButton = domMake.Button('<i class="fas fa-arrows-alt-v"></i> Degradado Vertical'); verticalGradientButton.addEventListener("click", () => this.#simulateGradientFill("vertical")); gradientGroup.appendChild(domMake.Tree("div", {}, [verticalGradientButton])); const conicalGradientButton = domMake.Button('<i class="fas fa-circle-notch"></i> Degradado Cónico'); conicalGradientButton.addEventListener("click", () => this.#simulateGradientFill("conical")); gradientGroup.appendChild(domMake.Tree("div", {}, [conicalGradientButton])); const waveGradientButton = domMake.Button('<i class="fas fa-water"></i> Degradado Ondulado'); waveGradientButton.addEventListener("click", () => this.#simulateGradientFill("wave")); gradientGroup.appendChild(domMake.Tree("div", {}, [waveGradientButton])); strokeSection.appendChild(gradientGroup); container.appendChild(strokeSection); this.htmlElements.section.appendChild(container); } // --- MoreColorPalettes Methods --- #loadCustomColors() { try { const storedColors = localStorage.getItem(LOCAL_STORAGE_KEY); this.#customColors = storedColors ? JSON.parse(storedColors) : [...DEFAULT_CUSTOM_COLORS]; } catch (e) { this.notify("error", `Error al cargar colores: ${e.message}. Usando colores por defecto.`); this.#customColors = [...DEFAULT_CUSTOM_COLORS]; } this.#renderCustomColorButtons(); } #saveCustomColors() { try { localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(this.#customColors)); this.notify("success", "Colores personalizados guardados."); } catch (e) { this.notify("error", `Error al guardar colores: ${e.message}`); } } #renderCustomColorButtons() { this.#colorButtonsContainer.innerHTML = ''; this.#customColors.forEach(color => { this.#createColorButton(color.hex, color.name); }); } #addNewCustomColor(hexColor) { if (this.#customColors.some(color => color.hex.toLowerCase() === hexColor.toLowerCase())) { this.notify("info", `El color ${hexColor} ya existe en tu paleta.`); return; } const newColor = { name: `Custom-${hexColor}`, hex: hexColor }; this.#customColors.push(newColor); this.#saveCustomColors(); this.#createColorButton(newColor.hex, newColor.name); this.notify("info", `Color ${hexColor} añadido.`); } addCustomColorFromExternal(hexColor) { // Public method for other modules this.#addNewCustomColor(hexColor); } #clearAllCustomColors() { if (confirm("¿Estás seguro de que quieres eliminar todos los colores personalizados?")) { this.#customColors = [...DEFAULT_CUSTOM_COLORS]; this.#saveCustomColors(); this.#renderCustomColorButtons(); this.notify("info", "Colores personalizados reiniciados a los valores por defecto."); } } #createColorButton(hexColor, name) { const newButton = domMake.Tree("div", { class: "drawcontrols-button drawcontrols-color custom-color-button", style: `background-color: ${hexColor};`, title: name, "data-hex": hexColor }); newButton.addEventListener('click', (event) => this.#handleCustomColorClick(newButton)); this.#colorButtonsContainer.appendChild(newButton); } #findGameElementsForColorPalette() { if (!this.#gameTriangleElement || !document.body.contains(this.#gameTriangleElement)) { this.#gameTriangleElement = document.getElementById('colorpicker-cursor'); } if (!this.#proxyGameButton || !document.body.contains(this.#proxyGameButton)) { const drawControls = document.getElementById('drawcontrols-colors') || document.getElementById('drawcontrols'); if (drawControls) { this.#proxyGameButton = drawControls.querySelector('.drawcontrols-button.drawcontrols-color:not(.drawcontrols-colorpicker):not(.custom-color-button)'); } } } #handleCustomColorClick(clickedButton) { this.#findGameElementsForColorPalette(); if (!this.#proxyGameButton) { this.notify("warning", "No se encontró un botón de color de juego para proxy. La funcionalidad de paleta puede ser limitada."); return; } const customColor = clickedButton.dataset.hex; const originalProxyColor = this.#proxyGameButton.style.backgroundColor; this.#proxyGameButton.style.backgroundColor = customColor; this.#proxyGameButton.click(); requestAnimationFrame(() => { this.#proxyGameButton.style.backgroundColor = originalProxyColor; this.#updateTrianglePosition(clickedButton); document.querySelectorAll('.custom-color-button.custom-active-color').forEach(btn => { btn.classList.remove('custom-active-color'); }); clickedButton.classList.add('custom-active-color'); }); } #updateTrianglePosition(targetButton) { const triangle = this.#gameTriangleElement; if (!triangle || !targetButton) return; const buttonContainer = document.getElementById('drawcontrols-colors') || document.getElementById('drawcontrols'); if (!buttonContainer) return; const buttonRect = targetButton.getBoundingClientRect(); const containerRect = buttonContainer.getBoundingClientRect(); const buttonCenterRelativeToContainer = (buttonRect.left - containerRect.left) + (buttonRect.width / 2); const triangleWidth = triangle.offsetWidth || 8; const newLeft = buttonCenterRelativeToContainer - (triangleWidth / 2); triangle.style.left = `${newLeft}px`; } #setupColorPaletteObserver() { const observerTarget = document.getElementById('drawcontrols-colors') || document.getElementById('drawcontrols'); if (!observerTarget) { this.notify("warning", "Contenedor de controles de dibujo no encontrado. Los colores personalizados pueden no funcionar bien."); setTimeout(() => this.#setupColorPaletteObserver(), 1000); return; } this.#colorPaletteObserver = new MutationObserver((mutations) => { mutations.forEach(mutation => { if (mutation.type === 'childList' || mutation.type === 'attributes') { this.#addListenersToGameColorButtons(); } }); }); this.#colorPaletteObserver.observe(observerTarget, { childList: true, subtree: true, attributes: true, attributeFilter: ['class', 'style'] }); this.#addListenersToGameColorButtons(); } #addListenersToGameColorButtons() { this.#findGameElementsForColorPalette(); const gameColorButtons = document.querySelectorAll('.drawcontrols-button.drawcontrols-color:not(.custom-color-button)'); gameColorButtons.forEach(gameBtn => { gameBtn.removeEventListener('click', this.#handleGameColorClick); gameBtn.addEventListener('click', this.#handleGameColorClick.bind(this)); }); } #handleGameColorClick() { document.querySelectorAll('.custom-color-button.custom-active-color').forEach(customBtn => { customBtn.classList.remove('custom-active-color'); }); } // --- StrokeMaster Methods --- #getBot() { const botManagerClass = this.findGlobal("BotClientManager"); if (!botManagerClass || !botManagerClass.siblings || botManagerClass.siblings.length === 0) { this.notify("warning", "No hay instancias activas de 'BotClientManager'. Por favor, crea uno desde 'CubeEngine'."); return null; } const botManagerInstance = botManagerClass.siblings[0]; const botClientInterfaces = botManagerInstance.children; let activeBotClientInterface = null; const selectedBotInput = document.querySelector('input[name="botClient"]:checked'); if (selectedBotInput) { activeBotClientInterface = botClientInterfaces.find(bci => bci.htmlElements.input === selectedBotInput); } if (!activeBotClientInterface && botClientInterfaces.length > 0) { activeBotClientInterface = botClientInterfaces[0]; this.notify("info", `No se seleccionó un bot. Usando el primer bot disponible: ${activeBotClientInterface.getName()}.`); } if (!activeBotClientInterface || !activeBotClientInterface.bot || !activeBotClientInterface.bot.getReadyState()) { this.notify("warning", `El bot "${activeBotClientInterface ? activeBotClientInterface.getName() : 'desconocido'}" no está conectado y listo para enviar comandos.`); return null; } return activeBotClientInterface.bot; } #hookDrawingEvents() { const gameCanvas = document.querySelector("#canvas"); if (!gameCanvas) return; // Remove previous listeners to prevent duplicates gameCanvas.removeEventListener("mousedown", this.#boundMouseDownHandler); gameCanvas.removeEventListener("mousemove", this.#boundMouseMoveHandler); gameCanvas.removeEventListener("mouseup", this.#boundMouseUpHandler); gameCanvas.removeEventListener("mouseout", this.#boundMouseUpHandler); // mouseout also stops drawing // Attach listeners using the pre-bound handlers gameCanvas.addEventListener("mousedown", this.#boundMouseDownHandler); gameCanvas.addEventListener("mousemove", this.#boundMouseMoveHandler); gameCanvas.addEventListener("mouseup", this.#boundMouseUpHandler); gameCanvas.addEventListener("mouseout", this.#boundMouseUpHandler); } // MODIFICADO: Handlers ahora son métodos de la clase #handleMouseDown(e) { this.isDrawingLocal = true; const rect = e.target.getBoundingClientRect(); // Use e.target for robustness this.#lastMousePos.x = ((e.clientX - rect.left) / rect.width) * 100; this.#lastMousePos.y = ((e.clientY - rect.top) / rect.height) * 100; this.#lastTimestamp = performance.now(); } #handleMouseMove(e) { if (!this.isDrawingLocal) return; const rect = e.target.getBoundingClientRect(); const currentX = ((e.clientX - rect.left) / rect.width) * 100; const currentY = ((e.clientY - rect.top) / rect.height) * 100; let currentThickness = this.#lastDrawThickness; let currentColor = this.#getCurrentBrushColor(); if (this.#isPressureActive) { const currentTimestamp = performance.now(); const dx = e.clientX - this.#lastMousePos.x; const dy = e.clientY - this.#lastMousePos.y; const distance = Math.sqrt(dx * dx + dy * dy); const timeDelta = currentTimestamp - this.#lastTimestamp; const speed = distance / timeDelta; const minThickness = 2; const maxThickness = 20; const speedFactor = 0.5; currentThickness = maxThickness - (speed * speedFactor); currentThickness = Math.max(minThickness, Math.min(maxThickness, currentThickness)); this.#lastDrawThickness = currentThickness; } if (this.#isTextureActive) { currentColor = this.#applyColorNoise(currentColor, 10); } const bot = this.#getBot(); if (bot && bot.getReadyState()) { bot.emit("line", -1, this.#lastMousePos.x, this.#lastMousePos.y, currentX, currentY, true, currentThickness, currentColor, false); } this.#lastMousePos.x = currentX; this.#lastMousePos.y = currentY; this.#lastTimestamp = performance.now(); } #handleMouseUp() { this.isDrawingLocal = false; this.#lastDrawThickness = 5; } #getCurrentBrushColor() { const colorPicker = document.querySelector('.drawcontrols-color.active'); if (colorPicker) { const rgb = colorPicker.style.backgroundColor; if (rgb) return rgb; } return "#000000"; } #applyColorNoise(color, noiseAmount) { let r, g, b; if (color.startsWith("rgb")) { const parts = color.match(/\d+/g).map(Number); r = parts[0]; g = parts[1]; b = parts[2]; } else if (color.startsWith("#")) { const hex = color.slice(1); r = parseInt(hex.substring(0, 2), 16); g = parseInt(hex.substring(2, 4), 16); b = parseInt(hex.substring(4, 6), 16); } else { return color; } const addNoise = (value) => { const noise = (Math.random() - 0.5) * 2 * noiseAmount; return Math.max(0, Math.min(255, Math.floor(value + noise))); }; return `rgb(${addNoise(r)},${addNoise(g)},${addNoise(b)})`; } #togglePressureControl(button) { this.#isPressureActive = !this.#isPressureActive; button.classList.toggle("active", this.#isPressureActive); button.textContent = this.#isPressureActive ? "Control de Presión Activo" : "Control de Presión"; this.notify("info", `Control de Presión ${this.#isPressureActive ? 'activado' : 'desactivado'}.`); } #toggleTextureBrush(button) { this.#isTextureActive = !this.#isTextureActive; button.classList.toggle("active", this.#isTextureActive); button.textContent = this.#isTextureActive ? "Pincel Texturizado Activo" : "Pincel Texturizado"; this.notify("info", `Pincel Texturizado ${this.#isTextureActive ? 'activado' : 'desactivado'}.`); } async #simulateGradientFill(type) { this.notify("info", `Simulando relleno degradado tipo '${type}' (conceptual).`); const bot = this.#getBot(); if (!bot) return; const startX = 20, endX = 80; const startY = 20, endY = 80; const steps = 20; const thickness = 25; const delayMs = 50; for (let i = 0; i <= steps; i++) { const ratio = i / steps; let r, g, b; let currentColor; switch (type) { case "diamond": { // <-- Añadido bloque r = Math.floor(0 + (255 - 0) * (1 - ratio)); g = Math.floor(0 + (215 - 0) * (1 - ratio)); b = Math.floor(139 + (0 - 139) * (1 - ratio)); currentColor = `rgb(${r},${g},${b})`; const currentDistance = 40 * ratio; const centerX = 50, centerY = 50; const p1x = centerX, p1y = centerY - currentDistance; const p2x = centerX + currentDistance, p2y = centerY; const p3x = centerX, p3y = centerY + currentDistance; const p4x = centerX - currentDistance, p4y = centerY; bot.emit("line", -1, p1x, p1y, p2x, p2y, true, thickness, currentColor, false); bot.emit("line", -1, p2x, p2y, p3x, p3y, true, thickness, currentColor, false); bot.emit("line", -1, p3x, p3y, p4x, p4y, true, thickness, currentColor, false); bot.emit("line", -1, p4x, p4y, p1x, p1y, true, thickness, currentColor, false); break; } // <-- Cierre de bloque case "radial": { // <-- Añadido bloque r = 255; g = Math.floor(165 + (255 - 165) * (1 - ratio)); b = 0; currentColor = `rgb(${r},${g},${b})`; const currentRadius = 30 * ratio; const numSegments = 36; for (let j = 0; j < numSegments; j++) { const angle = (j / numSegments) * 2 * Math.PI; const x = 50 + currentRadius * Math.cos(angle); const y = 50 + currentRadius * Math.sin(angle); // Cambiado ligeramente para que simule mejor un punto con una línea muy pequeña bot.emit("line", -1, x, y, x + 0.1, y + 0.1, true, thickness, currentColor, false); } break; } // <-- Cierre de bloque case "linear": { // <-- Añadido bloque r = Math.floor(255 * (1 - ratio)); g = 0; b = Math.floor(255 * ratio); currentColor = `rgb(${r},${g},${b})`; const currentY = startY + (endY - startY) * ratio; bot.emit("line", -1, startX, currentY, endX, currentY, true, thickness, currentColor, false); break; } // <-- Cierre de bloque case "vertical": { // <-- Añadido bloque r = Math.floor(128 + (255 - 128) * ratio); g = Math.floor(0 + (192 - 0) * ratio); b = Math.floor(128 + (203 - 128) * ratio); currentColor = `rgb(${r},${g},${b})`; const vertY = startY + (endY - startY) * ratio; bot.emit("line", -1, startX, vertY, endX, vertY, true, thickness, currentColor, false); break; } // <-- Cierre de bloque case "conical": { // <-- Añadido bloque r = Math.floor(0 + (255 - 0) * ratio); g = 0; b = 255; currentColor = `rgb(${r},${g},${b})`; const angle = (ratio * 2 * Math.PI); const radius = 40; const cx = 50, cy = 50; const x2 = cx + radius * Math.cos(angle); const y2 = cy + radius * Math.sin(angle); bot.emit("line", -1, cx, cy, x2, y2, true, thickness, currentColor, false); break; } // <-- Cierre de bloque case "wave": { // <-- Añadido bloque r = Math.floor(0 + (255 - 0) * ratio); g = Math.floor(255 + (127 - 255) * ratio); b = Math.floor(255 + (80 - 255) * ratio); currentColor = `rgb(${r},${g},${b})`; const waveAmplitude = 10; const waveFrequency = 0.1; const wavyY = startY + (endY - startY) * ratio + waveAmplitude * Math.sin(ratio * Math.PI * 2 * waveFrequency); bot.emit("line", -1, startX, wavyY, endX, wavyY, true, thickness, currentColor, false); break; } // <-- Cierre de bloque } await new Promise(resolve => setTimeout(resolve, delayMs)); } this.notify("success", `Degradado tipo '${type}' dibujado.`); } } })("QBit"); // --- END NEW MODULE: PaletteMaster --- // --- START NEW MODULE: SwarmCommander (COMBINED MODULE: TacticalBotSwarm + BugGeneratorModule) --- (function SwarmCommanderModule() { const QBit = globalThis[arguments[0]]; QBit.Styles.addRules([ `#${QBit.identifier} .swarm-commander-section { border: 1px solid var(--CE-color); border-radius: .25rem; padding: 5px; margin-bottom: 10px; background-color: var(--CE-bg_color); }`, `#${QBit.identifier} .swarm-commander-section-title { font-weight: bold; margin-bottom: 5px; color: var(--dark-blue-title); text-align: center; }`, `#${QBit.identifier} .swarm-commander-toggle-button.active { background-color: var(--info); color: white; }`, `#${QBit.identifier} .swarm-commander-control-group { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 5px; padding-top: 5px; border-top: 1px solid rgba(0,0,0,0.1); }`, `#${QBit.identifier} .swarm-commander-control-group > div { flex: 1 1 48%; /* For responsiveness */ display: flex; flex-direction: column; align-items: flex-start; }`, `#${QBit.identifier} .swarm-commander-control-group input, #${QBit.identifier} .swarm-commander-control-group select { width: 100%; }` ]); class SwarmCommander extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); // BugGeneratorModule properties #lagInterval = null; #secretSpamInterval = null; #bugExperienceInterval = null; // Renamed to avoid generic 'bugInterval' #playerChaosInterval = null; #visualGlitchInterval = null; constructor() { super("Manipulacion avanzada de Bots", '<i class="fas fa-gamepad"></i>'); // New icon this.#onStartup(); } #onStartup() { this.#loadInterface(); } #loadInterface() { const container = domMake.Tree("div"); // --- Section: Tácticas de Enjambre (TacticalBotSwarm) --- const swarmTacticsSection = domMake.Tree("div", { class: "swarm-commander-section" }); swarmTacticsSection.appendChild(domMake.Tree("div", { style: "width: 100%; text-align: center; font-weight: bold; margin-bottom: 5px;" }, ["Modos de bot"])); // Collaborative Drawing const collaborativeDrawRow = domMake.Row(); const collaborativeDrawButton = domMake.Button("Dibujo Colaborativo"); collaborativeDrawButton.title = "Divide el lienzo en zonas para que cada bot dibuje una parte (usa Autodraw V2 para cargar imagen)."; collaborativeDrawButton.addEventListener("click", () => this.#startCollaborativeDrawing()); collaborativeDrawRow.appendChild(collaborativeDrawButton); swarmTacticsSection.appendChild(collaborativeDrawRow); // Smart Guessing const smartGuessRow = domMake.Row(); const smartGuessButton = domMake.Button("Bot de Adivinanza"); smartGuessButton.title = "Un bot intentará adivinar la palabra (simulado)."; smartGuessButton.addEventListener("click", () => this.#smartGuess()); smartGuessRow.appendChild(smartGuessButton); swarmTacticsSection.appendChild(smartGuessRow); // Bot Personality Controls const personalityGroup = domMake.Tree("div", { class: "swarm-commander-control-group" }); personalityGroup.appendChild(domMake.Tree("label", { style: "width: 100%; text-align: center; font-weight: bold; margin-bottom: 5px;" }, ["Personalidad del Bot"])); const drawingSpeedDiv = domMake.Tree("div"); drawingSpeedDiv.appendChild(domMake.Tree("label", {}, ["Velocidad Dibujo (ms/línea):"])); const drawingSpeedInput = domMake.Tree("input", { type: "number", min: "1", max: "100", value: "10" }); drawingSpeedInput.addEventListener("change", (e) => this.#setBotDrawingSpeed(parseInt(e.target.value))); drawingSpeedDiv.appendChild(drawingSpeedInput); personalityGroup.appendChild(drawingSpeedDiv); const verbosityDiv = domMake.Tree("div"); verbosityDiv.appendChild(domMake.Tree("label", {}, ["Verbosidad Mensajes:"])); const verbositySelect = domMake.Tree("select"); ['Silencioso', 'Normal', 'Charlatán'].forEach(level => { verbositySelect.appendChild(domMake.Tree("option", { value: level }, [level])); }); verbositySelect.addEventListener("change", (e) => this.#setBotChatVerbosity(e.target.value)); verbosityDiv.appendChild(verbositySelect); personalityGroup.appendChild(verbosityDiv); swarmTacticsSection.appendChild(personalityGroup); container.appendChild(swarmTacticsSection); // --- Section: Herramientas de Disrupción (BugGeneratorModule) --- const disruptionSection = domMake.Tree("div", { class: "swarm-commander-section" }); disruptionSection.appendChild(domMake.Tree("div", { style: "width: 100%; text-align: center; font-weight: bold; margin-bottom: 5px;" }, ["Herramientas de Caos"])); const lagRow = domMake.Row(); const lagButton = domMake.Button('<i class="fas fa-dizzy"></i> Generar Lag'); lagButton.classList.add("swarm-commander-toggle-button"); lagButton.addEventListener("click", () => this.#toggleLag(lagButton)); lagRow.appendChild(lagButton); disruptionSection.appendChild(lagRow); const bugExperienceRow = domMake.Row(); const bugExperienceButton = domMake.Button('<i class="fas fa-gamepad"></i> Bugear Experiencia'); bugExperienceButton.classList.add("swarm-commander-toggle-button"); bugExperienceButton.addEventListener("click", () => this.#toggleBugExperience(bugExperienceButton)); bugExperienceRow.appendChild(bugExperienceButton); disruptionSection.appendChild(bugExperienceRow); const playerChaosRow = domMake.Row(); const playerChaosButton = domMake.Button('<i class="fas fa-running"></i> Caos de Jugador'); playerChaosButton.classList.add("swarm-commander-toggle-button"); playerChaosButton.addEventListener("click", () => this.#togglePlayerChaos(playerChaosButton)); playerChaosRow.appendChild(playerChaosButton); disruptionSection.appendChild(playerChaosRow); const visualGlitchRow = domMake.Row(); const visualGlitchButton = domMake.Button('<i class="fas fa-ghost"></i> Glitch Visual'); visualGlitchButton.classList.add("swarm-commander-toggle-button"); visualGlitchButton.addEventListener("click", () => this.#toggleVisualGlitch(visualGlitchButton)); visualGlitchRow.appendChild(visualGlitchButton); disruptionSection.appendChild(visualGlitchRow); const secretSpamRow = domMake.Row(); const secretSpamButton = domMake.Button('<i class="fas fa-mask"></i> Spam Visual Secreto'); secretSpamButton.classList.add("swarm-commander-toggle-button"); secretSpamButton.addEventListener("click", () => this.#toggleSecretSpam(secretSpamButton)); secretSpamRow.appendChild(secretSpamButton); disruptionSection.appendChild(secretSpamRow); container.appendChild(disruptionSection); this.htmlElements.section.appendChild(container); } // --- Helper: Get Bot Instance --- #getBot() { const botManagerClass = this.findGlobal("BotClientManager"); if (!botManagerClass || !botManagerClass.siblings || botManagerClass.siblings.length === 0) { this.notify("warning", "No hay instancias activas de 'BotClientManager'. Por favor, crea uno desde 'CubeEngine'."); return null; } const botManagerInstance = botManagerClass.siblings[0]; const botClientInterfaces = botManagerInstance.children; let activeBotClientInterface = null; const selectedBotInput = document.querySelector('input[name="botClient"]:checked'); if (selectedBotInput) { activeBotClientInterface = botClientInterfaces.find(bci => bci.htmlElements.input === selectedBotInput); } if (!activeBotClientInterface && botClientInterfaces.length > 0) { activeBotClientInterface = botClientInterfaces[0]; this.notify("info", `No se seleccionó un bot. Usando el primer bot disponible: ${activeBotClientInterface.getName()}.`); } if (!activeBotClientInterface || !activeBotClientInterface.bot || !activeBotClientInterface.bot.getReadyState()) { this.notify("warning", `El bot "${activeBotClientInterface ? activeBotClientInterface.getName() : 'desconocido'}" no está conectado y listo para enviar comandos.`); return null; } return activeBotClientInterface.bot; } // --- TacticalBotSwarm Methods --- #startCollaborativeDrawing() { const autodrawV2Class = this.findGlobal("AutodrawV2"); if (!autodrawV2Class || !autodrawV2Class.siblings || autodrawV2Class.siblings.length === 0) { this.notify("warning", "El módulo 'Autodraw V2' no está activo. No se puede iniciar el dibujo colaborativo."); return; } const autodrawV2Instance = autodrawV2Class.siblings[0]; if (autodrawV2Instance && typeof autodrawV2Instance.startDrawing === 'function') { autodrawV2Instance.startDrawing(); this.notify("info", "Iniciando dibujo colaborativo a través del módulo Autodraw V2."); } else { this.notify("warning", "La instancia del módulo 'Autodraw V2' no está lista. Asegúrate de que Autodraw V2 se inicializó correctamente."); } } #smartGuess() { const bot = this.#getBot(); if (!bot) return; const commonWords = ["casa", "flor", "mesa", "sol", "perro", "gato", "arbol", "coche", "libro"]; // Expanded words const randomWord = commonWords[Math.floor(Math.random() * commonWords.length)]; bot.emit("chatmsg", randomWord); this.notify("info", `Bot ${bot.name} intentó adivinar: "${randomWord}" (simulado).`); } #setBotDrawingSpeed(speed) { const botManagerClass = this.findGlobal("BotClientManager"); if (botManagerClass) { botManagerClass.siblings.forEach(botManagerInstance => { if (botManagerInstance && botManagerInstance.children) { botManagerInstance.children.forEach(botI => { if (botI.bot) { botI.bot.drawingDelay = speed; // Assuming BotClient has this property this.notify("log", `Velocidad de dibujo de ${botI.getName()}: ${speed}ms/línea.`); } }); } }); } } #setBotChatVerbosity(verbosity) { const botManagerClass = this.findGlobal("BotClientManager"); if (botManagerClass) { botManagerClass.siblings.forEach(botManagerInstance => { if (botManagerInstance && botManagerInstance.children) { botManagerInstance.children.forEach(botI => { if (botI.bot) { botI.bot.chatVerbosity = verbosity; // Assuming BotClient has this property this.notify("log", `Verbosidad de ${botI.getName()}: ${verbosity}.`); } }); } }); } } // --- BugGeneratorModule Methods --- #toggleLag(button) { if (this.#lagInterval) { clearInterval(this.#lagInterval); this.#lagInterval = null; button.classList.remove("active"); button.textContent = '<i class="fas fa-dizzy"></i> Generar Lag'; this.notify("info", "Generador de Lag Detenido."); } else { const bot = this.#getBot(); if (!bot) return; button.classList.add("active"); button.textContent = '<i class="fas fa-stop"></i> Detener Lag'; this.notify("info", "Generador de Lag Iniciado (enviando comandos de dibujo agresivos)."); let counter = 0; this.#lagInterval = setInterval(() => { if (!bot.getReadyState()) { this.notify("warning", "Bot desconectado, deteniendo Lag."); this.#toggleLag(button); return; } if (counter % 50 === 0) { bot.emit("clear"); this.notify("log", "Lag: Enviando comando 'Clear'!"); } for (let i = 0; i < 5; i++) { const x1 = Math.random() * 100; const y1 = Math.random() * 100; const x2 = Math.random() * 100; const y2 = Math.random() * 100; const size = Math.floor(Math.random() * 70) + 20; const color = `#${Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0')}`; bot.emit("line", -1, x1, y1, x2, y2, true, size, color, false); } counter++; if (counter % 20 === 0) { this.notify("log", `Lag: ${counter * 5} líneas enviadas.`); } }, 25); } } #toggleBugExperience(button) { if (this.#bugExperienceInterval) { clearInterval(this.#bugExperienceInterval); this.#bugExperienceInterval = null; button.classList.remove("active"); button.textContent = '<i class="fas fa-gamepad"></i> Bugear Experiencia'; this.notify("info", "Deteniendo 'Bugear Experiencia'."); } else { const bot = this.#getBot(); if (!bot) return; button.classList.add("active"); button.textContent = '<i class="fas fa-stop"></i> Detener Bug'; this.notify("info", "Iniciando 'Bugear Experiencia': El bot enviará ráfagas de movimientos y gestos. Esto puede ser molesto para otros."); let count = 0; this.#bugExperienceInterval = setInterval(() => { if (!bot.getReadyState()) { this.notify("warning", "Bot desconectado, deteniendo 'Bugear Experiencia'."); this.#toggleBugExperience(button); return; } const randomX = Math.random() * 100; const randomY = Math.random() * 100; bot.emit("moveavatar", randomX, randomY); const randomGesture = Math.floor(Math.random() * 32); bot.emit("sendgesture", randomGesture); count++; if (count >= 9000) { this.notify("info", "'Bugear Experiencia' completado (200 acciones). Deteniendo."); this.#toggleBugExperience(button); } }, 100); } } #togglePlayerChaos(button) { if (this.#playerChaosInterval) { clearInterval(this.#playerChaosInterval); this.#playerChaosInterval = null; button.classList.remove("active"); button.textContent = '<i class="fas fa-running"></i> Caos de Jugador'; this.notify("info", "Deteniendo 'Caos de Jugador'."); } else { const bot = this.#getBot(); if (!bot) return; button.classList.add("active"); button.textContent = '<i class="fas fa-stop"></i> Detener Caos'; this.notify("info", "Iniciando 'Caos de Jugador' (Agresivo): El bot enviará ráfagas de movimientos, gestos, AFK y cambios de estado. Esto puede ser muy molesto para otros."); let count = 0; this.#playerChaosInterval = setInterval(() => { if (!bot.getReadyState()) { this.notify("warning", "Bot desconectado, deteniendo 'Caos de Jugador'."); this.#togglePlayerChaos(button); return; } const randomX = Math.random() * 100; const randomY = Math.random() * 100; bot.emit("moveavatar", randomX, randomY); const randomGesture = Math.floor(Math.random() * 32); bot.emit("sendgesture", randomGesture); bot.emit("playerafk"); const randomFlagId = Math.floor(Math.random() * 5); const randomFlagState = Math.random() < 0.5; bot.emit("setstatusflag", randomFlagId, randomFlagState); count++; if (count >= 750) { this.notify("info", "'Caos de Jugador' completado (750 acciones). Deteniendo."); this.#togglePlayerChaos(button); } }, 100); } } #toggleVisualGlitch(button) { if (this.#visualGlitchInterval) { clearInterval(this.#visualGlitchInterval); this.#visualGlitchInterval = null; button.classList.remove("active"); button.textContent = '<i class="fas fa-ghost"></i> Glitch Visual'; this.notify("info", "Deteniendo 'Glitch Visual'."); } else { const bot = this.#getBot(); if (!bot) return; button.classList.add("active"); button.textContent = '<i class="fas fa-stop"></i> Detener Glitch'; this.notify("info", "Iniciando 'Glitch Visual' (Extremo): El bot forzará cambios de avatar, spam de chat y interrupciones de UI. Puede causar freezes."); let count = 0; this.#visualGlitchInterval = setInterval(() => { if (!bot.getReadyState()) { this.notify("warning", "Bot desconectado, deteniendo 'Glitch Visual'."); this.#toggleVisualGlitch(button); return; } bot.emit("spawnavatar"); bot.emit("setavatarprop"); const chatMessages = [ "!! GLITCH DETECTED !!", "ERROR CODE 404: REALITY NOT FOUND", "SYSTEM OVERLOAD", "// VISUAL ANOMALY //", "PACKET CORRUPTION", "DISCONNECTING...", "RECALIBRATING... X_X", "SCREEN_SHAKE_ACTIVE", "DATA STREAM INTERRUPTED", "SERVER REBOOTING (FAKE)" ]; const randomChatMessage = chatMessages[Math.floor(Math.random() * chatMessages.length)]; bot.emit("chatmsg", randomChatMessage); const playersInRoom = bot.room.players || []; const otherPlayers = playersInRoom.filter(p => p.id !== bot.id && p.id !== 0); if (otherPlayers.length > 0) { const randomPlayer = otherPlayers[Math.floor(Math.random() * otherPlayers.length)]; bot.emit("sendvotekick", randomPlayer.id); this.notify("log", `Glitch: Votekick spam a ${randomPlayer.name}`); } bot.emit("sendvote"); if (otherPlayers.length > 0) { const randomPlayerForToken = otherPlayers[Math.floor(Math.random() * otherPlayers.length)]; const randomTokenId = Math.floor(Math.random() * 9); bot.emit("settoken", randomPlayerForToken.id, randomTokenId); this.notify("log", `Glitch: Token spam a ${randomPlayerForToken.name}`); } count++; if (count >= 1000) { this.notify("info", "'Glitch Visual' completado (1000 acciones). Deteniendo."); this.#toggleVisualGlitch(button); } }, 200); } } #toggleSecretSpam(button) { if (this.#secretSpamInterval) { clearInterval(this.#secretSpamInterval); this.#secretSpamInterval = null; button.classList.remove("active"); button.textContent = '<i class="fas fa-mask"></i> Spam Visual Secreto'; this.notify("info", "Spam Visual 'Secreto' Detenido."); } else { const bot = this.#getBot(); if (!bot) return; button.classList.add("active"); button.textContent = '<i class="fas fa-stop"></i> Detener Spam'; this.notify("info", "Spam Visual 'Secreto' Iniciado (inundando la sala con dibujos)."); let counter = 0; this.#secretSpamInterval = setInterval(() => { if (!bot.getReadyState()) { this.notify("warning", "Bot desconectado, deteniendo Spam Visual 'Secreto'."); this.#toggleSecretSpam(button); return; } for (let i = 0; i < 10; i++) { const x1 = Math.random() * 100; const y1 = Math.random() * 100; const x2 = x1 + (Math.random() * 2 - 1) * 5; const y2 = y1 + (Math.random() * 2 - 1) * 5; const size = Math.floor(Math.random() * 3) + 1; const color = `#${Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0')}`; bot.emit("line", -1, x1, y1, x2, y2, true, size, color, false); } counter += 10; if (counter % 1000 === 0) { this.notify("log", `Secreto: ${counter} comandos enviados.`); } }, 100); } } } })("QBit"); // --- END NEW MODULE: SwarmCommander --- (function PlayerProfileExtractorModule() { const QBit = globalThis[arguments[0]]; // Define token names for better readability. These are from Drawaria's common.js. const TOKEN_NAMES = { 0: "Thumbs Up", 1: "Heart", 2: "Paint Brush", 3: "Cocktail", 4: "Peace Sign", 5: "Feather", 6: "Trophy", 7: "Mug", 8: "Gift" }; class PlayerProfileExtractor extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); #profileUrlInput; #extractedDataDisplay; #downloadButton; #lastExtractedData = null; // Store data for download constructor() { super("Extractor de Perfil (Pegar URL de perfil)", '<i class="fas fa-id-card"></i>'); this.#onStartup(); } #onStartup() { this.#loadInterface(); this.notify("info", "Módulo Extractor de Perfil cargado."); } #loadInterface() { const container = domMake.Tree("div"); // URL Input Row const urlRow = domMake.Row(); this.#profileUrlInput = domMake.Tree("input", { type: "text", placeholder: "https://drawaria.online/profile/?uid=...)", style: "width: 100%; padding: 5px; box-sizing: border-box;" }); urlRow.appendChild(this.#profileUrlInput); container.appendChild(urlRow); // Search Button Row const searchRow = domMake.Row(); const searchButton = domMake.Button('<i class="fas fa-search"></i> Buscar Perfil'); searchButton.addEventListener("click", () => this.#fetchAndExtractProfile()); searchRow.appendChild(searchButton); container.appendChild(searchRow); // Extracted Data Display Area const displayRow = domMake.Row(); this.#extractedDataDisplay = domMake.Tree("div", { style: ` max-height: 400px; overflow-y: auto; border: 1px solid var(--CE-color); padding: 8px; font-size: 0.85em; background-color: var(--CE-bg_color); color: var(--CE-color); margin-top: 5px; white-space: pre-wrap; /* Preserve whitespace and wrap lines */ font-family: monospace; /* For better readability of structured data */ ` }, ["Datos del perfil aparecerán aquí."]); displayRow.appendChild(this.#extractedDataDisplay); container.appendChild(displayRow); // Download Button Row const downloadRow = domMake.Row(); this.#downloadButton = domMake.Button('<i class="fas fa-download"></i> Descargar Datos (JSON)'); this.#downloadButton.disabled = true; // Disabled until data is extracted this.#downloadButton.addEventListener("click", () => this.#downloadExtractedData()); downloadRow.appendChild(this.#downloadButton); container.appendChild(downloadRow); this.htmlElements.section.appendChild(container); } /** * Fetches the profile page HTML and extracts data. */ async #fetchAndExtractProfile() { const profileUrl = this.#profileUrlInput.value.trim(); if (!profileUrl) { this.notify("warning", "Por favor, introduce una URL de perfil."); return; } // Basic URL validation const uidMatch = profileUrl.match(/uid=([a-f0-9-]+)/i); if (!uidMatch || !uidMatch[1]) { this.notify("error", "URL inválida. No se pudo extraer el UID. Asegúrate de que es una URL de perfil de Drawaria (ej. https://drawaria.online/profile/?uid=...)."); return; } const uid = uidMatch[1]; this.notify("info", `Extrayendo datos de: ${profileUrl}...`); this.#extractedDataDisplay.textContent = "Cargando..."; this.#downloadButton.disabled = true; // Declare DOMParser once here so it's accessible to all inner parsing blocks const parser = new DOMParser(); try { // --- 1. Fetch Main Profile Page --- const profileResponse = await fetch(profileUrl); if (!profileResponse.ok) { throw new Error(`Error HTTP (${profileUrl}): ${profileResponse.status} ${profileResponse.statusText}`); } const profileHtmlContent = await profileResponse.text(); // Pass the parser instance to the parsing function const extracted = this.#parseMainProfileHTML(profileHtmlContent, parser); extracted.uid = uid; // Ensure UID is set // --- 2. Fetch Gallery Count (from HTML page) --- const galleryPageUrl = `https://drawaria.online/gallery/?uid=${uid}`; try { const galleryResponse = await fetch(galleryPageUrl); if (galleryResponse.ok) { const galleryHtmlContent = await galleryResponse.text(); // Use the shared parser instance const galleryDoc = parser.parseFromString(galleryHtmlContent, 'text/html'); // Count all .grid-item elements within the .grid container extracted.galleryImagesCount = galleryDoc.querySelectorAll('.grid .grid-item.galleryimage').length; } else { extracted.galleryImagesCount = `Error (${galleryResponse.status})`; this.notify("warning", `Fallo al cargar la página de galería: ${galleryResponse.status}`); } } catch (e) { extracted.galleryImagesCount = `Error al parsear galería (${e.message.substring(0, 50)})`; this.notify("warning", `Fallo al consultar página de galería: ${e.message}`); } // --- 3. Fetch Friends Count (from HTML page) --- const friendsPageUrl = `https://drawaria.online/friends/?uid=${uid}`; try { const friendsResponse = await fetch(friendsPageUrl); if (friendsResponse.ok) { const friendsHtmlContent = await friendsResponse.text(); // Use the shared parser instance const friendsDoc = parser.parseFromString(friendsHtmlContent, 'text/html'); extracted.friendsCount = friendsDoc.querySelectorAll('#friendscontainer .friendcard').length || 0; } else { extracted.friendsCount = `Error (${friendsResponse.status})`; this.notify("warning", `Fallo al cargar la página de amigos: ${friendsResponse.status}`); } } catch (e) { extracted.friendsCount = `Error al parsear amigos (${e.message.substring(0, 50)})`; this.notify("warning", `Fallo al consultar página de amigos: ${e.message}`); } // --- 4. Fetch Palettes Count (from HTML page) --- const palettesPageUrl = `https://drawaria.online/palettes/?uid=${uid}`; try { const palettesResponse = await fetch(palettesPageUrl); if (palettesResponse.ok) { const palettesHtmlContent = await palettesResponse.text(); // Use the shared parser instance const palettesDoc = parser.parseFromString(palettesHtmlContent, 'text/html'); extracted.palettesCount = palettesDoc.querySelectorAll('.palettelist .rowitem').length || 0; } else { extracted.palettesCount = `Error (${palettesResponse.status})`; this.notify("warning", `Fallo al cargar la página de paletas: ${palettesResponse.status}`); } } catch (e) { extracted.palettesCount = `Error al parsear paletas (${e.message.substring(0, 50)})`; this.notify("warning", `Fallo al consultar página de paletas: ${e.message}`); } // Final check: if primary player name was not found, notify as invalid profile // We re-parse here just for this specific final check, using the same parser instance. const doc = parser.parseFromString(profileHtmlContent, 'text/html'); if (extracted.playerName === 'N/A' && !doc.querySelector('h1')) { this.#extractedDataDisplay.textContent = "No se pudo encontrar el perfil o extraer datos. Asegúrate de que la URL es correcta y el perfil existe."; this.#lastExtractedData = null; this.#downloadButton.disabled = true; this.notify("error", "Perfil no encontrado o datos no extraíbles."); return; } // Display extracted data this.#lastExtractedData = extracted; this.#displayExtractedData(); this.#downloadButton.disabled = false; this.notify("success", "Datos del perfil extraídos exitosamente."); } catch (error) { this.notify("error", `Fallo general al cargar o procesar el perfil: ${error.message}`); this.#extractedDataDisplay.textContent = `Error: ${error.message}`; this.#lastExtractedData = null; } } /** * Parses the HTML content of the main profile page and extracts relevant information. * @param {string} htmlContent - The HTML content of the profile page. * @param {DOMParser} parser - The shared DOMParser instance. * @returns {object} Extracted data. */ #parseMainProfileHTML(htmlContent, parser) { // Accept parser instance const doc = parser.parseFromString(htmlContent, 'text/html'); const extracted = {}; // --- Player Info (from profile page) --- const playerInfoAnchor = doc.querySelector('h1 a[href*="profile/?uid="]'); if (playerInfoAnchor) { extracted.avatarUrl = playerInfoAnchor.querySelector('img.turnresults-avatar')?.src || 'N/A'; // Player name is the text content of the anchor AFTER the image const playerNameNode = Array.from(playerInfoAnchor.childNodes).find(node => node.nodeType === Node.TEXT_NODE && node.textContent.trim().length > 0); extracted.playerName = playerNameNode?.textContent.trim() || 'N/A'; } else { extracted.playerName = doc.querySelector('h1')?.textContent.trim() || 'N/A'; // Fallback to just H1 text if no anchor extracted.avatarUrl = 'N/A'; } // --- Level & Experience (from profile page) --- extracted.level = doc.getElementById('levelval')?.textContent.trim() || 'N/A'; // Kept for JSON export, not displayed in text output extracted.experience = doc.getElementById('exp-val')?.textContent.trim() || 'N/A'; // --- Pictionary / Guessing Stats (from profile page) --- extracted.pictionaryStats = {}; const playStatsTableBody = doc.querySelector('#playstats table tbody'); if (playStatsTableBody) { playStatsTableBody.querySelectorAll('tr').forEach(row => { const label = row.querySelector('td:first-child')?.textContent.trim(); const value = row.querySelector('td:last-child')?.textContent.trim(); if (label && value) { const cleanLabel = label.replace(/[:\s]/g, '').toLowerCase(); // "Total score:" -> "totalscore" extracted.pictionaryStats[cleanLabel] = value; } }); } // --- Playground Accolades (Tokens) (from profile page) --- extracted.accusedTokens = {}; const tokensTableBody = doc.querySelector('#tokens table tbody'); if (tokensTableBody) { tokensTableBody.querySelectorAll('i[data-tokenid]').forEach(iconElement => { const tokenId = parseInt(iconElement.dataset.tokenid); const tokenName = TOKEN_NAMES[tokenId] || `Unknown Token ${tokenId}`; const count = parseInt(iconElement.textContent.trim()) || 0; // Text content holds the number extracted.accusedTokens[tokenName] = count; }); } return extracted; } /** * Formats and displays the extracted data in the UI. */ #displayExtractedData() { if (!this.#lastExtractedData) { this.#extractedDataDisplay.textContent = "No hay datos para mostrar."; return; } let displayTxt = `--- Datos del Perfil ---\n`; displayTxt += ` UID: ${this.#lastExtractedData.uid}\n`; displayTxt += ` Nombre del Jugador: ${this.#lastExtractedData.playerName}\n`; // Level is removed from text display, but still in #lastExtractedData for JSON export displayTxt += ` Experiencia: ${this.#lastExtractedData.experience}\n`; displayTxt += ` URL del Avatar: ${this.#lastExtractedData.avatarUrl}\n`; // GalleryImagesCount is removed from text display, but still in #lastExtractedData for JSON export displayTxt += ` Cantidad de Amigos: ${this.#lastExtractedData.friendsCount}\n`; displayTxt += ` Cantidad de Paletas: ${this.#lastExtractedData.palettesCount}\n\n`; displayTxt += `--- Estadísticas de Pictionary / Adivinanza ---\n`; if (Object.keys(this.#lastExtractedData.pictionaryStats).length > 0) { for (const key in this.#lastExtractedData.pictionaryStats) { const displayKey = key.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase()); displayTxt += ` ${displayKey}: ${this.#lastExtractedData.pictionaryStats[key]}\n`; } } else { displayTxt += ` No se encontraron estadísticas detalladas de Pictionary.\n`; } displayTxt += `\n`; displayTxt += `--- Menciones de Homenaje (Tokens) ---\n`; if (Object.keys(this.#lastExtractedData.accusedTokens).length > 0) { for (const tokenName in this.#lastExtractedData.accusedTokens) { displayTxt += ` ${tokenName}: ${this.#lastExtractedData.accusedTokens[tokenName]}\n`; } } else { displayTxt += ` No se encontraron Menciones de Homenaje.\n`; } this.#extractedDataDisplay.textContent = displayTxt; } /** * Downloads the extracted data as a JSON file. */ #downloadExtractedData() { if (!this.#lastExtractedData) { this.notify("warning", "No hay datos extraídos para descargar."); return; } const dataStr = JSON.stringify(this.#lastExtractedData, null, 2); const blob = new Blob([dataStr], { type: 'application/json' }); const url = URL.createObjectURL(blob); const playerNameForFilename = this.#lastExtractedData.playerName.replace(/[^a-zA-Z0-9]/g, '_').substring(0, 30); // Clean for filename const filename = `drawaria_profile_${playerNameForFilename}_${this.#lastExtractedData.uid ? this.#lastExtractedData.uid.substring(0, 8) : 'data'}.json`; const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); this.notify("success", `Datos del perfil descargados como ${filename}.`); } } })("QBit"); // --- START NEW MODULE: ImageAnalyzer (ACTUALIZADO con Copy/Download/Add to Palette) --- (function ImageAnalyzerModule() { const QBit = globalThis[arguments[0]]; QBit.Styles.addRules([ `#image-analyzer-container { display: flex; flex-direction: column; gap: 10px; padding: 5px; border: 1px solid var(--CE-color); border-radius: .25rem; background-color: var(--CE-bg_color); }`, `#image-analyzer-container input[type="text"], #image-analyzer-container button { width: 100%; padding: 5px; box-sizing: border-box; }`, `#image-preview-canvas { border: 1px dashed var(--CE-color); max-width: 100%; height: auto; display: block; margin: 5px auto; }`, `#dominant-colors-display { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 5px; }`, `#dominant-colors-display .color-swatch { width: 30px; height: 30px; border: 1px solid #ccc; border-radius: 3px; cursor: pointer; box-shadow: 0 0 3px rgba(0,0,0,0.2); }`, `#analysis-results { background-color: var(--CE-bg_color); border: 1px solid var(--CE-color); padding: 8px; margin-top: 10px; font-size: 0.8em; max-height: 150px; overflow-y: auto; white-space: pre-wrap; font-family: monospace; }`, `#image-analyzer-container .action-buttons { display: flex; gap: 5px; margin-top: 5px; }`, `#image-analyzer-container .action-buttons button { flex: 1; }` ]); class ImageAnalyzer extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); #imageUrlInput; #loadAndAnalyzeButton; #imagePreviewCanvas; #imagePreviewCtx; #dominantColorsDisplay; #analysisResultsDisplay; #copyResultsButton; #downloadResultsButton; constructor() { super("Analizador de Imágenes", '<i class="fas fa-camera-retro"></i>'); this.#onStartup(); } #onStartup() { this.#loadInterface(); this.notify("info", "Módulo Analizador de Imágenes cargado."); this.#loadAndAnalyzeButton.disabled = false; this.#loadAndAnalyzeButton.textContent = 'Cargar y Analizar'; this.#copyResultsButton.disabled = true; this.#downloadResultsButton.disabled = true; } #loadInterface() { const container = domMake.Tree("div", { id: "image-analyzer-container" }); this.#imageUrlInput = domMake.Tree("input", { type: "text", placeholder: "Pegar URL de imagen (ej. avatar)", value: "https://drawaria.online/avatar/cache/1a5f4450-7153-11ef-acaf-250da20bac69.jpg" }); container.appendChild(this.#imageUrlInput); this.#loadAndAnalyzeButton = domMake.Button('<i class="fas fa-chart-pie"></i> Cargar y Analizar'); this.#loadAndAnalyzeButton.addEventListener("click", () => this.#loadImageAndAnalyze()); container.appendChild(this.#loadAndAnalyzeButton); this.#imagePreviewCanvas = domMake.Tree("canvas", { id: "image-preview-canvas" }); container.appendChild(this.#imagePreviewCanvas); this.#imagePreviewCtx = this.#imagePreviewCanvas.getContext('2d'); container.appendChild(domMake.Tree("div", {}, ["Colores Dominantes (clic para añadir a Paletas):"])); this.#dominantColorsDisplay = domMake.Tree("div", { id: "dominant-colors-display" }); container.appendChild(this.#dominantColorsDisplay); this.#analysisResultsDisplay = domMake.Tree("pre", { id: "analysis-results" }, ["Resultados del análisis aparecerán aquí."]); container.appendChild(this.#analysisResultsDisplay); // Action Buttons Row (Copy/Download) const actionButtonsRow = domMake.Row({ class: "action-buttons" }); this.#copyResultsButton = domMake.Button('<i class="fas fa-copy"></i> Copiar'); this.#copyResultsButton.addEventListener("click", () => this.#copyResultsToClipboard()); actionButtonsRow.appendChild(this.#copyResultsButton); this.#downloadResultsButton = domMake.Button('<i class="fas fa-download"></i> Descargar'); this.#downloadResultsButton.addEventListener("click", () => this.#downloadResults()); actionButtonsRow.appendChild(this.#downloadResultsButton); container.appendChild(actionButtonsRow); this.htmlElements.section.appendChild(container); } async #loadImageAndAnalyze() { const imageUrl = this.#imageUrlInput.value.trim(); if (!imageUrl) { this.notify("warning", "Por favor, introduce una URL de imagen."); return; } this.notify("info", "Cargando imagen para análisis..."); this.#analysisResultsDisplay.textContent = "Cargando..."; this.#dominantColorsDisplay.innerHTML = ''; this.#imagePreviewCtx.clearRect(0, 0, this.#imagePreviewCanvas.width, this.#imagePreviewCanvas.height); this.#copyResultsButton.disabled = true; this.#downloadResultsButton.disabled = true; const img = new window.Image(); img.crossOrigin = "Anonymous"; // Crucial for CORS img.src = imageUrl; img.onload = async () => { try { const maxWidth = 300; const maxHeight = 300; let width = img.width; let height = img.height; if (width > maxWidth || height > maxHeight) { if (width / maxWidth > height / maxHeight) { height = height * (maxWidth / width); width = maxWidth; } else { width = width * (maxHeight / height); height = maxHeight; } } this.#imagePreviewCanvas.width = width; this.#imagePreviewCanvas.height = height; this.#imagePreviewCtx.drawImage(img, 0, 0, width, height); const imageData = this.#imagePreviewCtx.getImageData(0, 0, width, height); const pixels = imageData.data; const results = {}; const colorMap = new Map(); const sampleStep = Math.max(1, Math.floor(pixels.length / 4 / 5000)); for (let i = 0; i < pixels.length; i += 4 * sampleStep) { const r = pixels[i]; const g = pixels[i + 1]; const b = pixels[i + 2]; const a = pixels[i + 3]; if (a > 20) { const colorKey = `${r},${g},${b}`; colorMap.set(colorKey, (colorMap.get(colorKey) || 0) + 1); } } const sortedColors = Array.from(colorMap.entries()) .sort((a, b) => b[1] - a[1]) .slice(0, 5); results.dominantColors = sortedColors.map(([rgbStr, count]) => { const [r, g, b] = rgbStr.split(',').map(Number); return { r, g, b, hex: `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}` }; }); this.#dominantColorsDisplay.innerHTML = ''; results.dominantColors.forEach(color => { const swatch = domMake.Tree("div", { class: "color-swatch", style: `background-color: ${color.hex};`, title: `Clic para añadir ${color.hex} a Paletas de Color.` }); swatch.addEventListener('click', () => this.#addDominantColorToPalette(color.hex)); this.#dominantColorsDisplay.appendChild(swatch); }); let totalBrightness = 0; let nonTransparentPixelCount = 0; for (let i = 0; i < pixels.length; i += 4) { const r = pixels[i]; const g = pixels[i + 1]; const b = pixels[i + 2]; const a = pixels[i + 3]; if (a > 0) { totalBrightness += (0.299 * r + 0.587 * g + 0.114 * b); nonTransparentPixelCount++; } } results.averageBrightness = nonTransparentPixelCount > 0 ? (totalBrightness / nonTransparentPixelCount).toFixed(2) : 'N/A'; results.imageDimensions = `${width}x${height}`; results.totalPixels = width * height; results.nonTransparentPixels = nonTransparentPixelCount; let resultsText = "--- Resultados del Análisis de Imagen ---\n"; resultsText += `Dimensiones: ${results.imageDimensions} píxeles\n`; resultsText += `Píxeles no transparentes: ${results.nonTransparentPixels}\n`; resultsText += `Brillo promedio: ${results.averageBrightness}\n`; resultsText += `Colores Dominantes (HEX): ${results.dominantColors.map(c => c.hex).join(', ')}\n`; resultsText += "\n¡Análisis completado!"; this.#analysisResultsDisplay.textContent = resultsText; this.notify("success", "Análisis de imagen completado."); this.#copyResultsButton.disabled = false; this.#downloadResultsButton.disabled = false; } catch (e) { if (e.name === "SecurityError" || (e.message && e.message.includes("tainted"))) { this.notify("error", "Error de CORS: No se pudo acceder a los píxeles de la imagen. La imagen debe estar en el mismo origen o permitir CORS."); this.#analysisResultsDisplay.textContent = "Error: No se pudo leer la imagen debido a restricciones de seguridad (CORS)."; } else { this.notify("error", `Error al analizar la imagen: ${e.message}`); this.#analysisResultsDisplay.textContent = `Error: ${e.message}`; console.error("Image analysis error:", e); } } }; img.onerror = () => { this.notify("error", "Fallo al cargar la imagen. ¿URL correcta o problema de red?"); this.#analysisResultsDisplay.textContent = "Error: Fallo al cargar la imagen."; }; } // --- Nuevas funciones de Utilidad --- async #copyResultsToClipboard() { const resultsText = this.#analysisResultsDisplay.textContent; if (!resultsText.trim()) { this.notify("warning", "No hay resultados para copiar."); return; } try { await navigator.clipboard.writeText(resultsText); this.notify("success", "Resultados copiados al portapapeles."); } catch (err) { this.notify("error", `Error al copiar: ${err.message}`); console.error("Copy to clipboard failed:", err); } } #downloadResults() { const resultsText = this.#analysisResultsDisplay.textContent; if (!resultsText.trim()) { this.notify("warning", "No hay resultados para descargar."); return; } const blob = new Blob([resultsText], { type: 'text/plain;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `drawaria_image_analysis_${Date.now()}.txt`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); this.notify("success", "Resultados descargados como TXT."); } #addDominantColorToPalette(hexColor) { const moreColorPalettesModule = this.findGlobal("MoreColorPalettes"); if (moreColorPalettesModule && moreColorPalettesModule.siblings && moreColorPalettesModule.siblings.length > 0) { const paletteInstance = moreColorPalettesModule.siblings[0]; // Assuming first instance if (paletteInstance && typeof paletteInstance.addCustomColorFromExternal === 'function') { paletteInstance.addCustomColorFromExternal(hexColor); this.notify("info", `Color ${hexColor} enviado a 'Paletas de Color'.`); } else { this.notify("warning", "El módulo 'Paletas de Color' no está listo o le falta la función para añadir colores."); } } else { this.notify("warning", "El módulo 'Paletas de Color' no se encontró o no está activo."); } } } })("QBit"); // --- END NEW MODULE: ImageAnalyzer --- (function RoomNavigatorModule() { const QBit = globalThis[arguments[0]]; // Corrected access to QBit // Helper to fetch JSON (re-declaring if not globally accessible or just for clarity) function fetchJson(url) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('GET', url); xhr.onload = () => { if (xhr.status >= 200 && xhr.status < 300) { try { let parsedData = JSON.parse(xhr.responseText); // Handle potential double-encoded JSON if (typeof parsedData === 'string') { parsedData = JSON.parse(parsedData); } resolve(parsedData); } catch (e) { reject(new Error('Fallo al analizar la respuesta JSON: ' + e.message + ` (Raw: ${xhr.responseText.substring(0, 100)}...)`)); } } else { reject(new Error(`La respuesta de la red no fue correcta, estado: ${xhr.status} ${xhr.statusText}`)); } }; xhr.onerror = () => reject(new Error('Fallo la solicitud de red. Revisa tu conexión o el servidor.')); xhr.send(); }); } class RoomNavigator extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); #roomListContainer; #refreshButton; #loadingIndicator; #filterNameInput; #filterMinPlayersInput; #filterMaxPlayersInput; #roomDataCache = []; // Store fetched raw room data constructor() { super("Navegador de Salas", '<i class="fas fa-search-location"></i>'); this.#onStartup(); } #onStartup() { this.#loadInterface(); this.#fetchAndApplyFilters(); // Initial fetch and display } #loadInterface() { const container = domMake.Tree("div"); // Refresh and Loading Row const headerRow = domMake.Row(); headerRow.style.marginBottom = "10px"; this.#refreshButton = domMake.Button('<i class="fas fa-sync-alt"></i> Actualizar Salas'); this.#refreshButton.title = "Actualiza la lista de salas disponibles."; this.#refreshButton.addEventListener("click", () => this.#fetchAndApplyFilters()); headerRow.appendChild(this.#refreshButton); this.#loadingIndicator = domMake.Tree("span", { style: "margin-left: 10px; color: var(--info); display: none;" }, ['Cargando...']); headerRow.appendChild(this.#loadingIndicator); container.appendChild(headerRow); // Filters Row const filtersRow = domMake.Row(); filtersRow.style.flexWrap = "wrap"; filtersRow.style.gap = "5px"; this.#filterNameInput = domMake.Tree("input", { type: "text", placeholder: "Filtrar por nombre", style: "flex: 1 1 120px;" }); this.#filterNameInput.addEventListener("input", () => this.#applyFiltersAndDisplayRooms()); filtersRow.appendChild(this.#filterNameInput); this.#filterMinPlayersInput = domMake.Tree("input", { type: "number", min: "0", placeholder: "Min Jugadores", style: "width: 80px;" }); this.#filterMinPlayersInput.addEventListener("input", () => this.#applyFiltersAndDisplayRooms()); filtersRow.appendChild(this.#filterMinPlayersInput); this.#filterMaxPlayersInput = domMake.Tree("input", { type: "number", min: "0", placeholder: "Max Jugadores", style: "width: 80px;" }); this.#filterMaxPlayersInput.addEventListener("input", () => this.#applyFiltersAndDisplayRooms()); filtersRow.appendChild(this.#filterMaxPlayersInput); container.appendChild(filtersRow); // Room List Display Area this.#roomListContainer = domMake.Tree("div", { class: "room-list-display", style: ` display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 10px; padding: 5px; max-height: 400px; /* Limits height and adds scrollbar */ overflow-y: auto; border: 1px solid var(--CE-color); border-radius: .25rem; margin-top: 10px; ` }); container.appendChild(this.#roomListContainer); this.htmlElements.section.appendChild(container); } async #fetchAndApplyFilters() { this.#roomListContainer.innerHTML = ''; this.#loadingIndicator.style.display = 'inline'; this.#refreshButton.disabled = true; try { const roomData = await fetchJson('https://drawaria.online/getroomlist'); if (!Array.isArray(roomData)) { throw new Error('La respuesta no es un array de salas válido.'); } this.#roomDataCache = roomData; // Cache the raw data this.notify("info", `Se encontraron ${roomData.length} salas.`); this.#applyFiltersAndDisplayRooms(); } catch (error) { this.notify("error", `Error al cargar la lista de salas: ${error.message}`); this.#roomListContainer.appendChild(domMake.TextNode("Error al cargar las salas. Inténtalo de nuevo.")); console.error("Fetch room list error:", error); } finally { this.#loadingIndicator.style.display = 'none'; this.#refreshButton.disabled = false; } } #applyFiltersAndDisplayRooms() { let filteredRooms = [...this.#roomDataCache]; // Start with cached data const nameFilter = this.#filterNameInput.value.toLowerCase(); const minPlayers = parseInt(this.#filterMinPlayersInput.value); const maxPlayers = parseInt(this.#filterMaxPlayersInput.value); if (nameFilter) { filteredRooms = filteredRooms.filter(room => { const roomName = room[3] ? room[3].toLowerCase() : ''; // Access roomName by index 3 return roomName.includes(nameFilter); }); } if (!isNaN(minPlayers) && minPlayers >= 0) { filteredRooms = filteredRooms.filter(room => room[1] >= minPlayers); // Access currentPlayers by index 1 } if (!isNaN(maxPlayers) && maxPlayers >= 0) { filteredRooms = filteredRooms.filter(room => room[1] <= maxPlayers); // Access currentPlayers by index 1 } this.#displayRooms(filteredRooms); } #displayRooms(rooms) { this.#roomListContainer.innerHTML = ''; if (rooms.length === 0) { this.#roomListContainer.appendChild(domMake.TextNode("No hay salas que coincidan con los filtros.")); return; } rooms.forEach(roomArray => { // Structure observed from the provided JSON example: // [0] roomId: string (e.g., "f0e1c44b-fc49-4160-91c9-b297fb662692" or "f49bf487-e96a-45a9-9616-c4b71662ac50.3") // [1] currentPlayers: number // [2] maxPlayers: number // [3] roomName: string (e.g., "жду друзей", "sos :)", or "") // [4] gameModeType: number (2 for public, 3 for private/friend/custom) // [5] unknown_number: number (e.g., 273052, 4176 - seems like a server-side counter or ID) // [6] flags_array: Array (e.g., [null,true,null,null,null,true] or [null,true]) // - flags_array[0]: boolean (possibly password protected if true) // - flags_array[1]: boolean (this flag seems always true in sample, not a public/private indicator) // [7] roundsPlayed: number // [8] serverId: number or null (e.g., 5 or null if main server, part of room ID for specific servers) const roomId = roomArray[0]; const currentPlayers = roomArray[1]; const maxPlayers = roomArray[2]; const roomName = roomArray[3]; const gameModeType = roomArray[4]; const flags = roomArray[6]; // Flags are at index 6 const roundsPlayed = roomArray[7]; const serverId = roomArray[8]; let roomModeLabel = 'Desconocido'; if (gameModeType === 2) { roomModeLabel = 'Público'; } else if (gameModeType === 3) { roomModeLabel = 'Amigos/Privado'; } // Access flags_array[0] for password protected status const isPasswordProtected = flags && flags.length > 0 && flags[0] === true; const isFull = currentPlayers >= maxPlayers; const statusColor = isFull ? 'var(--danger)' : 'var(--success)'; const joinableStatus = isFull ? 'LLENA' : 'DISPONIBLE'; const roomCard = domMake.Tree("div", { class: "room-card", style: ` border: 1px solid var(--CE-color); border-radius: .25rem; padding: 8px; background-color: var(--CE-bg_color); display: flex; flex-direction: column; justify-content: space-between; gap: 5px; font-size: 0.85em; box-shadow: 0 2px 4px rgba(0,0,0,0.1); ` }); const nodesToAppend = [ domMake.Tree("div", { style: "font-weight: bold; color: var(--dark-blue-title); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;", title: roomName }, [`Sala: ${roomName || '(Sin Nombre)'}`]), domMake.Tree("div", {}, [`Jugadores: ${currentPlayers}/${maxPlayers}`]), domMake.Tree("div", {}, [`Rondas jugadas: ${roundsPlayed || 'N/A'}`]), domMake.Tree("div", {}, [`Modo: ${roomModeLabel}`]), isPasswordProtected ? domMake.Tree("div", {style: "color: var(--warning);"}, [`Contraseña: Sí`]) : null, domMake.Tree("div", { style: `color: ${statusColor}; font-weight: bold;` }, [`Estado: ${joinableStatus}`]) ].filter(node => node instanceof Node); roomCard.appendAll(...nodesToAppend); const joinButton = domMake.Button("Unirse"); joinButton.classList.add("btn-primary"); joinButton.style.width = "100%"; joinButton.style.marginTop = "5px"; // joinButton.disabled = isFull; // REMOVED: Allow joining even if full joinButton.addEventListener("click", () => this.#joinRoom(roomId, serverId)); roomCard.appendChild(joinButton); this.#roomListContainer.appendChild(roomCard); }); } #joinRoom(roomId, serverId) { let fullRoomIdentifier = roomId; // Construct full room ID, e.g., "uuid.serverId" if (serverId !== null && serverId !== undefined && !String(roomId).includes(`.${serverId}`)) { fullRoomIdentifier = `${roomId}.${serverId}`; } const roomUrl = `https://drawaria.online/room/${fullRoomIdentifier}`; window.location.assign(roomUrl); // Navigate to the room } } })("QBit"); (function ClientCommandSender() { const QBit = globalThis[arguments[0]]; class ClientCommandSender extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); #cmdIdInput; #param1Input; #param2Input; #param3Input; constructor() { super("Client Cmd", '<i class="fas fa-code"></i>'); this.#onStartup(); } #onStartup() { this.#loadInterface(); } #loadInterface() { const row1 = domMake.Row(); this.#cmdIdInput = domMake.Tree("input", { type: "number", placeholder: "Command ID (e.g., 10)" }); row1.appendChild(this.#cmdIdInput); this.htmlElements.section.appendChild(row1); const row2 = domMake.Row(); this.#param1Input = domMake.Tree("input", { type: "text", placeholder: "Param 1 (e.g., true)" }); this.#param2Input = domMake.Tree("input", { type: "text", placeholder: "Param 2 (e.g., 1)" }); row2.appendAll(this.#param1Input, this.#param2Input); this.htmlElements.section.appendChild(row2); const row3 = domMake.Row(); this.#param3Input = domMake.Tree("input", { type: "text", placeholder: "Param 3 (e.g., 'text')" }); const sendButton = domMake.Button("Send CMD"); sendButton.addEventListener("click", () => this.sendCommand()); row3.appendAll(this.#param3Input, sendButton); this.htmlElements.section.appendChild(row3); } parseParam(paramStr) { if (paramStr.toLowerCase() === 'true') return true; if (paramStr.toLowerCase() === 'false') return false; if (!isNaN(paramStr) && paramStr.trim() !== '') return Number(paramStr); if (paramStr.startsWith('[') && paramStr.endsWith(']')) { try { return JSON.parse(paramStr); // For arrays } catch (e) { return paramStr; } } if (paramStr.startsWith('{') && paramStr.endsWith('}')) { try { return JSON.parse(paramStr); // For objects } catch (e) { return paramStr; } } return paramStr; // Default to string } sendCommand() { const cmdId = parseInt(this.#cmdIdInput.value); if (isNaN(cmdId)) { this.notify("warning", "Command ID must be a number."); return; } const params = []; const param1 = this.#param1Input.value.trim(); const param2 = this.#param2Input.value.trim(); const param3 = this.#param3Input.value.trim(); if (param1) params.push(this.parseParam(param1)); if (param2) params.push(this.parseParam(param2)); if (param3) params.push(this.parseParam(param3)); if (globalThis.sockets && globalThis.sockets.length > 0) { const payload = ["clientcmd", cmdId, params]; const dataToSend = `${42}${JSON.stringify(payload)}`; globalThis.sockets[0].send(dataToSend); this.notify("info", `Custom clientcmd ${cmdId} sent with params: ${JSON.stringify(params)}.`); } else { this.notify("warning", "No active WebSocket connection found."); } } } })("QBit"); (function AdvancedTelemetry() { const QBit = globalThis[arguments[0]]; class AdvancedTelemetry extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); #playerMetricsContainer; #snapshotContainer; #snapshots = []; #maxSnapshots = 3; constructor() { super("Telemetría Avanzada", '<i class="fas fa-chart-line"></i>'); this.#onStartup(); } #onStartup() { this.#loadInterface(); this.#listenToGameEvents(); } #loadInterface() { this.#row1(); // Panel de Control de Jugadores this.#row2(); // Historial Visual de Rondas this.#row3(); // Temas Dinámicos del HUD } #row1() { const row = domMake.Row(); this.#playerMetricsContainer = domMake.Tree("div", { class: "player-metrics-list" }); row.appendChild(domMake.Tree("label", {}, ["Métricas de Jugadores:"])); row.appendChild(this.#playerMetricsContainer); this.htmlElements.section.appendChild(row); this.updatePlayerMetrics(); // Initial update } #row2() { const row = domMake.Row(); const captureSnapshotButton = domMake.Button("Capturar Lienzo"); captureSnapshotButton.title = "Guarda una imagen del lienzo actual."; captureSnapshotButton.addEventListener("click", () => this.captureCanvasSnapshot()); this.#snapshotContainer = domMake.Tree("div", { class: "snapshot-previews icon-list" }); row.appendAll(captureSnapshotButton, this.#snapshotContainer); this.htmlElements.section.appendChild(row); } #row3() { const row = domMake.Row(); const hudColorLabel = domMake.Tree("label", {}, ["Color del HUD:"]); const hudColorInput = domMake.Tree("input", { type: "color", value: "#007bff" }); // Default Bootstrap primary hudColorInput.addEventListener("change", (e) => { const newColor = e.target.value; document.documentElement.style.setProperty('--primary', newColor); document.documentElement.style.setProperty('--success', newColor); // Apply to success as well for consistency this.notify("info", `Color del HUD cambiado a: ${newColor}`); }); row.appendAll(hudColorLabel, hudColorInput); this.htmlElements.section.appendChild(row); } #listenToGameEvents() { // Update player metrics whenever player list changes const playerListElement = document.getElementById("playerlist"); if (playerListElement) { const observer = new MutationObserver(() => this.updatePlayerMetrics()); observer.observe(playerListElement, { childList: true, subtree: true }); } // Listen for chat messages for conceptual "heatmap" if (globalThis._io && globalThis._io.events) { // This is a placeholder as direct binding to _io.events.bc_chatmessage might not always work without a bot. // A more robust solution would be to observe the #chatbox_messages div. const chatboxMessages = document.getElementById("chatbox_messages"); if (chatboxMessages) { const chatObserver = new MutationObserver((mutations) => { mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node.classList && node.classList.contains('chatmessage') && !node.classList.contains('systemchatmessage5')) { const playerNameElement = node.querySelector('.playerchatmessage-name a'); const playerName = playerNameElement ? playerNameElement.textContent : 'Unknown'; } }); }); }); chatObserver.observe(chatboxMessages, { childList: true }); } } } updatePlayerMetrics() { this.#playerMetricsContainer.innerHTML = ''; const playerRows = document.querySelectorAll("#playerlist .playerlist-row"); if (playerRows.length === 0) { this.#playerMetricsContainer.appendChild(domMake.TextNode("No hay jugadores en la sala.")); return; } playerRows.forEach(playerRow => { const playerId = playerRow.dataset.playerid; const playerName = playerRow.querySelector(".playerlist-name a")?.textContent || `Player ${playerId}`; const score = playerRow.querySelector(".playerlist-rank")?.textContent || 'N/A'; const turnScore = playerRow.querySelector(".playerlist-turnscore")?.textContent || 'N/A'; const metricItem = domMake.Tree("div", { style: "margin: 2px 0; font-size: 0.8rem;" }, [ domMake.Tree("strong", {}, [`${playerName} (ID: ${playerId}): `]), domMake.TextNode(`Puntuación: ${score}, Turno: ${turnScore}`) ]); this.#playerMetricsContainer.appendChild(metricItem); }); } updatePlayerActivity(playerName) { // This is a conceptual update. In a real scenario, this would update // a dedicated "activity heatmap" visual. this.notify("debug", ``); const playerElements = document.querySelectorAll(`#playerlist .playerlist-row .playerlist-name a`); playerElements.forEach(el => { if (el.textContent === playerName) { el.closest('.playerlist-row').style.backgroundColor = 'rgba(0, 255, 0, 0.1)'; // Flash green setTimeout(() => { el.closest('.playerlist-row').style.backgroundColor = ''; }, 500); } }); } captureCanvasSnapshot() { const gameCanvas = document.body.querySelector("canvas#canvas"); if (!gameCanvas) { this.notify("error", "Lienzo de juego no encontrado para capturar."); return; } try { const base64Image = gameCanvas.toDataURL("image/png"); const timestamp = new Date().toLocaleString(); this.#snapshots.push({ data: base64Image, timestamp: timestamp }); if (this.#snapshots.length > this.#maxSnapshots) { this.#snapshots.shift(); // Keep only the last N snapshots } this.updateSnapshotPreviews(); this.notify("success", `Instantánea del lienzo capturada: ${timestamp}`); } catch (e) { this.notify("error", `Error al capturar el lienzo: ${e.message}`); console.error("Canvas snapshot error:", e); } } updateSnapshotPreviews() { this.#snapshotContainer.innerHTML = ''; if (this.#snapshots.length === 0) { this.#snapshotContainer.appendChild(domMake.TextNode("No hay instantáneas guardadas.")); return; } this.#snapshots.forEach((snapshot, index) => { const img = domMake.Tree("img", { src: snapshot.data, style: "width: 50px; height: 50px; border: 1px solid #ccc; margin: 2px; cursor: pointer;", title: `Instantánea ${index + 1}: ${snapshot.timestamp}` }); img.addEventListener("click", () => this.displaySnapshot(snapshot.data)); this.#snapshotContainer.appendChild(img); }); } displaySnapshot(imageData) { // Create a temporary overlay to display the full snapshot const overlay = domMake.Tree("div", { style: ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 10000; display: flex; justify-content: center; align-items: center; ` }); const img = domMake.Tree("img", { src: imageData, style: `max-width: 90%; max-height: 90%; border: 2px solid white;` }); overlay.appendChild(img); overlay.addEventListener("click", () => overlay.remove()); // Close on click document.body.appendChild(overlay); } } })("QBit"); (function GameLogModule() { const QBit = globalThis[arguments[0]]; // Corrected access to QBit class GameLog extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); #gameLog = []; // Stores log entries #logDisplayElement; #isLoggingActive = true; // Default to active logging constructor() { super("Registro del Juego", '<i class="fas fa-clipboard-list"></i>'); this.#onStartup(); } #onStartup() { this.#loadInterface(); this.#setupLogHooks(); this.notify("info", "Registro de Juego iniciado."); } #loadInterface() { const container = domMake.Tree("div"); // Toggle Logging Button const toggleRow = domMake.Row(); const toggleButton = domMake.Button("Desactivar Registro"); toggleButton.addEventListener("click", () => this.#toggleLogging(toggleButton)); toggleRow.appendChild(toggleButton); container.appendChild(toggleRow); // Log Display Area const displayRow = domMake.Row(); this.#logDisplayElement = domMake.Tree("div", { style: ` max-height: 250px; overflow-y: auto; border: 1px solid var(--CE-color); padding: 5px; font-size: 0.75em; background-color: var(--CE-bg_color); color: var(--CE-color); margin-top: 5px; ` }, ["Registro de eventos vacío."]); displayRow.appendChild(this.#logDisplayElement); container.appendChild(displayRow); // Control Buttons const controlRow = domMake.Row(); const clearButton = domMake.Button("Limpiar Log"); clearButton.addEventListener("click", () => this.#clearLog()); controlRow.appendChild(clearButton); const exportTxtButton = domMake.Button("Exportar TXT"); exportTxtButton.addEventListener("click", () => this.#exportLog('txt')); controlRow.appendChild(exportTxtButton); const exportJsonButton = domMake.Button("Exportar JSON"); exportJsonButton.addEventListener("click", () => this.#exportLog('json')); controlRow.appendChild(exportJsonButton); container.appendChild(controlRow); this.htmlElements.section.appendChild(container); } #setupLogHooks() { // Hook into incoming WebSocket events (using _io.events where possible) if (globalThis._io && globalThis._io.events) { // Example of hooking: const eventsToLog = [ "bc_chatmessage", "uc_turn_begindraw", "uc_turn_selectword", "bc_round_results", "bc_turn_results", "bc_votekick", "bc_clientnotify", "bc_announcement", "bc_extannouncement", "mc_roomplayerschange" // To see player list changes ]; eventsToLog.forEach(eventName => { const originalEventCallback = globalThis._io.events[eventName]; globalThis._io.events[eventName] = (...args) => { this.#logEvent(eventName, args); if (originalEventCallback) { originalEventCallback.apply(this, args); } }; }); } // Also observe the actual chatbox for messages to ensure all chat is captured, // as some might not pass through _io.events in a way we can easily intercept. const chatboxMessages = document.getElementById("chatbox_messages"); if (chatboxMessages) { const chatObserver = new MutationObserver((mutations) => { if (!this.#isLoggingActive) return; mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node.nodeType === 1 && node.classList.contains('chatmessage')) { this.#logChatMessage(node); } }); }); }); chatObserver.observe(chatboxMessages, { childList: true }); } } #logEvent(type, data) { if (!this.#isLoggingActive) return; const timestamp = new Date().toISOString(); this.#gameLog.push({ timestamp, type, data: JSON.parse(JSON.stringify(data)) }); // Deep copy data this.#updateLogDisplay(); } #logChatMessage(messageNode) { if (!this.#isLoggingActive) return; const timestamp = messageNode.dataset.ts ? new Date(parseInt(messageNode.dataset.ts)).toISOString() : new Date().toISOString(); let entry = { timestamp, type: "chatmsg" }; if (messageNode.classList.contains('systemchatmessage') || messageNode.classList.contains('systemchatmessage5')) { entry.subtype = "system"; entry.content = messageNode.textContent.trim(); } else { entry.subtype = "player"; entry.playerName = messageNode.querySelector('.playerchatmessage-name')?.textContent?.trim() || 'Unknown'; entry.playerId = messageNode.querySelector('.playerchatmessage-name')?.parentElement?.dataset?.playerid || 'N/A'; entry.content = messageNode.querySelector('.playerchatmessage-text')?.textContent?.trim() || ''; entry.isSelf = messageNode.classList.contains('playerchatmessage-selfname'); } this.#gameLog.push(entry); this.#updateLogDisplay(); } #updateLogDisplay() { // Display only the last N messages to avoid performance issues const maxDisplayEntries = 50; const entriesToDisplay = this.#gameLog.slice(-maxDisplayEntries); this.#logDisplayElement.innerHTML = ''; // Clear previous content entriesToDisplay.forEach(entry => { const logLine = domMake.Tree("div", { style: ` white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: ${entry.type.includes('error') ? 'var(--danger)' : entry.type.includes('warning') ? 'var(--warning)' : entry.type.includes('system') ? 'var(--info)' : 'var(--CE-color)'}; `, title: JSON.stringify(entry) // Show full details on hover }); let displayTxt = `[${new Date(entry.timestamp).toLocaleTimeString()}] `; if (entry.type === "chatmsg") { if (entry.subtype === "system") { displayTxt += `[SISTEMA] ${entry.content}`; } else { displayTxt += `[CHAT] ${entry.playerName} (${entry.playerId}): ${entry.content}`; } } else if (entry.type === "uc_turn_begindraw" && entry.data && entry.data) { displayTxt += `[TURNO] Comienza dibujo. Palabra: ${entry.data[1] || 'Desconocida'}`; } else if (entry.type === "uc_turn_selectword" && entry.data && entry.data) { displayTxt += `[TURNO] Seleccionar palabra: [${entry.data[2]?.join(', ') || 'N/A'}]`; } else if (entry.type === "bc_round_results") { displayTxt += `[RONDA] Resultados de ronda.`; // Too much data to display directly } else if (entry.type === "bc_turn_results") { displayTxt += `[TURNO] Resultados de turno.`; } else { displayTxt += `[${entry.type}] ${JSON.stringify(entry.data).substring(0, 50)}...`; } logLine.textContent = displayTxt; this.#logDisplayElement.appendChild(logLine); }); // Scroll to bottom this.#logDisplayElement.scrollTop = this.#logDisplayElement.scrollHeight; } #toggleLogging(button) { this.#isLoggingActive = !this.#isLoggingActive; button.classList.toggle("active", !this.#isLoggingActive); button.textContent = this.#isLoggingActive ? "Desactivar Registro" : "Activar Registro"; this.notify("info", `Registro de Juego: ${this.#isLoggingActive ? 'Activo' : 'Inactivo'}`); } #clearLog() { if (confirm("¿Estás seguro de que quieres limpiar todo el registro del juego?")) { this.#gameLog = []; this.#updateLogDisplay(); this.notify("info", "Registro de Juego limpiado."); } } #exportLog(format) { if (this.#gameLog.length === 0) { this.notify("warning", "No hay datos en el registro para exportar."); return; } let dataString; let mimeType; let filename = `drawaria_game_log_${new Date().toISOString().slice(0, 10)}`; if (format === 'json') { dataString = JSON.stringify(this.#gameLog, null, 2); mimeType = 'application/json'; filename += '.json'; } else { // Default to TXT dataString = this.#gameLog.map(entry => { const time = new Date(entry.timestamp).toLocaleTimeString(); if (entry.type === "chatmsg") { if (entry.subtype === "system") { return `[${time}] [SISTEMA] ${entry.content}`; } else { return `[${time}] [CHAT] ${entry.playerName} (${entry.playerId}): ${entry.content}`; } } else if (entry.type === "uc_turn_begindraw" && entry.data && entry.data) { return `[${time}] [TURNO_INICIO] Palabra: ${entry.data[1] || 'Desconocida'}`; } else if (entry.type === "uc_turn_selectword" && entry.data && entry.data) { return `[${time}] [TURNO_PALABRA] Opciones: [${entry.data[2]?.join(', ') || 'N/A'}]`; } else if (entry.type === "bc_round_results") { return `[${time}] [RONDA_FIN] Resultados: ${JSON.stringify(entry.data[0].map(p => ({name: p[1], score: p[2]})))}`; } return `[${time}] [${entry.type}] ${JSON.stringify(entry.data)}`; }).join('\n'); mimeType = 'text/plain'; filename += '.txt'; } const blob = new Blob([dataString], { type: mimeType }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); this.notify("success", `Log exportado como ${filename}.`); } } })("QBit"); // END GAME LOG })();