Fixed bug where color filter was 1 tile only

This commit is contained in:
SwingTheVine 2026-02-19 01:19:28 -05:00
parent 426e6caf59
commit 1b3ad20fec
13 changed files with 187 additions and 82 deletions

View file

@ -40,7 +40,7 @@
grid-template-columns: auto 1fr auto;
align-items: center;
gap: 0.5ch;
background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="5" height="5"><circle cx="3" cy="3" r="1.5" fill="CornflowerBlue" /></svg>') repeat;
background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="5" height="5"><circle cx="3" cy="3" r="1.5" fill="CornflowerBlue"/></svg>') repeat;
cursor: grab;
width: 100%;
height: fit-content;
@ -266,6 +266,27 @@ input[type=file] {
height: fit-content;
padding: 1ch;
}
.bm-filter-color[data-id="-2"] .bm-filter-container-rgb {
background:
conic-gradient(
#aa0000 0%,
#aaaa00 16.6%,
#00aa00 33.3%,
#00aaaa 50%,
#0000aa 66.6%,
#aa00aa 83.3%,
#aa0000 100%);
}
.bm-filter-color[data-id="-1"] .bm-filter-container-rgb {
background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 8 8" width="1em" height="1em"><path d="M0,0V8H16V16H8V0" fill="rgba(0,0,0,0.5)"/></svg>') repeat;
background-color: transparent !important;
}
.bm-filter-color[data-id="-1"] .bm-filter-container-rgb svg {
fill: white !important;
}
.bm-filter-color[data-id="0"] .bm-filter-container-rgb {
background-color: transparent !important;
}
.bm-filter-container-rgb button {
padding: 0.75em 0.5ch;
}

View file

@ -2,7 +2,7 @@
// @name Blue Marble
// @name:en Blue Marble
// @namespace https://github.com/SwingTheVine/
// @version 0.88.261
// @version 0.88.293
// @description A userscript to automate and/or enhance the user experience on Wplace.live. Make sure to comply with the site's Terms of Service, and rules! This script is not affiliated with Wplace.live in any way, use at your own risk. This script is not affiliated with TamperMonkey. The author of this userscript is not responsible for any damages, issues, loss of data, or punishment that may occur as a result of using this script. This script is provided "as is" under the MPL-2.0 license. The "Blue Marble" icon is licensed under CC0 1.0 Universal (CC0 1.0) Public Domain Dedication. The image is owned by NASA.
// @description:en A userscript to automate and/or enhance the user experience on Wplace.live. Make sure to comply with the site's Terms of Service, and rules! This script is not affiliated with Wplace.live in any way, use at your own risk. This script is not affiliated with TamperMonkey. The author of this userscript is not responsible for any damages, issues, loss of data, or punishment that may occur as a result of using this script. This script is provided "as is" under the MPL-2.0 license. The "Blue Marble" icon is licensed under CC0 1.0 Universal (CC0 1.0) Public Domain Dedication. The image is owned by NASA.
// @author SwingTheVine
@ -1760,7 +1760,10 @@ Version: ${this.version}`);
}
console.log(`Finished calculating correct pixels for the tile ${tileCoords} in ${(Date.now() - timer) / 1e3} seconds!
There are ${pixelsCorrectTotal} correct pixels.`);
template.instance.pixelCount["correct"] = pixelsCorrect;
if (typeof template.instance.pixelCount["correct"] == "undefined") {
template.instance.pixelCount["correct"] = {};
}
template.instance.pixelCount["correct"][tileCoords] = pixelsCorrect;
}
return await canvas.convertToBlob({ type: "image/png" });
}
@ -2232,7 +2235,9 @@ Time Since Blink: ${String(Math.floor(elapsed / 6e4)).padStart(2, "0")}:${String
function buildWindowMain() {
overlayMain.addDiv({ "id": "bm-window-main", "class": "bm-window", "style": "top: 10px; left: unset; right: 75px;" }).addDragbar().addButton({ "class": "bm-button-circle", "textContent": "\u25BC", "aria-label": 'Minimize window "Blue Marble"', "data-button-status": "expanded" }, (instance, button) => {
button.onclick = () => instance.handleMinimization(button);
button.ontouchend = () => instance.handleMinimization(button);
button.ontouchend = () => {
button.click();
};
}).buildElement().addDiv().buildElement().buildElement().addDiv({ "class": "bm-window-content" }).addDiv({ "class": "bm-container" }).addImg({ "class": "bm-favicon", "src": "https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/assets/Favicon.png" }).buildElement().addHeader(1, { "textContent": name }).buildElement().buildElement().addHr().buildElement().addDiv({ "class": "bm-container" }).addP({ "id": "bm-user-droplets", "textContent": "Droplets:" }).buildElement().addP({ "id": "bm-user-nextlevel", "textContent": "Next level in..." }).buildElement().buildElement().addHr().buildElement().addDiv({ "class": "bm-container" }).addDiv({ "class": "bm-container" }).addButton(
{ "class": "bm-button-circle bm-button-pin", "style": "margin-top: 0;", "innerHTML": '<svg viewBox="0 0 4 6"><path d="M.5,3.4A2,2 0 1 1 3.5,3.4L2,6"/><circle cx="2" cy="2" r=".7" fill="#fff"/></svg>' },
(instance, button) => {
@ -2358,6 +2363,8 @@ Version: ${version}`, "readOnly": true }).buildElement().buildElement().addDiv({
}
const eyeOpen = '<svg viewBox="0 .5 6 3"><path d="M0,2Q3-1 6,2Q3,5 0,2H2A1,1 0 1 0 3,1Q3,2 2,2"/></svg>';
const eyeClosed = '<svg viewBox="0 1 12 6"><mask id="a"><path d="M0,0H12V8L0,2" fill="#fff"/></mask><path d="M0,4Q6-2 12,4Q6,10 0,4H4A2,2 0 1 0 6,2Q6,4 4,4ZM1,2L10,6.5L9.5,7L.5,2.5" mask="url(#a)"/></svg>';
const localizeNumber = new Intl.NumberFormat();
const localizePercent = new Intl.NumberFormat(void 0, { style: "percent", minimumFractionDigits: 2, maximumFractionDigits: 2 });
const overlayFilter = new Overlay(name, version);
overlayFilter.addDiv({ "id": "bm-window-filter", "class": "bm-window" }).addDragbar().addButton({ "class": "bm-button-circle", "textContent": "\u25BC", "aria-label": 'Minimize window "Color Filter"', "data-button-status": "expanded" }, (instance, button) => {
button.onclick = () => instance.handleMinimization(button);
@ -2372,12 +2379,10 @@ Version: ${version}`, "readOnly": true }).buildElement().buildElement().addDiv({
button.click();
};
}).buildElement().buildElement().addDiv({ "class": "bm-window-content" }).addDiv({ "class": "bm-container bm-center-vertically" }).addHeader(1, { "textContent": "Color Filter" }).buildElement().buildElement().addHr().buildElement().addDiv({ "class": "bm-container bm-flex-between", "style": "gap: 1.5ch; width: fit-content; margin-left: auto; margin-right: auto;" }).addButton({ "textContent": "Select All" }, (instance, button) => {
button.onclick = () => {
};
button.onclick = () => selectColorList(false);
}).buildElement().addButton({ "textContent": "Unselect All" }, (instance, button) => {
button.onclick = () => {
};
}).buildElement().buildElement().addDiv({ "class": "bm-container bm-scrollable" }).addDiv({ "class": "bm-container", "style": "margin-left: 2.5ch; margin-right: 2.5ch;" }).addForm({ "class": "bm-container" }).addFieldset().addLegend({ "textContent": "Sort Options:" }).buildElement().addDiv({ "class": "bm-container" }).addSelect({ "id": "bm-filter-sort-primary", "name": "sortPrimary", "textContent": "I want to view " }).addOption({ "value": "id", "textContent": "color IDs" }).buildElement().addOption({ "value": "name", "textContent": "color names" }).buildElement().addOption({ "value": "premium", "textContent": "premium colors" }).buildElement().addOption({ "value": "percent", "textContent": "percentage" }).buildElement().addOption({ "value": "correct", "textContent": "correct pixels" }).buildElement().addOption({ "value": "incorrect", "textContent": "incorrect pixels" }).buildElement().addOption({ "value": "total", "textContent": "total pixels" }).buildElement().buildElement().addSelect({ "id": "bm-filter-sort-secondary", "name": "sortSecondary", "textContent": " in " }).addOption({ "value": "ascending", "textContent": "ascending" }).buildElement().addOption({ "value": "descending", "textContent": "descending" }).buildElement().buildElement().addSpan({ "textContent": " order." }).buildElement().buildElement().addDiv({ "class": "bm-container" }).addCheckbox({ "id": "bm-filter-show-unused", "name": "showUnused", "textContent": "Show unused colors" }).buildElement().buildElement().buildElement().addDiv({ "class": "bm-container" }).addButton({ "textContent": "Refresh", "type": "submit" }, (instance, button) => {
button.onclick = () => selectColorList(true);
}).buildElement().buildElement().addDiv({ "class": "bm-container bm-scrollable" }).addDiv({ "class": "bm-container", "style": "margin-left: 2.5ch; margin-right: 2.5ch;" }).addDiv({ "class": "bm-container" }).addSpan({ "id": "bm-filter-tot-correct", "innerHTML": "<b>Correct Pixels:</b> ???" }).buildElement().addBr().buildElement().addSpan({ "id": "bm-filter-tot-total", "innerHTML": "<b>Total Pixels:</b> ???" }).buildElement().addBr().buildElement().addSpan({ "id": "bm-filter-tot-remaining", "innerHTML": "<b>Complete:</b> ??? (???)" }).buildElement().buildElement().addDiv({ "class": "bm-container" }).addP({ "innerHTML": `Colors with the icon ${eyeOpen.replace("<svg", '<svg aria-label="Eye Open"')} will be shown on the canvas. Colors with the icon ${eyeClosed.replace("<svg", '<svg aria-label="Eye Closed"')} will not be shown on the canvas. The "Select All" and "Unselect All" buttons only apply to colors that display in the list below. The amount of correct pixels is dependent on how much of the template you have loaded since you opened Wplace.live.` }).buildElement().buildElement().addHr().buildElement().addForm({ "class": "bm-container" }).addFieldset().addLegend({ "textContent": "Sort Options:", "style": "font-weight: 700;" }).buildElement().addDiv({ "class": "bm-container" }).addSelect({ "id": "bm-filter-sort-primary", "name": "sortPrimary", "textContent": "I want to view " }).addOption({ "value": "id", "textContent": "color IDs" }).buildElement().addOption({ "value": "name", "textContent": "color names" }).buildElement().addOption({ "value": "premium", "textContent": "premium colors" }).buildElement().addOption({ "value": "percent", "textContent": "percentage" }).buildElement().addOption({ "value": "correct", "textContent": "correct pixels" }).buildElement().addOption({ "value": "incorrect", "textContent": "incorrect pixels" }).buildElement().addOption({ "value": "total", "textContent": "total pixels" }).buildElement().buildElement().addSelect({ "id": "bm-filter-sort-secondary", "name": "sortSecondary", "textContent": " in " }).addOption({ "value": "ascending", "textContent": "ascending" }).buildElement().addOption({ "value": "descending", "textContent": "descending" }).buildElement().buildElement().addSpan({ "textContent": " order." }).buildElement().buildElement().addDiv({ "class": "bm-container" }).addCheckbox({ "id": "bm-filter-show-unused", "name": "showUnused", "textContent": "Show unused colors" }).buildElement().buildElement().buildElement().addDiv({ "class": "bm-container" }).addButton({ "textContent": "Sort Colors", "type": "submit" }, (instance, button) => {
button.onclick = (event) => {
event.preventDefault();
const formData = new FormData(document.querySelector("#bm-window-filter form"));
@ -2388,7 +2393,7 @@ Version: ${version}`, "readOnly": true }).buildElement().buildElement().addDiv({
console.log(`Primary: ${formValues["sortPrimary"]}; Secondary: ${formValues["sortSecondary"]}; Unused: ${formValues["showUnused"] == "on"}`);
sortColorList(formValues["sortPrimary"], formValues["sortSecondary"], formValues["showUnused"] == "on");
};
}).buildElement().buildElement().buildElement().addP({ "innerHTML": `Colors with the icon ${eyeOpen} will be shown on the canvas. Colors with the icon ${eyeClosed} will not be shown on the canvas. The "Select All" and "Unselect All" buttons only apply to colors that display in the list below.` }).buildElement().buildElement().buildElement().buildElement().buildElement().buildOverlay(document.body);
}).buildElement().buildElement().buildElement().buildElement().buildElement().buildElement().buildElement().buildOverlay(document.body);
overlayFilter.handleDrag("#bm-window-filter.bm-window", "#bm-window-filter .bm-dragbar");
const scrollableContainer = document.querySelector("#bm-window-filter .bm-container.bm-scrollable");
const { palette, LUT: _ } = templateManager.paletteBM;
@ -2398,22 +2403,28 @@ Version: ${version}`, "readOnly": true }).buildElement().buildElement().addDiv({
const allPixelsColor = /* @__PURE__ */ new Map();
for (const template of templateManager.templatesArray) {
const total = template.pixelCount?.total ?? 0;
const colors = template.pixelCount?.colors ?? /* @__PURE__ */ new Map();
const correct = template.pixelCount?.correct ?? /* @__PURE__ */ new Map();
allPixelsTotal += total ?? 0;
for (const [colorID, correctPixels] of correct) {
const _correctPixels = Number(correctPixels) || 0;
allPixelsCorrectTotal += _correctPixels;
const allPixelsCorrectSoFar = allPixelsCorrect.get(colorID) ?? 0;
allPixelsCorrect.set(colorID, allPixelsCorrectSoFar + _correctPixels);
}
const colors = template.pixelCount?.colors ?? /* @__PURE__ */ new Map();
for (const [colorID, colorPixels] of colors) {
const _colorPixels = Number(colorPixels) || 0;
const allPixelsColorSoFar = allPixelsColor.get(colorID) ?? 0;
allPixelsColor.set(colorID, allPixelsColorSoFar + _colorPixels);
}
const correctObject = template.pixelCount?.correct ?? {};
for (const map of Object.values(correctObject)) {
for (const [colorID, correctPixels] of map) {
const _correctPixels = Number(correctPixels) || 0;
allPixelsCorrectTotal += _correctPixels;
const allPixelsCorrectSoFar = allPixelsCorrect.get(colorID) ?? 0;
allPixelsCorrect.set(colorID, allPixelsCorrectSoFar + _correctPixels);
}
}
}
overlayFilter.updateInnerHTML("#bm-filter-tot-correct", `<b>Correct Pixels:</b> ${localizeNumber.format(allPixelsCorrectTotal)}`);
overlayFilter.updateInnerHTML("#bm-filter-tot-total", `<b>Total Pixels:</b> ${localizeNumber.format(allPixelsTotal)}`);
overlayFilter.updateInnerHTML("#bm-filter-tot-remaining", `<b>Remaining:</b> ${localizeNumber.format((allPixelsTotal || 0) - (allPixelsCorrectTotal || 0))} (${localizePercent.format(((allPixelsTotal || 0) - (allPixelsCorrectTotal || 0)) / (allPixelsTotal || 1))})`);
buildColorList();
sortColorList("id", "ascending", false);
function buildColorList() {
const colorList = new Overlay(name, version);
colorList.addDiv({ "class": "bm-filter-flex" });
@ -2422,14 +2433,14 @@ Version: ${version}`, "readOnly": true }).buildElement().buildElement().addDiv({
const textColorForPaletteColorBackground = 1.05 / (lumin + 0.05) > (lumin + 0.05) / 0.05 ? "white" : "black";
const bgEffectForButtons = textColorForPaletteColorBackground == "white" ? "bm-button-hover-white" : "bm-button-hover-black";
const colorTotal = allPixelsColor.get(color.id) ?? 0;
const colorTotalLocalized = new Intl.NumberFormat().format(colorTotal);
const colorTotalLocalized = localizeNumber.format(colorTotal);
let colorCorrect = 0;
let colorCorrectLocalized = "0";
let colorPercent = new Intl.NumberFormat(void 0, { style: "percent", minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(1);
let colorPercent = localizePercent.format(1);
if (colorTotal != 0) {
colorCorrect = allPixelsCorrect.get(color.id) ?? "???";
colorCorrectLocalized = typeof colorCorrect == "string" ? colorCorrect : new Intl.NumberFormat().format(colorCorrect);
colorPercent = isNaN(colorCorrect / colorTotal) ? "???" : new Intl.NumberFormat(void 0, { style: "percent", minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(colorCorrect / colorTotal);
colorCorrectLocalized = typeof colorCorrect == "string" ? colorCorrect : localizeNumber.format(colorCorrect);
colorPercent = isNaN(colorCorrect / colorTotal) ? "???" : localizePercent.format(colorCorrect / colorTotal);
}
const colorIncorrect = parseInt(colorTotal) - parseInt(colorCorrect);
colorList.addDiv({
@ -2489,5 +2500,22 @@ Version: ${version}`, "readOnly": true }).buildElement().buildElement().addDiv({
});
colors.forEach((color) => colorList.appendChild(color));
}
function selectColorList(userWantsUnselect) {
const colorList = document.querySelector(".bm-filter-flex");
const colors = Array.from(colorList.children);
for (const color of colors) {
if (color.classList?.contains("bm-color-hide")) {
continue;
}
const button = color.querySelector(".bm-filter-container-rgb button");
if (button.dataset["state"] == "hidden" && !userWantsUnselect) {
continue;
}
if (button.dataset["state"] == "shown" && userWantsUnselect) {
continue;
}
button.click();
}
}
}
})();

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
.bm-screenreader{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.bm-1j{position:fixed;background-color:#153063e6;color:#fff;padding:10px;border-radius:8px;z-index:9000;transition:all .3s ease,transform 0s;top:75px;left:60px;width:auto;max-height:calc(100vh - 150px);max-width:calc(100% - 135px);font-family:Roboto Mono,Courier New,Monaco,DejaVu Sans Mono,monospace,Arial;letter-spacing:.05em}#bm-14{max-width:300px}.bm-1h{display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:.5ch;background:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="5" height="5"><circle cx="3" cy="3" r="1.5" fill="CornflowerBlue" /></svg>') repeat;cursor:grab;width:100%;height:fit-content}.bm-1h.bm-1c{cursor:grabbing}.bm-1j:has(.bm-1h.bm-1c){pointer-events:none;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.bm-1h.bm-1c{pointer-events:auto}.bm-1i{display:inline-block;height:2.5em;margin-right:1ch;vertical-align:middle}.bm-1j h1{display:inline-block;font-size:x-large;font-weight:700;vertical-align:middle}.bm-1h h1{font-size:1.2em;user-select:none;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;text-shadow:3px 0px rgba(21,48,99,.5),-3px 0px rgba(21,48,99,.5),0px 3px rgba(21,48,99,.5),0px -3px rgba(21,48,99,.5),3px 3px rgba(21,48,99,.5),-3px 3px rgba(21,48,99,.5),3px -3px rgba(21,48,99,.5),-3px -3px rgba(21,48,99,.5)}.bm-1h div:has(h1){display:contents}.bm-1j h2{display:inline-block;font-size:larger;font-weight:700;vertical-align:middle}.bm-1b.bm-U:has(>:where(h1,h2,h3,h4,h5,h6)){width:fit-content;margin-left:auto;margin-right:auto}.bm-1b{margin:.5em 0}.bm-1j button{background-color:#144eb9;border-radius:1em;padding:0 .75ch}.bm-1j button:hover,.bm-1j button:focus-visible{background-color:#1061e5}.bm-1j button:active,.bm-1j button:disabled{background-color:#2e97ff}.bm-1j button:disabled{text-decoration:line-through}.bm--{border:white 1px solid;height:1.5em;width:1.5em;margin-top:2px;text-align:center;line-height:1em;padding:0!important}.bm-17{vertical-align:middle}.bm-17 svg{width:50%;margin:0 auto;fill:#111}.bm-1j button.bm-13{background-color:unset}.bm-13.bm-Q:hover,.bm-13.bm-Q:focus{background-color:#ffffff2b}.bm-13.bm-Q:active{background-color:#ffffff38}.bm-13.bm-R:hover,.bm-13.bm-R:focus{background-color:#0000002b}.bm-13.bm-R:active{background-color:#00000038}input[type=number].bm-10{appearance:auto;-moz-appearance:textfield;width:5.5ch;margin-left:1ch;background-color:#0003;padding:0 .5ch;font-size:small}input[type=number].bm-10::-webkit-outer-spin-button,input[type=number].bm-10::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}div:has(>.bm-18)>button{width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.bm-18,input[type=file]{display:none!important;visibility:hidden!important;position:absolute!important;left:-9999px!important;top:-9999px!important;width:0!important;height:0!important;opacity:0!important;z-index:-9999!important;pointer-events:none!important}.bm-1j select{color:#fff;background-color:#144eb9;border-radius:1em;padding:0 .5ch}.bm-1j label:has(input[type=checkbox]){display:flex;width:fit-content;gap:1ch}.bm-1j input[type=checkbox]{width:1em}.bm-V{overflow:hidden;transition:height .3s cubic-bezier(.4,0,.2,1)}.bm-1j textarea{font-size:small;background-color:#0003;padding:0 .5ch;height:5.25em;width:100%}.bm-1j small{font-size:x-small;color:#d3d3d3}.bm-11{display:flex;align-content:center;justify-content:space-between;align-items:center;gap:.5ch}.bm-flex-center{display:flex;align-content:center;justify-content:center;align-items:center;gap:.5ch}#bm-_ p svg{display:inline;height:1em;fill:#fff}#bm-_ .bm-1b.bm-19{max-height:calc(80vh - 150px);overflow:auto}.bm-15{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:center;gap:1em 3ch}.bm-12{width:fit-content;max-width:35ch;background-color:#153063e6;border-radius:1em;padding:.5em;gap:1ch;transition:background-color .3s ease}.bm-12:hover,.bm-12:focus-within{background-color:#112855e6}.bm-N{display:block;border:thick double darkslategray;width:fit-content;height:fit-content;padding:1ch}.bm-N button{padding:.75em .5ch}.bm-N svg{width:4ch}.bm-12>.bm-11{flex-direction:column;align-items:flex-start;gap:0}.bm-12 small{font-size:.75em}#bm-_ .bm-12.bm-1a{display:none}
.bm-screenreader{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.bm-1m{position:fixed;background-color:#153063e6;color:#fff;padding:10px;border-radius:8px;z-index:9000;transition:all .3s ease,transform 0s;top:75px;left:60px;width:auto;max-height:calc(100vh - 150px);max-width:calc(100% - 135px);font-family:Roboto Mono,Courier New,Monaco,DejaVu Sans Mono,monospace,Arial;letter-spacing:.05em}#bm-17{max-width:300px}.bm-1k{display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:.5ch;background:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="5" height="5"><circle cx="3" cy="3" r="1.5" fill="CornflowerBlue"/></svg>') repeat;cursor:grab;width:100%;height:fit-content}.bm-1k.bm-1f{cursor:grabbing}.bm-1m:has(.bm-1k.bm-1f){pointer-events:none;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.bm-1k.bm-1f{pointer-events:auto}.bm-1l{display:inline-block;height:2.5em;margin-right:1ch;vertical-align:middle}.bm-1m h1{display:inline-block;font-size:x-large;font-weight:700;vertical-align:middle}.bm-1k h1{font-size:1.2em;user-select:none;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;text-shadow:3px 0px rgba(21,48,99,.5),-3px 0px rgba(21,48,99,.5),0px 3px rgba(21,48,99,.5),0px -3px rgba(21,48,99,.5),3px 3px rgba(21,48,99,.5),-3px 3px rgba(21,48,99,.5),3px -3px rgba(21,48,99,.5),-3px -3px rgba(21,48,99,.5)}.bm-1k div:has(h1){display:contents}.bm-1m h2{display:inline-block;font-size:larger;font-weight:700;vertical-align:middle}.bm-1e.bm-W:has(>:where(h1,h2,h3,h4,h5,h6)){width:fit-content;margin-left:auto;margin-right:auto}.bm-1e{margin:.5em 0}.bm-1m button{background-color:#144eb9;border-radius:1em;padding:0 .75ch}.bm-1m button:hover,.bm-1m button:focus-visible{background-color:#1061e5}.bm-1m button:active,.bm-1m button:disabled{background-color:#2e97ff}.bm-1m button:disabled{text-decoration:line-through}.bm-11{border:white 1px solid;height:1.5em;width:1.5em;margin-top:2px;text-align:center;line-height:1em;padding:0!important}.bm-1a{vertical-align:middle}.bm-1a svg{width:50%;margin:0 auto;fill:#111}.bm-1m button.bm-16{background-color:unset}.bm-16.bm-S:hover,.bm-16.bm-S:focus{background-color:#ffffff2b}.bm-16.bm-S:active{background-color:#ffffff38}.bm-16.bm-T:hover,.bm-16.bm-T:focus{background-color:#0000002b}.bm-16.bm-T:active{background-color:#00000038}input[type=number].bm-13{appearance:auto;-moz-appearance:textfield;width:5.5ch;margin-left:1ch;background-color:#0003;padding:0 .5ch;font-size:small}input[type=number].bm-13::-webkit-outer-spin-button,input[type=number].bm-13::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}div:has(>.bm-1b)>button{width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.bm-1b,input[type=file]{display:none!important;visibility:hidden!important;position:absolute!important;left:-9999px!important;top:-9999px!important;width:0!important;height:0!important;opacity:0!important;z-index:-9999!important;pointer-events:none!important}.bm-1m select{color:#fff;background-color:#144eb9;border-radius:1em;padding:0 .5ch}.bm-1m label:has(input[type=checkbox]){display:flex;width:fit-content;gap:1ch}.bm-1m input[type=checkbox]{width:1em}.bm-Y{overflow:hidden;transition:height .3s cubic-bezier(.4,0,.2,1)}.bm-1m textarea{font-size:small;background-color:#0003;padding:0 .5ch;height:5.25em;width:100%}.bm-1m small{font-size:x-small;color:#d3d3d3}.bm-14{display:flex;align-content:center;justify-content:space-between;align-items:center;gap:.5ch}.bm-flex-center{display:flex;align-content:center;justify-content:center;align-items:center;gap:.5ch}#bm-12 p svg{display:inline;height:1em;fill:#fff}#bm-12 .bm-1e.bm-1c{max-height:calc(80vh - 150px);overflow:auto}.bm-18{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:center;gap:1em 3ch}.bm-15{width:fit-content;max-width:35ch;background-color:#153063e6;border-radius:1em;padding:.5em;gap:1ch;transition:background-color .3s ease}.bm-15:hover,.bm-15:focus-within{background-color:#112855e6}.bm-O{display:block;border:thick double darkslategray;width:fit-content;height:fit-content;padding:1ch}.bm-15[data-id="-2"] .bm-O{background:conic-gradient(#a00,#aa0 16.6%,#0a0,#0aa 50%,#00a 66.6%,#a0a,#a00)}.bm-15[data-id="-1"] .bm-O{background:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 8 8" width="1em" height="1em"><path d="M0,0V8H16V16H8V0" fill="rgba(0,0,0,0.5)"/></svg>') repeat;background-color:transparent!important}.bm-15[data-id="-1"] .bm-O svg{fill:#fff!important}.bm-15[data-id="0"] .bm-O{background-color:transparent!important}.bm-O button{padding:.75em .5ch}.bm-O svg{width:4ch}.bm-15>.bm-14{flex-direction:column;align-items:flex-start;gap:0}.bm-15 small{font-size:.75em}#bm-12 .bm-15.bm-1d{display:none}

File diff suppressed because one or more lines are too long

View file

@ -51,7 +51,7 @@
<a href="https://discord.gg/tpeBPy46hf" target="_blank" rel="noopener noreferrer"><img alt="Contact Me" src="https://img.shields.io/badge/Contact_Me-gray?style=flat&logo=Discord&logoColor=white&logoSize=auto&labelColor=cornflowerblue"></a>
<a href="https://bluemarble.lol/" target="_blank" rel="noopener noreferrer"><img alt="Blue Marble Website" src="https://img.shields.io/badge/Blue_Marble_Website-crqch-blue?style=flat&logo=globe&logoColor=white"></a>
<a href="" target="_blank" rel="noopener noreferrer"><img alt="WakaTime" src="https://img.shields.io/badge/Coding_Time-169hrs_20mins-blue?style=flat&logo=wakatime&logoColor=black&logoSize=auto&labelColor=white"></a>
<a href="" target="_blank" rel="noopener noreferrer"><img alt="Total Patches" src="https://img.shields.io/badge/Total_Patches-759-black?style=flat"></a>
<a href="" target="_blank" rel="noopener noreferrer"><img alt="Total Patches" src="https://img.shields.io/badge/Total_Patches-791-black?style=flat"></a>
<a href="" target="_blank" rel="noopener noreferrer"><img alt="Total Lines of Code" src="https://img.shields.io/badge/Lines_Of_Code-498-blue?style=flat"></a>
<a href="" target="_blank" rel="noopener noreferrer"><img alt="Total Comments" src="https://img.shields.io/badge/Lines_Of_Comments-498-blue?style=flat"></a>
<a href="" target="_blank" rel="noopener noreferrer"><img alt="Compression" src="https://img.shields.io/badge/Compression-70.19%25-blue"></a>

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "wplace-bluemarble",
"version": "0.88.261",
"version": "0.88.293",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "wplace-bluemarble",
"version": "0.88.261",
"version": "0.88.293",
"devDependencies": {
"esbuild": "^0.25.0",
"jsdoc": "^4.0.5",

View file

@ -1,6 +1,6 @@
{
"name": "wplace-bluemarble",
"version": "0.88.261",
"version": "0.88.293",
"type": "module",
"homepage": "https://bluemarble.lol/",
"repository": {

View file

@ -2,7 +2,7 @@
// @name Blue Marble
// @name:en Blue Marble
// @namespace https://github.com/SwingTheVine/
// @version 0.88.261
// @version 0.88.293
// @description A userscript to automate and/or enhance the user experience on Wplace.live. Make sure to comply with the site's Terms of Service, and rules! This script is not affiliated with Wplace.live in any way, use at your own risk. This script is not affiliated with TamperMonkey. The author of this userscript is not responsible for any damages, issues, loss of data, or punishment that may occur as a result of using this script. This script is provided "as is" under the MPL-2.0 license. The "Blue Marble" icon is licensed under CC0 1.0 Universal (CC0 1.0) Public Domain Dedication. The image is owned by NASA.
// @description:en A userscript to automate and/or enhance the user experience on Wplace.live. Make sure to comply with the site's Terms of Service, and rules! This script is not affiliated with Wplace.live in any way, use at your own risk. This script is not affiliated with TamperMonkey. The author of this userscript is not responsible for any damages, issues, loss of data, or punishment that may occur as a result of using this script. This script is provided "as is" under the MPL-2.0 license. The "Blue Marble" icon is licensed under CC0 1.0 Universal (CC0 1.0) Public Domain Dedication. The image is owned by NASA.
// @author SwingTheVine

View file

@ -41,7 +41,7 @@ export default class Template {
this.chunked = chunked;
this.chunked32 = chunked32;
this.tileSize = tileSize;
/** Total pixel count in template @type {{total: number, colors: Map<number, number>, correct?: Map<number, number>}} */
/** Total pixel count in template @type {{total: number, colors: Map<number, number>, correct?: { [key: string]: Map<number, number> }}} */
this.pixelCount = { total: 0, colors: new Map() };
}

View file

@ -465,6 +465,9 @@ function buildWindowFilter() {
const eyeOpen = '<svg viewBox="0 .5 6 3"><path d="M0,2Q3-1 6,2Q3,5 0,2H2A1,1 0 1 0 3,1Q3,2 2,2"/></svg>';
const eyeClosed = '<svg viewBox="0 1 12 6"><mask id="a"><path d="M0,0H12V8L0,2" fill="#fff"/></mask><path d="M0,4Q6-2 12,4Q6,10 0,4H4A2,2 0 1 0 6,2Q6,4 4,4ZM1,2L10,6.5L9.5,7L.5,2.5" mask="url(#a)"/></svg>';
const localizeNumber = new Intl.NumberFormat();
const localizePercent = new Intl.NumberFormat(undefined, { style: 'percent', minimumFractionDigits: 2, maximumFractionDigits: 2 });
// Creates a new color filter window
const overlayFilter = new Overlay(name, version);
overlayFilter.addDiv({'id': 'bm-window-filter', 'class': 'bm-window'})
@ -486,21 +489,28 @@ function buildWindowFilter() {
.addHr().buildElement()
.addDiv({'class': 'bm-container bm-flex-between', 'style': 'gap: 1.5ch; width: fit-content; margin-left: auto; margin-right: auto;'})
.addButton({'textContent': 'Select All'}, (instance, button) => {
button.onclick = () => {
}
button.onclick = () => selectColorList(false);
}).buildElement()
.addButton({'textContent': 'Unselect All'}, (instance, button) => {
button.onclick = () => {
}
button.onclick = () => selectColorList(true);
}).buildElement()
.buildElement()
.addDiv({'class': 'bm-container bm-scrollable'})
.addDiv({'class': 'bm-container', 'style': 'margin-left: 2.5ch; margin-right: 2.5ch;'})
.addDiv({'class': 'bm-container'})
.addSpan({'id': 'bm-filter-tot-correct', 'innerHTML': '<b>Correct Pixels:</b> ???'}).buildElement()
.addBr().buildElement()
.addSpan({'id': 'bm-filter-tot-total', 'innerHTML': '<b>Total Pixels:</b> ???'}).buildElement()
.addBr().buildElement()
.addSpan({'id': 'bm-filter-tot-remaining', 'innerHTML': '<b>Complete:</b> ??? (???)'}).buildElement()
.buildElement()
.addDiv({'class': 'bm-container'})
.addP({'innerHTML': `Colors with the icon ${eyeOpen.replace('<svg', '<svg aria-label="Eye Open"')} will be shown on the canvas. Colors with the icon ${eyeClosed.replace('<svg', '<svg aria-label="Eye Closed"')} will not be shown on the canvas. The "Select All" and "Unselect All" buttons only apply to colors that display in the list below. The amount of correct pixels is dependent on how much of the template you have loaded since you opened Wplace.live.`}).buildElement()
.buildElement()
.addHr().buildElement()
.addForm({'class': 'bm-container'})
.addFieldset()
.addLegend({'textContent': 'Sort Options:'}).buildElement()
.addLegend({'textContent': 'Sort Options:', 'style': 'font-weight: 700;'}).buildElement()
.addDiv({'class': 'bm-container'})
.addSelect({'id': 'bm-filter-sort-primary', 'name': 'sortPrimary', 'textContent': 'I want to view '})
.addOption({'value': 'id', 'textContent': 'color IDs'}).buildElement()
@ -522,7 +532,7 @@ function buildWindowFilter() {
.buildElement()
.buildElement()
.addDiv({'class': 'bm-container'})
.addButton({'textContent': 'Refresh', 'type': 'submit'}, (instance, button) => {
.addButton({'textContent': 'Sort Colors', 'type': 'submit'}, (instance, button) => {
button.onclick = (event) => {
event.preventDefault(); // Stop default form submission
@ -540,7 +550,6 @@ function buildWindowFilter() {
}).buildElement()
.buildElement()
.buildElement()
.addP({'innerHTML': `Colors with the icon ${eyeOpen} will be shown on the canvas. Colors with the icon ${eyeClosed} will not be shown on the canvas. The "Select All" and "Unselect All" buttons only apply to colors that display in the list below.`}).buildElement()
.buildElement()
// Color list will appear here in the DOM tree
.buildElement()
@ -567,18 +576,9 @@ function buildWindowFilter() {
for (const template of templateManager.templatesArray) {
const total = template.pixelCount?.total ?? 0;
const colors = template.pixelCount?.colors ?? new Map();
const correct = template.pixelCount?.correct ?? new Map();
allPixelsTotal += total ?? 0; // Sums the pixels placed as "total" per everything
// Sums the pixels placed as "correct" per color ID
for (const [colorID, correctPixels] of correct) {
const _correctPixels = Number(correctPixels) || 0; // Boilerplate
allPixelsCorrectTotal += _correctPixels; // Sums the pixels placed as "correct" per everything
const allPixelsCorrectSoFar = allPixelsCorrect.get(colorID) ?? 0; // The total correct pixels for this color ID so far, or zero if none counted so far
allPixelsCorrect.set(colorID, allPixelsCorrectSoFar + _correctPixels);
}
const colors = template.pixelCount?.colors ?? new Map();
// Sums the color pixels placed as "total" per color ID
for (const [colorID, colorPixels] of colors) {
@ -586,9 +586,29 @@ function buildWindowFilter() {
const allPixelsColorSoFar = allPixelsColor.get(colorID) ?? 0; // The total color pixels for this color ID so far, or zero if none counted so far
allPixelsColor.set(colorID, allPixelsColorSoFar + _colorPixels);
}
// Object that contains the tiles which contain Maps as correct pixels per tile as the value in the key-value pair
const correctObject = template.pixelCount?.correct ?? {};
// Sums the pixels placed as "correct" per color ID
for (const map of Object.values(correctObject)) { // Per tile per template
for (const [colorID, correctPixels] of map) { // Per color per tile per template
const _correctPixels = Number(correctPixels) || 0; // Boilerplate
allPixelsCorrectTotal += _correctPixels; // Sums the pixels placed as "correct" per everything
const allPixelsCorrectSoFar = allPixelsCorrect.get(colorID) ?? 0; // The total correct pixels for this color ID so far, or zero if none counted so far
allPixelsCorrect.set(colorID, allPixelsCorrectSoFar + _correctPixels);
}
}
}
// Displays the total amounts across all colors to the user
overlayFilter.updateInnerHTML('#bm-filter-tot-correct', `<b>Correct Pixels:</b> ${localizeNumber.format(allPixelsCorrectTotal)}`);
overlayFilter.updateInnerHTML('#bm-filter-tot-total', `<b>Total Pixels:</b> ${localizeNumber.format(allPixelsTotal)}`);
overlayFilter.updateInnerHTML('#bm-filter-tot-remaining', `<b>Remaining:</b> ${localizeNumber.format((allPixelsTotal || 0) - (allPixelsCorrectTotal || 0))} (${localizePercent.format(((allPixelsTotal || 0) - (allPixelsCorrectTotal || 0)) / (allPixelsTotal || 1))})`);
// These run when the user opens the Color Filter window
buildColorList();
sortColorList('id', 'ascending', false);
// Creates the color list container
function buildColorList() {
@ -612,18 +632,18 @@ function buildWindowFilter() {
// Turns "total" color into a string of a number; "0" if unknown
const colorTotal = allPixelsColor.get(color.id) ?? 0
const colorTotalLocalized = new Intl.NumberFormat().format(colorTotal);
const colorTotalLocalized = localizeNumber.format(colorTotal);
// This will be displayed if the total pixels for this color is zero
let colorCorrect = 0;
let colorCorrectLocalized = '0';
let colorPercent = new Intl.NumberFormat(undefined, { style: 'percent', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(1);
let colorPercent = localizePercent.format(1);
// This will be displayed if the total pixels for this color is non-zero
if (colorTotal != 0) {
colorCorrect = allPixelsCorrect.get(color.id) ?? '???';
colorCorrectLocalized = (typeof colorCorrect == 'string') ? colorCorrect : new Intl.NumberFormat().format(colorCorrect);
colorPercent = isNaN(colorCorrect / colorTotal) ? '???' : new Intl.NumberFormat(undefined, { style: 'percent', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(colorCorrect / colorTotal);
colorCorrectLocalized = (typeof colorCorrect == 'string') ? colorCorrect : localizeNumber.format(colorCorrect);
colorPercent = isNaN(colorCorrect / colorTotal) ? '???' : localizePercent.format(colorCorrect / colorTotal);
}
// Incorrect pixels for this color
@ -672,6 +692,7 @@ function buildWindowFilter() {
colorList.buildOverlay(scrollableContainer);
}
// Sorts the color list & hides unused colors
function sortColorList(sortPrimary, sortSecondary, showUnused) {
// "sortSecondary" can be either 'ascending' or 'descending'
@ -714,28 +735,28 @@ function buildWindowFilter() {
colors.forEach(color => colorList.appendChild(color));
}
}
function buildOverlayTabTemplate() {
overlayTabTemplate.addDiv({'id': 'bm-tab-template', 'style': 'top: 20%; left: 10%;'})
.addDiv()
.addDiv({'className': 'bm-dragbar'}).buildElement()
.addButton({'className': 'bm-button-minimize', 'textContent': '↑'},
(instance, button) => {
button.onclick = () => {
let isMinimized = false;
if (button.textContent == '↑') {
button.textContent = '↓';
} else {
button.textContent = '↑';
isMinimized = true;
}
// (Un)selects all colors in the color list that are visible to the user
function selectColorList(userWantsUnselect) {
}
}
).buildElement()
.buildElement()
.buildElement()
.buildOverlay();
// Gets the colors
const colorList = document.querySelector('.bm-filter-flex');
const colors = Array.from(colorList.children);
// For each color...
for (const color of colors) {
// Skip this color if it is hidden
if (color.classList?.contains('bm-color-hide')) {continue;}
// Gets the button to click
const button = color.querySelector('.bm-filter-container-rgb button');
// Exits early if the button is in its proper state
if ((button.dataset['state'] == 'hidden') && !userWantsUnselect) {continue;} // If the button is selected, and the user wants to select all buttons, then skip this one
if ((button.dataset['state'] == 'shown') && userWantsUnselect) {continue;} // If the button is not selected, and the user wants to unselect all buttons, then skip this one
button.click(); // If the button is not in its proper state, then we click it
}
}
}

View file

@ -50,7 +50,7 @@
align-items: center;
gap: 0.5ch;
/* For background circles, width & height should be odd, cx & cy should be half of width & height, and r should be less than or equal to cx & cy */
background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="5" height="5"><circle cx="3" cy="3" r="1.5" fill="CornflowerBlue" /></svg>') repeat;
background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="5" height="5"><circle cx="3" cy="3" r="1.5" fill="CornflowerBlue"/></svg>') repeat;
cursor: grab;
width: 100%;
height: fit-content;
@ -355,6 +355,33 @@ input[type="file"] {
padding: 1ch;
}
/* Filter window container for RGB color display for Other color */
.bm-filter-color[data-id="-2"] .bm-filter-container-rgb {
background: conic-gradient(
#aa0000 0%,
#aaaa00 16.6%,
#00aa00 33.3%,
#00aaaa 50%,
#0000aa 66.6%,
#aa00aa 83.3%,
#aa0000 100%
);
}
/* Filter window container for RGB color display for Erased color */
.bm-filter-color[data-id="-1"] .bm-filter-container-rgb {
background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 8 8" width="1em" height="1em"><path d="M0,0V8H16V16H8V0" fill="rgba(0,0,0,0.5)"/></svg>') repeat;
background-color: transparent !important;
}
.bm-filter-color[data-id="-1"] .bm-filter-container-rgb svg {
fill: white !important;
}
/* Filter window container for RGB color display for Transparent color */
.bm-filter-color[data-id="0"] .bm-filter-container-rgb {
background-color: transparent !important;
}
/* Filter window hide color button */
.bm-filter-container-rgb button {
padding: 0.75em 0.5ch;
@ -380,4 +407,5 @@ input[type="file"] {
/* Filter window hide unused colors */
#bm-window-filter .bm-filter-color.bm-color-hide {
display: none;
}
}

View file

@ -310,15 +310,22 @@ export default class TemplateManager {
let pixelsCorrectTotal = 0;
const transparentColorID = 0;
// For each color with correct pixels placed for this template...
for (const [color, total] of pixelsCorrect) {
if (color == transparentColorID) {continue;} // Skip Transparent color
pixelsCorrectTotal += total;
pixelsCorrectTotal += total; // Add the current total for this color to the summed total of all correct
}
console.log(`Finished calculating correct pixels for the tile ${tileCoords} in ${(Date.now() - timer) / 1000} seconds!\nThere are ${pixelsCorrectTotal} correct pixels.`);
template.instance.pixelCount['correct'] = pixelsCorrect; // Adds the correct pixel Map to the template instance
// If "correct" does not exist as a key of the object "pixelCount", we create it
if (typeof template.instance.pixelCount['correct'] == 'undefined') {
template.instance.pixelCount['correct'] = {};
}
// Adds the correct pixel Map to the template instance
template.instance.pixelCount['correct'][tileCoords] = pixelsCorrect;
}
return await canvas.convertToBlob({ type: 'image/png' });