您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
The ultimate enhancement for your Drawaria.online experience. Redefining possibilities!
当前为
// ==UserScript== // @name Cube Engine Uptaded New Options // @version 9.0.0 // @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."); }; } } else { console.error("Cube Engine: El objeto global 'cv' de OpenCV.js no se encontró. Asegúrate de que @require es correcto y no hay problemas de bloqueo."); } }); // --- 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"); (function BiggerBrush() { const QBit = globalThis[arguments[0]]; class BiggerBrush extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); active; constructor() { super("BiggerBrush", '<i class="fas fa-brush"></i>'); this.active = false; this.#onStartup(); } #onStartup() { this.#loadInterface(); this.drawwidthrangeSlider = document.querySelector("#drawwidthrange"); // this.enable(); } #loadInterface() { this.#row1(); } #row1() { const row = domMake.Row(); { const enableButton = domMake.Button("Enable"); enableButton.addEventListener("click", (event) => { this.active ? this.disable() : this.enable(); }); row.appendChild(enableButton); this.htmlElements.toggleStatusButton = enableButton; } this.htmlElements.section.appendChild(row); } enable() { document.querySelectorAll(".drawcontrols-button").forEach((n) => { n.classList.remove("drawcontrols-disabled"); }); this.active = true; this.htmlElements.toggleStatusButton.classList.add("active"); this.htmlElements.toggleStatusButton.textContent = "Active"; this.drawwidthrangeSlider.parentElement.previousElementSibling.lastElementChild.click(); this.drawwidthrangeSlider.parentElement.style.display = "flex"; this.drawwidthrangeSlider.max = 48; this.drawwidthrangeSlider.min = -2000; this.notify("success", `enabled`); } disable() { this.active = false; this.htmlElements.toggleStatusButton.classList.remove("active"); this.htmlElements.toggleStatusButton.textContent = "Inactive"; this.drawwidthrangeSlider.max = 45; this.drawwidthrangeSlider.min = -100; this.notify("warning", `disabled`); } } })("QBit"); (function BetterBrush() { const QBit = globalThis[arguments[0]]; class BetterBrush extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); constructor() { super("BetterBrush", '<i class="fas fa-magic"></i>'); this.#onStartup(); } #onStartup() { this.#loadInterface(); { const target = document.querySelector(".drawcontrols-popuplist"); const visibilityOvserver = new MutationObserver((mutations) => { if (this.active) if (mutations[0].target.style != "display:none") { mutations[0].target.querySelectorAll("div").forEach((n) => { n.removeAttribute("style"); }); } }); visibilityOvserver.observe(target, { attributes: true, }); } } #loadInterface() { this.#row1(); } #row1() { const row = domMake.Row(); { const enableButton = domMake.Button("Enable"); enableButton.addEventListener("click", (event) => { this.active ? this.disable() : this.enable(); }); row.appendChild(enableButton); this.htmlElements.toggleStatusButton = enableButton; } // --- INICIO: Nueva opción de "Enable rapid Color Change" --- { // Generamos un UUID completamente nuevo para este checkbox, // asegurando que no haya conflictos y evitando el uso de propiedades privadas. const rapidColorChangeCheckboxId = generate.uuidv4() + "-rapidColorChange"; // Creamos el input (checkbox). No es necesario que esté "hidden: true" aquí // porque será un hijo del label y el label es la parte visual y clicable. const rapidColorChangeInput = domMake.Tree("input", { type: "checkbox", id: rapidColorChangeCheckboxId, }); // Creamos el label, y añadimos el checkbox, el icono y el texto como sus hijos. // Esto hace que todo el área del label sea clicable para el checkbox. const rapidColorChangeLabel = domMake.Tree("label", { for: rapidColorChangeCheckboxId, // Vincula el label al input por su ID class: "col", // Aplica la clase 'col' al label para el diseño flexbox dentro de la fila title: "Enable rapid Color Change", }); // Orden de los elementos dentro del label: checkbox, luego icono, luego texto. rapidColorChangeLabel.appendChild(rapidColorChangeInput); rapidColorChangeLabel.appendChild(domMake.Tree("i", { class: "fas fa-adjust" })); rapidColorChangeLabel.appendChild(domMake.TextNode(" Rapid Color Change")); // Añadimos el evento 'input' directamente al checkbox. // El click en el label propagará al input, por lo que este listener funcionará. rapidColorChangeInput.addEventListener("input", () => { 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", "Color flow controls not found. Make sure drawing controls are visible."); return; } if (rapidColorChangeInput.checked) { colorflowTypeSelect.value = "2"; colorflowSpeedInput.max = 10; colorflowSpeedInput.value = 10; } else { colorflowTypeSelect.value = "0"; colorflowSpeedInput.max = 1; colorflowSpeedInput.value = 0; } settingsContainer.dispatchEvent(new CustomEvent("change")); }); // Solo el label (que ahora contiene el checkbox y los demás elementos) se añade a la fila. row.appendChild(rapidColorChangeLabel); } // --- FIN: Nueva opción de "Enable rapid Color Change" --- this.htmlElements.section.appendChild(row); } enable() { this.active = true; this.htmlElements.toggleStatusButton.classList.add("active"); this.htmlElements.toggleStatusButton.textContent = "Active"; this.notify("success", `enabled`); } disable() { this.active = false; this.htmlElements.toggleStatusButton.classList.remove("active"); this.htmlElements.toggleStatusButton.textContent = "Inactive"; this.notify("warning", `disabled`); } } })("QBit"); (function BiggerStencil() { const QBit = globalThis[arguments[0]]; class BiggerStencil extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); active; constructor() { super("BiggerStencil", '<i class="fas fa-parachute-box"></i>'); this.active = false; this.#onStartup(); } #onStartup() { this.#loadInterface(); { const target = document.querySelector(".fa-parachute-box").parentElement; const accessabilityObserver = new MutationObserver((mutations) => { if (this.active) if (mutations[0].target.disabled) { mutations[0].target.disabled = ""; } }); accessabilityObserver.observe(target, { attributes: true, }); } } #loadInterface() { this.#row1(); } #row1() { const row = domMake.Row(); { const enableButton = domMake.Button("Enable"); enableButton.addEventListener("click", (event) => { this.active ? this.disable() : this.enable(); }); row.appendChild(enableButton); this.htmlElements.toggleStatusButton = enableButton; } this.htmlElements.section.appendChild(row); } enable() { this.active = true; this.htmlElements.toggleStatusButton.classList.add("active"); this.htmlElements.toggleStatusButton.textContent = "Active"; this.notify("success", `enabled`); } disable() { this.active = false; this.htmlElements.toggleStatusButton.classList.remove("active"); this.htmlElements.toggleStatusButton.textContent = "Inactive"; this.notify("warning", `disabled`); } } })("QBit"); (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"); (function AutoTranslate() { const QBit = globalThis[arguments[0]]; const unicodeLanguagePatterns = new Map(); unicodeLanguagePatterns.set("Common", /\p{Script=Common}+/u); // CommonPattern unicodeLanguagePatterns.set("Arabic", /\p{Script=Arabic}+/u); // ArabicPattern unicodeLanguagePatterns.set("Armenian", /\p{Script=Armenian}+/u); // ArmenianPattern unicodeLanguagePatterns.set("Bengali", /\p{Script=Bengali}+/u); // BengaliPattern unicodeLanguagePatterns.set("Bopomofo", /\p{Script=Bopomofo}+/u); // BopomofoPattern unicodeLanguagePatterns.set("Braille", /\p{Script=Braille}+/u); // BraillePattern unicodeLanguagePatterns.set("Buhid", /\p{Script=Buhid}+/u); // BuhidPattern unicodeLanguagePatterns.set("Canadian_Aboriginal", /\p{Script=Canadian_Aboriginal}+/u); // Canadian_AboriginalPattern unicodeLanguagePatterns.set("Cherokee", /\p{Script=Cherokee}+/u); // CherokeePattern unicodeLanguagePatterns.set("Cyrillic", /\p{Script=Cyrillic}+/u); // CyrillicPattern unicodeLanguagePatterns.set("Devanagari", /\p{Script=Devanagari}+/u); // DevanagariPattern unicodeLanguagePatterns.set("Ethiopic", /\p{Script=Ethiopic}+/u); // EthiopicPattern unicodeLanguagePatterns.set("Georgian", /\p{Script=Georgian}+/u); // GeorgianPattern unicodeLanguagePatterns.set("Greek", /\p{Script=Greek}+/u); // GreekPattern unicodeLanguagePatterns.set("Gujarati", /\p{Script=Gujarati}+/u); // GujaratiPattern unicodeLanguagePatterns.set("Gurmukhi", /\p{Script=Gurmukhi}+/u); // GurmukhiPattern unicodeLanguagePatterns.set("Han", /\p{Script=Han}+/u); // HanPattern unicodeLanguagePatterns.set("Hangul", /\p{Script=Hangul}+/u); // HangulPattern unicodeLanguagePatterns.set("Hanunoo", /\p{Script=Hanunoo}+/u); // HanunooPattern unicodeLanguagePatterns.set("Hebrew", /\p{Script=Hebrew}+/u); // HebrewPattern unicodeLanguagePatterns.set("Hiragana", /\p{Script=Hiragana}+/u); // HiraganaPattern unicodeLanguagePatterns.set("Inherited", /\p{Script=Inherited}+/u); // InheritedPattern unicodeLanguagePatterns.set("Kannada", /\p{Script=Kannada}+/u); // KannadaPattern unicodeLanguagePatterns.set("Katakana", /\p{Script=Katakana}+/u); // KatakanaPattern unicodeLanguagePatterns.set("Khmer", /\p{Script=Khmer}+/u); // KhmerPattern unicodeLanguagePatterns.set("Lao", /\p{Script=Lao}+/u); // LaoPattern unicodeLanguagePatterns.set("Latin", /\p{Script=Latin}+/u); // LatinPattern unicodeLanguagePatterns.set("Limbu", /\p{Script=Limbu}+/u); // LimbuPattern unicodeLanguagePatterns.set("Malayalam", /\p{Script=Malayalam}+/u); // MalayalamPattern unicodeLanguagePatterns.set("Mongolian", /\p{Script=Mongolian}+/u); // MongolianPattern unicodeLanguagePatterns.set("Myanmar", /\p{Script=Myanmar}+/u); // MyanmarPattern unicodeLanguagePatterns.set("Ogham", /\p{Script=Ogham}+/u); // OghamPattern unicodeLanguagePatterns.set("Oriya", /\p{Script=Oriya}+/u); // OriyaPattern unicodeLanguagePatterns.set("Runic", /\p{Script=Runic}+/u); // RunicPattern unicodeLanguagePatterns.set("Sinhala", /\p{Script=Sinhala}+/u); // SinhalaPattern unicodeLanguagePatterns.set("Syriac", /\p{Script=Syriac}+/u); // SyriacPattern unicodeLanguagePatterns.set("Tagalog", /\p{Script=Tagalog}+/u); // TagalogPattern unicodeLanguagePatterns.set("Tagbanwa", /\p{Script=Tagbanwa}+/u); // TagbanwaPattern unicodeLanguagePatterns.set("Tamil", /\p{Script=Tamil}+/u); // TamilPattern unicodeLanguagePatterns.set("Telugu", /\p{Script=Telugu}+/u); // TeluguPattern unicodeLanguagePatterns.set("Thaana", /\p{Script=Thaana}+/u); // ThaanaPattern unicodeLanguagePatterns.set("Thai", /\p{Script=Thai}+/u); // ThaiPattern unicodeLanguagePatterns.set("Tibetan", /\p{Script=Tibetan}+/u); // TibetanPattern unicodeLanguagePatterns.set("Yi", /\p{Script=Yi}+/u); // YiPattern const observeDOM = (function () { const MutationObserver = window.MutationObserver || window.WebKitMutationObserver; /** * @param {HTMLElement} nodeToObserve * @param {Function} callback */ return function (nodeToObserve, callback) { if (!nodeToObserve || nodeToObserve.nodeType !== 1) return; if (MutationObserver) { // define a new observer var mutationObserver = new MutationObserver(callback); // have the observer observe for changes in children mutationObserver.observe(nodeToObserve, { childList: true, subtree: !1 }); return mutationObserver; } // browser support fallback else if (window.addEventListener) { nodeToObserve.addEventListener("DOMNodeInserted", callback, false); nodeToObserve.addEventListener("DOMNodeRemoved", callback, false); } }; })(); class AutoTranslate extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); active; constructor() { super("AutoTranslate", '<i class="fas fa-language"></i>'); this.#onStartup(); } #onStartup() { this.#loadInterface(); this.active = false; const observable = document.querySelector("#chatbox_messages"); observeDOM(observable, (mutation) => { if (!this.active) return; const addedNodes = []; const removedNodes = []; mutation.forEach((record) => record.addedNodes.length & addedNodes.push(...record.addedNodes)); mutation.forEach((record) => record.removedNodes.length & removedNodes.push(...record.removedNodes)); // console.log('Added:', addedNodes, 'Removed:', removedNodes); addedNodes.forEach((node) => { if (node.classList.contains("systemchatmessage5")) return; if (node.querySelector(".playerchatmessage-selfname")) return; if (!node.querySelector(".playerchatmessage-text")) return; // console.log(node); const message = node.querySelector(".playerchatmessage-text"); const text = message.textContent; const language = this.detectLanguage(text); if (language) this.translate(text, language, "en", (translation) => { applyTitleToChatMessage(translation, message); }); }); function applyTitleToChatMessage(text, node) { node.title = text; } }); } #loadInterface() { this.#row1(); this.#row2(); } #row1() { const row = domMake.Row(); { const enableButton = domMake.Button("Enable"); enableButton.addEventListener("click", (event) => { this.active ? this.disable() : this.enable(); }); row.appendChild(enableButton); this.htmlElements.toggleStatusButton = enableButton; } this.htmlElements.section.appendChild(row); } #row2() {} enable() { this.active = true; this.htmlElements.toggleStatusButton.classList.add("active"); this.htmlElements.toggleStatusButton.textContent = "Active"; this.notify("success", `enabled`); } disable() { this.active = false; this.htmlElements.toggleStatusButton.classList.remove("active"); this.htmlElements.toggleStatusButton.textContent = "Inactive"; this.notify("warning", `disabled`); } detectLanguage(text) { if (unicodeLanguagePatterns.get("Cyrillic").test(text)) return "ru"; if (unicodeLanguagePatterns.get("Arabic").test(text)) return "ar"; if (unicodeLanguagePatterns.get("Greek").test(text)) return "el"; } translate(textToTranslate, from = "ru", to = "en", callback) { const sourceText = textToTranslate; const sourceLang = from; const targetLang = to; const url = "https://translate.googleapis.com/translate_a/single?client=gtx&sl=" + sourceLang + "&tl=" + targetLang + "&dt=t&q=" + encodeURI(sourceText); xhrGetJson(url, log); function log(data) { callback(data[0][0][0]); } } } function xhrGetJson(url, callback) { const req = new XMLHttpRequest(); req.onload = (e) => { const response = req.response; // not responseText if (!callback) return; try { callback(JSON.parse(response)); } catch (error) { console.log(error); } }; req.open("GET", url); req.send(); } })("QBit"); // --- NEW FUNCTIONALITIES START HERE --- (function PlayerVoteKick() { const QBit = globalThis[arguments[0]]; class PlayerVoteKick extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); #playerListContainer; constructor() { super("Vote Kick", '<i class="fas fa-gavel"></i>'); this.#onStartup(); } #onStartup() { this.#loadInterface(); this.updatePlayerList(); // MutationObserver to update player list when players change (join/leave) const playerListElement = document.getElementById("playerlist"); if (playerListElement) { const observer = new MutationObserver(() => this.updatePlayerList()); observer.observe(playerListElement, { childList: true, subtree: true }); } } #loadInterface() { const row = domMake.Row(); this.#playerListContainer = domMake.IconList(); row.appendChild(this.#playerListContainer); this.htmlElements.section.appendChild(row); } updatePlayerList() { this.#playerListContainer.innerHTML = ''; // Clear existing buttons const playerRows = document.querySelectorAll("#playerlist .playerlist-row"); playerRows.forEach(playerRow => { const playerId = playerRow.dataset.playerid; const playerName = playerRow.querySelector(".playerlist-name a")?.textContent || `Player ${playerId}`; // Avoid self-kick button const myPlayerIdElement = document.querySelector(".playerlist-name-self")?.parentElement; const myPlayerId = myPlayerIdElement ? myPlayerIdElement.dataset.playerid : null; if (playerId === myPlayerId) { return; } const playerButton = domMake.Button(playerName); playerButton.title = `Vote to kick ${playerName}`; playerButton.addEventListener("click", () => { if (globalThis.sockets && globalThis.sockets.length > 0) { const data = _io.emits.sendvotekick(parseInt(playerId)); globalThis.sockets[0].send(data); this.notify("info", `Vote kick command sent for ${playerName} (ID: ${playerId}).`); } else { this.notify("warning", "No active WebSocket connection found."); } }); this.#playerListContainer.appendChild(playerButton); }); if (playerRows.length <= 1) { // Only self or no players const noPlayersMessage = domMake.Tree("span", {}, ["No other players to kick."]); this.#playerListContainer.appendChild(noPlayersMessage); } } } })("QBit"); (function BotKickModule() { const QBit = globalThis[arguments[0]]; // Corrected access to QBit class BotKick extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); #playerListContainer; // Container for player buttons constructor() { super("Kick con Bot", '<i class="fas fa-user-times"></i>'); // Icon for kicking (user + times) this.#onStartup(); } #onStartup() { this.#loadInterface(); // Update player list whenever the playerlist DOM changes const playerListElement = document.getElementById("playerlist"); if (playerListElement) { const observer = new MutationObserver(() => this.#updatePlayerListButtons()); observer.observe(playerListElement, { childList: true, subtree: true }); } this.#updatePlayerListButtons(); // Initial update } #loadInterface() { const row = domMake.Row(); this.#playerListContainer = domMake.IconList(); // Use IconList for flexible layout row.appendChild(this.#playerListContainer); this.htmlElements.section.appendChild(row); } #updatePlayerListButtons() { this.#playerListContainer.innerHTML = ''; // Clear existing buttons 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 kickear."]); this.#playerListContainer.appendChild(noPlayersMessage); return; } playerRows.forEach(playerRow => { const playerId = playerRow.dataset.playerid; const playerName = playerRow.querySelector(".playerlist-name a")?.textContent || `Player ${playerId}`; // Don't create a button for the current player (self) if (playerId === myPlayerId) { return; } const playerButton = domMake.Button(playerName); playerButton.title = `Kickear a ${playerName} (ID: ${playerId}) con el bot seleccionado.`; playerButton.style.margin = '2px'; playerButton.style.padding = '2px 5px'; playerButton.style.fontSize = '0.7em'; playerButton.addEventListener("click", () => { this.#sendBotKick(parseInt(playerId), playerName); }); this.#playerListContainer.appendChild(playerButton); }); } /** * Retrieves the currently selected bot instance from BotClientManager. * Falls back to the first available bot if none is explicitly selected. * Notifies the user if no bot is active or connected. * @returns {object|null} The active bot instance, or null if none found/ready. */ #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; } // Assuming there's only one main BotClientManager instance that holds all BotClientInterfaces 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]; 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; } /** * Sends a vote kick command via the selected (or first available) bot. * @param {number} targetPlayerId - The ID of the player to kick. * @param {string} targetPlayerName - The name of the player to kick. */ #sendBotKick(targetPlayerId, targetPlayerName) { const bot = this.#getBot(); if (!bot) return; // #getBot already handles notifications // Send the vote kick command through the bot const data = _io.emits.sendvotekick(targetPlayerId); bot.send(data); this.notify("success", `Bot "${bot.name}" envió solicitud de kick para ${targetPlayerName}.`); } } })("QBit"); (function CustomChatMessage() { const QBit = globalThis[arguments[0]]; class CustomChatMessage extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); #messageInput; constructor() { super("Custom Chat", '<i class="fas fa-comments"></i>'); this.#onStartup(); } #onStartup() { this.#loadInterface(); } #loadInterface() { const row = domMake.Row(); { this.#messageInput = domMake.Tree("input", { type: "text", placeholder: "Your message..." }); 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) { // Enter key this.sendMessage(this.#messageInput.value); this.#messageInput.value = ''; } }); row.appendAll(this.#messageInput, sendButton); } this.htmlElements.section.appendChild(row); } sendMessage(message) { if (!message.trim()) return; // Don't send empty messages if (globalThis.sockets && globalThis.sockets.length > 0) { const data = _io.emits.chatmsg(message); globalThis.sockets[0].send(data); this.notify("info", `Message sent: "${message}"`); } else { this.notify("warning", "No active WebSocket connection found."); } } } })("QBit"); (function ToggleAFK() { const QBit = globalThis[arguments[0]]; class ToggleAFK extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); active; constructor() { super("Toggle AFK", '<i class="fas fa-mug-hot"></i>'); this.active = false; // Initial state this.#onStartup(); } #onStartup() { this.#loadInterface(); } #loadInterface() { const row = domMake.Row(); { const toggleButton = domMake.Button("Toggle AFK"); toggleButton.addEventListener("click", () => { this.active = !this.active; if (globalThis.sockets && globalThis.sockets.length > 0) { const data = _io.emits.playerafk(); // playerafk() toggles AFK state globalThis.sockets[0].send(data); toggleButton.classList[this.active ? "add" : "remove"]("active"); toggleButton.textContent = this.active ? "AFK Active" : "AFK Inactive"; this.notify("info", `AFK status toggled to: ${this.active}`); } else { this.notify("warning", "No active WebSocket connection found."); } }); row.appendChild(toggleButton); } this.htmlElements.section.appendChild(row); } } })("QBit"); /* (function PlayerMovement() { const QBit = globalThis[arguments[0]]; class PlayerMovement extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); #posXInput; #posYInput; constructor() { super("Move Avatar", '<i class="fas fa-arrows-alt"></i>'); this.#onStartup(); } #onStartup() { this.#loadInterface(); } #loadInterface() { const row = domMake.Row(); { this.#posXInput = domMake.Tree("input", { type: "number", min: 0, max: 100, value: 50, step: 1, title: "X Position (0-100)" }); this.#posYInput = domMake.Tree("input", { type: "number", min: 0, max: 100, value: 50, step: 1, title: "Y Position (0-100)" }); const moveButton = domMake.Button('<i class="fas fa-location-arrow"></i>'); moveButton.classList.add("icon"); moveButton.addEventListener("click", () => { this.moveAvatar(); }); this.#posXInput.addEventListener("change", () => this.moveAvatar()); this.#posYInput.addEventListener("change", () => this.moveAvatar()); row.appendAll(this.#posXInput, this.#posYInput, moveButton); } this.htmlElements.section.appendChild(row); } moveAvatar() { const posX = parseFloat(this.#posXInput.value); const posY = parseFloat(this.#posYInput.value); if (isNaN(posX) || isNaN(posY) || posX < 0 || posX > 100 || posY < 0 || posY > 100) { this.notify("warning", "Invalid X or Y position. Must be between 0 and 100."); return; } if (globalThis.sockets && globalThis.sockets.length > 0) { const data = _io.emits.moveavatar(posX, posY); globalThis.sockets[0].send(data); this.notify("info", `Avatar moved to X:${posX}, Y:${posY}.`); } else { this.notify("warning", "No active WebSocket connection found."); } } } })("QBit"); */ (function GlobalTokenGiver() { const QBit = globalThis[arguments[0]]; class GlobalTokenGiver extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); #playerList = []; // To store player IDs and names #tokenButtons = []; // Store references to token buttons constructor() { super("Give Tokens", '<i class="fas fa-hand-holding-heart"></i>'); this.#onStartup(); } #onStartup() { this.#loadInterface(); this.updatePlayerList(); // Observer for player list changes const playerListElement = document.getElementById("playerlist"); if (playerListElement) { const observer = new MutationObserver(() => this.updatePlayerList()); observer.observe(playerListElement, { childList: true, subtree: true }); } } #loadInterface() { const row = domMake.IconList(); { const tokens = [ { id: 0, icon: '<i class="fas fa-thumbs-up"></i>' }, // Thumbs Up { id: 1, icon: '<i class="fas fa-heart"></i>' }, // Heart { id: 2, icon: '<i class="fas fa-paint-brush"></i>' }, // Paint Brush { id: 3, icon: '<i class="fas fa-cocktail"></i>' }, // Cocktail { id: 4, icon: '<i class="fas fa-hand-peace"></i>' }, // Hand Peace { id: 5, icon: '<i class="fas fa-feather-alt"></i>' }, // Feather { id: 6, icon: '<i class="fas fa-trophy"></i>' }, // Trophy { id: 7, icon: '<i class="fas fa-mug-hot"></i>' }, // Mug { id: 8, icon: '<i class="fas fa-gift"></i>' } // Gift ]; tokens.forEach(token => { const tokenButton = domMake.Button(token.icon); tokenButton.classList.add("icon"); tokenButton.title = `Give Token ${token.id}`; tokenButton.addEventListener("click", () => { this.giveToken(token.id); }); row.appendChild(tokenButton); this.#tokenButtons.push(tokenButton); }); } this.htmlElements.section.appendChild(row); } 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 || `Player ${playerId}`; this.#playerList.push({ id: parseInt(playerId), name: playerName }); }); // You might want to enable/disable token buttons based on player count or drawing status } 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", `Token ${tokenId} sent to ${player.name} (ID: ${player.id}).`); }); } else { this.notify("warning", "No players found in the room to give tokens to."); } } else { this.notify("warning", "No active WebSocket connection found."); } } } })("QBit"); (function SetStatusFlag() { const QBit = globalThis[arguments[0]]; class SetStatusFlag extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); constructor() { super("Set Status", '<i class="fas fa-flag"></i>'); this.#onStartup(); } #onStartup() { this.#loadInterface(); } #loadInterface() { const flags = [ { id: 0, name: "Music Enabled", 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: "Inventory Open", icon: '<i class="fas fa-box-open"></i>' }, { id: 4, name: "Friendlist Open", icon: '<i class="fas fa-user-friends"></i>' } ]; flags.forEach(flag => { const row = 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 toggleButton.addEventListener("click", () => { const isActive = toggleButton.dataset.isActive === "true"; const newActiveState = !isActive; toggleButton.dataset.isActive = newActiveState; toggleButton.classList[newActiveState ? "add" : "remove"]("active"); if (globalThis.sockets && globalThis.sockets.length > 0) { const data = _io.emits.setstatusflag(flag.id, newActiveState); globalThis.sockets[0].send(data); this.notify("info", `Status flag '${flag.name}' toggled to: ${newActiveState}`); } else { this.notify("warning", "No active WebSocket connection found."); } }); row.appendChild(toggleButton); this.htmlElements.section.appendChild(row); }); } } })("QBit"); (function ReportPlayer() { const QBit = globalThis[arguments[0]]; class ReportPlayer extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); #playerSelect; #reasonSelect; #reasonInput; constructor() { super("Report Player", '<i class="fas fa-flag-checkered"></i>'); this.#onStartup(); } #onStartup() { this.#loadInterface(); this.updatePlayerList(); const playerListElement = document.getElementById("playerlist"); if (playerListElement) { const observer = new MutationObserver(() => this.updatePlayerList()); observer.observe(playerListElement, { childList: true, subtree: true }); } } #loadInterface() { // Player Selection Row const playerRow = domMake.Row(); const playerLabel = domMake.Tree("label", {}, ["Player: "]); this.#playerSelect = domMake.Tree("select", { class: "form-control" }); playerRow.appendAll(playerLabel, this.#playerSelect); this.htmlElements.section.appendChild(playerRow); // Reason Selection Row const reasonRow = domMake.Row(); const reasonLabel = domMake.Tree("label", {}, ["Reason: "]); this.#reasonSelect = domMake.Tree("select", { class: "form-control" }); const reasons = [ { value: "hack", text: "Hack / Exploits" }, { value: "bot", text: "Bot" }, { value: "spam", text: "Spamming" }, { value: "content", text: "Inappropriate drawings / Offensive content" }, { value: "other", text: "Other" } ]; reasons.forEach(reason => { const option = domMake.Tree("option", { value: reason.value }, [reason.text]); this.#reasonSelect.appendChild(option); }); reasonRow.appendAll(reasonLabel, this.#reasonSelect); this.htmlElements.section.appendChild(reasonRow); // Additional Reason Input const reasonInputRow = domMake.Row(); const reasonInputLabel = domMake.Tree("label", {}, ["Additional Comments: "]); this.#reasonInput = domMake.Tree("textarea", { rows: 2, placeholder: "Optional details..." }); reasonInputRow.appendAll(reasonInputLabel, this.#reasonInput); this.htmlElements.section.appendChild(reasonInputRow); // Send Report Button const sendRow = domMake.Row(); const sendButton = domMake.Button("Send Report"); sendButton.addEventListener("click", () => this.sendReport()); sendRow.appendChild(sendButton); this.htmlElements.section.appendChild(sendRow); } updatePlayerList() { this.#playerSelect.innerHTML = ''; // Clear existing options const defaultOption = domMake.Tree("option", { value: "" }, ["-- Select Player --"]); this.#playerSelect.appendChild(defaultOption); const playerRows = document.querySelectorAll("#playerlist .playerlist-row"); playerRows.forEach(playerRow => { const playerId = playerRow.dataset.playerid; const playerName = playerRow.querySelector(".playerlist-name a")?.textContent || `Player ${playerId}`; const myPlayerIdElement = document.querySelector(".playerlist-name-self")?.parentElement; const myPlayerId = myPlayerIdElement ? myPlayerIdElement.dataset.playerid : null; if (playerId === myPlayerId) { return; // Don't allow reporting self } const option = domMake.Tree("option", { value: playerName, "data-playerid": playerId }, [playerName]); this.#playerSelect.appendChild(option); }); } sendReport() { const selectedPlayerOption = this.#playerSelect.options[this.#playerSelect.selectedIndex]; const targetPlayerId = selectedPlayerOption.dataset.playerid; const targetPlayerName = selectedPlayerOption.value; const reason = this.#reasonSelect.value; const additionalReason = this.#reasonInput.value.trim(); if (!targetPlayerId || !reason) { this.notify("warning", "Please select a player and a reason."); return; } const fullReason = additionalReason ? `${reason}: ${additionalReason}` : reason; if (globalThis.sockets && globalThis.sockets.length > 0) { const data = _io.emits.report(parseInt(targetPlayerId), fullReason, targetPlayerName); globalThis.sockets[0].send(data); this.notify("success", `Report sent for ${targetPlayerName} (Reason: ${fullReason}).`); this.#playerSelect.value = ""; this.#reasonSelect.value = reasons[0].value; this.#reasonInput.value = ""; } else { this.notify("warning", "No active WebSocket connection found."); } } } })("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-terminal"></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 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"); (function TacticalBotSwarm() { const QBit = globalThis[arguments[0]]; class TacticalBotSwarm extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); constructor() { super("Swarm de Bots Tácticos", '<i class="fas fa-users-cog"></i>'); this.#onStartup(); } #onStartup() { this.#loadInterface(); } #loadInterface() { this.#row1(); // Dibujo Colaborativo por Zonas this.#row2(); // Bot de Adivinanza "Táctico" this.#row3(); // Personalidad del Bot (Velocidad de dibujo y verborrea) } #row1() { const row = domMake.Row(); { const coordinateDrawButton = domMake.Button("Dibujo Colaborativo"); coordinateDrawButton.title = "Divide el lienzo en zonas para que cada bot dibuje una parte (usa GhostCanvas para cargar imagen)."; coordinateDrawButton.addEventListener("click", async () => { const botManager = this.findGlobal("BotClientManager")?.siblings[0]; const ghostCanvas = this.findGlobal("GhostCanvas")?.siblings[0]; if (!botManager || botManager.children.length === 0) { this.notify("warning", "Necesitas bots activos para el dibujo colaborativo."); return; } if (!ghostCanvas || ghostCanvas.drawingManager.pixelList.length === 0) { this.notify("warning", "Carga una imagen en 'Ghost Canvas' primero."); return; } const activeBots = botManager.children.filter(b => b.bot.getReadyState()); if (activeBots.length === 0) { this.notify("warning", "Ningún bot activo para la colaboración."); return; } this.notify("info", `Iniciando dibujo colaborativo con ${activeBots.length} bots.`); const totalPixels = ghostCanvas.drawingManager.pixelList.length; const pixelsPerBot = Math.ceil(totalPixels / activeBots.length); for (let i = 0; i < activeBots.length; i++) { const botInterface = activeBots[i]; const botPixels = ghostCanvas.drawingManager.pixelList.slice(i * pixelsPerBot, (i + 1) * pixelsPerBot); if (botPixels.length > 0) { // Temporarily assign a subset of pixels to each bot's internal drawing manager // This is a conceptual assignment, as the GhostCanvas TaskManager handles sending // We will need to modify GhostCanvas.TaskManager to distribute tasks based on available bots. // For this simulation, we will just show it dividing work. this.notify("log", `Bot ${botInterface.getName()} asignado a ${botPixels.length} píxeles.`); // For a true implementation, GhostCanvas.TaskManager.parseAndSendPixel would need // to know which bot is drawing which pixel, or each bot would need its own TaskManager subset. // Here, we'll just indicate the start. The current GhostCanvas TaskManager // already round-robins available pixels among *all* connected bots. // So, this button mainly serves to trigger that mechanism with a collaborative message. } } ghostCanvas.drawingManager.startDrawing(); this.notify("success", "El dibujo colaborativo ha comenzado. ¡Observa a tus bots trabajar!"); }); row.appendChild(coordinateDrawButton); } this.htmlElements.section.appendChild(row); } #row2() { const row = domMake.Row(); { const smartGuessButton = domMake.Button("Bot de Adivinanza (Conceptual)"); smartGuessButton.title = "Un bot intentará adivinar la palabra (simulado)."; smartGuessButton.addEventListener("click", () => { const botManager = this.findGlobal("BotClientManager")?.siblings[0]; if (!botManager || botManager.children.length === 0) { this.notify("warning", "Necesitas al menos 1 bot activo para esta función."); return; } const activeBot = botManager.children[0].bot; if (!activeBot || !activeBot.getReadyState()) { this.notify("warning", "El bot seleccionado no está conectado."); return; } const commonWords = ["casa", "flor", "mesa", "sol", "perro", "gato"]; const randomWord = commonWords[Math.floor(Math.random() * commonWords.length)]; activeBot.emit("chatmsg", randomWord); this.notify("info", `Bot ${activeBot.name} intentó adivinar: "${randomWord}" (simulado).`); }); row.appendChild(smartGuessButton); } this.htmlElements.section.appendChild(row); } #row3() { const row = domMake.Row(); { const botPersonalityLabel = domMake.Tree("label", {}, ["Personalidad del Bot:"]); const drawingSpeedInput = domMake.Tree("input", { type: "number", min: 1, max: 100, value: 10, title: "Velocidad de Dibujo (ms/px)" }); const verbositySelect = domMake.Tree("select", { title: "Verbosidad de Mensajes" }); ['Silencioso', 'Normal', 'Charlatán'].forEach(level => { verbositySelect.appendChild(domMake.Tree("option", { value: level }, [level])); }); drawingSpeedInput.addEventListener("change", (e) => { const speed = parseInt(e.target.value); const botManager = this.findGlobal("BotClientManager")?.siblings[0]; if (botManager) { botManager.children.forEach(botI => { if (botI.bot) { // Apply conceptual speed to bots' drawing botI.bot.drawingDelay = speed; // Add a new property to bot this.notify("log", `Velocidad de dibujo de ${botI.getName()}: ${speed}ms/px.`); } }); } }); verbositySelect.addEventListener("change", (e) => { const verbosity = e.target.value; const botManager = this.findGlobal("BotClientManager")?.siblings[0]; if (botManager) { botManager.children.forEach(botI => { if (botI.bot) { botI.bot.chatVerbosity = verbosity; // New property this.notify("log", `Verbosidad de ${botI.getName()}: ${verbosity}.`); } }); } }); row.appendAll(botPersonalityLabel, drawingSpeedInput, verbositySelect); } this.htmlElements.section.appendChild(row); } } })("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("", '<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 StrokeMaster() { const QBit = globalThis[arguments[0]]; class StrokeMaster extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); #isPressureActive = false; #isTextureActive = false; #lastMousePos = { x: 0, y: 0 }; #lastTimestamp = 0; constructor() { super("Maestría de Trazo y Realismo (necesita bot)", '<i class="fas fa-palette"></i>'); this.#onStartup(); } #onStartup() { this.#loadInterface(); this.#hookDrawingEvents(); } #loadInterface() { this.#row1(); this.#row2(); this.#row3(); this.#row4(); this.#row5(); this.#row6(); } #row1() { const row = domMake.Row(); { const diamondGradientFillButton = domMake.Button("Relleno Degradado Diamante"); diamondGradientFillButton.title = "Activa la herramienta de relleno con degradado en forma de diamante (conceptual)."; diamondGradientFillButton.addEventListener("click", () => { this.notify("info", "Esta función es conceptual. Simula un degradado que se expande desde el centro en forma de diamante."); const botManager = this.findGlobal("BotClientManager")?.siblings[0]; if (botManager && botManager.children.length > 0) { const activeBot = botManager.children[0].bot; if (activeBot && activeBot.getReadyState()) { this.notify("log", "Simulando relleno degradado diamante con el bot..."); const centerX = 50; const centerY = 50; const maxDistance = 40; // Max distance from center for the diamond effect const steps = 60; // Number of concentric diamond "layers" for (let i = steps; i >= 0; i--) { // Draw from outside in const ratio = i / steps; // Dark Blue (0, 0, 139) to Bright Gold (255, 215, 0) const r = Math.floor(0 + (255 - 0) * (1 - ratio)); const g = Math.floor(0 + (215 - 0) * (1 - ratio)); const b = Math.floor(139 + (0 - 139) * (1 - ratio)); const color = `rgb(${r},${g},${b})`; const currentDistance = maxDistance * ratio; // Define the corners of the diamond for this step const p1x = centerX; const p1y = centerY - currentDistance; // Top point const p2x = centerX + currentDistance; const p2y = centerY; // Right point const p3x = centerX; const p3y = centerY + currentDistance; // Bottom point const p4x = centerX - currentDistance; const p4y = centerY; // Left point // Draw the diamond outline for this step, effectively filling from outside in activeBot.emit("line", -1, p1x, p1y, p2x, p2y, true, 25, color, false); activeBot.emit("line", -1, p2x, p2y, p3x, p3y, true, 25, color, false); activeBot.emit("line", -1, p3x, p3y, p4x, p4y, true, 25, color, false); activeBot.emit("line", -1, p4x, p4y, p1x, p1y, true, 25, color, false); } this.notify("success", "Degradado diamante conceptual dibujado."); } } }); row.appendChild(diamondGradientFillButton); } this.htmlElements.section.appendChild(row); } #row2() { const row = domMake.Row(); { const radialGradientFillButton = domMake.Button("Relleno Degradado Radial"); radialGradientFillButton.title = "Activa la herramienta de relleno con degradado radial (conceptual)."; radialGradientFillButton.addEventListener("click", () => { this.notify("info", "Esta función es conceptual. En un entorno real, permitiría seleccionar un centro, un radio y colores para un degradado radial."); const botManager = this.findGlobal("BotClientManager")?.siblings[0]; if (botManager && botManager.children.length > 0) { const activeBot = botManager.children[0].bot; if (activeBot && activeBot.getReadyState()) { this.notify("log", "Simulando relleno degradado radial con el bot..."); const centerX = 50; const centerY = 50; const maxRadius = 30; const steps = 20; // Number of concentric circles to draw the gradient for (let i = steps; i >= 0; i--) { // Draw from outside in const ratio = i / steps; // Orange (255, 165, 0) to Yellow (255, 255, 0) const r = 255; const g = Math.floor(165 + (255 - 165) * (1 - ratio)); // Green goes from 165 to 255 (more yellow) const b = 0; const color = `rgb(${r},${g},${b})`; const currentRadius = maxRadius * ratio; // Simulate by drawing small circles or many lines // For simplicity, let's draw several points in a circle to approximate const numSegments = 36; // More segments for smoother circle for (let j = 0; j < numSegments; j++) { const angle = (j / numSegments) * 2 * Math.PI; const x = centerX + currentRadius * Math.cos(angle); const y = centerY + currentRadius * Math.sin(angle); activeBot.emit("line", -1, x, y, x + 1, y + 1, true, 25, color, false); // Draw a tiny line as a "point" } } this.notify("success", "Degradado radial conceptual dibujado."); } } }); row.appendChild(radialGradientFillButton); } this.htmlElements.section.appendChild(row); } #row3() { const row = domMake.Row(); { const gradientFillButton = domMake.Button("Relleno Degradado"); gradientFillButton.title = "Activa la herramienta de relleno con degradado lineal (conceptual)."; gradientFillButton.addEventListener("click", () => { this.notify("info", "Esta función es conceptual. En un entorno real, permitiría seleccionar dos puntos y dos colores para un degradado."); // For simulation, we can demonstrate a simple gradient fill using the bot on a small area. const botManager = this.findGlobal("BotClientManager")?.siblings[0]; if (botManager && botManager.children.length > 0) { const activeBot = botManager.children[0].bot; if (activeBot && activeBot.getReadyState()) { // Simulate a simple rectangular gradient // This would ideally be a flood fill with gradient, but that's complex // For a simple demo, drawing multiple lines of varying color this.notify("log", "Simulando relleno degradado con el bot..."); const startX = 20, endX = 80; const startY = 20, endY = 80; const steps = 20; // Number of lines to draw the gradient for (let i = 0; i <= steps; i++) { const ratio = i / steps; const r = Math.floor(255 * (1 - ratio)); // Red from 255 to 0 const g = 0; const b = Math.floor(255 * ratio); // Blue from 0 to 255 const color = `rgb(${r},${g},${b})`; const yPos = startY + (endY - startY) * ratio; activeBot.emit("line", -1, startX, yPos, endX, yPos, true, 25, color, false); } this.notify("success", "Degradado conceptual dibujado."); } } }); row.appendChild(gradientFillButton); } this.htmlElements.section.appendChild(row); } #row4() { const row = domMake.Row(); { const verticalLinearGradientButton = domMake.Button("Relleno Degradado Vertical"); verticalLinearGradientButton.title = "Activa la herramienta de relleno con degradado lineal vertical (conceptual)."; verticalLinearGradientButton.addEventListener("click", () => { this.notify("info", "Esta función es conceptual. En un entorno real, permitiría un degradado lineal de arriba a abajo con dos colores."); const botManager = this.findGlobal("BotClientManager")?.siblings[0]; if (botManager && botManager.children.length > 0) { const activeBot = botManager.children[0].bot; if (activeBot && activeBot.getReadyState()) { this.notify("log", "Simulando relleno degradado vertical con el bot..."); const startX = 20, endX = 80; const startY = 20, endY = 80; const steps = 20; // Number of lines to draw the gradient for (let i = 0; i <= steps; i++) { const ratio = i / steps; // Purple (128, 0, 128) to Pink (255, 192, 203) const r = Math.floor(128 + (255 - 128) * ratio); const g = Math.floor(0 + (192 - 0) * ratio); const b = Math.floor(128 + (203 - 128) * ratio); const color = `rgb(${r},${g},${b})`; const yPos = startY + (endY - startY) * ratio; activeBot.emit("line", -1, startX, yPos, endX, yPos, true, 25, color, false); } this.notify("success", "Degradado vertical conceptual dibujado."); } } }); row.appendChild(verticalLinearGradientButton); } this.htmlElements.section.appendChild(row); } #row5() { const row = domMake.Row(); { const conicalGradientFillButton = domMake.Button("Relleno Degradado Cónico"); conicalGradientFillButton.title = "Activa la herramienta de relleno con degradado cónico/angular (conceptual)."; conicalGradientFillButton.addEventListener("click", () => { this.notify("info", "Esta función es conceptual. En un entorno real, permitiría seleccionar un centro y un ángulo de inicio para un degradado cónico."); const botManager = this.findGlobal("BotClientManager")?.siblings[0]; if (botManager && botManager.children.length > 0) { const activeBot = botManager.children[0].bot; if (activeBot && activeBot.getReadyState()) { this.notify("log", "Simulando relleno degradado cónico con el bot..."); const centerX = 50; const centerY = 50; const maxRadius = 40; // Max radius for the conical effect const steps = 60; // More steps for a smoother conical sweep for (let i = 0; i <= steps; i++) { const ratio = i / steps; // Electric Blue (0, 0, 255) to Vibrant Magenta (255, 0, 255) const r = Math.floor(0 + (255 - 0) * ratio); const g = 0; const b = 255; // Blue remains constant at 255 for the transition const color = `rgb(${r},${g},${b})`; // Calculate angle for this step (full circle) const angle = (ratio * 2 * Math.PI); // From 0 to 2PI (360 degrees) // Draw a line from the center outwards at this angle const x2 = centerX + maxRadius * Math.cos(angle); const y2 = centerY + maxRadius * Math.sin(angle); // To simulate a fill, we'll draw many lines from the center to the edge, // each with the color corresponding to its angle. // Instead of drawing just one line, we'll draw a small wedge for each step // by drawing multiple lines very close to each other. // For simplicity in this simulation, let's draw a line from the center to the edge. // The "fill" effect comes from drawing many such lines very close together. activeBot.emit("line", -1, centerX, centerY, x2, y2, true, 25, color, false); } this.notify("success", "Degradado cónico conceptual dibujado."); } } }); row.appendChild(conicalGradientFillButton); } this.htmlElements.section.appendChild(row); } #row6() { const row = domMake.Row(); { const waveGradientFillButton = domMake.Button("Relleno Degradado Ondulado"); waveGradientFillButton.title = "Activa la herramienta de relleno con degradado ondulado (conceptual)."; waveGradientFillButton.addEventListener("click", () => { this.notify("info", "Esta función es conceptual. Simula un degradado que se propaga en ondas con cambio de color."); const botManager = this.findGlobal("BotClientManager")?.siblings[0]; if (botManager && botManager.children.length > 0) { const activeBot = botManager.children[0].bot; if (activeBot && activeBot.getReadyState()) { this.notify("log", "Simulando relleno degradado ondulado con el bot..."); const startX = 10, endX = 90; const startY = 20, endY = 80; const waveAmplitude = 10; // How high/low the waves go const waveFrequency = 0.1; // How many waves across the area const steps = 60; // Number of lines to draw for smoothness for (let i = 0; i <= steps; i++) { const ratio = i / steps; // Cyan (0, 255, 255) to Coral (255, 127, 80) const r = Math.floor(0 + (255 - 0) * ratio); const g = Math.floor(255 + (127 - 255) * ratio); const b = Math.floor(255 + (80 - 255) * ratio); const color = `rgb(${r},${g},${b})`; // Calculate the y-position for the wave // We'll draw horizontal lines that oscillate up and down const yOffset = waveAmplitude * Math.sin(ratio * Math.PI * 2 * waveFrequency); const currentY = startY + (endY - startY) * ratio + yOffset; // To create a "fill" effect with waves, we'll draw a short vertical line // or a very thick horizontal line that follows the wave path. // Let's draw a horizontal line that follows the wave. activeBot.emit("line", -1, startX, currentY, endX, currentY, true, 25, color, false); } this.notify("success", "Degradado ondulado conceptual dibujado."); } } }); row.appendChild(waveGradientFillButton); } this.htmlElements.section.appendChild(row); } #hookDrawingEvents() { const gameCanvas = document.querySelector("#canvas"); if (!gameCanvas) return; let isDrawingLocal = false; let lastX = 0, lastY = 0; let lastDrawThickness = 5; // Default thickness gameCanvas.addEventListener("mousedown", (e) => { isDrawingLocal = true; const rect = gameCanvas.getBoundingClientRect(); lastX = ((e.clientX - rect.left) / rect.width) * 1000; lastY = ((e.clientY - rect.top) / rect.height) * 1000; this.#lastMousePos = { x: e.clientX, y: e.clientY }; this.#lastTimestamp = performance.now(); }); gameCanvas.addEventListener("mousemove", (e) => { if (!isDrawingLocal) return; const rect = gameCanvas.getBoundingClientRect(); const currentX = ((e.clientX - rect.left) / rect.width) * 1000; const currentY = ((e.clientY - rect.top) / rect.height) * 1000; let currentThickness = lastDrawThickness; let currentColor = this.getCurrentBrushColor(); // Assuming a way to get current color // Simulate Pressure Control 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; // Pixels per millisecond // Map speed to thickness (slower = thicker, faster = thinner) // Adjust these values for desired effect const minThickness = 2; const maxThickness = 20; const speedFactor = 0.5; // Adjust how much speed influences thickness currentThickness = maxThickness - (speed * speedFactor); currentThickness = Math.max(minThickness, Math.min(maxThickness, currentThickness)); lastDrawThickness = currentThickness; } // Simulate Texture Brush (apply noise to color) if (this.#isTextureActive) { const originalColorHex = document.querySelector('.drawcontrols-color.active')?.style.backgroundColor || "#000000"; currentColor = this.applyColorNoise(originalColorHex, 10); // 10 is the noise amount } // Send drawing command with adjusted thickness and color const botManager = this.findGlobal("BotClientManager")?.siblings[0]; if (botManager && botManager.children.length > 0) { const activeBot = botManager.children[0].bot; if (activeBot && activeBot.getReadyState()) { activeBot.emit("line", -1, lastX, lastY, currentX, currentY, true, currentThickness, currentColor, false); } } lastX = currentX; lastY = currentY; this.#lastMousePos = { x: e.clientX, y: e.clientY }; this.#lastTimestamp = performance.now(); }); gameCanvas.addEventListener("mouseup", () => { isDrawingLocal = false; lastDrawThickness = 5; // Reset thickness after drawing }); gameCanvas.addEventListener("mouseout", () => { isDrawingLocal = false; lastDrawThickness = 5; // Reset thickness }); } getCurrentBrushColor() { // Attempt to get the currently selected color from Drawaria's UI const colorPicker = document.querySelector('.drawcontrols-color.active'); if (colorPicker) { const rgb = colorPicker.style.backgroundColor; if (rgb) return rgb; } return "#000000"; // Default to black } rgbToHex(rgb) { // Choose a hex color from any RGB color (conceptual). if (rgb.startsWith("rgb")) { const parts = rgb.match(/\d+/g).map(Number); if (parts.length >= 3) { const toHex = (c) => c.toString(16).padStart(2, '0'); return `#${toHex(parts[0])}${toHex(parts[1])}${toHex(parts[2])}`; } } return rgb; // Return as is if not RGB } applyColorNoise(color, noiseAmount) { // Convert RGB string to array, apply noise, convert back to RGB string 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("#")) { // Handle hex colors 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; // Cannot parse, return original } const addNoise = (value) => { const noise = (Math.random() - 0.5) * 2 * noiseAmount; return Math.max(0, Math.min(255, Math.floor(value + noise))); }; const noisyR = addNoise(r); const noisyG = addNoise(g); const noisyB = addNoise(b); return `rgb(${noisyR},${noisyG},${noisyB})`; } } })("QBit"); // --- NEW CUBIC ENGINE FUNCTIONALITIES START HERE --- (function HideShowMenusModule() { const QBit = globalThis[arguments[0]]; // Add CSS rules to QBit.Styles for the Hide/Show Menus module QBit.Styles.addRules([ `#${QBit.identifier} #hide-show-menu-container { background: linear-gradient(135deg, var(--light-blue), var(--accent-blue)); border: 1px solid var(--border-blue); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25); color: var(--dark-text); border-radius: .25rem; padding: 5px; margin-top: 5px; cursor: default; }`, `#${QBit.identifier} #hide-show-menu-container div:first-child { color: var(--dark-blue-title); }`, `#${QBit.identifier} #element-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; }`, `#${QBit.identifier} #hide-show-menu-container .control-buttons button { padding: 5px 10px; border: none; border-radius: .25rem; cursor: pointer; transition: background-color 0.2s ease; width: 48%; /* Adjust for spacing */ box-sizing: border-box; }`, `#${QBit.identifier} #show-element { background-color: var(--info); color: white; }`, `#${QBit.identifier} #show-element:hover { background-color: var(--info-hover); }`, `#${QBit.identifier} #hide-element { background-color: var(--danger); color: white; }`, `#${QBit.identifier} #hide-element:hover { background-color: var(--danger-hover); }`, // Define custom properties for consistency `:root { --light-blue: #e0f2f7; --accent-blue: #a7d9f7; --border-blue: #7cb9e8; --dark-blue-title: #004080; --input-border-blue: #80a4e8; --input-bg: #f8f8f8; --dark-text: #333; --info: #007bff; --info-hover: #0056b3; --danger: #dc3545; --danger-hover: #bd2130; }` ]); // Comprehensive list of common elements on Drawaria.online const allKnownElements = [ // --- In-Game Specific Elements --- { 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' }, // --- Modals (can appear in both states) --- { 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' }, // Add more specific modal sub-elements for finer control { 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)' }, ]; class HideShowMenus extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); #elementSelector; #mutationObserver; constructor() { super("Ocultar/Mostrar Menús", '<i class="fas fa-eye-slash"></i>'); this.#onStartup(); } #onStartup() { this.#loadInterface(); this.#setupObserver(); // Initial population after interface is loaded setTimeout(() => this.#populateSelector(), 500); } #loadInterface() { const container = domMake.Tree("div", { id: "hide-show-menu-container" }); container.appendChild(domMake.Tree("div", { style: "padding-bottom: 5px; text-align: center; font-weight: bold; cursor: default; font-size: 0.9em;" }, ["Menús de Drawaria"])); this.#elementSelector = domMake.Tree("select", { id: "element-selector" }); container.appendChild(this.#elementSelector); const buttonRow = domMake.Tree("div", { class: "control-buttons", style: "display: flex; justify-content: space-between;" }); const showButton = domMake.Button("Mostrar"); showButton.id = "show-element"; showButton.addEventListener('click', () => this.#toggleElementVisibility(false)); const hideButton = domMake.Button("Ocultar"); hideButton.id = "hide-element"; hideButton.addEventListener('click', () => this.#toggleElementVisibility(true)); buttonRow.appendAll(showButton, hideButton); container.appendChild(buttonRow); this.htmlElements.section.appendChild(container); } #populateSelector() { const currentSelectedValue = this.#elementSelector.value; this.#elementSelector.innerHTML = ''; const addedSelectors = new Set(); const placeholderOption = domMake.Tree('option', { value: '' }, ['-- Selecciona un elemento --']); this.#elementSelector.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.#elementSelector.appendChild(option); addedSelectors.add(item.selector); } } catch (e) { // Only log to console, avoid chat spam for selector errors console.warn(`[HideShowMenus] Selector inválido: ${item.selector}. Error: ${e.message}`); } }); if (currentSelectedValue && Array.from(this.#elementSelector.options).some(opt => opt.value === currentSelectedValue)) { this.#elementSelector.value = currentSelectedValue; } else { this.#elementSelector.value = ''; } // Removed: this.notify("log", "Selector de elementos actualizado."); // This was the source of spam, no longer notifying chat for mere selector updates. } #toggleElementVisibility(hide) { const selectedValue = this.#elementSelector.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(); // Remove modal backdrop if hiding it } 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}`); } } #setupObserver() { if (this.#mutationObserver) { this.#mutationObserver.disconnect(); } this.#mutationObserver = new MutationObserver(() => { clearTimeout(this.#mutationObserver._timer); this.#mutationObserver._timer = setTimeout(() => { this.#populateSelector(); }, 500); }); this.#mutationObserver.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); } } })("QBit"); (function ExportChatModule() { const QBit = globalThis[arguments[0]]; class ExportChat extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); constructor() { super("Exportar Chat", '<i class="fas fa-file-export"></i>'); this.#onStartup(); } #onStartup() { this.#loadInterface(); } #loadInterface() { const row = domMake.Row(); const exportButton = domMake.Button("Exportar Chat (TXT)"); exportButton.title = "Exporta todos los mensajes del chat a un archivo de texto."; exportButton.addEventListener("click", () => this.#exportChatMessages()); row.appendChild(exportButton); this.htmlElements.section.appendChild(row); } #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."); } } })("QBit"); (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.`); } }, // ... dentro de (function BotClientModifications() { ... })("QBit"); ... // ... busca Object.assign(OriginalBotClient.prototype, { ... // ... y dentro de ese objeto, busca _onSocketMessageHandler y reemplázalo: ... _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); }); } }, // ... el resto de BotClientModifications continúa ... _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: MoreColorPalettes (ACTUALIZADO para añadir colores externos) --- (function MoreColorPalettes() { 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([ `#${QBit.identifier} .custom-color-button { box-shadow: 0 0 2px rgba(0,0,0,0.3); cursor: pointer; border: 1px solid transparent; /* default border */ }`, `#${QBit.identifier} .custom-color-button.custom-active-color { box-shadow: 0 0 5px 2px var(--info); /* Highlight for active custom color */ border: 1px solid var(--info); }`, // Ensure Drawaria's native triangle transitions smoothly `#colorpicker-cursor { transition: left 0.1s ease-out; }` ]); class MoreColorPalettes extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); #customColors = []; #colorButtonsContainer; #colorInput; #drawControlsObserver; #gameTriangleElement = null; #proxyGameButton = null; // The game's native button we'll use to proxy clicks constructor() { super("Paletas de Color", '<i class="fas fa-palette"></i>'); this.#onStartup(); } #onStartup() { this.#loadInterface(); this.#loadCustomColors(); this.#setupDrawControlsObserver(); } #loadInterface() { const row1 = 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)); row1.appendAll(addColorLabel, this.#colorInput, addColorButton); } this.htmlElements.section.appendChild(row1); const row2 = domMake.Row(); { const clearAllColorsButton = domMake.Button("Limpiar Todos"); clearAllColorsButton.addEventListener("click", () => this.#clearAllCustomColors()); row2.appendChild(clearAllColorsButton); } this.htmlElements.section.appendChild(row2); const row3 = domMake.Row(); this.#colorButtonsContainer = domMake.IconList(); // Using IconList for flex display row3.appendChild(this.#colorButtonsContainer); this.htmlElements.section.appendChild(row3); } #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 = ''; // Clear existing buttons this.#customColors.forEach(color => { this.#createColorButton(color.hex, color.name); }); } #addNewCustomColor(hexColor) { // Check for duplicates before adding 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); // Add single button this.notify("info", `Color ${hexColor} añadido.`); } // Nuevo método para añadir colores desde otros módulos addCustomColorFromExternal(hexColor) { this.#addNewCustomColor(hexColor); } #clearAllCustomColors() { if (confirm("¿Estás seguro de que quieres eliminar todos los colores personalizados?")) { this.#customColors = [...DEFAULT_CUSTOM_COLORS]; // Reset to defaults 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); } #findGameElements() { // Find the game's native color picker triangle if (!this.#gameTriangleElement || !document.body.contains(this.#gameTriangleElement)) { this.#gameTriangleElement = document.getElementById('colorpicker-cursor'); } // Find a suitable proxy button from the game's original palette if (!this.#proxyGameButton || !document.body.contains(this.#proxyGameButton)) { const drawControls = document.getElementById('drawcontrols-colors') || document.getElementById('drawcontrols'); if (drawControls) { // Get first non-custom, non-colorpicker button that is a color this.#proxyGameButton = drawControls.querySelector('.drawcontrols-button.drawcontrols-color:not(.drawcontrols-colorpicker):not(.custom-color-button)'); } } } #handleCustomColorClick(clickedButton) { this.#findGameElements(); if (!this.#proxyGameButton) { this.notify("warning", "No se encontró un botón de color de juego para proxy. La funcionalidad puede ser limitada."); return; } const customColor = clickedButton.dataset.hex; const originalProxyColor = this.#proxyGameButton.style.backgroundColor; // Step 1: Set the proxy button's background to our custom color this.#proxyGameButton.style.backgroundColor = customColor; // Step 2: Simulate a click on the proxy button. This makes the game's internal logic think one of its own // buttons was clicked, updating the actual drawing color and moving its triangle. this.#proxyGameButton.click(); // Step 3: Use requestAnimationFrame to ensure the game's triangle movement completes, // then immediately revert the proxy button's color and move the triangle to our custom button. requestAnimationFrame(() => { this.#proxyGameButton.style.backgroundColor = originalProxyColor; // Restore visual of proxy button // Manually move Drawaria's triangle element this.#updateTrianglePosition(clickedButton); // Manage active visual state for custom buttons document.querySelectorAll('.custom-color-button.custom-active-color').forEach(btn => { btn.classList.remove('custom-active-color'); }); clickedButton.classList.add('custom-active-color'); // Ensure native game buttons lose their active state (the game handles this implicitly by proxy click) }); } #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`; } #setupDrawControlsObserver() { // We need to observe the .drawcontrols-colors container to find game buttons // and attach listeners to them, so that when a native color is picked, our custom // buttons get their 'active' class removed. 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."); // Retry finding later setTimeout(() => this.#setupDrawControlsObserver(), 1000); return; } this.#drawControlsObserver = new MutationObserver((mutations) => { mutations.forEach(mutation => { if (mutation.type === 'childList' || mutation.type === 'attributes') { // Re-evaluate game buttons if DOM changes, in case they are dynamically added/removed // or their active state changes. this.#addListenersToGameColorButtons(); } }); }); this.#drawControlsObserver.observe(observerTarget, { childList: true, subtree: true, attributes: true, attributeFilter: ['class', 'style'] }); // Initial setup for existing buttons this.#addListenersToGameColorButtons(); } #addListenersToGameColorButtons() { this.#findGameElements(); // Ensure latest references // Add listeners to all native game color buttons const gameColorButtons = document.querySelectorAll('.drawcontrols-button.drawcontrols-color:not(.custom-color-button)'); gameColorButtons.forEach(gameBtn => { // Remove existing listener to prevent duplicates if called multiple times by observer gameBtn.removeEventListener('click', this.#handleGameColorClick); gameBtn.addEventListener('click', this.#handleGameColorClick.bind(this)); }); } #handleGameColorClick() { // When a native game color button is clicked, remove the custom-active-color class from our buttons document.querySelectorAll('.custom-color-button.custom-active-color').forEach(customBtn => { customBtn.classList.remove('custom-active-color'); }); // The game's native triangle will move automatically for its own buttons. } } })("QBit"); // --- END UPDATED MODULE: MoreColorPalettes --- // --- 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 V2", '<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 --- // --- START UPDATED CUBIC ENGINE FUNCTIONALITIES --- (function BugGeneratorModule() { const QBit = globalThis[arguments[0]]; // Corrected access to QBit class BugGenerator extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); #lagInterval = null; #secretSpamInterval = null; #bugInterval = null; #playerChaosInterval = null; #visualGlitchInterval = null; constructor() { super("Generador de Errores (necesita bot)", '<i class="fas fa-bug"></i>'); this.#onStartup(); } #onStartup() { this.#loadInterface(); } #loadInterface() { // Row for Lag controls const lagRow = domMake.Row(); const lagButton = domMake.Button('<i class="fas fa-dizzy"></i> Iniciar Lag'); lagButton.addEventListener("click", () => this.#toggleLag(lagButton)); lagButton.title = "Envía una ráfaga de comandos de dibujo complejos para causar lag en la sala. Requiere un bot activo."; lagRow.appendChild(lagButton); this.htmlElements.section.appendChild(lagRow); // Row for Bug/Secret const bugSecretRow = domMake.Row(); const bugButton = domMake.Button('<i class="fas fa-gamepad"></i> Bug Experiencia'); bugButton.addEventListener("click", () => this.#toggleBugExperience(bugButton)); bugButton.title = "Envía comandos disruptivos de movimiento y gestos para intentar bugear la experiencia de otros jugadores. Requiere un bot activo."; bugSecretRow.appendChild(bugButton); // NEW Row for Player Chaos and Visual Glitch const chaosRow = domMake.Row(); const playerChaosButton = domMake.Button('<i class="fas fa-running"></i> Caos de Jugador'); playerChaosButton.addEventListener("click", () => this.#togglePlayerChaos(playerChaosButton)); playerChaosButton.title = "Causa caos de movimiento y estado al bot: Mueve el avatar, hace gestos, activa/desactiva AFK y cambia banderas de estado rápidamente. Requiere un bot activo."; chaosRow.appendChild(playerChaosButton); const visualGlitchButton = domMake.Button('<i class="fas fa-ghost"></i> Glitch Visual'); visualGlitchButton.addEventListener("click", () => this.#toggleVisualGlitch(visualGlitchButton)); visualGlitchButton.title = "Bugea la experiencia visual de otros jugadores: Spawnea/despawnear avatares, cambia propiedades, y spammea comandos de objetos/mensajes. Requiere un bot activo."; chaosRow.appendChild(visualGlitchButton); this.htmlElements.section.appendChild(chaosRow); const secretButton = domMake.Button('<i class="fas fa-mask"></i> Secret (Spam Visua)'); secretButton.addEventListener("click", () => this.#toggleSecretSpam(secretButton)); secretButton.title = "Envía una gran cantidad de comandos de dibujo sin parar y de todos los colores para ralentizar la sala. Requiere un bot activo."; bugSecretRow.appendChild(secretButton); this.htmlElements.section.appendChild(bugSecretRow); } /** * Retrieves the currently selected bot instance from BotClientManager. * Falls back to the first available bot if none is explicitly selected. * Notifies the user if no bot is active or connected. * @returns {object|null} The active bot instance, or null if none found/ready. */ #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; } // CORRECTED LINE: 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); } // CORRECTED LINE: Ensure we always assign a single instance, not an array 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; } /** * Toggles sending of large, random drawing commands to induce lag. * @param {HTMLElement} button - The button element to toggle its text/class. */ #toggleLag(button) { if (this.#lagInterval) { clearInterval(this.#lagInterval); this.#lagInterval = null; button.classList.remove("active"); button.textContent = 'Iniciar Lag'; this.notify("info", "Generador de Lag Detenido."); } else { const bot = this.#getBot(); if (!bot) return; button.classList.add("active"); button.textContent = '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); // Stop the interval return; } // Occasionally clear the canvas for maximum disruption if (counter % 50 === 0) { // Clear every ~1.25 seconds bot.emit("clear"); this.notify("log", "Lag: Enviando comando 'Clear'!"); } // Send multiple large, random lines per tick for (let i = 0; i < 5; i++) { // Send 5 lines per interval tick 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; // Larger size: 20-89 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) { // Log every 20 intervals this.notify("log", `Lag: ${counter * 5} líneas enviadas.`); } }, 25); // Send a command every 25ms (40 ticks/sec * 5 lines/tick = 200 lines/sec) } } /** * Toggles sending rapid avatar movements and gestures to disrupt other players' experience. * @param {HTMLElement} button - The button element to toggle its text/class. */ #toggleBugExperience(button) { if (this.#bugInterval) { clearInterval(this.#bugInterval); this.#bugInterval = null; button.classList.remove("active"); button.textContent = 'Bug Experiencia'; this.notify("info", "Deteniendo 'Bugear Experiencia'."); } else { const bot = this.#getBot(); if (!bot) return; button.classList.add("active"); button.textContent = '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.#bugInterval = setInterval(() => { if (!bot.getReadyState()) { this.notify("warning", "Bot desconectado, deteniendo 'Bugear Experiencia'."); this.#toggleBugExperience(button); return; } // Rapidly change avatar position const randomX = Math.random() * 100; const randomY = Math.random() * 100; bot.emit("moveavatar", randomX, randomY); // Spam random gestures const randomGesture = Math.floor(Math.random() * 32); // Assuming 0-31 are valid gestures bot.emit("sendgesture", randomGesture); count++; if (count >= 9000) { // Limit for a period to prevent infinite loop this.notify("info", "'Bugear Experiencia' completado (200 acciones). Deteniendo."); this.#toggleBugExperience(button); } }, 100); // Every 100ms } } /** * Toggles sending rapid avatar movements and gestures to disrupt other players' experience. * @param {HTMLElement} button - The button element to toggle its text/class. */ #togglePlayerChaos(button) { // Renamed function if (this.#playerChaosInterval) { clearInterval(this.#playerChaosInterval); this.#playerChaosInterval = null; button.classList.remove("active"); button.textContent = 'Caos de Jugador'; this.notify("info", "Deteniendo 'Caos de Jugador'."); } else { const bot = this.#getBot(); if (!bot) return; button.classList.add("active"); button.textContent = '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; } // Rapidly change avatar position const randomX = Math.random() * 100; const randomY = Math.random() * 100; bot.emit("moveavatar", randomX, randomY); // Spam random gestures const randomGesture = Math.floor(Math.random() * 32); // Assuming 0-31 are valid gestures bot.emit("sendgesture", randomGesture); // Toggle AFK status repeatedly bot.emit("playerafk"); // This toggles the AFK state // Toggle a random status flag const randomFlagId = Math.floor(Math.random() * 5); // Assuming 0-4 are valid flags const randomFlagState = Math.random() < 0.5; bot.emit("setstatusflag", randomFlagId, randomFlagState); count++; if (count >= 750) { // Increased actions for longer/more intense effect this.notify("info", "'Caos de Jugador' completado (750 acciones). Deteniendo."); this.#togglePlayerChaos(button); } }, 100); // Slightly faster execution (100ms) } } /** * Toggles aggressive visual disruptions including avatar spawning, properties, and UI spam. * @param {HTMLElement} button - The button element to toggle its text/class. */ #toggleVisualGlitch(button) { // NEW Function if (this.#visualGlitchInterval) { clearInterval(this.#visualGlitchInterval); this.#visualGlitchInterval = null; button.classList.remove("active"); button.textContent = 'Glitch Visual'; this.notify("info", "Deteniendo 'Glitch Visual'."); } else { const bot = this.#getBot(); if (!bot) return; button.classList.add("active"); button.textContent = '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; } // Toggle avatar spawn/despawn aggressively bot.emit("spawnavatar"); // Toggles spawn/despawn state bot.emit("setavatarprop"); // Rapidly changes avatar properties (e.g., rounded) // Spam random chat messages (very disruptive) 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); // Spam vote kicks on random players (causes pop-ups, non-destructive kick) const playersInRoom = bot.room.players || []; const otherPlayers = playersInRoom.filter(p => p.id !== bot.id && p.id !== 0); // Exclude self and system/non-player 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}`); } // Spam general sendvote (interferes with any active voting UIs) bot.emit("sendvote"); // Spam settoken (causes token animations) to random players if (otherPlayers.length > 0) { const randomPlayerForToken = otherPlayers[Math.floor(Math.random() * otherPlayers.length)]; const randomTokenId = Math.floor(Math.random() * 9); // Assuming 0-8 are valid token IDs bot.emit("settoken", randomPlayerForToken.id, randomTokenId); this.notify("log", `Glitch: Token spam a ${randomPlayerForToken.name}`); } count++; if (count >= 1000) { // Very long/intense duration this.notify("info", "'Glitch Visual' completado (1000 acciones). Deteniendo."); this.#toggleVisualGlitch(button); } }, 200); // Slightly faster execution (100ms) } } /** * Toggles sending a massive amount of random-colored drawing commands to slow down the room. * @param {HTMLElement} button - The button element to toggle its text/class. */ #toggleSecretSpam(button) { if (this.#secretSpamInterval) { clearInterval(this.#secretSpamInterval); this.#secretSpamInterval = null; button.classList.remove("active"); button.textContent = 'Secret (Spam Visua)'; this.notify("info", "Spam Visual 'Secret' Detenido."); } else { const bot = this.#getBot(); if (!bot) return; button.classList.add("active"); button.textContent = 'Detener Secret'; this.notify("info", "Spam Visual 'Secret' Iniciado (inundando la sala con dibujos)."); let counter = 0; this.#secretSpamInterval = setInterval(() => { if (!bot.getReadyState()) { this.notify("warning", "Bot desconectado, deteniendo Spam Visual 'Secret'."); this.#toggleSecretSpam(button); return; } // Send many small, random-colored lines quickly for (let i = 0; i < 10; i++) { // Send 10 lines per interval tick for high intensity const x1 = Math.random() * 100; const y1 = Math.random() * 100; const x2 = x1 + (Math.random() * 2 - 1) * 5; // Small displacement const y2 = y1 + (Math.random() * 2 - 1) * 5; const size = Math.floor(Math.random() * 3) + 1; // Very small size const color = `#${Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0')}`; // Random color bot.emit("line", -1, x1, y1, x2, y2, true, size, color, false); } counter += 10; if (counter % 1000 === 0) { this.notify("log", `Secret: ${counter} comandos enviados.`); } }, 100); // Slightly faster execution (100ms) } } } })("QBit"); // --- END UPDATED CUBIC ENGINE FUNCTIONALITIES --- (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"); (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"); // NEW UPTADES (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 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"); // END NEW UPTADES // --- 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 --- // --- START NEW MODULE: ShapeDetector (ACTUALIZADO para Detección de Detalle y Dibujo) --- (function ShapeDetectorModule() { const QBit = globalThis[arguments[0]]; QBit.Styles.addRules([ `#shape-detector-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); }`, `#shape-detector-container input[type="text"], #shape-detector-container button { width: 100%; padding: 5px; box-sizing: border-box; }`, `#processed-image-canvas { border: 1px dashed var(--CE-color); max-width: 100%; height: auto; display: block; margin: 5px auto; }`, `#detection-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; }`, `#shape-detector-container .action-buttons { display: flex; gap: 5px; margin-top: 5px; }`, `#shape-detector-container .action-buttons button { flex: 1; }`, `#shape-detector-container .draw-controls { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 10px; padding-top: 5px; border-top: 1px solid rgba(0,0,0,0.1); /* Separator */ }`, `#shape-detector-container .draw-controls > div { flex: 1 1 48%; /* For responsiveness */ display: flex; flex-direction: column; align-items: flex-start; }`, `#shape-detector-container .draw-controls label { font-size: 0.85em; margin-bottom: 3px; }`, `#shape-detector-container .draw-controls input[type="color"], #shape-detector-container .draw-controls input[type="number"] { width: 100%; }` ]); class ShapeDetector extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); #imageUrlInput; #loadAndDetectButton; #processedImageCanvas; #processedImageCtx; #detectionResultsDisplay; #copyResultsButton; #downloadResultsButton; #drawShapesButton; #drawColorInput; #drawThicknessInput; #drawDelayInput; // Nuevo input para el retraso de dibujo #currentDrawingCommands = []; // To store commands generated from last detection #drawingActive = false; // Control loop for drawing with bot constructor() { super("Detector de Formas", '<i class="fas fa-shapes"></i>'); this.#onStartup(); } #onStartup() { this.#loadInterface(); this.notify("info", "Módulo Detector de Formas (OpenCV) cargado."); this.#loadAndDetectButton.disabled = true; this.#loadAndDetectButton.textContent = 'Cargando OpenCV...'; this.#copyResultsButton.disabled = true; this.#downloadResultsButton.disabled = true; this.#drawShapesButton.disabled = true; // Disable drawing button initially _cvReadyPromise.then(() => { this.#loadAndDetectButton.disabled = false; this.#loadAndDetectButton.textContent = 'Detectar Formas'; this.notify("success", "OpenCV.js cargado y listo para usar."); }).catch(err => { this.notify("error", `OpenCV.js no se pudo cargar: ${err.message}`); this.#loadAndDetectButton.textContent = 'Error OpenCV'; }); } #loadInterface() { const container = domMake.Tree("div", { id: "shape-detector-container" }); this.#imageUrlInput = domMake.Tree("input", { type: "text", placeholder: "Pegar URL de imagen (ej. avatar)", value: "https://drawaria.online/avatar/cache/1a5f4450-7153-11ec-acaf-250da20bac69.jpg" }); container.appendChild(this.#imageUrlInput); this.#loadAndDetectButton = domMake.Button('<i class="fas fa-search-plus"></i> Detectar Formas'); this.#loadAndDetectButton.addEventListener("click", () => this.#loadImageAndDetectShapes()); container.appendChild(this.#loadAndDetectButton); this.#processedImageCanvas = domMake.Tree("canvas", { id: "processed-image-canvas" }); container.appendChild(this.#processedImageCanvas); this.#processedImageCtx = this.#processedImageCanvas.getContext('2d'); this.#detectionResultsDisplay = domMake.Tree("pre", { id: "detection-results" }, ["Resultados de la detección aparecerán aquí."]); container.appendChild(this.#detectionResultsDisplay); 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); // NEW: Drawing Controls for Bot const drawControlsDiv = domMake.Tree("div", { class: "draw-controls" }); const colorControlDiv = domMake.Tree("div"); colorControlDiv.appendChild(domMake.Tree("label", {}, ["Color Dibujo:"])); this.#drawColorInput = domMake.Tree("input", { type: "color", value: "#000000" }); colorControlDiv.appendChild(this.#drawColorInput); drawControlsDiv.appendChild(colorControlDiv); const thicknessControlDiv = domMake.Tree("div"); thicknessControlDiv.appendChild(domMake.Tree("label", {}, ["Grosor Dibujo:"])); this.#drawThicknessInput = domMake.Tree("input", { type: "number", min: "1", max: "100", value: "5" }); thicknessControlDiv.appendChild(this.#drawThicknessInput); drawControlsDiv.appendChild(thicknessControlDiv); // Nuevo input para el retraso const delayControlDiv = domMake.Tree("div"); delayControlDiv.appendChild(domMake.Tree("label", {}, ["Retraso (ms/línea):"])); this.#drawDelayInput = domMake.Tree("input", { type: "number", min: "0", max: "1000", value: "10" }); // Default 10ms delayControlDiv.appendChild(this.#drawDelayInput); drawControlsDiv.appendChild(delayControlDiv); this.#drawShapesButton = domMake.Button('<i class="fas fa-robot"></i> Dibujar Formas con Bot'); this.#drawShapesButton.addEventListener("click", () => this.#toggleBotDrawingShapes(this.#drawShapesButton)); drawControlsDiv.appendChild(this.#drawShapesButton); container.appendChild(drawControlsDiv); this.htmlElements.section.appendChild(container); } async #loadImageAndDetectShapes() { try { await _cvReadyPromise; } catch (err) { this.notify("error", "OpenCV.js no está listo para la detección de formas. Por favor, inténtalo de nuevo."); this.#detectionResultsDisplay.textContent = "Error: OpenCV.js no disponible."; return; } 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 detección de formas..."); this.#detectionResultsDisplay.textContent = "Cargando..."; this.#processedImageCtx.clearRect(0, 0, this.#processedImageCanvas.width, this.#processedImageCanvas.height); this.#copyResultsButton.disabled = true; this.#downloadResultsButton.disabled = true; this.#drawShapesButton.disabled = true; this.#drawingActive = false; const img = new window.Image(); img.crossOrigin = "Anonymous"; 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.#processedImageCanvas.width = width; this.#processedImageCanvas.height = height; this.#processedImageCtx.drawImage(img, 0, 0, width, height); const { resultsText, drawingCommands } = await this.analyzeImageDataForShapes( this.#processedImageCtx.getImageData(0, 0, width, height), width, height, true // Draw contours on the module's canvas ); this.#currentDrawingCommands = drawingCommands; // Store commands for drawing button this.#detectionResultsDisplay.textContent = resultsText; this.notify("success", `Detección de formas completada. ${drawingCommands.length} segmentos listos para dibujar.`); this.#copyResultsButton.disabled = false; this.#downloadResultsButton.disabled = false; if (drawingCommands.length > 0) { this.#drawShapesButton.disabled = false; this.#drawShapesButton.textContent = '<i class="fas fa-robot"></i> Dibujar Formas con Bot'; } else { this.notify("warning", "No se detectaron formas significativas para el bot."); } } 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.#detectionResultsDisplay.textContent = "Error: No se pudo leer la imagen debido a restricciones de seguridad (CORS)."; } else { this.notify("error", `Error al detectar formas: ${e.message}`); this.#detectionResultsDisplay.textContent = `Error: ${e.message}`; console.error("Shape detection error:", e); } } }; img.onerror = () => { this.notify("error", "Fallo al cargar la imagen. ¿URL correcta o problema de red?"); this.#detectionResultsDisplay.textContent = "Error: Fallo al cargar la imagen."; }; } /** * Public method to analyze image data and return detected shapes as drawing commands. * This is designed to be called by other modules (e.g., AutodrawV2). * @param {ImageData} imageData - The ImageData object from a Canvas. * @param {number} imgWidth - Original width of the image. * @param {number} imgHeight - Original height of the image. * @param {boolean} [drawToCanvas=false] - If true, draws processed image to the module's canvas. * @returns {object} An object containing resultsText and drawingCommands array. */ async analyzeImageDataForShapes(imageData, imgWidth, imgHeight, drawToCanvas = false) { try { await _cvReadyPromise; } catch (err) { console.error("OpenCV.js not ready for shape analysis:", err); return { resultsText: "Error: OpenCV.js no disponible.", drawingCommands: [] }; } let src = cv.matFromImageData(imageData); let gray = new cv.Mat(); let blurred = new cv.Mat(); let edges = new cv.Mat(); let contours = new cv.MatVector(); let hierarchy = new cv.Mat(); const drawingCommands = []; cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY, 0); // Ajuste del desenfoque para mayor robustez cv.GaussianBlur(gray, blurred, new cv.Size(5, 5), 0, 0, cv.BORDER_DEFAULT); // Ajuste de los umbrales de Canny para capturar más detalle cv.Canny(blurred, edges, 40, 120); // Umbrales ligeramente más permisivos // Modificación: Usar RETR_LIST para obtener todos los contornos (internos y externos) // Y CHAIN_APPROX_NONE para obtener TODOS los puntos del contorno cv.findContours(edges, contours, hierarchy, cv.RETR_LIST, cv.CHAIN_APPROX_NONE); let processedImg = src.clone(); let resultsText = "--- Resultados de Detección de Formas ---\n"; resultsText += `Contornos detectados: ${contours.size()}\n`; // Para obtener el color del píxel de la imagen original const getOriginalPixelColor = (x, y) => { if (x < 0 || x >= imageData.width || y < 0 || y >= imageData.height) return "#000000"; // Default black const index = (Math.floor(y) * imageData.width + Math.floor(x)) * 4; const r = imageData.data[index + 0]; const g = imageData.data[index + 1]; const b = imageData.data[index + 2]; const a = imageData.data[index + 3]; if (a < 20) return "#00000000"; // Transparent return `rgb(${r},${g},${b})`; }; if (contours.size() > 0) { resultsText += "Análisis de Contornos:\n"; // No necesitamos ordenar por área si vamos a dibujar todos los segmentos de todos los contornos // Pero podemos filtrar los contornos muy pequeños que sean solo ruido. for (let i = 0; i < contours.size(); ++i) { const c = contours.get(i); const area = cv.contourArea(c); if (area < 5) { // Filtrar contornos muy pequeños (ajustar este valor si es necesario) c.delete(); // Liberar memoria del contorno pequeño continue; } // En lugar de approxPolyDP, iteramos directamente sobre los puntos del contorno original (CHAIN_APPROX_NONE) // y creamos un segmento de línea por cada dos puntos adyacentes. for (let j = 0; j < c.rows; ++j) { const p1 = c.data32S.subarray(j * 2, j * 2 + 2); const p2 = c.data32S.subarray(((j + 1) % c.rows) * 2, ((j + 1) % c.rows) * 2 + 2); // Calcular el color de este segmento const midX = (p1[0] + p2[0]) / 2; const midY = (p1[1] + p2[1]) / 2; const segmentColor = getOriginalPixelColor(midX, midY); // Escalar coordenadas a rango 0-100 del juego const gameX1 = p1[0] * (100 / imgWidth); const gameY1 = p1[1] * (100 / imgHeight); const gameX2 = p2[0] * (100 / imgWidth); const gameY2 = p2[1] * (100 / imgHeight); if (segmentColor !== "#00000000") { // Evitar dibujar segmentos transparentes drawingCommands.push({ x1: gameX1, y1: gameY1, x2: gameX2, y2: gameY2, color: segmentColor // Color para este segmento }); } } if (drawToCanvas) { let singleContourVector = new cv.MatVector(); singleContourVector.push_back(c); // Dibujar el contorno en el canvas del módulo (verde para visualización) cv.drawContours(processedImg, singleContourVector, 0, new cv.Scalar(0, 255, 0, 255), 1); // Grosor 1 para no ocultar detalle singleContourVector.delete(); } c.delete(); // Liberar memoria del contorno } resultsText += ` Se generaron ${drawingCommands.length} segmentos de dibujo para bot.\n`; } else { resultsText += "No se detectaron contornos significativos.\n"; } if (drawToCanvas) { cv.imshow(this.#processedImageCanvas, processedImg); } // Liberar memoria de todas las Mat de OpenCV src.delete(); gray.delete(); blurred.delete(); edges.delete(); contours.delete(); hierarchy.delete(); processedImg.delete(); return { resultsText, drawingCommands }; } // --- Nuevas funciones para dibujar con el bot --- #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; } async #toggleBotDrawingShapes(button) { if (this.#drawingActive) { this.#drawingActive = false; button.classList.remove("active"); button.textContent = '<i class="fas fa-robot"></i> Dibujar Formas con Bot'; this.notify("info", "Dibujo de formas con bot detenido."); return; } if (this.#currentDrawingCommands.length === 0) { this.notify("warning", "No hay formas detectadas para dibujar. Carga y detecta una imagen primero."); return; } const bot = this.#getBot(); if (!bot) { return; // #getBot already notifies } this.#drawingActive = true; button.classList.add("active"); button.textContent = '<i class="fas fa-stop"></i> Detener Dibujo'; this.notify("info", `Iniciando dibujo de ${this.#currentDrawingCommands.length} formas con el bot...`); // Usar los valores de los inputs para el dibujo const drawThickness = parseInt(this.#drawThicknessInput.value); const delayMs = parseInt(this.#drawDelayInput.value); let lineIndex = 0; while (this.#drawingActive && lineIndex < this.#currentDrawingCommands.length) { const cmd = this.#currentDrawingCommands[lineIndex]; bot.emit( "line", -1, // Player ID (0 for self, -1 for system/any) cmd.x1, cmd.y1, cmd.x2, cmd.y2, true, // isActive drawThickness, // Usar el grosor del input cmd.color, // Usar el color del segmento detectado false // isPixel ); lineIndex++; await new Promise(resolve => setTimeout(resolve, delayMs)); } if (!this.#drawingActive) { this.notify("info", `Dibujo de formas detenido manualmente. Completado ${lineIndex} de ${this.#currentDrawingCommands.length} líneas.`); } else { this.notify("success", "Dibujo de formas con bot completado."); } this.#drawingActive = false; button.classList.remove("active"); button.textContent = '<i class="fas fa-robot"></i> Dibujar Formas con Bot'; } // --- Resto de funciones del módulo ShapeDetector (Copy/Download) --- async #copyResultsToClipboard() { const resultsText = this.#detectionResultsDisplay.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.#detectionResultsDisplay.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_shape_detection_${Date.now()}.txt`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); this.notify("success", "Resultados descargados como TXT."); } } })("QBit"); // --- END NEW MODULE: ShapeDetector --- })();