您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Overlay da Missão para WPlace com contadores
// ==UserScript== // @name Overlay da Missão com Contadores // @namespace http://tampermonkey.net/ // @version 2.1.21 // @description Overlay da Missão para WPlace com contadores // @author Víkish // @match https://wplace.live/* // @icon https://www.google.com/s2/favicons?sz=64&domain=partidomissao.com // @license MIT // @grant none // ==/UserScript== (async function () { 'use strict'; const CHUNK_WIDTH = 1000; const CHUNK_HEIGHT = 1000; const overlays = await fetchData(); for (const obj of overlays) { obj.chunksString = `/${obj.chunk[0]}/${obj.chunk[1]}.png`; const { img, width, height } = await loadImage(obj.url); const overlayCanvas = new OffscreenCanvas(1000, 1000); const overlayCtx = overlayCanvas.getContext("2d"); overlayCtx.drawImage(img, obj.coords[0], obj.coords[1], width, height); obj.imageData = overlayCtx.getImageData(0, 0, 1000, 1000); } const OVERLAY_MODES = ["overlay", "original", "chunks"]; let overlayMode = OVERLAY_MODES[0]; // Criação do contêiner dos contadores (mantém-se centralizado no topo) const counterContainer = document.createElement("div"); counterContainer.id = "pixel-counter"; Object.assign(counterContainer.style, { position: "fixed", top: "5px", left: "50%", transform: "translateX(-50%)", zIndex: "10000", padding: "6px 10px", fontSize: "12px", fontFamily: "Arial, sans-serif", backgroundColor: "rgba(0,0,0,0.66)", color: "white", borderRadius: "6px", pointerEvents: "none", backdropFilter: "blur(3px)", lineHeight: "1.25", textAlign: "center" }); document.body.appendChild(counterContainer); const pixelCounter = document.createElement("div"); pixelCounter.textContent = "Pixeis restantes: 0"; counterContainer.appendChild(pixelCounter); const percentageCounter = document.createElement("div"); percentageCounter.textContent = "Progresso atual: 0,00%"; counterContainer.appendChild(percentageCounter); // === TABELA DE CORES (no canto superior esquerdo, um pouco abaixo do topo) === const colorStatsContainer = document.createElement("div"); Object.assign(colorStatsContainer.style, { position: "fixed", top: "170px", // um pouco abaixo do topo para não sobrepor os botões do site left: "10px", backgroundColor: "rgba(0,0,0,0.8)", color: "white", fontSize: "11px", padding: "6px", borderRadius: "8px", zIndex: "10000", maxHeight: "320px", overflowY: "auto", minWidth: "25px", maxWidth: "150px", boxSizing: "border-box" }); // Toggle: seta para cima = ocultar, seta para baixo = mostrar const toggleButton = document.createElement("div"); toggleButton.textContent = "↑"; // inicialmente visÃvel -> seta para cima (ocultar) Object.assign(toggleButton.style, { cursor: "pointer", textAlign: "center", marginBottom: "6px", fontSize: "14px", userSelect: "none", pointerEvents: "auto" }); const colorList = document.createElement("div"); colorList.style.display = "block"; colorStatsContainer.appendChild(toggleButton); colorStatsContainer.appendChild(colorList); document.body.appendChild(colorStatsContainer); // Mantém a largura do container mesmo quando escondido let listVisible = true; toggleButton.addEventListener("click", () => { listVisible = !listVisible; colorList.style.display = listVisible ? "block" : "none"; toggleButton.textContent = listVisible ? "↑" : "↓"; // não altera a largura do colorStatsContainer para evitar "encolher" ao esconder }); fetch = new Proxy(fetch, { apply: async (target, thisArg, argList) => { const urlString = typeof argList[0] === "object" ? argList[0].url : argList[0]; let url; try { url = new URL(urlString); } catch (e) { throw new Error("Invalid URL provided to fetch"); } if (overlayMode === "overlay") { if (url.hostname === "backend.wplace.live" && url.pathname.startsWith("/files/")) { for (const obj of overlays) { if (url.pathname.endsWith(obj.chunksString)) { const originalResponse = await target.apply(thisArg, argList); const originalBlob = await originalResponse.blob(); const originalImage = await blobToImage(originalBlob); const width = originalImage.width; const height = originalImage.height; const canvas = new OffscreenCanvas(width, height); const ctx = canvas.getContext("2d", { willReadFrequently: true }); ctx.drawImage(originalImage, 0, 0, width, height); const originalData = ctx.getImageData(0, 0, width, height); const resultData = ctx.getImageData(0, 0, width, height); const d1 = originalData.data; const d2 = obj.imageData.data; const dr = resultData.data; let wrongPixels = 0; let totalTargetPixels = 0; const localColorCount = {}; for (let i = 0; i < d1.length; i += 4) { const isTransparent = d2[i + 3] === 0; if (!isTransparent) totalTargetPixels++; const samePixel = d1[i] === d2[i] && d1[i + 1] === d2[i + 1] && d1[i + 2] === d2[i + 2] && d1[i + 3] === d2[i + 3]; if (!samePixel && !isTransparent) { wrongPixels++; const key = `${d2[i]},${d2[i + 1]},${d2[i + 2]}`; localColorCount[key] = (localColorCount[key] || 0) + 1; } if (samePixel && !isTransparent) { dr[i] = 0; dr[i + 1] = 255; dr[i + 2] = 0; dr[i + 3] = 255; } else if (!isTransparent) { dr[i] = d2[i]; dr[i + 1] = d2[i + 1]; dr[i + 2] = d2[i + 2]; dr[i + 3] = d2[i + 3]; } } // Atualiza os contadores no topo pixelCounter.textContent = `Pixeis restantes: ${wrongPixels}`; const percentage = totalTargetPixels === 0 ? "100,00" : (((totalTargetPixels - wrongPixels) / totalTargetPixels) * 100).toFixed(2).replace(".", ","); percentageCounter.textContent = `Progresso atual: ${percentage}%`; // Atualiza a lista de cores (ordenada descrescente, remove zeros) colorList.innerHTML = ""; const sorted = Object.entries(localColorCount) .filter(([,cnt]) => cnt > 0) .sort((a, b) => b[1] - a[1]); for (const [key, count] of sorted) { const [r, g, b] = key.split(",").map(Number); const item = document.createElement("div"); item.style.display = "flex"; item.style.alignItems = "center"; item.style.marginBottom = "4px"; item.style.pointerEvents = "none"; const colorSquare = document.createElement("div"); colorSquare.style.width = "12px"; colorSquare.style.height = "12px"; colorSquare.style.marginRight = "6px"; colorSquare.style.backgroundColor = `rgb(${r}, ${g}, ${b})`; colorSquare.style.borderRadius = "2px"; const label = document.createElement("span"); label.textContent = `${count}`; label.style.whiteSpace = "nowrap"; item.appendChild(colorSquare); item.appendChild(label); colorList.appendChild(item); } ctx.putImageData(resultData, 0, 0); const mergedBlob = await canvas.convertToBlob(); return new Response(mergedBlob, { headers: { "Content-Type": "image/png" } }); } } } } else if (overlayMode === "chunks") { if (url.hostname === "backend.wplace.live" && url.pathname.startsWith("/files/")) { const parts = url.pathname.split("/"); const [chunk1, chunk2] = [parts.at(-2), parts.at(-1).split(".")[0]]; const canvas = new OffscreenCanvas(CHUNK_WIDTH, CHUNK_HEIGHT); const ctx = canvas.getContext("2d", { willReadFrequently: true }); ctx.strokeStyle = 'red'; ctx.lineWidth = 1; ctx.strokeRect(0, 0, CHUNK_WIDTH, CHUNK_HEIGHT); ctx.font = '30px Arial'; ctx.fillStyle = 'red'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(`${chunk1}, ${chunk2}`, CHUNK_WIDTH / 2, CHUNK_HEIGHT / 2); const mergedBlob = await canvas.convertToBlob(); return new Response(mergedBlob, { headers: { "Content-Type": "image/png" } }); } } return target.apply(thisArg, argList); } }); async function fetchData() { const response = await fetch("https://gist.githubusercontent.com/yl99a/45ec3df57cc75c4b93c45251b87eb20b/raw/overlays.json?" + Date.now()); return await response.json(); } function blobToImage(blob) { return new Promise((resolve, reject) => { const img = new Image(); img.onload = () => resolve(img); img.onerror = reject; img.src = URL.createObjectURL(blob); }); } function loadImage(src) { return new Promise((resolve, reject) => { const img = new Image(); img.crossOrigin = "anonymous"; img.onload = () => { resolve({ img, width: img.naturalWidth, height: img.naturalHeight }); }; img.onerror = reject; img.src = src; }); } function patchUI() { if (document.getElementById("overlay-blend-button")) return; let blendButton = document.createElement("button"); blendButton.id = "overlay-blend-button"; blendButton.textContent = overlayMode.charAt(0).toUpperCase() + overlayMode.slice(1); blendButton.style.backgroundColor = "#0e0e0e7f"; blendButton.style.color = "white"; blendButton.style.border = "solid"; blendButton.style.borderColor = "#1d1d1d7f"; blendButton.style.borderRadius = "4px"; blendButton.style.padding = "5px 10px"; blendButton.style.cursor = "pointer"; blendButton.style.backdropFilter = "blur(2px)"; blendButton.addEventListener("click", () => { overlayMode = OVERLAY_MODES[(OVERLAY_MODES.indexOf(overlayMode) + 1) % OVERLAY_MODES.length]; blendButton.textContent = overlayMode.charAt(0).toUpperCase() + overlayMode.slice(1); }); const buttonContainer = document.querySelector("div.gap-4:nth-child(1) > div:nth-child(2)"); const leftSidebar = document.querySelector("html body div div.disable-pinch-zoom.relative.h-full.overflow-hidden.svelte-6wmtgk div.absolute.right-2.top-2.z-30 div.flex.flex-col.gap-4.items-center"); if (buttonContainer) { buttonContainer.appendChild(blendButton); buttonContainer.classList.remove("items-center"); buttonContainer.classList.add("items-end"); } if (leftSidebar) { leftSidebar.classList.add("items-end"); leftSidebar.classList.remove("items-center"); } } const observer = new MutationObserver(() => { patchUI(); }); observer.observe(document.querySelector("div.gap-4:nth-child(1)"), { childList: true, subtree: true }); patchUI(); })();