Pixel Overlay

Pixel Battle Overlay

// ==UserScript==
// @name         Pixel Overlay
// @namespace    http://tampermonkey.net/
// @version      2.4
// @description  Pixel Battle Overlay
// @author       .hilkach.
// @match        https://pixelbattles.ru/*
// @match        https://api.pixelbattles.ru/*
// @license MIT
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // === Всплывающее окно "Сделано Хилкачом" ===
    const infoBox = document.createElement("div");
    infoBox.innerHTML = `
        <div id="hilkachInfo" style="
            position: fixed;
            top: 20px;
            left: 50%;
            transform: translateX(-50%);
            background: rgba(0,0,0,0.85);
            color: white;
            padding: 16px 20px;
            border-radius: 12px;
            box-shadow: 0 0 12px rgba(0,0,0,0.6);
            z-index: 30000;
            font-family: 'Segoe UI', sans-serif;
            text-align: center;
            width: 320px;
            animation: fadeIn 0.6s ease;
        ">
            <div style="font-weight: bold; font-size: 18px; margin-bottom: 6px;">Сделано Хилкачом</div>
            <div style="font-size: 14px; margin-bottom: 8px; line-height: 1.5;">
                Discord: <span style="color:#5cc87f;">.hilkach.</span><br>
                YouTube: <a href="https://www.youtube.com/@hilkashosho" target="_blank" style="color:#4d90e3; text-decoration:none;">youtube.com/@hilkashosho</a><br>
                Twitch: <a href="https://www.twitch.tv/mreklick" target="_blank" style="color:#a754ba; text-decoration:none;">twitch.tv/mreklick</a>
            </div>
            <button id="closeInfoBtn" style="
                background: #ec5427;
                color: white;
                border: none;
                padding: 6px 10px;
                border-radius: 6px;
                cursor: pointer;
                font-size: 13px;
            ">Закрыть</button>
        </div>
        <style>
            @keyframes fadeIn {
                from { opacity: 0; transform: translate(-50%, -20px); }
                to { opacity: 1; transform: translate(-50%, 0); }
            }
        </style>
    `;
    document.body.appendChild(infoBox);

    document.getElementById("closeInfoBtn").addEventListener("click", () => {
        infoBox.remove();
    });

    // === Основной скрипт Pixel Overlay ===
    const CANVAS_WIDTH = 1024;
    const CANVAS_HEIGHT = 768;

    const canvas = document.querySelector("#root > div > div.bZsByG_root > div.bZsByG_workbench > div.bZsByG_draggable > canvas");
    const container = document.querySelector("#root > div > div.bZsByG_root > div.bZsByG_workbench > div.bZsByG_draggable");
    if (!canvas || !container) return console.warn("Канвас не найден");

    const overlayCanvas = document.createElement("canvas");
    overlayCanvas.width = CANVAS_WIDTH;
    overlayCanvas.height = CANVAS_HEIGHT;
    overlayCanvas.style.position = "absolute";
    overlayCanvas.style.left = canvas.offsetLeft + "px";
    overlayCanvas.style.top = canvas.offsetTop + "px";
    overlayCanvas.style.pointerEvents = "none";
    overlayCanvas.style.zIndex = 9999;
    overlayCanvas.style.imageRendering = "pixelated";

    container.style.position = "relative";
    container.appendChild(overlayCanvas);

    const octx = overlayCanvas.getContext("2d");
    octx.imageSmoothingEnabled = false;

    let pixelSize = 1;
    let img = new Image();
    let offsetX = 0;
    let offsetY = 0;
    let opacity = 1.0;

    const PALETTE = [
        "#ae233d", "#ec5427", "#f4ab3c", "#f9d759", "#48a06d",
        "#5cc87f", "#9ae96c", "#317270", "#469ca8", "#2d519e",
        "#4d90e3", "#7ee6f2", "#4440ba", "#6662f6", "#772b99",
        "#a754ba", "#eb4e81", "#f19eab", "#684a34", "#956a34",
        "#000000", "#898d90", "#d5d7d9", "#ffffff"
    ].map(hex => {
        const bigint = parseInt(hex.slice(1), 16);
        return [ (bigint >> 16) & 255, (bigint >> 8) & 255, bigint & 255 ];
    });

    function nearestPaletteColor(r, g, b) {
        let minDist = Infinity;
        let nearest = [0,0,0];
        for (const [pr, pg, pb] of PALETTE) {
            const dist = (r - pr) ** 2 + (g - pg) ** 2 + (b - pb) ** 2;
            if (dist < minDist) {
                minDist = dist;
                nearest = [pr, pg, pb];
            }
        }
        return nearest;
    }

    const menu = document.createElement("div");
    menu.style.position = "fixed";
    menu.style.top = "10px";
    menu.style.right = "10px";
    menu.style.background = "rgba(0,0,0,0.85)";
    menu.style.color = "#fff";
    menu.style.padding = "0";
    menu.style.zIndex = 20000;
    menu.style.fontFamily = "sans-serif";
    menu.style.borderRadius = "8px";
    menu.style.width = "190px";
    menu.style.fontSize = "14px";
    menu.style.userSelect = "none";
    menu.style.boxShadow = "0 0 8px rgba(0,0,0,0.5)";

    menu.innerHTML = `
        <div id="menuHeader" style="
            background: #222;
            padding: 6px 10px;
            cursor: move;
            border-top-left-radius: 8px;
            border-top-right-radius: 8px;
            font-weight: bold;
            text-align: center;
        ">Pixel Overlay</div>
        <div style="padding: 10px;">
            <label>Пиксельный размер: <span id="pixelSizeVal">1</span></label><br>
            <input type="range" min="1" max="20" value="1" id="pixelRange" style="width:100%;"><br><br>

            <div style="
                display: flex;
                justify-content: space-between;
                align-items: center;
                gap: 6px;
                margin-top: 4px;
                margin-bottom: 10px;
            ">
                <label>X: <input type="number" id="xCoord" value="0" style="width:60px;"></label>
                <label>Y: <input type="number" id="yCoord" value="0" style="width:60px;"></label>
            </div>

            <label>Прозрачность: <span id="opacityVal">100%</span></label><br>
            <input type="range" min="0" max="100" value="100" id="opacityRange" style="width:100%;"><br><br>

            <input type="file" id="imgFile" style="width:100%;">
        </div>
    `;
    document.body.appendChild(menu);

    document.getElementById("pixelRange").addEventListener("input", (e) => {
        pixelSize = parseInt(e.target.value);
        document.getElementById("pixelSizeVal").innerText = pixelSize;
        drawPixelImage();
    });

    document.getElementById("xCoord").addEventListener("change", (e) => {
        offsetX = parseInt(e.target.value);
        drawPixelImage();
    });
    document.getElementById("yCoord").addEventListener("change", (e) => {
        offsetY = parseInt(e.target.value);
        drawPixelImage();
    });

    document.getElementById("opacityRange").addEventListener("input", (e) => {
        opacity = e.target.value / 100;
        document.getElementById("opacityVal").innerText = `${e.target.value}%`;
        drawPixelImage();
    });

    document.getElementById("imgFile").addEventListener("change", (e) => {
        const file = e.target.files[0];
        if (!file) return;
        const reader = new FileReader();
        reader.onload = function(ev) {
            img.src = ev.target.result;
        };
        reader.readAsDataURL(file);
    });

    img.onload = drawPixelImage;

    function drawPixelImage() {
        if (!img.complete) return;
        octx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);

        const tempCanvas = document.createElement("canvas");
        tempCanvas.width = img.width;
        tempCanvas.height = img.height;
        const tctx = tempCanvas.getContext("2d");
        tctx.imageSmoothingEnabled = false;
        tctx.drawImage(img, 0, 0);
        const data = tctx.getImageData(0, 0, img.width, img.height).data;

        for (let y = 0; y < img.height; y++) {
            for (let x = 0; x < img.width; x++) {
                const i = (y * img.width + x) * 4;
                const r = data[i];
                const g = data[i + 1];
                const b = data[i + 2];
                const a = data[i + 3] * opacity;
                if (a === 0) continue;

                const [nr, ng, nb] = nearestPaletteColor(r, g, b);
                octx.fillStyle = `rgba(${nr},${ng},${nb},${a / 255})`;
                octx.fillRect(offsetX + x * pixelSize, offsetY + y * pixelSize, pixelSize, pixelSize);
            }
        }

        document.getElementById("xCoord").value = Math.round(offsetX);
        document.getElementById("yCoord").value = Math.round(offsetY);
    }

    // Перемещение оверлея
    let isDraggingOverlay = false, startX, startY;
    overlayCanvas.addEventListener("mousedown", (e) => {
        isDraggingOverlay = true;
        startX = e.clientX;
        startY = e.clientY;
    });
    window.addEventListener("mousemove", (e) => {
        if (!isDraggingOverlay) return;
        const dx = e.clientX - startX;
        const dy = e.clientY - startY;
        startX = e.clientX;
        startY = e.clientY;
        offsetX += dx;
        offsetY += dy;
        drawPixelImage();
    });
    window.addEventListener("mouseup", () => isDraggingOverlay = false);

    // Перемещение меню
    const menuHeader = document.getElementById("menuHeader");
    let isDraggingMenu = false, menuStartX, menuStartY, menuOffsetX, menuOffsetY;

    menuHeader.addEventListener("mousedown", (e) => {
        isDraggingMenu = true;
        menuStartX = e.clientX;
        menuStartY = e.clientY;
        const rect = menu.getBoundingClientRect();
        menuOffsetX = menuStartX - rect.left;
        menuOffsetY = menuStartY - rect.top;
        e.preventDefault();
    });

    window.addEventListener("mousemove", (e) => {
        if (!isDraggingMenu) return;
        menu.style.left = (e.clientX - menuOffsetX) + "px";
        menu.style.top = (e.clientY - menuOffsetY) + "px";
        menu.style.right = "auto";
    });

    window.addEventListener("mouseup", () => isDraggingMenu = false);

})();