B7P Pix-Radar

Pixel Radar for PPF Clones.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Для установки этого скрипта вам необходимо установить расширение, такое как Tampermonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

// ==UserScript==
// @name         B7P Pix-Radar
// @namespace    https://tampermonkey.net/
// @version      1.1
// @description  Pixel Radar for PPF Clones.
// @author       B7P.eu
// @grant        none
// @icon         https://port.b7p.eu/static/pfp.png
// @connect      githubusercontent.com
// @connect      github.com
// @connect      fuckyouarkeros.fun
// @connect      pixelplanet.fun
// @connect      pixmap.fun
// @connect      chillpixel.xyz
// @connect      pixelya.fun
// @connect      pixuniverse.fun
// @connect      globepixel.fun
// @connect      pixelroyal.fun
// @connect      localhost
// @connect      pixel-bot-5lns.onrender.com
// @match        *://fuckyouarkeros.fun/*
// @match        *://pixelplanet.fun/*
// @match        *://pixmap.fun/*
// @match        *://chillpixel.xyz/*
// @match        *://pixelya.fun/*
// @match        *://pixuniverse.fun/*
// @match        *://globepixel.fun/*
// @match        *://pixelroyal.fun/*
// ==/UserScript==


let notificationRadius = 300;
const NOTIFICATION_TIME = 2000;

let pixelList = [];
let canvas;
let notifCircle;

const args = window.location.href.split(',');
let globalScale = 1;
let viewX = parseInt(args[args.length - 3]);
let viewY = parseInt(args[args.length - 2]);

const PING_OP = 0xB0;
const REG_MCHUNKS_OP = 0xA3;
const PIXEL_UPDATE_OP = 0xC1;
const REG_CANVAS_OP = 0xA0;

if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", init);
} else {
    init();
}

function init() {
    setTimeout(radarMain);
}


function showInfo(info) {
    if (info.text.length > 0) {
        closeModal();
        const wrapper = document.createElement('div');
        wrapper.innerHTML = `
        <div class="Alert show" id="my_modal">
        <h2>Останній закріп</h2>
        <p>${info.text}</p>
        <button type="button" id="my_button">OK</button>
        </div>
        `;
        document.body.appendChild(wrapper);
        const button = document.querySelector('#my_button');
        button.addEventListener('click', closeModal);
    }
}

function closeModal() {
    const modal = document.querySelector('#my_modal');
    if (modal) modal.remove();
}


async function loadFile(src) {
    const resp = await fetch(src);
    const blob = await resp.blob();
    return new File([blob], 'result.png', {
        type: 'image/png',
    });
}

async function loadInfo(src) {
    const resp = await fetch(src);
    return await resp.json();
}


function worldToScreen(x, y) {
    return [
        ((x - viewX) * globalScale) + (canvas.width / 2),
        ((y - viewY) * globalScale) + (canvas.height / 2),
    ];
}

function render() {
    try {
        const ctx = canvas.getContext('2d');
        ctx.clearRect(0, 0, canvas.width, canvas.height)
        if (globalScale < 0.8) {
            const curTime = Date.now();
            let index = pixelList.length;
            while (index > 0) {
                index--;
                let [setTime, x, y, i, j] = pixelList[index];
                const timePassed = curTime - setTime;
                if (timePassed > NOTIFICATION_TIME) {
                    pixelList.splice(index, 1);
                    continue;
                }
                const [sx, sy] = worldToScreen(x, y)
                    .map((z) => z + globalScale / 2);
                if (sx < 0 || sy < 0 || sx > canvas.width || sx > canvas.height) {
                    pixelList.splice(index, 1);
                    continue;
                }
                const notRadius = timePassed / NOTIFICATION_TIME * notificationRadius;
                const circleScale = notRadius / 100;
                ctx.save();
                ctx.scale(circleScale, circleScale);
                ctx.drawImage(
                    notifCircle,
                    Math.round(sx / circleScale - 100),
                    Math.round(sy / circleScale - 100),
                );
                ctx.restore();
            }
        }
    } catch (err) {
        console.error(`Render error`, err,);
    }
    setTimeout(render, 10);
}

function addPixel(x, y, i, j) {
    for (let k = 0; k < pixelList.length; k++) {
        if (pixelList[k][3] === i && pixelList[k][4] === j) {
            pixelList[k][1] = x;
            pixelList[k][2] = y;
            return;
        }
    }
    pixelList.unshift([Date.now(), x, y, i, j]);
}

function getPixelFromChunkOffset(i, j, offset, canvasSize) {
    const tileSize = 256;
    const x = i * tileSize - canvasSize / 2 + offset % tileSize;
    const y = j * tileSize - canvasSize / 2 + Math.trunc(offset / tileSize);
    //const x = i * tileSize - canvasSize / 2 + 128;
    //const y = j * tileSize - canvasSize / 2 + 128;
    return [x, y];
}

function renderPixel(i, j, offset) {
    const canvasSize = 65536;
    const [x, y] = getPixelFromChunkOffset(i, j, offset, canvasSize);
    addPixel(x, y, i, j);
}

function renderPixels({i, j, pixels}) {
    pixels.forEach((pxl) => {
        const [offset, color] = pxl;
        renderPixel(i, j, offset);
    });
}

function clamp(n, min, max) {
    return Math.max(min, Math.min(n, max));
}

function updateScale(viewscale) {
    globalScale = viewscale;
    notificationRadius = clamp(viewscale * 10, 20, 400);
}

function updateView(val) {
    viewX = val[0];
    viewY = val[1];
}

function onWindowResize() {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
}

function dehydratePing() {
    return new Uint8Array([PING_OP]).buffer;
}

function dehydrateRegMChunks(chunks) {
    const buffer = new ArrayBuffer(1 + 1 + chunks.length * 2);
    const view = new Uint16Array(buffer);
    // this will result into a double first byte, but still better than
    // shifting 16bit integers around later
    view[0] = REG_MCHUNKS_OP;
    for (let cnt = 0; cnt < chunks.length; cnt += 1) {
        view[cnt + 1] = chunks[cnt];
    }
    return buffer;
}

function hydratePixelUpdate(data) {
    const i = data.getUint8(1);
    const j = data.getUint8(2);
    /*
     * offset and color of every pixel
     * 3 bytes offset
     * 1 byte color
     */
    const pixels = [];
    let off = data.byteLength;
    while (off > 3) {
        const color = data.getUint8(off -= 1);
        const offsetL = data.getUint16(off -= 2);
        const offsetH = data.getUint8(off -= 1) << 16;
        pixels.push([offsetH | offsetL, color]);
    }
    return {
        i, j, pixels,
    };
}

function onBinaryMessage(buffer) {
    if (buffer.byteLength === 0) return;
    const data = new DataView(buffer);
    const opcode = data.getUint8(0);
    if (opcode === PIXEL_UPDATE_OP || opcode === 145) {
        renderPixels(hydratePixelUpdate(data));
    }
}

function dehydrateRegCanvas(canvasId) {
    const buffer = new ArrayBuffer(1 + 1);
    const view = new DataView(buffer);
    view.setInt8(0, REG_CANVAS_OP);
    view.setInt8(1, Number(canvasId));
    return buffer;
}

function onMessage({data: message}) {
    try {
        if (typeof message !== 'string') {
            onBinaryMessage(message);
        }
    } catch (err) {
        console.error(`An error occurred while parsing websocket message ${message}`, err,);
    }
}

function socketConnect(i, url, allChunks) {
    const ws = new WebSocket(url);
    ws.binaryType = 'arraybuffer';
    ws.onopen = () => {
        console.log(`Socket ${i} opened`);
        ws.send(dehydrateRegCanvas(0));
        const chunkids = [];
        for (let j = 17000 * i; j < 17000 * (i + 1) && j < allChunks.length; j++) {
            chunkids.push(allChunks[j]);
        }
        ws.send(dehydrateRegMChunks(chunkids));
    };
    ws.onmessage = onMessage;
    ws.onclose = () => {
        console.log(`Socket ${i} closed`);
        setTimeout(() => {
            socketConnect(i, url, allChunks)
        }, 1000);
    };
    ws.onerror = (err) => {
        console.error('Socket encountered error, closing socket', err);
    };
    setInterval(() => {
        if (ws.readyState !== WebSocket.CLOSED) {
            ws.send(dehydratePing());
        }
    }, 23000)
}

function radarMain() {
    canvas = document.createElement('canvas');
    canvas.style.position = 'fixed';
    canvas.style.top = '0';
    canvas.style.left = '0';
    canvas.style.zIndex = '0';
    canvas.style.pointerEvents = 'none';
    onWindowResize();
    const ctx = canvas.getContext('2d');
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    document.body.appendChild(canvas);

    window.addEventListener('resize', onWindowResize);

    notifCircle = document.createElement('canvas');
    notifCircle.width = 200;
    notifCircle.height = 200;
    const notifcontext = notifCircle.getContext('2d');
    notifcontext.fillStyle = `rgba(255, 0, 0, 0.5)`;
    notifcontext.beginPath();
    notifcontext.arc(100, 100, 100, 0, 2 * Math.PI);
    notifcontext.closePath();
    notifcontext.fill();

    pixelPlanetEvents.on('setscale', updateScale);
    pixelPlanetEvents.on('setviewcoordinates', updateView);

    setTimeout(render, 10);

    const url = `${
        window.location.protocol === 'https:' ? 'wss:' : 'ws:'
    }//${
        window.location.host
    }/ws`;
    const allChunks = []
    for (let i = 0; i <= 255; i++) {
        for (let j = 0; j <= 255; j++) {
            allChunks.push((i << 8) | j);
        }
    }
    for (let i = 0; i < 4; i++) {
        setTimeout(() => {
            socketConnect(i, url, allChunks)
        });
    }
}