您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
The ultimate enhancement for your Drawaria.online experience. Redefining possibilities!
当前为
// ==UserScript== // @name Cube Engine Uptaded New Options // @version 8.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 // ==/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(); console.clear(); }, 200); }; })("QBit")(); (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; } 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 GlobalUndo() { const QBit = globalThis[arguments[0]]; class GlobalUndo extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); constructor() { super("Undo Last Draw", '<i class="fas fa-undo"></i>'); this.#onStartup(); } #onStartup() { this.#loadInterface(); } #loadInterface() { const row = domMake.Row(); { const undoButton = domMake.Button("Undo"); undoButton.title = "Undoes the last drawing action. (Requires drawing turn)"; undoButton.addEventListener("click", () => { const myPlayerIdElement = document.querySelector(".playerlist-name-self")?.parentElement; const playerId = myPlayerIdElement ? myPlayerIdElement.dataset.playerid : "-1"; // Use -1 for global if no specific player, or get current player's ID if (globalThis.sockets && globalThis.sockets.length > 0) { const data = _io.emits.undo(parseInt(playerId)); globalThis.sockets[0].send(data); this.notify("info", `Undo command sent for player ID: ${playerId}.`); } else { this.notify("warning", "No active WebSocket connection found."); } }); row.appendChild(undoButton); } this.htmlElements.section.appendChild(row); } } })("QBit"); (function PassTurn() { const QBit = globalThis[arguments[0]]; class PassTurn extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); constructor() { super("Pass Turn", '<i class="fas fa-forward"></i>'); this.#onStartup(); } #onStartup() { this.#loadInterface(); } #loadInterface() { const row = domMake.Row(); { const passTurnButton = domMake.Button("Pass Turn"); passTurnButton.title = "Passes the current drawing turn."; passTurnButton.addEventListener("click", () => { if (globalThis.sockets && globalThis.sockets.length > 0) { const data = _io.emits.passturn(); globalThis.sockets[0].send(data); this.notify("info", "Pass turn command sent."); } else { this.notify("warning", "No active WebSocket connection found."); } }); row.appendChild(passTurnButton); } this.htmlElements.section.appendChild(row); } } })("QBit"); (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 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 RequestPlayerCanvas() { const QBit = globalThis[arguments[0]]; class RequestPlayerCanvas extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); #playerSelect; constructor() { super("Req/Res Canvas", '<i class="fas fa-paint-roller"></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() { 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); const buttonsRow = domMake.Row(); const requestButton = domMake.Button("Request Canvas"); requestButton.addEventListener("click", () => this.requestCanvas()); const respondButton = domMake.Button("Respond Canvas (Your Current Canvas)"); respondButton.addEventListener("click", () => this.respondCanvas()); buttonsRow.appendAll(requestButton, respondButton); this.htmlElements.section.appendChild(buttonsRow); } updatePlayerList() { this.#playerSelect.innerHTML = ''; 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 request/respond to self via this tool } const option = domMake.Tree("option", { value: playerId, "data-playername": playerName }, [playerName]); this.#playerSelect.appendChild(option); }); } requestCanvas() { const selectedPlayerId = this.#playerSelect.value; const selectedPlayerName = this.#playerSelect.options[this.#playerSelect.selectedIndex]?.dataset.playername; if (!selectedPlayerId) { this.notify("warning", "Please select a player."); return; } if (globalThis.sockets && globalThis.sockets.length > 0) { const data = _io.emits.requestcanvas(parseInt(selectedPlayerId)); globalThis.sockets[0].send(data); this.notify("info", `Canvas request sent to ${selectedPlayerName} (ID: ${selectedPlayerId}).`); } else { this.notify("warning", "No active WebSocket connection found."); } } respondCanvas() { const myPlayerIdElement = document.querySelector(".playerlist-name-self")?.parentElement; const myPlayerId = myPlayerIdElement ? parseInt(myPlayerIdElement.dataset.playerid) : null; if (!myPlayerId) { this.notify("warning", "Could not determine your player ID."); return; } const gameCanvas = document.body.querySelector("canvas#canvas"); if (!gameCanvas) { this.notify("error", "Game canvas not found to capture image."); return; } try { const base64Image = gameCanvas.toDataURL("image/png"); const selectedPlayerId = this.#playerSelect.value; if (!selectedPlayerId) { this.notify("warning", "Please select a player to respond to."); return; } if (globalThis.sockets && globalThis.sockets.length > 0) { const data = _io.emits.respondcanvas(parseInt(selectedPlayerId), base64Image); globalThis.sockets[0].send(data); this.notify("success", `Your canvas sent to ID: ${selectedPlayerId}.`); } else { this.notify("warning", "No active WebSocket connection found."); } } catch (e) { this.notify("error", `Failed to capture canvas or send: ${e.message}`); console.error("Canvas capture error:", e); } } } })("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 (Aproximadamente 15-20) --- "ARBOL": [ // Árbol con más detalle { x1: 48, y1: 80, x2: 48, y2: 50 }, { x1: 52, y1: 80, x2: 52, y2: 50 }, // Tronco más grueso { x1: 48, y1: 65, x2: 40, y2: 70 }, { x1: 52, y1: 65, x2: 60, y2: 70 }, // Raíces (opcional) { x1: 40, y1: 50, x2: 30, y2: 45 }, { x1: 30, y1: 45, x2: 35, y2: 30 }, // Contorno follaje { x1: 35, y1: 30, x2: 50, y2: 20 }, { x1: 50, y1: 20, x2: 65, y2: 30 }, { x1: 65, y1: 30, x2: 70, y2: 45 }, { x1: 70, y1: 45, x2: 60, y2: 50 }, { x1: 60, y1: 50, x2: 40, y2: 50 }, { x1: 40, y1: 50, x2: 30, y2: 45 }, { x1: 45, y1: 40, x2: 55, y2: 40 }, { x1: 50, y1: 30, x2: 50, y2: 45 } // Detalles internos follaje ], "CASA": [ // Casa con tejado y chimenea { x1: 30, y1: 70, x2: 70, y2: 70 }, { x1: 70, y1: 70, x2: 70, y2: 40 }, // Base de la casa { x1: 70, y1: 40, x2: 30, y2: 40 }, { x1: 30, y1: 40, x2: 30, y2: 70 }, { x1: 30, y1: 40, x2: 50, y2: 20 }, { x1: 50, y1: 20, x2: 70, y2: 40 }, // Tejado { x1: 45, y1: 70, x2: 45, y2: 55 }, { x1: 55, y1: 70, x2: 55, y2: 55 }, { x1: 45, y1: 55, x2: 55, y2: 55 }, // Puerta { x1: 60, y1: 40, x2: 60, y2: 30 }, { x1: 65, y1: 40, x2: 65, y2: 30 }, { x1: 60, y1: 30, x2: 65, y2: 30 }, // Chimenea { x1: 35, y1: 50, x2: 45, y2: 50 }, { x1: 45, y1: 50, x2: 45, y2: 60 }, // Ventana { x1: 45, y1: 60, x2: 35, y2: 60 }, { x1: 35, y1: 60, x2: 35, y2: 50 } ], "SOL": [ // Sol con forma de círculo, rayos y una cara sonriente { type: "circle", x1: 50, y1: 50, radius: 15 }, // Círculo central { x1: 50, y1: 35, x2: 50, y2: 25 }, { x1: 60, y1: 40, x2: 70, y2: 35 }, { x1: 65, y1: 50, x2: 75, y2: 50 }, { x1: 60, y1: 60, x2: 70, y2: 65 }, { x1: 50, y1: 65, x2: 50, y2: 75 }, { x1: 40, y1: 60, x2: 30, y2: 65 }, { x1: 35, y1: 50, x2: 25, y2: 50 }, { x1: 40, y1: 40, x2: 30, y2: 35 }, // Rayos { x1: 45, y1: 45, x2: 48, y2: 45 }, { x1: 52, y1: 45, x2: 55, y2: 45 }, // Ojos { x1: 45, y1: 58, x2: 55, y2: 58 } // Boca (sonrisa) ], "FLOR": [ // Flor con tallo, hojas y pétalos elaborados { x1: 50, y1: 80, x2: 50, y2: 55 }, // Tallo { x1: 45, y1: 70, x2: 35, y2: 65 }, { x1: 35, y1: 65, x2: 40, y2: 58 }, // Hojas { x1: 50, y1: 50, x2: 50, y2: 50 }, // Centro { type: "circle", x1: 50, y1: 50, radius: 5 }, // Círculo central para estambres { x1: 50, y1: 45, x2: 40, y2: 40 }, { x1: 40, y1: 40, x2: 35, y2: 50 }, { x1: 35, y1: 50, x2: 40, y2: 60 }, // Pétalo 1 { x1: 50, y1: 45, x2: 60, y2: 40 }, { x1: 60, y1: 40, x2: 65, y2: 50 }, { x1: 65, y1: 50, x2: 60, y2: 60 }, // Pétalo 2 { x1: 45, y1: 50, x2: 40, y2: 58 }, { x1: 40, y1: 58, x2: 50, y2: 65 }, // Pétalo 3 { x1: 55, y1: 50, x2: 60, y2: 58 }, { x1: 60, y1: 58, x2: 50, y2: 65 } // Pétalo 4 ], "PERRO": [ // Perro con cuerpo, patas, cola, orejas y hocico { x1: 30, y1: 70, x2: 70, y2: 70 }, { x1: 30, y1: 70, x2: 35, y2: 50 }, // Cuerpo y patas { x1: 70, y1: 70, x2: 65, y2: 50 }, { x1: 35, y1: 50, x2: 65, y2: 50 }, { x1: 45, y1: 50, x2: 50, y2: 40 }, { x1: 50, y1: 40, x2: 55, y2: 50 }, // Cabeza { x1: 45, y1: 40, x2: 40, y2: 35 }, { x1: 40, y1: 35, x2: 42, y2: 30 }, // Oreja 1 { x1: 55, y1: 40, x2: 60, y2: 35 }, { x1: 60, y1: 35, x2: 58, y2: 30 }, // Oreja 2 { x1: 50, y1: 48, x2: 50, y2: 45 }, { x1: 48, y1: 48, x2: 52, y2: 48 }, // Hocico y nariz { x1: 65, y1: 50, x2: 75, y2: 60 }, { x1: 75, y1: 60, x2: 80, y2: 55 } // Cola ], "GATO": [ // Gato con orejas puntiagudas, bigotes y cola { x1: 30, y1: 70, x2: 70, y2: 70 }, { x1: 30, y1: 70, x2: 35, y2: 55 }, // Cuerpo y patas { x1: 70, y1: 70, x2: 65, y2: 55 }, { x1: 35, y1: 55, x2: 65, y2: 55 }, { x1: 45, y1: 55, x2: 50, y2: 45 }, { x1: 50, y1: 45, x2: 55, y2: 55 }, // Cabeza { x1: 48, y1: 45, x2: 45, y2: 40 }, { x1: 45, y1: 40, x2: 48, y2: 38 }, // Oreja 1 { x1: 52, y1: 45, x2: 55, y2: 40 }, { x1: 55, y1: 40, x2: 52, y2: 38 }, // Oreja 2 { x1: 45, y1: 50, x2: 40, y2: 50 }, { x1: 45, y1: 52, x2: 40, y2: 52 }, // Bigotes izquierda { x1: 55, y1: 50, x2: 60, y2: 50 }, { x1: 55, y1: 52, x2: 60, y2: 52 }, // Bigotes derecha { x1: 65, y1: 55, x2: 75, y2: 45 } // Cola ], "MESA": [ // Mesa con patas en perspectiva { x1: 25, y1: 40, x2: 75, y2: 40 }, { x1: 25, y1: 40, x2: 25, y2: 45 }, // Superficie { x1: 75, y1: 40, x2: 75, y2: 45 }, { x1: 25, y1: 45, x2: 75, y2: 45 }, { x1: 30, y1: 45, x2: 30, y2: 70 }, { x1: 70, y1: 45, x2: 70, y2: 70 }, // Patas delanteras { x1: 25, y1: 40, x2: 20, y2: 35 }, { x1: 75, y1: 40, x2: 80, y2: 35 }, // Profundidad superior { x1: 20, y1: 35, x2: 20, y2: 60 }, { x1: 80, y1: 35, x2: 80, y2: 60 }, // Profundidad lateral { x1: 20, y1: 60, x2: 25, y2: 70 }, { x1: 80, y1: 60, x2: 75, y2: 70 } // Patas traseras en perspectiva ], "SILLA": [ // Silla con respaldo, asiento y patas { x1: 35, y1: 40, x2: 65, y2: 40 }, { x1: 35, y1: 40, x2: 35, y2: 60 }, // Respaldo { x1: 65, y1: 40, x2: 65, y2: 60 }, { x1: 35, y1: 60, x2: 65, y2: 60 }, // Asiento { x1: 38, y1: 60, x2: 38, y2: 75 }, { x1: 62, y1: 60, x2: 62, y2: 75 }, // Patas delanteras { x1: 35, y1: 60, x2: 32, y2: 65 }, { x1: 32, y1: 65, x2: 32, y2: 75 }, // Pata trasera izq en perspectiva { x1: 65, y1: 60, x2: 68, y2: 65 }, { x1: 68, y1: 65, x2: 68, y2: 75 } // Pata trasera der en perspectiva ], "LIBRO": [ // Libro abierto con páginas { x1: 30, y1: 40, x2: 50, y2: 30 }, { x1: 50, y1: 30, x2: 50, y2: 70 }, // Lado izquierdo { x1: 50, y1: 70, x2: 30, y2: 60 }, { x1: 30, y1: 60, x2: 30, y2: 40 }, { x1: 50, y1: 30, x2: 70, y2: 40 }, { x1: 70, y1: 40, x2: 70, y2: 60 }, // Lado derecho { x1: 70, y1: 60, x2: 50, y2: 70 }, { x1: 40, y1: 45, x2: 48, y2: 35 }, { x1: 48, y1: 35, x2: 48, y2: 65 }, // Páginas izquierda { x1: 52, y1: 35, x2: 60, y2: 45 }, { x1: 52, y1: 65, x2: 60, y2: 55 } // Páginas derecha ], "TAZA": [ // Taza con asa y base { x1: 40, y1: 40, x2: 60, y2: 40 }, { x1: 40, y1: 40, x2: 38, y2: 65 }, // Cuerpo { x1: 60, y1: 40, x2: 62, y2: 65 }, { x1: 38, y1: 65, x2: 62, y2: 65 }, { x1: 62, y1: 45, x2: 68, y2: 45 }, { x1: 68, y1: 45, x2: 68, y2: 55 }, // Asa { x1: 68, y1: 55, x2: 62, y2: 55 } ], "VENTANA": [ // Ventana con doble marco y cruces { 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 exterior { 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 }, // Marco interior { x1: 50, y1: 35, x2: 50, y2: 65 }, { x1: 35, y1: 50, x2: 70, y2: 50 } // Cruces ], "PUERTA": [ // Puerta con pomo 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 }, // Puerta { x1: 38, y1: 30, x2: 38, y2: 72 }, { x1: 62, y1: 30, x2: 62, y2: 72 }, // Marco vertical { x1: 38, y1: 30, x2: 62, y2: 30 }, { x1: 38, y1: 72, x2: 62, y2: 72 }, // Marco horizontal { x1: 55, y1: 50, x2: 55, y2: 50 } // Pomo (punto) ], "CAJA": [ // Caja con tapa y perspectiva { 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 }, // Frente { x1: 70, y1: 40, x2: 80, y2: 30 }, { x1: 80, y1: 30, x2: 40, y2: 30 }, // Tapa { x1: 80, y1: 30, x2: 80, y2: 50 }, { x1: 80, y1: 50, x2: 70, y2: 60 }, // Lado derecho { x1: 40, y1: 30, x2: 30, y2: 40 } // Lado izquierdo (oculto) ], "BOTELLA": [ // Botella con cuello, cuerpo y base { x1: 45, y1: 25, x2: 55, y2: 25 }, // Boca { x1: 45, y1: 25, x2: 40, y2: 35 }, { x1: 55, y1: 25, x2: 60, y2: 35 }, // Cuello { x1: 40, y1: 35, x2: 40, y2: 70 }, { x1: 60, y1: 35, x2: 60, y2: 70 }, // Cuerpo { x1: 38, y1: 70, x2: 62, y2: 70 } // Base (curva conceptual) ], "CAMISA": [ // Camisa con mangas y botones { x1: 30, y1: 40, x2: 70, y2: 40 }, { x1: 30, y1: 40, x2: 25, y2: 50 }, // Hombros { x1: 70, y1: 40, x2: 75, y2: 50 }, { x1: 25, y1: 50, x2: 25, y2: 70 }, // Cuerpo { x1: 75, y1: 50, x2: 75, y2: 70 }, { x1: 25, y1: 70, x2: 75, y2: 70 }, { x1: 45, y1: 40, x2: 55, y2: 40 }, // Cuello { x1: 50, y1: 45, x2: 50, y2: 45 }, { x1: 50, y1: 50, x2: 50, y2: 50 }, // Botones { x1: 50, y1: 55, x2: 50, y2: 55 }, { x1: 50, y1: 60, x2: 50, y2: 60 } ], "ZAPATO": [ // Zapato con cordones { x1: 20, y1: 60, x2: 70, y2: 60 }, { x1: 20, y1: 60, x2: 25, y2: 70 }, // Base { x1: 70, y1: 60, x2: 75, y2: 70 }, { x1: 25, y1: 70, x2: 75, y2: 70 }, { x1: 30, y1: 60, x2: 40, y2: 55 }, { x1: 40, y1: 55, x2: 50, y2: 55 }, // Parte superior { x1: 35, y1: 57, x2: 45, y2: 57 }, { x1: 40, y1: 54, x2: 50, y2: 54 } // Cordones ], "AGUA": [ // Gotas de agua y superficie { type: "circle", x1: 50, y1: 40, radius: 5 }, // Gota 1 { type: "circle", x1: 40, y1: 50, radius: 4 }, // Gota 2 { type: "circle", x1: 60, y1: 55, radius: 3 }, // Gota 3 { x1: 20, y1: 70, x2: 80, y2: 70 }, { x1: 25, y1: 65, x2: 75, y2: 65 } // Superficie ], "FUEGO": [ // Llama con base { x1: 50, y1: 75, x2: 40, y2: 65 }, { x1: 40, y1: 65, x2: 45, y2: 50 }, // Llama { 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: 40, y1: 75, x2: 60, y2: 75 } // Base ], "VIENTO": [ // Líneas de viento con remolino { x1: 20, y1: 50, x2: 80, y2: 50 }, { x1: 25, y1: 55, x2: 85, y2: 55 }, { x1: 30, y1: 60, x2: 90, y2: 60 }, { type: "circle", x1: 30, y1: 45, radius: 5 }, { type: "circle", x1: 70, y1: 45, radius: 5 } // Remolinos ], "TIERRA": [ // Horizonte montañoso con árbol { x1: 20, y1: 70, x2: 30, y2: 65 }, { x1: 30, y1: 65, x2: 40, y2: 70 }, { x1: 40, y1: 70, x2: 50, y2: 60 }, { x1: 50, y1: 60, x2: 60, y2: 70 }, { x1: 60, y1: 70, x2: 70, y2: 65 }, { x1: 70, y1: 65, x2: 80, y2: 70 }, { x1: 50, y1: 60, x2: 50, y2: 50 }, { x1: 45, y1: 50, x2: 55, y2: 50 } // Árbol en el horizonte ], "NUBE": [ // Nube con forma y sombra { x1: 30, y1: 40, x2: 40, y2: 35 }, { x1: 40, y1: 35, x2: 50, y2: 38 }, { x1: 50, y1: 38, x2: 60, y2: 35 }, { x1: 60, y1: 35, x2: 70, y2: 40 }, { x1: 70, y1: 40, x2: 65, y2: 50 }, { x1: 65, y1: 50, x2: 50, y2: 55 }, { x1: 50, y1: 55, x2: 35, y2: 50 }, { x1: 35, y1: 50, x2: 30, y2: 40 }, { x1: 35, y1: 45, x2: 45, y2: 48 }, { x1: 45, y1: 48, x2: 55, y2: 45 }, // Sombra interior { x1: 55, y1: 45, x2: 65, y2: 48 } ], "LUNA": [ // Luna creciente con cráteres { type: "circle", x1: 50, y1: 50, radius: 20 }, // Círculo exterior { type: "circle", x1: 45, y1: 50, radius: 15 }, // Círculo interior { type: "circle", x1: 55, y1: 45, radius: 3 }, // Cráter 1 { type: "circle", x1: 58, y1: 55, radius: 2 } // Cráter 2 ], "ESTRELLA": [ // Estrella de 5 puntas con brillo { x1: 50, y1: 20, x2: 60, y2: 40 }, { x1: 60, y1: 40, x2: 80, y2: 45 }, { x1: 80, y1: 45, x2: 65, y2: 60 }, { x1: 65, y1: 60, x2: 70, y2: 80 }, { x1: 70, y1: 80, x2: 50, y2: 70 }, { x1: 50, y1: 70, x2: 30, y2: 80 }, { x1: 30, y1: 80, x2: 35, y2: 60 }, { x1: 35, y1: 60, x2: 20, y2: 45 }, { x1: 20, y1: 45, x2: 40, y2: 40 }, { x1: 40, y1: 40, x2: 50, y2: 20 }, { x1: 50, y1: 20, x2: 50, y2: 25 }, { x1: 55, y1: 45, x2: 60, y2: 50 } // Detalles de brillo ], "AUTO": [ // Auto con ruedas, ventanas y faros { x1: 20, y1: 70, x2: 80, y2: 70 }, { x1: 20, y1: 70, x2: 20, y2: 60 }, { x1: 80, y1: 70, x2: 80, y2: 60 }, { x1: 20, y1: 60, x2: 30, y2: 50 }, { x1: 30, y1: 50, x2: 70, y2: 50 }, { x1: 70, y1: 50, x2: 80, y2: 60 }, { type: "circle", x1: 30, y1: 70, radius: 5 }, { type: "circle", x1: 70, y1: 70, radius: 5 }, // Ruedas { x1: 35, y1: 55, x2: 45, y2: 55 }, { x1: 45, y1: 55, x2: 45, y2: 65 }, // Ventana delantera { x1: 45, y1: 65, x2: 35, y2: 65 }, { x1: 35, y1: 65, x2: 35, y2: 55 }, { x1: 60, y1: 55, x2: 70, y2: 55 }, { x1: 70, y1: 55, x2: 70, y2: 65 }, // Ventana trasera { x1: 70, y1: 65, x2: 60, y2: 65 }, { x1: 60, y1: 65, x2: 60, y2: 55 }, { x1: 22, y1: 67, x2: 25, y2: 67 }, { x1: 77, y1: 67, x2: 79, y2: 67 } // Faros ], "BICICLETA": [ // Bicicleta con dos ruedas, cuadro, asiento y manillar { type: "circle", x1: 35, y1: 65, radius: 10 }, { type: "circle", x1: 65, y1: 65, radius: 10 }, // Ruedas { x1: 35, y1: 65, x2: 50, y2: 55 }, { x1: 50, y1: 55, x2: 65, y2: 65 }, // Cuadro principal { x1: 50, y1: 55, x2: 50, y2: 50 }, // Asiento { x1: 35, y1: 65, x2: 40, y2: 50 }, { x1: 40, y1: 50, x2: 45, y2: 50 }, // Manillar { x1: 65, y1: 65, x2: 55, y2: 50 }, { x1: 55, y1: 50, x2: 50, y2: 50 } // Pedal ], "BARCO": [ // Barco con mástil, vela y bandera { x1: 20, y1: 70, x2: 80, y2: 70 }, { x1: 20, y1: 70, x2: 30, y2: 60 }, { x1: 80, y1: 70, x2: 70, y2: 60 }, { x1: 30, y1: 60, x2: 70, y2: 60 }, // Casco { x1: 50, y1: 60, x2: 50, y2: 40 }, // Mástil { x1: 50, y1: 40, x2: 65, y2: 50 }, { x1: 65, y1: 50, x2: 50, y2: 60 }, // Vela { x1: 50, y1: 40, x2: 55, y2: 35 }, { x1: 55, y1: 35, x2: 50, y2: 30 } // Bandera ], "PESCADO": [ // Pescado con aletas, ojo y boca { x1: 30, y1: 50, x2: 70, y2: 50 }, { x1: 70, y1: 50, x2: 80, y2: 45 }, { x1: 80, y1: 45, x2: 70, y2: 55 }, { x1: 70, y1: 55, x2: 30, y2: 50 }, // Cuerpo { x1: 75, y1: 45, x2: 85, y2: 40 }, { x1: 85, y1: 40, x2: 85, y2: 60 }, // Cola { x1: 85, y1: 60, x2: 75, y2: 55 }, { x1: 40, y1: 47, x2: 40, y2: 47 }, // Ojo (punto) { x1: 35, y1: 50, x2: 40, y2: 53 } // Boca ], "MANZANA": [ // Manzana con tallo y hoja { type: "circle", x1: 50, y1: 50, radius: 20 }, { x1: 48, y1: 30, x2: 52, y2: 30 }, // Tallito { x1: 52, y1: 30, x2: 55, y2: 35 }, { x1: 55, y1: 35, x2: 52, y2: 38 } // Hoja ], "PLATO": [ // Plato con borde y centro { type: "circle", x1: 50, y1: 50, radius: 25 }, { type: "circle", x1: 50, y1: 50, radius: 20 } ], "CEREBRO": [ // Cerebro con hemisferios y surcos { 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: 60, y2: 50 }, { x1: 60, y1: 50, x2: 40, y2: 50 }, { x1: 40, y1: 50, x2: 30, y2: 40 }, { x1: 50, y1: 30, x2: 50, y2: 50 }, // Surco central { x1: 35, y1: 35, x2: 45, y2: 45 }, { x1: 55, y1: 35, x2: 65, y2: 45 } // Surcos laterales ], "CORAZON": [ // Corazón con curvas y base { 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: 40 }, { x1: 50, y1: 40, 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 } ], "MANO": [ // Mano con dedos y palma { x1: 40, y1: 70, x2: 40, y2: 40 }, { x1: 40, y1: 40, x2: 45, y2: 30 }, // Pulgar { x1: 45, y1: 30, x2: 50, y2: 40 }, { x1: 50, y1: 40, x2: 50, y2: 25 }, { x1: 50, y1: 25, x2: 55, y2: 25 }, { x1: 55, y1: 25, x2: 55, y2: 40 }, // Índice { x1: 55, y1: 40, x2: 60, y2: 25 }, { x1: 60, y1: 25, x2: 65, y2: 25 }, { x1: 65, y1: 25, x2: 65, y2: 40 }, // Medio { x1: 65, y1: 40, x2: 70, y2: 30 }, { x1: 70, y1: 30, x2: 75, y2: 35 }, { x1: 75, y1: 35, x2: 75, y2: 60 }, // Anular { x1: 40, y1: 70, x2: 75, y2: 70 } // Palma ], "OJO": [ // Ojo con iris, pupila, y pestañas { type: "circle", x1: 50, y1: 50, radius: 10 }, // Iris { type: "circle", x1: 50, y1: 50, radius: 3 }, // Pupila { x1: 30, y1: 50, x2: 70, y2: 50 }, // Línea central del ojo { x1: 35, y1: 45, x2: 65, y2: 45 }, { x1: 35, y1: 55, x2: 65, y2: 55 }, // Párpados { x1: 35, y1: 45, x2: 38, y2: 40 }, { x1: 45, y1: 45, x2: 48, y2: 40 } // Pestañas ], "BOCA": [ // Boca con labios y comisuras { x1: 35, y1: 50, x2: 65, y2: 50 }, // Línea de la boca { x1: 38, y1: 45, x2: 45, y2: 50 }, { x1: 55, y1: 50, x2: 62, y2: 45 }, // Labio superior { x1: 38, y1: 55, x2: 45, y2: 50 }, { x1: 55, y1: 50, x2: 62, y2: 55 } // Labio inferior ], "NARIZ": [ // Nariz detallada { x1: 50, y1: 40, x2: 45, y2: 55 }, { x1: 45, y1: 55, x2: 55, y2: 55 }, // Puente y base { x1: 55, y1: 55, x2: 50, y2: 40 }, { x1: 47, y1: 55, x2: 47, y2: 58 }, { x1: 53, y1: 55, x2: 53, y2: 58 } // Orificios ], "OREJA": [ // Oreja con contorno y detalles internos { x1: 50, y1: 40, x2: 40, y2: 50 }, { x1: 40, y1: 50, x2: 45, y2: 60 }, { x1: 45, y1: 60, x2: 55, y2: 60 }, { x1: 55, y1: 60, x2: 60, y2: 50 }, { x1: 60, y1: 50, x2: 50, y1: 40 }, // Contorno exterior { x1: 47, y1: 45, x2: 43, y2: 50 }, { x1: 43, y1: 50, x2: 48, y2: 55 }, // Detalles internos { x1: 52, y1: 45, x2: 57, y2: 50 }, { x1: 57, y1: 50, x2: 52, y2: 55 } ], "DIENTE": [ // Diente con raíz y encía { x1: 45, y1: 30, x2: 55, y2: 30 }, { x1: 55, y1: 30, x2: 55, y2: 60 }, { x1: 55, y1: 60, x2: 45, y2: 60 }, { x1: 45, y1: 60, x2: 45, y2: 30 }, // Cuerpo del diente { x1: 45, y1: 60, x2: 40, y2: 65 }, { x1: 55, y1: 60, x2: 60, y2: 65 }, // Raíces { x1: 40, y1: 65, x2: 60, y2: 65 } // Encía ], "PAN": [ // Barra de pan con cortes { x1: 20, y1: 60, x2: 80, y2: 60 }, { x1: 20, y1: 60, x2: 25, y2: 70 }, // Base { x1: 80, y1: 60, x2: 75, y2: 70 }, { x1: 25, y1: 70, x2: 75, y2: 70 }, { x1: 20, y1: 60, x2: 20, y2: 50 }, { x1: 80, y1: 60, x2: 80, y2: 50 }, // Lados { x1: 20, y1: 50, x2: 80, y2: 50 }, // Tapa { x1: 30, y1: 55, x2: 35, y2: 65 }, { x1: 40, y1: 55, x2: 45, y2: 65 }, // Cortes { x1: 50, y1: 55, x2: 55, y2: 65 }, { x1: 60, y1: 55, x2: 65, y2: 65 } ], "QUESO": [ // Trozo de queso triangular con agujeros { x1: 30, y1: 70, x2: 70, y2: 70 }, { x1: 70, y1: 70, x2: 50, y2: 30 }, // Triángulo { x1: 50, y1: 30, x2: 30, y2: 70 }, { type: "circle", x1: 45, y1: 55, radius: 5 }, // Agujero 1 { type: "circle", x1: 58, y1: 60, radius: 4 }, // Agujero 2 { type: "circle", x1: 40, y1: 65, radius: 3 } // Agujero 3 ], "HUEVO": [ // Huevo con yema { type: "circle", x1: 50, y1: 50, radius: 20 }, // Contorno { type: "circle", x1: 50, y1: 50, radius: 8 } // Yema ], "CARNE": [ // Trozo de carne con hueso (conceptual) { x1: 30, y1: 50, x2: 70, y2: 50 }, { x1: 70, y1: 50, x2: 70, y2: 70 }, { x1: 70, y1: 70, x2: 30, y2: 70 }, { x1: 30, y1: 70, x2: 30, y2: 50 }, // Contorno { x1: 50, y1: 50, x2: 50, y2: 40 }, { x1: 50, y1: 40, x2: 55, y2: 35 }, // Hueso (conceptual) { x1: 55, y1: 35, x2: 60, y2: 40 }, { x1: 60, y1: 40, x2: 60, y2: 50 } ], "POLLO": [ // Pollo asado simplificado { 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 { x1: 30, y1: 55, x2: 25, y2: 50 }, { x1: 70, y1: 55, x2: 75, y2: 50 } // Patas/alas ], "ARROZ": [ // Bol de arroz con palillos { type: "circle", x1: 50, y1: 65, radius: 15 }, // Bol { x1: 40, y1: 50, x2: 40, y2: 40 }, { x1: 40, y1: 40, x2: 45, y2: 40 }, // Palillo 1 { x1: 45, y1: 40, x2: 45, y2: 50 }, { x1: 55, y1: 50, x2: 55, y2: 40 }, { x1: 55, y1: 40, x2: 60, y2: 40 }, // Palillo 2 { x1: 60, y1: 40, x2: 60, y2: 50 } ], "PASTA": [ // Plato de pasta con tenedor { type: "circle", x1: 50, y1: 65, radius: 20 }, // Plato { x1: 45, y1: 55, x2: 55, y2: 55 }, { x1: 50, y1: 55, x2: 50, y2: 45 }, // Tenedor { x1: 48, y1: 45, x2: 48, y2: 40 }, { x1: 52, y1: 45, x2: 52, y2: 40 } ], "FRUTA": [ // Canasta de frutas (conceptual) { x1: 30, y1: 70, x2: 70, y2: 70 }, { x1: 30, y1: 70, x2: 35, y2: 60 }, // Canasta { x1: 70, y1: 70, x2: 65, y2: 60 }, { x1: 35, y1: 60, x2: 65, y2: 60 }, { type: "circle", x1: 40, y1: 55, radius: 5 }, // Fruta 1 { type: "circle", x1: 60, y1: 55, radius: 5 }, // Fruta 2 { type: "circle", x1: 50, y1: 45, radius: 5 } // Fruta 3 ], "UVA": [ // Racimo de uvas { type: "circle", x1: 50, y1: 40, radius: 5 }, { 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 }, { x1: 50, y1: 35, x2: 50, y2: 25 } // Tallo ], "PIÑA": [ // Piña con hojas { x1: 40, y1: 40, x2: 60, y2: 40 }, { x1: 40, y1: 40, x2: 35, y2: 60 }, // Cuerpo ovalado { x1: 60, y1: 40, x2: 65, y2: 60 }, { x1: 35, y1: 60, x2: 65, y2: 60 }, { x1: 45, y1: 35, x2: 40, y2: 25 }, { x1: 50, y1: 35, x2: 45, y2: 25 }, // Hojas { x1: 55, y1: 35, x2: 60, y2: 25 } ], "KIWI": [ // Kiwi cortado { type: "circle", x1: 50, y1: 50, radius: 20 }, // Contorno { type: "circle", x1: 50, y1: 50, radius: 5 }, // Centro { x1: 50, y1: 30, x2: 50, y2: 70 }, { x1: 30, y1: 50, x2: 70, y2: 50 }, // Líneas internas { x1: 35, y1: 35, x2: 65, y2: 65 }, { x1: 35, y1: 65, x2: 65, y2: 35 } ], "CHOCOLATE": [ // Barra de chocolate { 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 }, // Rectángulo base { x1: 35, y1: 45, x2: 35, y2: 65 }, { x1: 45, y1: 45, x2: 45, y2: 65 }, // Divisiones { x1: 55, y1: 45, x2: 55, y2: 65 }, { x1: 65, y1: 45, x2: 65, y2: 65 } ], "HELADO": [ // Cono de helado { x1: 40, y1: 70, x2: 60, y2: 70 }, { x1: 40, y1: 70, x2: 50, y2: 30 }, { x1: 60, y1: 70, x2: 50, y2: 30 }, // Cono { type: "circle", x1: 50, y1: 30, radius: 15 } // Bola de helado ], "GALLETA": [ // Galleta con chispas { type: "circle", x1: 50, y1: 50, radius: 20 }, // Contorno { type: "circle", x1: 40, y1: 45, radius: 2 }, // Chispa 1 { type: "circle", x1: 60, y1: 45, radius: 2 }, // Chispa 2 { type: "circle", x1: 50, y1: 58, radius: 2 } // Chispa 3 ], "CARAMELO": [ // Caramelo envuelto { x1: 30, y1: 50, x2: 70, y2: 50 }, // Cuerpo central { x1: 25, y1: 45, x2: 30, y2: 50 }, { x1: 30, y1: 50, x2: 25, y2: 55 }, // Extremo izquierdo { x1: 70, y1: 50, x2: 75, y2: 45 }, { x1: 75, y1: 45, x2: 70, y2: 50 }, // Extremo derecho { x1: 70, y1: 50, x2: 75, y2: 55 } ], "TARTA": [ // Rebanada de tarta { 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 { x1: 40, y1: 45, x2: 60, y2: 45 } // Detalle de capa ], "PIZZA": [ // Rebanada de pizza { 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 { type: "circle", x1: 40, y1: 50, radius: 3 }, // Pepperoni 1 { type: "circle", x1: 60, y1: 55, radius: 3 } // Pepperoni 2 ], "HAMBURGUESA": [ // Hamburguesa con pan y carne { x1: 30, y1: 40, x2: 70, y2: 40 }, { x1: 30, y1: 40, x2: 70, y2: 40 }, // Pan superior (curva conceptual) { x1: 30, y1: 50, x2: 70, y2: 50 }, // Carne { x1: 30, y1: 60, x2: 70, y2: 60 } // Pan inferior ], "PERRITO": [ // Perrito caliente en bollo { x1: 30, y1: 50, x2: 70, y2: 50 }, // Salchicha { x1: 25, y1: 55, x2: 75, y2: 55 }, { x1: 25, y1: 55, x2: 20, y2: 65 }, // Bollo { x1: 75, y1: 55, x2: 80, y2: 65 }, { x1: 20, y1: 65, x2: 80, y2: 65 } ], "MAPACHE": [ // Cara de mapache { type: "circle", x1: 50, y1: 50, radius: 20 }, // Cabeza { x1: 40, y1: 45, x2: 40, y2: 55 }, { x1: 60, y1: 45, x2: 60, y2: 55 }, // Manchas de ojos { x1: 45, y1: 40, x2: 50, y2: 35 }, { x1: 50, y1: 35, x2: 55, y2: 40 }, // Orejas { x1: 50, y1: 55, x2: 50, y2: 58 } // Nariz ], "ZORRO": [ // Cara de zorro { x1: 50, y1: 50, x2: 50, y2: 50 }, { x1: 40, y1: 55, x2: 50, y2: 40 }, // Hocico { x1: 50, y1: 40, x2: 60, y2: 55 }, { x1: 45, y1: 35, x2: 50, y2: 30 }, // Orejas { x1: 50, y1: 30, x2: 55, y2: 35 } ], "LOBO": [ // Cara de lobo aullando { x1: 50, y1: 50, x2: 50, y2: 50 }, { x1: 40, y1: 55, x2: 50, y2: 40 }, { x1: 50, y1: 40, x2: 60, y2: 55 }, // Hocico { x1: 45, y1: 35, x2: 50, y2: 30 }, { x1: 50, y1: 30, x2: 55, y2: 35 }, // Orejas { x1: 50, y1: 60, x2: 50, y2: 70 } // Boca abierta (aullido) ], "OSO": [ // Cara de oso con orejas redondas { type: "circle", x1: 50, y1: 50, radius: 20 }, // Cabeza { type: "circle", x1: 40, y1: 40, radius: 5 }, { type: "circle", x1: 60, y1: 40, radius: 5 }, // Orejas { x1: 45, y1: 55, x2: 55, y2: 55 }, { x1: 50, y1: 55, x2: 50, y2: 60 } // Hocico ], "TIGRE": [ // Cara de tigre con rayas { type: "circle", x1: 50, y1: 50, radius: 25 }, // Cabeza { x1: 40, y1: 40, x2: 45, y2: 40 }, { x1: 38, y1: 48, x2: 43, y2: 48 }, // Rayas { x1: 60, y1: 40, x2: 55, y2: 40 }, { x1: 62, y1: 48, x2: 57, y2: 48 } ], "LEON": [ // Cara de león con melena (conceptual) { type: "circle", x1: 50, y1: 50, radius: 25 }, // Cabeza { type: "circle", x1: 50, y1: 50, radius: 15 }, // Cara interna { x1: 30, y1: 40, x2: 70, y2: 40 }, { x1: 30, y1: 60, x2: 70, y2: 60 } // Melena (líneas) ], "ELEFANTE": [ // Elefante con trompa y orejas grandes { type: "circle", x1: 50, y1: 50, radius: 20 }, // Cabeza { x1: 50, y1: 50, x2: 60, y2: 65 }, { x1: 60, y1: 65, x2: 55, y2: 75 }, // Trompa { x1: 35, y1: 40, x2: 25, y2: 55 }, { x1: 25, y2: 55, x2: 35, y2: 65 }, // Oreja 1 { x1: 65, y1: 40, x2: 75, y2: 55 }, { x1: 75, y2: 55, x2: 65, y2: 65 } // Oreja 2 ], "JIRAFA": [ // Jirafa con cuello largo y manchas { x1: 50, y1: 80, x2: 50, y2: 30 }, { x1: 48, y1: 80, x2: 48, y2: 30 }, // Cuello { type: "circle", x1: 50, y1: 25, radius: 8 }, // Cabeza { x1: 40, y1: 60, x2: 45, y2: 65 }, { x1: 55, y1: 60, x2: 60, y2: 65 } // Manchas ], "MONO": [ // Mono con cola y orejas { type: "circle", x1: 50, y1: 50, radius: 20 }, // Cuerpo { type: "circle", x1: 50, y1: 30, radius: 10 }, // Cabeza { type: "circle", x1: 40, y1: 25, radius: 5 }, { type: "circle", x1: 60, y1: 25, radius: 5 }, // Orejas { x1: 60, y1: 60, x2: 70, y2: 50 }, { x1: 70, y2: 50, x2: 75, y2: 60 } // Cola ], "KOALA": [ // Koala con orejas grandes y nariz { type: "circle", x1: 50, y1: 50, radius: 25 }, // Cuerpo { type: "circle", x1: 40, y1: 40, radius: 10 }, { type: "circle", x1: 60, y1: 40, radius: 10 }, // Orejas { x1: 50, y1: 55, x2: 50, y2: 58 } // Nariz ], "PINGUINO": [ // Pingüino con cuerpo ovalado { x1: 50, y1: 80, x2: 50, y2: 30 }, { x1: 40, y1: 80, x2: 40, y2: 35 }, { x1: 60, y1: 80, x2: 60, y2: 35 }, // Cuerpo { x1: 40, y1: 35, x2: 50, y2: 30 }, { x1: 50, y1: 30, x2: 60, y2: 35 }, { x1: 45, y1: 45, x2: 55, y2: 45 }, { x1: 50, y1: 45, x2: 50, y2: 50 } // Ojos y pico ], "BUHO": [ // Búho con ojos grandes { type: "circle", x1: 50, y1: 50, radius: 25 }, // Cuerpo { type: "circle", x1: 40, y1: 40, radius: 8 }, { type: "circle", x1: 60, y1: 40, radius: 8 }, // Ojos { x1: 50, y1: 48, x2: 50, y2: 52 } // Pico ], "AGUILA": [ // Águila con alas { x1: 50, y1: 40, x2: 50, y2: 30 }, { x1: 40, y1: 50, x2: 20, y2: 45 }, { x1: 20, y1: 45, x2: 45, y2: 40 }, // Cuerpo y ala izq. { x1: 60, y1: 50, x2: 80, y2: 45 }, { x1: 80, y1: 45, x2: 55, y2: 40 } // Ala der. ], "DELFIN": [ // Delfín saltando { x1: 30, y1: 60, x2: 70, y2: 40 }, { x1: 70, y1: 40, x2: 75, y2: 50 }, { x1: 75, y2: 50, x2: 70, y2: 60 }, { x1: 70, y2: 60, x2: 30, y2: 60 }, // Cuerpo { x1: 60, y1: 40, x2: 60, y2: 30 } // Aleta ], "BALLENA": [ // Ballena con cola { x1: 20, y1: 60, x2: 80, y2: 60 }, { x1: 20, y1: 60, x2: 25, y2: 50 }, // Cuerpo { x1: 80, y1: 60, x2: 75, y2: 50 }, { x1: 25, y1: 50, x2: 75, y2: 50 }, { x1: 70, y1: 55, x2: 80, y2: 45 }, { x1: 80, y2: 45, x2: 80, y2: 65 }, { x1: 80, y2: 65, x2: 70, y2: 55 } // Cola ], "TIBURON": [ // Tiburón con aletas y dientes (conceptual) { x1: 20, y1: 60, x2: 80, y2: 55 }, { x1: 80, y1: 55, x2: 75, y2: 65 }, { x1: 75, y2: 65, x2: 20, y2: 60 }, // Cuerpo { x1: 50, y1: 50, x2: 50, y2: 40 }, { x1: 45, y1: 40, x2: 55, y2: 40 } // Aleta dorsal ], "PULPO": [ // Pulpo con tentáculos { type: "circle", x1: 50, y1: 40, radius: 15 }, // Cabeza { x1: 40, y1: 55, x2: 30, y2: 65 }, { x1: 30, y2: 65, x2: 35, y2: 70 }, // Tentáculo 1 { x1: 50, y1: 55, x2: 45, y2: 65 }, { x1: 45, y2: 65, x2: 50, y2: 70 }, // Tentáculo 2 { x1: 60, y1: 55, x2: 70, y2: 65 }, { x1: 70, y2: 65, x2: 65, y2: 70 } // Tentáculo 3 ], "CANGREJO": [ // Cangrejo con pinzas { x1: 30, y1: 60, x2: 70, y2: 60 }, { x1: 30, y1: 60, x2: 30, y2: 50 }, { x1: 70, y1: 60, x2: 70, y2: 50 }, { x1: 30, y1: 50, x2: 70, y2: 50 }, // Cuerpo { x1: 25, y1: 55, x2: 15, y2: 50 }, { x1: 15, y1: 50, x2: 20, y2: 45 }, // Pinza 1 { x1: 75, y1: 55, x2: 85, y2: 50 }, { x1: 85, y1: 50, x2: 80, y2: 45 } // Pinza 2 ], "MEDUSA": [ // Medusa con tentáculos { type: "circle", x1: 50, y1: 40, radius: 15 }, // Cuerpo superior { x1: 40, y1: 55, x2: 40, y2: 70 }, { x1: 50, y1: 55, x2: 50, y2: 70 }, // Tentáculos { x1: 60, y1: 55, x2: 60, y2: 70 } ], "DINOSAURIO": [ // Contorno de dinosaurio (cuello largo) { x1: 20, y1: 70, x2: 60, y2: 70 }, { x1: 60, y1: 70, x2: 65, y2: 60 }, { x1: 65, y1: 60, x2: 60, y2: 50 }, { x1: 60, y1: 50, x2: 50, y2: 40 }, { x1: 50, y1: 40, x2: 40, y2: 50 }, { x1: 40, y1: 50, x2: 30, y2: 60 }, { x1: 30, y1: 60, x2: 20, y2: 70 } ], "DRAGON": [ // Contorno de dragón (alas y cola) { 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 }, // Cuerpo { x1: 40, y1: 40, x2: 30, y2: 30 }, { x1: 30, y1: 30, x2: 40, y2: 25 }, // Ala izq. { x1: 60, y1: 40, x2: 70, y2: 30 }, { x1: 70, y1: 30, x2: 60, y2: 25 }, // Ala der. { x1: 70, y1: 60, x2: 80, y2: 55 }, { x1: 80, y1: 55, x2: 75, y2: 65 } // Cola ], "UNICORNIO": [ // Unicornio (cabeza y cuerno) { x1: 50, y1: 50, x2: 50, y2: 50 }, { x1: 40, y1: 55, x2: 50, y2: 40 }, { x1: 50, y1: 40, x2: 60, y2: 55 }, // Cabeza { x1: 50, y1: 40, x2: 50, y2: 25 }, { x1: 48, y1: 25, x2: 52, y2: 25 } // Cuerno ], "SIRENA": [ // Sirena (cuerpo y cola) { x1: 50, y1: 40, x2: 50, y2: 60 }, // Cuerpo { x1: 45, y1: 60, x2: 40, y2: 70 }, { x1: 40, y1: 70, x2: 60, y2: 70 }, { x1: 60, y1: 70, x2: 55, y2: 60 } // Cola ], "HADA": [ // Hada (cuerpo y alas) { x1: 50, y1: 40, x2: 50, y2: 60 }, // Cuerpo { x1: 40, y1: 50, x2: 30, y2: 45 }, { x1: 30, y1: 45, x2: 40, y2: 50 }, // Ala izq. { x1: 60, y1: 50, x2: 70, y2: 45 }, { x1: 70, y1: 45, x2: 60, y2: 50 } // Ala der. ], "MAGO": [ // Mago (sombrero y varita) { x1: 50, y1: 50, x2: 50, y2: 50 }, { x1: 40, y1: 60, x2: 60, y2: 60 }, { x1: 45, y1: 60, x2: 50, y2: 40 }, { x1: 50, y1: 40, x2: 55, y2: 60 }, // Sombrero { x1: 65, y1: 50, x2: 75, y2: 45 } // Varita ], "GUERRERO": [ // Guerrero (armadura básica) { x1: 50, y1: 50, x2: 50, y2: 50 }, { x1: 40, y1: 60, x2: 60, y2: 60 }, { x1: 40, y1: 60, x2: 40, y2: 40 }, { x1: 60, y1: 60, x2: 60, y2: 40 }, // Cuerpo { x1: 45, y1: 40, x2: 55, y2: 40 } // Yelmo (base) ], "CABALLERO": [ // Caballero (yelmo y espada) { x1: 50, y1: 50, x2: 50, y2: 50 }, { x1: 40, y1: 60, x2: 60, y2: 60 }, { x1: 40, y1: 60, x2: 40, y2: 40 }, { x1: 60, y1: 60, x2: 60, y2: 40 }, // Cuerpo { x1: 45, y1: 40, x2: 55, y2: 40 }, { x1: 50, y1: 40, x2: 50, y2: 30 }, // Yelmo { x1: 65, y1: 50, x2: 75, y2: 40 } // Espada ], "PRINCESA": [ // Princesa (corona y vestido) { x1: 50, y1: 40, x2: 50, y2: 60 }, // Cuerpo { x1: 40, y1: 60, x2: 60, y2: 60 }, { x1: 35, y1: 60, x2: 40, y2: 70 }, // Vestido { x1: 65, y1: 60, x2: 60, y2: 70 }, { x1: 35, y1: 70, x2: 65, y2: 70 }, { x1: 45, y1: 35, x2: 55, y2: 35 }, { x1: 48, y1: 35, x2: 50, y2: 30 }, // Corona { x1: 50, y1: 30, x2: 52, y2: 35 } ], "REINA": [ // Reina (corona grande) { x1: 50, y1: 40, x2: 50, y2: 60 }, // Cuerpo { 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: 35, x2: 60, y2: 35 }, { x1: 42, y1: 35, x2: 45, y2: 30 }, // Corona grande { x1: 45, y1: 30, x2: 50, y2: 32 }, { x1: 50, y1: 32, x2: 55, y2: 30 }, { x1: 55, y1: 30, x2: 58, y2: 35 }, { x1: 58, y1: 35, x2: 60, y2: 35 } ], "REY": [ // Rey (corona y cetro) { x1: 50, y1: 40, x2: 50, y2: 60 }, // Cuerpo { x1: 40, y1: 60, x2: 60, y2: 60 }, { x1: 40, y1: 60, x2: 40, y2: 70 }, { x1: 60, y1: 60, x2: 60, y2: 70 }, { x1: 40, y1: 70, x2: 60, y2: 70 }, { x1: 45, y1: 35, x2: 55, y2: 35 }, { x1: 48, y1: 35, x2: 50, y2: 30 }, // Corona { x1: 50, y1: 30, x2: 52, y2: 35 }, { x1: 65, y1: 50, x2: 75, y2: 40 }, { x1: 75, y1: 40, x2: 78, y2: 43 } // Cetro ], "CASTILLO": [ // Castillo con almenas y torre { 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 }, // Base { x1: 20, y1: 40, x2: 20, y2: 35 }, { x1: 25, y1: 35, x2: 25, y2: 40 }, // Almenas { x1: 30, y1: 40, x2: 30, y2: 35 }, { x1: 35, y1: 35, x2: 35, y2: 40 }, { x1: 40, y1: 40, x2: 40, y2: 35 }, { x1: 45, y1: 35, x2: 45, y2: 40 }, { x1: 50, y1: 40, x2: 50, y2: 30 }, { x1: 50, y1: 30, x2: 60, y2: 30 }, // Torre { x1: 60, y1: 30, x2: 60, y2: 40 }, { x1: 55, y1: 30, x2: 55, y2: 25 } // Almena de torre ], "ESPADA": [ // Espada con empuñadura { x1: 50, y1: 20, x2: 50, y2: 70 }, // Hoja { x1: 45, y1: 60, x2: 55, y2: 60 }, // Guarda { x1: 48, y1: 70, x2: 52, y2: 70 }, { x1: 48, y1: 70, x2: 48, y2: 75 }, // Empuñadura { x1: 52, y1: 70, x2: 52, y2: 75 }, { x1: 48, y1: 75, x2: 52, y2: 75 } ], "ESCUDO": [ // Escudo medieval { 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 }, // Contorno { x1: 30, y1: 60, x2: 30, y2: 40 }, { x1: 40, y1: 45, x2: 60, y2: 45 }, { x1: 50, y1: 45, x2: 50, y2: 65 } // Cruz (blasón) ], "ARCO": [ // Arco y flecha { x1: 30, y1: 60, x2: 50, y2: 30 }, { x1: 50, y1: 30, x2: 70, y2: 60 }, // Arco { x1: 30, y1: 60, x2: 70, y2: 60 }, // Cuerda { x1: 50, y1: 45, x2: 80, y2: 45 }, { x1: 80, y1: 45, x2: 75, y2: 40 }, // Flecha { x1: 80, y1: 45, x2: 75, y2: 50 } ], "FLECHA": [ // Flecha con punta y cola { x1: 20, y1: 50, x2: 80, y2: 50 }, // Cuerpo { x1: 75, y1: 45, x2: 80, y2: 50 }, { x1: 80, y1: 50, x2: 75, y2: 55 }, // Punta { x1: 25, y1: 45, x2: 20, y2: 50 }, { x1: 20, y1: 50, x2: 25, y2: 55 } // Cola ], "POCION": [ // Frasco de poción { x1: 40, y1: 70, x2: 60, y2: 70 }, { x1: 40, y1: 70, x2: 40, y2: 50 }, { x1: 60, y1: 70, x2: 60, y2: 50 }, { x1: 40, y1: 50, x2: 60, y2: 50 }, // Cuerpo { x1: 45, y1: 50, x2: 45, y2: 40 }, { x1: 55, y1: 50, x2: 55, y2: 40 }, // Cuello { x1: 48, y1: 40, x2: 52, y2: 40 } // Tapa ], "ANILLO": [ // Anillo con gema { type: "circle", x1: 50, y1: 50, radius: 20 }, // Anillo { x1: 45, y1: 35, x2: 50, y2: 30 }, { x1: 50, y1: 30, x2: 55, y2: 35 } // Gema (triángulo) ], "COLLAR": [ // Collar con pendiente { x1: 30, y1: 40, x2: 70, y2: 40 }, // Cadena { x1: 45, y1: 40, x2: 50, y2: 50 }, { x1: 50, y1: 50, x2: 55, y2: 40 } // Pendiente (triángulo) ], "CORONA": [ // Corona con puntas { x1: 30, y1: 60, x2: 70, y2: 60 }, // Base { x1: 30, y1: 60, x2: 35, y2: 45 }, { x1: 35, y1: 45, x2: 40, y2: 60 }, // Punta 1 { x1: 45, y1: 60, x2: 50, y2: 45 }, { x1: 50, y1: 45, x2: 55, y2: 60 }, // Punta 2 { x1: 60, y1: 60, x2: 65, y2: 45 }, { x1: 65, y1: 45, x2: 70, y2: 60 } // Punta 3 ], "GEMA": [ // Gema facetada { 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 { x1: 50, y1: 30, x2: 50, y2: 70 }, { x1: 30, y1: 50, x2: 70, y2: 50 } // Facetas ], "TESORO": [ // Cofre del tesoro { x1: 30, y1: 60, x2: 70, y2: 60 }, { x1: 30, y1: 60, x2: 30, y2: 40 }, // Base { x1: 70, y1: 60, x2: 70, y2: 40 }, { x1: 30, y1: 40, x2: 70, y2: 40 }, { x1: 30, y1: 40, x2: 35, y2: 35 }, { x1: 70, y1: 40, x2: 75, y2: 35 }, // Tapa { x1: 35, y1: 35, x2: 75, y2: 35 }, { x1: 50, y1: 40, x2: 50, y2: 50 } // Cerradura ], "MONEDA": [ // Moneda con un signo de dólar { type: "circle", x1: 50, y1: 50, radius: 20 }, // Contorno { x1: 48, y1: 40, x2: 48, y2: 60 }, { x1: 52, y1: 40, x2: 52, y2: 60 }, // Barra vertical { x1: 45, y1: 45, x2: 55, y2: 45 }, { x1: 45, y1: 55, x2: 55, y2: 55 } // Barras horizontales del dólar ], "MAPA": [ // Mapa enrollado { 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 { x1: 35, y1: 40, x2: 35, y2: 60 }, { x1: 65, y1: 40, x2: 65, y2: 60 } // Enrollado ], "BRUJULA": [ // Brújula con aguja { type: "circle", x1: 50, y1: 50, radius: 20 }, // Contorno { x1: 50, y1: 35, x2: 50, y2: 65 }, { x1: 35, y1: 50, x2: 65, y2: 50 }, // Cruces { x1: 50, y1: 40, x2: 45, y2: 50 }, { x1: 45, y1: 50, x2: 50, y2: 60 }, // Aguja { x1: 50, y1: 60, x2: 55, y2: 50 }, { x1: 55, y1: 50, x2: 50, y2: 40 } ], "PERGAMINO": [ // Pergamino enrollado { 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 { x1: 25, y1: 40, x2: 30, y2: 45 }, { x1: 25, y1: 55, x2: 30, y2: 60 }, // Enrollado izq. { x1: 70, y1: 45, x2: 75, y2: 40 }, { x1: 70, y1: 55, x2: 75, y2: 60 } // Enrollado der. ], "ANTORCHA": [ // Antorcha con llama { x1: 50, y1: 80, x2: 50, y2: 60 }, { x1: 48, y1: 80, x2: 52, y2: 80 }, // Palo { x1: 45, y1: 60, x2: 55, y2: 60 }, { x1: 45, y1: 60, x2: 40, y2: 55 }, // Base llama { x1: 55, y1: 60, x2: 60, y2: 55 }, { x1: 50, y1: 50, x2: 45, y2: 40 }, { x1: 45, y1: 40, x2: 50, y2: 30 }, // Llama { x1: 50, y1: 30, x2: 55, y2: 40 }, { x1: 55, y1: 40, x2: 50, y2: 50 } ] }; // --- FIN BASE DE DATOS DE BOCETOS DETALLADOS --- 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", '<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", '<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.`); } }, // Separate message handler (extracted from connect for clarity) _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(); this.customObservers.forEach((listener) => { if (listener.event === event) if (listener.callback) listener.callback(data); }); } }, _onSocketCloseHandler(event) { clearInterval(this.interval_id); this.socket = null; // Ensure socket reference is cleared this.notify("info", `Bot ${this.name} socket cerrado. Código: ${event.code}, Razón: ${event.reason}`); // The global `sockets` array is managed by WebSocket.prototype.send override, no need to manually splice here. }, _onSocketErrorHandler(event) { this.notify("error", `Error de socket para bot ${this.name}.`); console.error(`WebSocket Error for ${this.name}:`, event); clearInterval(this.interval_id); this.socket = null; }, // Overriding connect method connect(serverUrlSegment = "") { if (this.getReadyState()) { // Already connected. Disconnect first to ensure a clean new connection. this.notify("info", `Bot ${this.name} ya está conectado. Desconectando para reconectar.`); this.disconnect(); } const fullServerUrl = parseServerUrl(serverUrlSegment); this.socket = new WebSocket(fullServerUrl); // Bind handlers to 'this' context of the BotClient instance this.socket.addEventListener("open", this._onSocketOpenHandler.bind(this)); this.socket.addEventListener("message", this._onSocketMessageHandler.bind(this)); this.socket.addEventListener("close", this._onSocketCloseHandler.bind(this)); this.socket.addEventListener("error", this._onSocketErrorHandler.bind(this)); this.notify("info", `Bot ${this.name} intentando conectar a: ${fullServerUrl}`); }, // Overriding enterRoom method enterRoom(fullRoomIdOrUrl) { this.room.id = parseRoomId(fullRoomIdOrUrl); // Gets "uuid" part let serverIdSegment = ""; const parts = String(fullRoomIdOrUrl).split('.'); // Check if the last part is a number and indicates a server ID if (parts.length > 1 && !isNaN(parseInt(parts[parts.length - 1]))) { serverIdSegment = parts[parts.length - 1]; } this.connect(serverIdSegment); // Connect using only the server ID part // The `startplay` command will be sent automatically once the socket opens (in _onSocketOpenHandler) }, // Overriding disconnect method disconnect() { if (!this.getReadyState()) { this.notify("info", `Bot ${this.name} ya está desconectado.`); return; } clearInterval(this.interval_id); // Clear ping interval this.send(41); // Explicitly send disconnect message this.socket.close(); // Close the WebSocket connection this.socket = null; // Clear the socket reference this.notify("info", `Bot ${this.name} se ha desconectado de la sala.`); }, // Overriding reconnect method - now primarily re-enters the current room reconnect() { if (this.room.id) { this.notify("info", `Bot ${this.name} intentando reconectar a la sala ${this.room.id}.`); this.enterRoom(this.room.id); // Re-use enterRoom logic } else { this.notify("warning", `Bot ${this.name} no tiene una sala establecida para reconectar.`); } } }); } else { console.error("BotClient class not found for modification. Join room functionality may not work."); } })("QBit"); (function ShowRoomInfoModule() { const QBit = globalThis[arguments[0]]; // Helper to fetch JSON 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); // If the first parse results in a string, try parsing again (handles 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 ShowRoomInfo extends QBit { static dummy1 = QBit.register(this); static dummy2 = QBit.bind(this, "CubeEngine"); #roomListContainer; #refreshButton; #loadingIndicator; constructor() { super("Información de Salas", '<i class="fas fa-door-open"></i>'); this.#onStartup(); } #onStartup() { this.#loadInterface(); this.fetchAndDisplayRooms(); // Initial fetch } #loadInterface() { 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.fetchAndDisplayRooms()); headerRow.appendChild(this.#refreshButton); this.#loadingIndicator = domMake.Tree("span", { style: "margin-left: 10px; color: var(--info); display: none;" }, ['Cargando...']); headerRow.appendChild(this.#loadingIndicator); this.htmlElements.section.appendChild(headerRow); 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; ` }); this.htmlElements.section.appendChild(this.#roomListContainer); } async fetchAndDisplayRooms() { 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.notify("info", `Se encontraron ${roomData.length} salas.`); this.#displayRooms(roomData); } 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; } } #displayRooms(rooms) { if (rooms.length === 0) { this.#roomListContainer.appendChild(domMake.TextNode("No hay salas disponibles en este momento.")); 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]; const roundsPlayed = roomArray[7]; const serverId = roomArray[8]; // Determine room mode label strictly based on gameModeType let roomModeLabel = 'Desconocido'; if (gameModeType === 2) { roomModeLabel = 'Público'; } else if (gameModeType === 3) { roomModeLabel = 'Amigos/Privado'; } else { roomModeLabel = `Tipo ${gameModeType}`; // Fallback for other potential types } 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); ` }); // Prepare an array of nodes to append, ensuring only valid Nodes are included 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}`]), // Use the corrected roomModeLabel // Only create the password element if isPasswordProtected is true 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); // This is the crucial filter for node validity roomCard.appendAll(...nodesToAppend); // --- MODIFICACIÓN AQUÍ: Cambiar a Copiar Enlace --- const copyLinkButton = domMake.Button("Copiar Enlace"); copyLinkButton.classList.add("btn-primary"); copyLinkButton.style.width = "100%"; copyLinkButton.style.marginTop = "5px"; copyLinkButton.addEventListener("click", () => this.#copyRoomLink(roomId, serverId)); roomCard.appendChild(copyLinkButton); // --- FIN DE MODIFICACIÓN --- this.#roomListContainer.appendChild(roomCard); }); } // --- NUEVA FUNCIÓN PARA COPIAR EL ENLACE --- async #copyRoomLink(roomId, serverId) { let fullRoomIdentifier = roomId; if (serverId !== null && serverId !== undefined && !String(roomId).includes(`.${serverId}`)) { fullRoomIdentifier = `${roomId}.${serverId}`; } const roomUrl = `https://drawaria.online/room/${fullRoomIdentifier}`; try { await navigator.clipboard.writeText(roomUrl); this.notify("success", `Enlace de la sala copiado: ${roomUrl}`); } catch (err) { this.notify("error", `Error al copiar el enlace: ${err.message}`); console.error("Error al copiar el enlace:", err); } } // --- FIN DE LA NUEVA FUNCIÓN --- } })("QBit"); // --- START NEW MODULE: MoreColorPalettes --- (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) { const newColor = { name: `Custom-${this.#customColors.length + 1}`, hex: hexColor }; this.#customColors.push(newColor); this.#saveCustomColors(); this.#createColorButton(newColor.hex, newColor.name); // Add single button this.notify("info", `Color ${hexColor} añadido.`); } #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 NEW MODULE: MoreColorPalettes --- // --- START NEW MODULE: AutodrawV2 --- (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; #originalCanvas; #imageData; // 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; // Corrected: Moved declaration here #modeButtons = {}; // To manage active state of mode buttons // Default Settings modified to match the image #defaultSettings = { imageSize: 4, // As per your image brushSize: 32, // As per your image pixelSize: 1, // As per your image offsetX: 10, // As per your image offsetY: 0, // As per your image drawingSpeed: 10 // As per your image }; 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(); const imageFileInput = domMake.Tree("input", { type: "file", id: "autodraw-image-input", title: "Cargar Imagen (PNG/JPG)" }); imageFileInput.addEventListener("change", (e) => this.#readImage(e.target)); imageLoadRow.appendChild(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(); // Assign inputs to properties *before* using them in appendAll 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"; // Allow wrapping for modes 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"); container.appendChild(modesRow); this.htmlElements.section.appendChild(container); } #setInitialSettings() { // Apply default settings and activate rain mode visually 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; // Activate rainMode by default this.#setDrawingMode('rainMode'); } #setupCanvas() { this.#previewCanvas = document.createElement('canvas'); this.#originalCanvas = document.getElementById('canvas'); // The game's drawing canvas // Match dimensions of the game canvas for accurate pixel data capture if (this.#originalCanvas) { this.#previewCanvas.width = this.#originalCanvas.width; this.#previewCanvas.height = this.#originalCanvas.height; } else { // Fallback dimensions if game canvas not immediately available this.#previewCanvas.width = 1000; this.#previewCanvas.height = 1000; this.notify("warning", "Canvas del juego no encontrado. Usando dimensiones por defecto."); } } #readImage(fileInput) { if (!fileInput.files || !fileInput.files[0]) { this.notify("warning", "No se seleccionó ninguna imagen."); 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); // Calculate aspect ratio to fit image without distortion let scale = 1; if (img.width > this.#previewCanvas.width || img.height > this.#previewCanvas.height) { scale = Math.min(this.#previewCanvas.width / img.width, this.#previewCanvas.height / img.height); } // Draw image scaled to fit canvas ctx.drawImage(img, 0, 0, img.width * scale, img.height * scale); this.#imageData = ctx.getImageData(0, 0, this.#previewCanvas.width, this.#previewCanvas.height).data; this.notify("success", "Imagen lista para dibujar."); }); img.crossOrigin = 'anonymous'; // Important for CORS if image is from another domain img.src = evt.target.result; }); FR.readAsDataURL(fileInput.files[0]); } #prepareDrawingCommands() { if (!this.#imageData) { this.notify("warning", "Carga una imagen primero."); return false; } this.#executionLine = []; this.#rainColumns = []; this.#spiralAngle = 0; // Reset for spiral mode const { width, height } = this.#previewCanvas; 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); // Function to convert canvas pixel coords (0 to width/height) to game coords (0-100%) const recalc = (x_pixel, y_pixel) => { // x_pixel and y_pixel are 0 to previewCanvas.width/height // We need to map them to 0-100%, and apply scaling and offset const gameX = (x_pixel / width) * 100 * size + offsetX; const gameY = (y_pixel / height) * 100 * size + offsetY; return [gameX, gameY]; }; const getPixelColor = (x, y) => { if (x < 0 || x >= width || y < 0 || y >= height) return null; const index = (Math.floor(y) * width + Math.floor(x)) * 4; const a = this.#imageData[index + 3]; if (a < 20) return null; // Transparent pixel const r = this.#imageData[index + 0]; const g = this.#imageData[index + 1]; const b = this.#imageData[index + 2]; return `rgb(${r},${g},${b})`; }; // --- Drawing Modes Logic --- if (this.#currentDrawingMode === 'rainMode') { // Group pixels by columns first 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); // Randomize column order for (let col of this.#rainColumns) { let pixels = col.pixels; if (pixels.length === 0) continue; pixels.sort((a, b) => a.y - b.y); // Sort pixels within column by Y for sequential drawing let startPixel = pixels[0]; let prevPixel = pixels[0]; for (let i = 1; i < pixels.length; i++) { let currentPixel = pixels[i]; // If current pixel is not directly below previous or color changes, start new line if (currentPixel.y !== prevPixel.y + modifier || currentPixel.color !== prevPixel.color) { this.#executionLine.push({ pos1: recalc(startPixel.x, startPixel.y), pos2: recalc(prevPixel.x, prevPixel.y), color: startPixel.color, thickness }); startPixel = currentPixel; } prevPixel = currentPixel; } // Add the last line segment in the column this.#executionLine.push({ pos1: recalc(startPixel.x, startPixel.y), pos2: recalc(prevPixel.x, prevPixel.y), color: startPixel.color, thickness }); } } else if (this.#currentDrawingMode === 'waveDraw') { const waveAmplitude = 15; // Max Y deviation const waveFrequency = 0.05; // How many waves across the X axis for (let y = 0; y < height; y += modifier) { let startPixel = null; let lastColor = null; for (let x = 0; x < width; x += modifier) { const originalColor = getPixelColor(x, y); let currentX = x; // Apply wave to X coordinate, making lines appear wavy let currentY = y + waveAmplitude * Math.sin(x * waveFrequency); const actualColor = getPixelColor(currentX, currentY); // Get color from original image based on wavy path if (actualColor) { if (!startPixel) { // Start of a new segment startPixel = { x: currentX, y: currentY, color: actualColor }; lastColor = actualColor; } else if (actualColor !== lastColor) { // Color changed, end segment this.#executionLine.push({ pos1: recalc(startPixel.x, startPixel.y), pos2: recalc(currentX, currentY), // End at current point, not previous color: lastColor, thickness }); startPixel = { x: currentX, y: currentY, color: actualColor }; lastColor = actualColor; } // Continue segment } else if (startPixel) { // Transparent pixel, end segment this.#executionLine.push({ pos1: recalc(startPixel.x, startPixel.y), pos2: recalc(currentX, currentY), // End at current point color: lastColor, thickness }); startPixel = null; lastColor = null; } } // Add any pending last segment in the row if (startPixel) { this.#executionLine.push({ pos1: recalc(startPixel.x, startPixel.y), pos2: recalc(width, y + waveAmplitude * Math.sin(width * waveFrequency)), // Go to end of row with wave 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; // Controls how tightly packed the spiral is (smaller = denser) for (let r = 0; r < maxRadius; r += modifier * density) { const numPoints = Math.floor(2 * Math.PI * r / modifier); // Points per spiral turn 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) { // Start of new segment startPoint = { x: spiralX, y: spiralY, color: color }; lastColor = color; } else if (color !== lastColor) { // Color change, end current segment this.#executionLine.push({ pos1: recalc(startPoint.x, startPoint.y), pos2: recalc(prevX, prevY), color: lastColor, thickness }); startPoint = { x: spiralX, y: spiralY, color: color }; lastColor = color; } prevX = spiralX; prevY = spiralY; } else if (startPoint !== null) { // Transparent pixel, end segment this.#executionLine.push({ pos1: recalc(startPoint.x, startPoint.y), pos2: recalc(prevX, prevY), color: lastColor, thickness }); startPoint = null; } } // Add any pending last segment in the spiral arm if (startPoint) { this.#executionLine.push({ pos1: recalc(startPoint.x, startPoint.y), pos2: recalc(prevX, prevY), 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); // Randomize all pixels allPixels.forEach(p => { this.#executionLine.push({ pos1: recalc(p.x, p.y), pos2: recalc(p.x + modifier / 2, p.y + modifier / 2), // Draw as small dot/line color: p.color, thickness }); }); } 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() { if (!this.#prepareDrawingCommands()) { 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()) { // Send command using the bot's emit function botInterface.bot.emit( "line", -1, // Player ID (0 for self, -1 for system/any) line.pos1[0], line.pos1[1], line.pos2[0], line.pos2[1], true, // isActive - always true for drawing line.thickness, line.color, false // isPixel - false for lines ); currentLineIndex++; } else { this.notify("warning", `Bot ${botInterface ? botInterface.getName() : 'desconocido'} no está listo. Saltando...`); // If a bot isn't ready, we might want to wait or try another bot, // but for simplicity, we'll just skip this turn for it. // Or, for crucial operations, you might want to `break` or `await` until ready. // For now, let's just increment and let the loop continue. currentLineIndex++; // Increment to prevent infinite loop on a bad bot } 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; // Update button active states 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}'`); } } })("QBit"); // --- END NEW MODULE: AutodrawV2 --- // --- END OF NEW FUNCTIONALITIES --- })();