Evades Helper

Nothing interesting. Just helper for evades.io.

目前為 2023-03-18 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Evades Helper
// @namespace    http://tampermonkey.net/
// @version      0.2.2.1
// @description  Nothing interesting. Just helper for evades.io.
// @author       TimiT#3626
// @match        https://evades.io/
// @run-at       document-start
// @grant        none
// ==/UserScript==

/*



Этот код сделал на коленке за пару вечеров чисто по фану :P



*/

const DEVS = ["TimiT"]
const PREFIX = "="
const COMMANDS = [{
    name: "help",
    description: "Показывает список команд"
}, {
    name: "follow",
    description: "Включает следование за игроком. Укажите ник для включения, ничего не указывайте для отключения"
}]
const FUNCTIONS = [
    "=== помимо команд ===",
    "Включение света на картах",
    "Переключение камеры на клон Виолы (клавиша T)",
    "Спам кнопкой (нажмите обратный слэш и дальше по инструкции)",
    "Враги на миникарте. Отрисовка сетки",
    "Клавиша Del завершает игру и перезагружает вкладку",
    "Защита от автокика при АФК",
    "Автоприцел на некро (в движении может работать некорректно!!! рекомендую останавливаться, стрелять и двигаться дальше)"
]

let counter = +new Date()

const hero = {
    necro: 4
}
const KEY = {
    k: 75
}

window.storage = {
    get: (key,type="bool") => {
        let res = localStorage.getItem(key)
        if (type === "bool") return res === "true" ? true : false
        return res;
    },
    set: (key, value,type="bool") => {
        if (type === "bool" || type === "num") value = String(value)
        localStorage.setItem(key,value)
    }
}

let _obs = new MutationObserver((ev) => {
    let elem = Array.from(document.querySelectorAll('script')).filter(a=>a.type === "module" && a.src.match(/\/index\.[0-9a-f]{8}\.js/))[0];
    if (!elem) return;
    let src = elem.src

    elem.remove()

    let req = new XMLHttpRequest()
    req.open("GET", src, false)
    req.send()
    let code = req.response
    let _tmp = 0
    code = code
        //.replace("b[p].render(this.context,this.camera)}", "b[p].render(this.context,this.camera)};window.customRender(this.context,this.camera);")
        // .replace("if(t.area.lighting<1)", "if(t.area.lighting<1 && !window.ignoreLighting)")
        // .replace("t.canvasScale=1/8,", "t.canvasScale=1/8,window.setRenderOptions(t),")
        // .replace("this.setState({leaderboardProps:this.initialLeaderboardProps()})", "this.setState({leaderboardProps:this.initialLeaderboardProps()});window.updateLeaderboard()")
        // .replace('e.beginPath(),e.arc(this.x+t.x,this.y+t.y,this.radius,0,2*Math.PI,!1)','this.color.length==7&&(this.color+="BD"),e.beginPath(),e.arc(this.x+t.x,this.y+t.y,this.radius,0,2*Math.PI,!1)')
        // .replace('this.isDeparted||','this.isDeparted&&(h="#0008"),')
        .replace("case\"focus\":case\"blur\":", "case\"focus\":case\"blur\":break;")


        .replace(/([a-zA-Z0-9\$]+)\=[a-zA-Z0-9\$]+\.FramePayload.decode\([a-zA-Z0-9]+\)/, (a,b) => {
            // console.log("Replace: ", a)
            return a + ",_=window._client.onMessage("+b+")"
        })
        .replace(/(ClientPayload\.encode\()([a-zA-Z0-9$]+)/, (a,b,c) => {
            return b + "window._client.input(" + c + ")"
        })
        .replace("this.sequence=0,","this.sequence=0,window._client.user=this,")
        .replace(/this\.camera\.centerOn\(([a-z])\.self\.entity\)\,/, (a,b) => {
            return "window.setCameraObject(this.camera),this.camera.centerOn(window.setCameraPosition(" + b + ")),"
        })
        .replace(/.=(.)\.sender,.=.\.style,.=.\.text[;,][a-z\, ]+(.)=null,(.)=null,.=null;.+"private-message"\),/, (a,b,c,d) => {
            // console.log("Replace: ", a, b, c, d)
            return a + "[" + c + "," + d + "]=window._client.checkSender(" + b + ".sender);"
        })
        .replace(/window\.tsmod&&\(window\.protobuf\=([a-zA-Z0-9$]+)\)/, (a,b) => {
            return "true&&(window.protobuf=" + b + ");window._client.decode = window.protobuf.FramePayload.decode;window._client.encode = window.protobuf.ClientPayload.encode;"
        })
        .replace(/([a-zA-Z0-9$]+)=new WebSocket\([a-z]\)/, (a,b) => {
            return a + ",window._client.ws = " + b
        })
        .replace(/this\.chatMessages\.pop\(\);/, (a) => {
            // console.log("replace", a)
            return a + "window._client.follow && ( this.mouseDown = window._client.processFollow() );"
        })
        .replace(/processServerMessage\(.\)\{/, (a) => {
            return a + "window._client.chat.add = this.updateChat;"
        })

        // IS DEPARTED
        .replace(/(rgba\(\$\{.\..\}, \$\{.\..\}, \$\{.\..\}, )0(\))/, (a,b,c) => {
            // console.log("Replaced", a)
            // console.log(b + "0.5" + c)
            return b + "0.5" + c
        })
        .replace(/if(.\.isDeparted)return;/, (a,b) => "").replace("this.bodyName||this.isDeparted", "this.bodyName").replaceAll("!this.isDeparted","true").replace("this.isDeparted||","false||")

        .replaceAll(/(.)(\.showOnMap)&&/g, (a,b,c) => {
            return "(" + b + c + " || (" + b + ".entityType !== 1 && " + b + ".brightness !== 0.281 && window._client.enemiesOnMinimap))&&"
        }).replace('fillStyle="rgba(80, 80, 80, 0.6)",', 'fillStyle = "rgba(0, 0, 0, 0.6)",')

        .replace(/window\.addEventListener\("keydown"\,(.)\)/, (a,b) => {
            // console.log("Replace: ", a)
            return `window.addEventListener("keydown", (_) => {
                window._client.onKeydown(_);${b}(_)
            })`
        })

        /* .replace(/this\.titleText=new ([a-zA-Z0-9$]+)/, (a,b) => {
            console.log("Replace", a)
            return a + ",this.notifyText = new " + b + ",window._client.text.notify.element = this.notifyText"
        }) */
        .replace(/(.)\.(fillText\(.,(.),40\),)([a-zA-Z0-9$]+\.get\(\)\.displayTimer)/, (a,b,c,d,e) => {
            // console.log("Replace",a,b,c,d)
            return b + "." + c + "window._client.text.notify && (" + b + ".font = " + b + ".font.replace('35', '22')) && (" +
                b + ".strokeText(window._client.text.notify," + d + ", 120) || " + b + ".fillText(window._client.text.notify," + d + ", 120))," + e
        })

        .replaceAll(".render(this.context,this.camera)",(a) => {
            if (++_tmp === 2) return ".render(this.context, this.camera);window._client.drawInfo(this)"
            return a
        })

        .replace(/drawImage\(([a-zA-Z0-9$]+tiles\.getImage\(\))/, (a,b) => {
            // console.log("Replace: ", a)
            return "drawImage(window._client.drawGrid ? " + b + " : window._client.tilesImg"
        })

    document.body.appendChild(panel)
    document.body.appendChild(openPanel)

    /* document.addEventListener("mousemove", (ev) => {
        window.mousePosition.x = ev.pageX
        window.mousePosition.y = ev.pageY
    })

    canvas = document.getElementById("canvas")
    canvas.addEventListener("wheel", (ev) => {
        window.scaleGame(ev.deltaY < 0)
    }) */

    setInterval(() => {
        if (client.antiAFK && client.ws){
            client.ws.send(client.encode({
                sequence: ++client.user.sequence
            }).finish())
        }
    }, 60000)

    let nScr = document.createElement("script")
    nScr.setAttribute("type", "module")
    nScr.innerHTML = code
    document.body.appendChild(nScr)

    console.log("Init")
    _obs.disconnect()

})
_obs.observe(document, {childList: true, subtree: true});

const onMessage = (msg) => {
    client.logMessages && console.log(msg)

    // LIGHT
    if (msg.area && client.ignoreLighting){
        msg.area.lighting = Math.max(msg.area.lighting, 0.5)
    }

    // VIOLA CLONE
    violaClone(msg)

    if (client.user.entities){
        let _ent = []
        for (let _e of msg.entities){
            // невидимки
            if (_e.hasOwnProperty("brightness")){
                if (_e.brightness < 0.2){
                    _e.brightness = 0.2
                }
            }

            // возвратные телепортеры
            let e = client.user.entities[_e.id]
            if (e && e.entityType === 55){
                if (e.x !== _e.x || e.y !== _e.y){
                    // console.log(e)
                    // client.user.entities[-_e.id] = Object.assign({}, e)
                    _ent.push({
                        id: -_e.id,
                        x: e.x,
                        y: e.y,
                        radius: e.radius,
                        entityType: e.entityType,
                        brightness: 0.281,
                    })
                }
            }
        }
        msg.entities = msg.entities.concat(_ent)
    }

    let _seq = client.seqQueue.find(q => q[0] === msg.sequence)
    if (_seq){
        client.ping = +new Date() - _seq[1]
        client.seqQueue = client.seqQueue.filter(q => q[0] > msg.sequence)
    }

    // CHAT

    // FOLLOW
    // if (client.follow) follow(msg, client.follow)

    // console.log(client.follow)
};

const necroCheck = (msg) => {
    if (client.ignoreNecroShoot || !msg.keys.find(k => k.keyType === 11 && k.keyEvent === 1) || !client.autoNecroShoot || !client.user.self.entity || client.user.self.entity.heroType !== hero.necro) return;
    let abil = client.user.heroInfoCard.abilityTwo
    if (abil.disabled || abil.cooldown !== 0 || abil.level === 0 || client.user.energy < 30) return

    let h = Object.values(client.user.globalEntities).find(e => {
        return e.deathTimer !== -1 && e.regionName === client.user.self.entity.regionName && e.areaName === client.user.self.entity.areaName
    })

    if (!h) return

    let vector = {
        x: Math.floor(h.x - client.user.self.entity.x),
        y: Math.floor(h.y - client.user.self.entity.y),
        updated: true
    }

    if (Math.sqrt(vector.x**2 + vector.y**2) > 1000) return;

    return vector;

    // window.dispatchEvent(new KeyboardEvent('keydown', {'keyCode': KEY.k}));

    // console.log("here", +new Date())
    // console.log("here", vector)
}

const processFollow = () => {
    let followTo = client.user.globalEntities[client.follow]
    let me = client.user.self.entity
    if (!followTo){
        chatMessage({text: "Игрок не найден. Следование отключено."})
        client.follow = null
        return
    }
    let x = followTo.x - me.x
    let y = followTo.y - me.y
    let length = Math.sqrt(x**2+y**2);

    let count = (v) => {
        if (length >30){
            return v/length * 200
        }
        return v * 2
    }

    let mouseDown = {
        updated: true,
        x: Math.floor( count(x) ),
        y: Math.floor( count(y) )
    }

    // console.log(mouseDown)

    return mouseDown
    // client.user.mouseDown = mouseDown
    // console.log(client.user.mouseDown)
    // console.log("follow", client.user.sequence, msg.sequence)
}

const violaClone = (msg) => {
    let abil = client.user.heroInfoCard.abilityTwo
    /* if (abil.abilityType === 52){
        if (client.clone.id){
            if (client.clone.frame){
                client.clone.frame = false
            } else {
                if (abil.cooldown >= abil.totalCooldown - abil.totalCooldown / 14.5) client.clone.watch = !client.clone.watch
            }
        }
    } */

    let e = !client.clone.id && msg.entities && client.user && client.user.self.entity && msg.entities.find(ee => {
        return ee.name === client.user.name && ee.id !== client.user.self.id
    })
    if (e) client.clone.id = e.id

    if (msg.area){
        client.clone.id = undefined
        client.clone.watch = false;
    }
}

const input = (msg) => {
    // console.log(msg)

    // console.log("common", client.user.sequence, window.sequence)
    if (msg.message) localMessageHandler(msg);

    if (client.ignoreNecroShoot){
        client.ignoreNecroShoot = false
    } else {
        let necroShoot = necroCheck(msg)
        // console.log(necroShoot)

        if (necroShoot){
            /* msg.keys = msg.keys.filter(k => {
            return !([1,2,3,4].includes(k.keyType) && k.keyEvent === 1)
        }) */
            // msg.keys = [...msg.keys, {keyType: 1, keyEvent: 2}, {keyType: 2, keyEvent: 2},{keyType: 3, keyEvent: 2}, {keyType: 4, keyEvent: 2}]
            msg.mouseDown = necroShoot
            client._keys = msg.keys
            msg.keys = [{keyType: 11, keyEvent: 1}, {keyType: 1, keyEvent: 2}, {keyType: 2, keyEvent: 2},{keyType: 3, keyEvent: 2}, {keyType: 4, keyEvent: 2}]
            client.ignoreNecroShoot = true
            /* setTimeout(() => {
            // window.dispatchEvent(new KeyboardEvent('keydown', {'keyCode': KEY.k}));
            client.ws.send(client.encode({keys: [{keyType: 11, keyEvent: 2}], sequence: ++client.user.sequence}).finish())
        }, 60)
        setTimeout(() => {
            client.ignoreNecroShoot = false
        }, 2000)
        delete client.necroShoot */
        }
    }
    // console.log(msg)

    client.seqQueue.push([msg.sequence, +new Date()])
    return msg
}

const localMessageHandler = (msg) => {
    let text = msg.message;
    if (!text.startsWith("=")) return;

    delete msg.message;
    text = text.slice(1)

    text = text.split(/ +/g)
    let command = text[0]
    let args = text.slice(1)
    if (command === "help") {
            chatMessage({text: [
                ...(COMMANDS.map(c => PREFIX + c.name + " | " + c.description)),
                ...FUNCTIONS]})
    } else if (command === "follow"){
        let name = args.join(" ")
        if (!name){
            chatMessage({text: "Следование отключено"})
            client.follow = null
            return
        }
        let e = Object.values(client.user.globalEntities).find(ee => ee.name === name)
        client.follow = e ? e.id : null
        chatMessage({text: "Следование активировано за: " + name})
        client.user.mouseDown = processFollow()
    } else {
        chatMessage({text: "Неопознанная команда"})
    }
}

const chatMessage = ({text, from, style}) => {
    Array.isArray(text) ? client.chat.add(client.user.globalEntities, {messages: text.map(t => {return{
        id: ++counter,
        text: t,
        style: 8,
        sender: ""
    }})}) : client.chat.add(client.user.globalEntities, {messages: [{
        id: ++counter,
        text,
        style: 8,
        sender: ""
    }]})
}

window.chatMessage = (msg) => {

}


window.setRenderOptions = (o) => {
    window.renderOptions = o
}
window.setCameraObject = (cam) => {
    if (!window.camera){
        window.camera = cam
    }
}

const checkSender = (sender, isConsole=false) => {
    if (DEVS.includes(sender)) return ["[E-H Dev]", "ehdev"];
    if (sender === "") return ["[E-H CONSOLE]", "ehconsole"]
    return [null,null];
}

window.setCameraPosition = (t) => {
    // console.log(t)
    let obj
//    if (window.focusCameraOn ?? window.focusCameraOn !== t.name){
//        let ent = Object.values(t.entities).find(e => e.showOnMap && e.name === window.focusCameraOn)
//        if (ent) obj = {x: ent.x, y: ent.y}
//    }
//    if (!obj){
        obj = {x: t.self.entity.x, y: t.self.entity.y}
        window.focusCameraOn = undefined
//    }

    if (client.clone.watch){
        let clone = t.entities[client.clone.id]
        if (!clone){
            client.clone.id = undefined
            client.clone.watch = false;
        } else {
            obj = {x: clone.x, y: clone.y}
        }
    }

    if (window.freeCameraMove){
        obj.x += (window.mousePosition.x - window.screen.width/2) * (window.startScale ? window.startScale[0] : 1)
        obj.y += (window.mousePosition.y - window.screen.height/2) * (window.startScale ? window.startScale[0] : 1)
    }
    return obj
}

addEventListener("keydown", (event) => {
    if (event.code === "End"){
        client.ws.send(client.encode({
            "sequence": ++client.user.sequence,
            "message": "/ff"
        }).finish())
        location.reload()
    }
});

const onKeydown = (e) => {
    const t = document.getElementById("chat-input");
    if (document.activeElement == t) return;

    if (client.listenKeyToSpam){
        let key = e.keyCode
        if (client.spam.includes(key)) client.spam = client.spam.filter(k => k !== key)
        else if (e.code !== "Backslash") client.spam.push(key)

        client.listenKeyToSpam = false
        client.text.notify = null

        // console.log(client.spam)
    } else if (e.code === "KeyT"){
        client.clone.watch = !client.clone.watch
    } else if (e.code === "Backslash"){
        client.listenKeyToSpam = true
        client.text.notify = "Нажми клавишу для включения/отключения спама"
    } else if (e.code === "KeyV"){
        client.showInfo = !client.showInfo
    }

    // console.log(e)
}
const onKeyup = (e) => {}

const drawInfo = (r) => {
    if (!client.showInfo) return
    let x = 14, y = 230
    let text1 = "Ping: " + client.ping
    let text2 = client.ws.url.split("wss://")[1].slice(0,2).toUpperCase() + " " + (+client.ws.url.split("backend=")[1][0] + 1)
    let h = 16

    r.context.lineWidth = 3
    r.context.font = "bold 15px Tahoma, Verdana, Segoe, sans-serif"
    // r.context.globalAlpha = 0
    r.context.textAlign = "left"
    r.context.strokeStyle = "#000000"
    r.context.fillStyle = "#ffffff"
    r.context.strokeText(text1, x, y)
    r.context.strokeText(text2, x, y + h)
    r.context.fillText(text1, x, y)
    r.context.fillText(text2, x, y + h)

    r.context.lineWidth = 1
}

setInterval(() => {
    if (client.listenKeyToSpam) return

    for (let i of client.spam){
        window.dispatchEvent(new KeyboardEvent('keydown', {'keyCode': i}));
        setTimeout(() => {
            window.dispatchEvent(new KeyboardEvent('keyup', {'keyCode': i}));
        }, 30)
    }
}, 50)

const client = {
    user: null,
    ws: null,

    clone: {
        id: null,
        frame: true
    },
    chat: {
        add: null
    },
    follow: null,

    antiAFK: window.storage.get("antiAFK"),
    ignoreLighting: window.storage.get("ignoreLighting"),
    enemiesOnMinimap: window.storage.get("enemiesOnMinimap"),
    autoNecroShoot: window.storage.get("autoNecroShoot"),
    drawGrid: window.storage.get("drawGrid"),

    ignoreNecroShoot: false,

    processFollow,
    onMessage,
    checkSender,
    input,
    onKeydown, onKeyup,

    encode: null,
    decode: null,

    logMessages: 0,

    spam: [],
    listenKeyToSpam: false,

    text: {
        notify: null
    },

    drawInfo,
    showInfo: 1,
    seqQueue: [],
    ping: 0,
    fps: 0,

    tilesSrc: "https://drive.google.com/u/0/uc?id=1OBYGRSdma-KAv-Rr_FQP99Hm1StDOyTi",
    tilesImg: null
}
window._client = client

/*
window.scaleGame = (wh) => {
    if (!window.freeScale) return;
    if (!window.startScale) { window.startScale = [
        1,
        window.camera.viewportSize.width,
        window.camera.viewportSize.height,
        canvas.width,
        canvas.height
    ] }
    let change = 0.05
    wh ? window.startScale[0] -= change : window.startScale[0] += change
    let scale = window.startScale[0]

    window.camera.viewportSize.width = window.startScale[1] * scale
    window.camera.viewportSize.height = window.startScale[2] * scale

    canvas.width = window.startScale[3] * scale
    canvas.height = window.startScale[4] * scale

    window.dispatchEvent(new Event('resize'));

    document.getElementById("free_scale").innerHTML = "Free scale (x" + window.startScale[0].toFixed(2) + ")"
}*/

/*
window.updateLeaderboard = () => {
	for (let names of [...document.getElementsByClassName('leaderboard-name')]) {
		names.onclick = event => {
			// window.client.openUcard(getAttrInParents(event.target,"ariaLabel"), [20,event.y], window.client.userlog);
            window.focusCameraOn = event.target.innerHTML.split(" ")[0]
		};
		names.style.cursor = "pointer";
	}
}
*/

window.mousePosition = {x: 0, y: 0}
var canvas

window.updateLighting = (th) => {
    client.ignoreLighting = th.checked
    window.storage.set("ignoreLighting", client.ignoreLighting)
}
window.updateAntiAFK = (th) => {
    client.antiAFK = th.checked
    window.storage.set("antiAFK", client.antiAFK)
}
window.updateEnemiesOnMinimap = (th) => {
    client.enemiesOnMinimap = th.checked
    window.storage.set("enemiesOnMinimap", client.enemiesOnMinimap)
}
window.updateautoNecroShoot = (th) => {
    client.autoNecroShoot = th.checked
    window.storage.set("autoNecroShoot", client.autoNecroShoot)
}
window.updateDrawGrid = (th) => {
    client.drawGrid = th.checked
    window.storage.set("drawGrid", client.drawGrid)
}


window.updateFreeCameraMove = (th) => {
    window.freeCameraMove = th.checked
}
window.updateFreeScale = (th) => {
    window.freeScale = th.checked
}


let panel = document.createElement("div")
panel.style.background = "rgba(200, 200, 200, 0.9)"
panel.style.width = "400px"
panel.align = "center"
panel.style.position = "fixed"
panel.style.top = "50px"
panel.style.left = "calc(50% - 200px)"
panel.style.borderRadius = "10px"
panel.style.visibility = "hidden"
panel.style.padding = "20px"

panel.innerHTML = `
<h3>
  Evades helper v0.1
</h3>
    <div align="left" style="width:max-content;">
      	<input type="checkbox" id="checkbox_1" ${client.ignoreLighting ? "checked" : ""} onclick="window.updateLighting(this)"/>
        <label for="checkbox_1">Свет</label>
        <br/>

      	<input type="checkbox" id="checkbox_2" ${client.antiAFK ? "checked" : ""} onclick="window.updateAntiAFK(this)"/>
        <label for="checkbox_2">АнтиАФК</label>
        <br/>

      	<input type="checkbox" id="checkbox_3" ${client.enemiesOnMinimap ? "checked" : ""} onclick="window.updateEnemiesOnMinimap(this)"/>
        <label for="checkbox_3">Враги на миникарте</label>
        <br/>

      	<input type="checkbox" id="checkbox_5" ${client.drawGrid ? "checked" : ""} onclick="window.updateDrawGrid(this)"/>
        <label for="checkbox_5">Отрисовка сетки</label>
        <br/>

      	<input type="checkbox" id="checkbox_4" ${client.autoNecroShoot ? "checked" : ""} onclick="window.updateautoNecroShoot(this)"/>
        <label for="checkbox_4">Автоприцел на некро (UNSTABLE!!!)</label>

    </div>
<p>=help в чат для получения списка команд<br/>
Developer [DISCORD]: <a id="developer" href="https://discordapp.com/users/998856554033987604">TimiT#3626</a> (предлагайте свои идеи)</p>

<style>
  #developer:link {
    color: black;
  }

  #developer:visited {
    color: black;
  }

  #developer:hover {
    color: #333333;
  }

  #developer:active {
    color: black;
  }
  .chat-message .ehdev {
    color: #ff4f00
  }
  .chat-message .ehconsole {
    color: #ff0800
  }
</style>
`

let openPanel = document.createElement("div")
openPanel.style.background = "rgba(100, 100, 100, 0.5)"
openPanel.style.borderRadius = "10px"
openPanel.style.bottom = "60px"
openPanel.style.right = "10px"
openPanel.style.position = "fixed"
openPanel.style.width = "40px"
openPanel.style.height = "40px"
openPanel.onclick = () => {
    panel.style.visibility = panel.style.visibility === "visible" ? "hidden" : "visible"
}

const tiles = new Image()
tiles.src = client.tilesSrc
tiles.onload = () => {
    client.tilesImg = tiles
}