From 083462d0f6c6e4f64c5156632db3a7f758f85687 Mon Sep 17 00:00:00 2001 From: SwingTheVine Date: Sun, 8 Feb 2026 23:08:23 -0500 Subject: [PATCH] Removed color filter (2) --- dist/BlueMarble.user.js | 4 +- docs/README.md | 2 +- package-lock.json | 4 +- package.json | 2 +- src/BlueMarble.meta.js | 2 +- src/main.js | 8 +- src/templateManager.js | 348 ++++------------------------------------ 7 files changed, 43 insertions(+), 327 deletions(-) diff --git a/dist/BlueMarble.user.js b/dist/BlueMarble.user.js index 200821d..1b195f7 100644 --- a/dist/BlueMarble.user.js +++ b/dist/BlueMarble.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Blue Marble // @namespace https://github.com/SwingTheVine/ -// @version 0.88.0 +// @version 0.88.3 // @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. // @author SwingTheVine // @license MPL-2.0 @@ -24,4 +24,4 @@ // Wplace --> https://wplace.live // License --> https://www.mozilla.org/en-US/MPL/2.0/ -(()=>{var t,e,i=t=>{throw TypeError(t)},n=(t,e,n)=>e.has(t)?i("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(t):e.set(t,n),o=(t,e,n)=>(((t,e)=>{e.has(t)||i("Cannot access private method")})(t,e),n),r=class{constructor(e,i){n(this,t),this.name=e,this.version=i,this.t=null,this.i="bm-o",this.o=null,this.m=null,this.l=[]}u(t){this.t=t}h(){return this.l.length>0&&(this.m=this.l.pop()),this}p(t){t?.appendChild(this.o),this.o=null,this.m=null,this.l=[]}v(i={},n=()=>{}){return n(this,o(this,t,e).call(this,"div",{},i)),this}$(i={},n=()=>{}){return n(this,o(this,t,e).call(this,"p",{},i)),this}S(i={},n=()=>{}){return n(this,o(this,t,e).call(this,"small",{},i)),this}M(i={},n=()=>{}){return n(this,o(this,t,e).call(this,"img",{},i)),this}O(i,n={},r=()=>{}){return r(this,o(this,t,e).call(this,"h"+i,{},n)),this}T(i={},n=()=>{}){return n(this,o(this,t,e).call(this,"hr",{},i)),this}C(i={},n=()=>{}){return n(this,o(this,t,e).call(this,"br",{},i)),this}D(i={},n=()=>{}){const r=o(this,t,e).call(this,"label",{textContent:i.textContent??""});delete i.textContent;const s=o(this,t,e).call(this,"input",{type:"checkbox"},i);return r.insertBefore(s,r.firstChild),this.h(),n(this,r,s),this}k(i={},n=()=>{}){return n(this,o(this,t,e).call(this,"button",{},i)),this}N(i={},n=()=>{}){const r=i.title??i.textContent??"Help: No info";delete i.textContent,i.title=`Help: ${r}`;const s={textContent:"?",className:"bm-D",onclick:()=>{this.B(this.i,r)}};return n(this,o(this,t,e).call(this,"button",s,i)),this}I(i={},n=()=>{}){return n(this,o(this,t,e).call(this,"input",{},i)),this}L(i={},n=()=>{}){const r=i.textContent??"";delete i.textContent;const s=o(this,t,e).call(this,"div"),a=o(this,t,e).call(this,"input",{type:"file",style:"display: none !important; visibility: hidden !important; position: absolute !important; left: -9999px !important; width: 0 !important; height: 0 !important; opacity: 0 !important;"},i);this.h();const m=o(this,t,e).call(this,"button",{textContent:r});return this.h(),this.h(),a.setAttribute("tabindex","-1"),a.setAttribute("aria-hidden","true"),m.addEventListener("click",()=>{a.click()}),a.addEventListener("change",()=>{m.style.maxWidth=`${m.offsetWidth}px`,a.files.length>0?m.textContent=a.files[0].name:m.textContent=r}),n(this,s,a,m),this}G(i={},n=()=>{}){return n(this,o(this,t,e).call(this,"textarea",{},i)),this}B(t,e,i=!1){const n=document.getElementById(t.replace(/^#/,""));n&&(n instanceof HTMLInputElement?n.value=e:i?n.textContent=e:n.innerHTML=e)}W(t,e){let i,n=!1,o=0,r=null,s=0,a=0,m=0,l=0;if(t=document.querySelector("#"==t?.[0]?t:"#"+t),e=document.querySelector("#"==e?.[0]?e:"#"+e),!t||!e)return void this.P(`Can not drag! ${t?"":"moveMe"} ${t||e?"":"and "}${e?"":"iMoveThings "}was not found!`);const c=()=>{if(n){const e=Math.abs(s-m),i=Math.abs(a-l);(e>.5||i>.5)&&(s=m,a=l,t.style.transform=`translate(${s}px, ${a}px)`,t.style.left="0px",t.style.top="0px",t.style.right=""),r=requestAnimationFrame(c)}};let u=null;const d=(d,h)=>{n=!0,u=t.getBoundingClientRect(),i=d-u.left,o=h-u.top;const b=window.getComputedStyle(t).transform;if(b&&"none"!==b){const t=new DOMMatrix(b);s=t.m41,a=t.m42}else s=u.left,a=u.top;m=s,l=a,document.body.style.userSelect="none",e.classList.add("dragging"),r&&cancelAnimationFrame(r),c()},h=()=>{n=!1,r&&(cancelAnimationFrame(r),r=null),document.body.style.userSelect="",e.classList.remove("dragging")};e.addEventListener("mousedown",function(t){t.preventDefault(),d(t.clientX,t.clientY)}),e.addEventListener("touchstart",function(t){const e=t?.touches?.[0];e&&(d(e.clientX,e.clientY),t.preventDefault())},{passive:!1}),document.addEventListener("mousemove",function(t){n&&u&&(m=t.clientX-i,l=t.clientY-o)},{passive:!0}),document.addEventListener("touchmove",function(t){if(n&&u){const e=t?.touches?.[0];if(!e)return;m=e.clientX-i,l=e.clientY-o,t.preventDefault()}},{passive:!1}),document.addEventListener("mouseup",h),document.addEventListener("touchend",h),document.addEventListener("touchcancel",h)}_(t){(0,console.info)(`${this.name}: ${t}`),this.B(this.i,"Status: "+t,!0)}P(t){(0,console.error)(`${this.name}: ${t}`),this.B(this.i,"Error: "+t,!0)}};function s(...t){(0,console.error)(...t)}function a(t,e){if(0===t)return e[0];let i="";const n=e.length;for(;t>0;)i=e[t%n]+i,t=Math.floor(t/n);return i}function m(t){let e="";for(let i=0;i"transparent"!==(t?.name||"").toLowerCase()&&Array.isArray(t?.rgb)).map(t=>`${t.rgb[0]},${t.rgb[1]},${t.rgb[2]}`));const l="222,250,206";this.Y.add(l);const c="other";this.Y.add(c),this.K=new Map(m.filter(t=>Array.isArray(t?.rgb)).map(t=>[`${t.rgb[0]},${t.rgb[1]},${t.rgb[2]}`,{id:t.id,premium:!!t.premium,name:t.name}]));try{const t=m.find(t=>"transparent"===(t?.name||"").toLowerCase());t&&Array.isArray(t.rgb)&&this.K.set(l,{id:t.id,premium:!!t.premium,name:t.name})}catch(t){}try{this.K.set(c,{id:"other",premium:!1,name:"Other"})}catch(t){}}async Z(){const t=await createImageBitmap(this.file),e=t.width,i=t.height,n=e*i;this.J=n;try{const n=new OffscreenCanvas(e,i).getContext("2d",{tt:!0});n.imageSmoothingEnabled=!1,n.clearRect(0,0,e,i),n.drawImage(t,0,0);const o=n.getImageData(0,0,e,i).data;let r=0,s=0;const a=new Map;for(let t=0;t0){for(const t in e){const i=t,n=e[t];if(e.hasOwnProperty(t)){const t=i.split(" "),o=Number(t?.[0]),r=t?.[1]||"0",s=n.name||`Template ${o||""}`,a=n.tiles,m={};let c=0;const u=new Map;for(const t in a)if(a.hasOwnProperty(t)){const e=l(a[t]),i=new Blob([e],{type:"image/png"}),n=await createImageBitmap(i);m[t]=n;try{const t=n.width,e=n.height,i=new OffscreenCanvas(t,e).getContext("2d",{tt:!0});i.imageSmoothingEnabled=!1,i.clearRect(0,0,t,e),i.drawImage(n,0,0);const o=i.getImageData(0,0,t,e).data;for(let i=0;i{d.q?.add(t.split(",").slice(0,2).join(","))})}catch(t){}try{const t=e?.[i]?.palette;if(t)for(const[e,i]of Object.entries(t))d.X[e]?d.X[e].enabled=!!i?.enabled:d.X[e]={count:i?.count||0,enabled:!!i?.enabled}}catch(t){}d.H=i,this.rt.push(d)}}try{const t=document.querySelector("#bm-9");t&&(t.style.display=""),window.postMessage({source:"blue-marble",st:"bm-b"},"*")}catch(t){}}},h=new WeakSet,b=async function(t=navigator.userAgent){return(t=t||"").includes("OPR/")||t.includes("Opera")?"Opera":t.includes("Edg/")?"Edge":t.includes("Vivaldi")?"Vivaldi":t.includes("YaBrowser")?"Yandex":t.includes("Kiwi")?"Kiwi":t.includes("Brave")?"Brave":t.includes("Firefox/")?"Firefox":t.includes("Chrome/")?"Chrome":t.includes("Safari/")?"Safari":navigator.brave&&"function"==typeof navigator.brave.isBrave&&await navigator.brave.isBrave()?"Brave":"Unknown"},p=function(t=navigator.userAgent){return/Windows NT 11/i.test(t=t||"")?"Windows 11":/Windows NT 10/i.test(t)?"Windows 10":/Windows NT 6\.3/i.test(t)?"Windows 8.1":/Windows NT 6\.2/i.test(t)?"Windows 8":/Windows NT 6\.1/i.test(t)?"Windows 7":/Windows NT 6\.0/i.test(t)?"Windows Vista":/Windows NT 5\.1|Windows XP/i.test(t)?"Windows XP":/Mac OS X 10[_\.]15/i.test(t)?"macOS Catalina":/Mac OS X 10[_\.]14/i.test(t)?"macOS Mojave":/Mac OS X 10[_\.]13/i.test(t)?"macOS High Sierra":/Mac OS X 10[_\.]12/i.test(t)?"macOS Sierra":/Mac OS X 10[_\.]11/i.test(t)?"OS X El Capitan":/Mac OS X 10[_\.]10/i.test(t)?"OS X Yosemite":/Mac OS X 10[_\.]/i.test(t)?"macOS":/Android/i.test(t)?"Android":/iPhone|iPad|iPod/i.test(t)?"iOS":/Linux/i.test(t)?"Linux":"Unknown"};var w=GM_info.script.name.toString(),y=GM_info.script.version.toString();!function(t){const e=document.createElement("script");e.setAttribute("bm-E",w),e.setAttribute("bm-B","color: cornflowerblue;"),e.textContent=`(${t})();`,document.documentElement?.appendChild(e),e.remove()}(()=>{const t=document.currentScript,e=t?.getAttribute("bm-E")||"Blue Marble",i=t?.getAttribute("bm-B")||"",n=new Map;window.addEventListener("message",t=>{const{source:o,endpoint:r,blobID:s,blobData:a,blink:m}=t.data;if(Date.now(),"blue-marble"==o&&s&&a&&!r){const t=n.get(s);"function"==typeof t?t(a):function(...t){(0,console.warn)(...t)}(`%c${e}%c: Attempted to retrieve a blob (%s) from queue, but the blobID was not a function! Skipping...`,i,"",s),n.delete(s)}});const o=window.fetch;window.fetch=async function(...t){const e=await o.apply(this,t),i=e.clone(),r=(t[0]instanceof Request?t[0]?.url:t[0])||"ignore",s=i.headers.get("content-type")||"";if(s.includes("application/json"))i.json().then(t=>{window.postMessage({source:"blue-marble",endpoint:r,jsonData:t},"*")}).catch(t=>{});else if(s.includes("image/")&&!r.includes("openfreemap")&&!r.includes("maps")){const t=Date.now(),e=await i.blob();return new Promise(o=>{const s=crypto.randomUUID();n.set(s,t=>{o(new Response(t,{headers:i.headers,status:i.status,statusText:i.statusText}))}),window.postMessage({source:"blue-marble",endpoint:r,blobID:s,blobData:e,blink:t})}).catch(t=>{Date.now()})}return e}});var v=GM_getResourceText("CSS-BM-File");GM_addStyle(v);var x=document.createElement("link");x.href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap",x.rel="preload",x.as="style",x.onload=function(){this.onload=null,this.rel="stylesheet"},document.head?.appendChild(x),new class{constructor(){this.lt=null,this.ct=null,this.ut="#bm-h"}dt(t){return this.ct=t,this.lt=new MutationObserver(t=>{for(const e of t)for(const t of e.addedNodes)t instanceof HTMLElement&&t.matches?.(this.ut)}),this}ht(){return this.lt}observe(t,e=!1,i=!1){t.observe(this.ct,{childList:e,subtree:i})}};var $=new r(w,y),S=(new r(w,y),new class{constructor(t,e,i){n(this,c),this.name=t,this.version=e,this.o=i,this.bt="1.0.0",this.gt=null,this.ft="!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~",this.A=1e3,this.ot=3,this.wt=null,this.yt=null,this.vt="bm-C",this.xt="div#map canvas.maplibregl-canvas",this.$t=null,this.St="",this.rt=[],this.nt=null,this.Mt=!0,this.Ot=new Map}Tt(){if(document.body.contains(this.wt))return this.wt;document.getElementById(this.vt)?.remove();const t=document.querySelector(this.xt),e=document.createElement("canvas");return e.id=this.vt,e.className="maplibregl-canvas",e.style.position="absolute",e.style.top="0",e.style.left="0",e.style.height=t?.clientHeight*(window.devicePixelRatio||1)+"px",e.style.width=t?.clientWidth*(window.devicePixelRatio||1)+"px",e.height=t?.clientHeight*(window.devicePixelRatio||1),e.width=t?.clientWidth*(window.devicePixelRatio||1),e.style.zIndex="8999",e.style.pointerEvents="none",t?.parentElement?.appendChild(e),this.wt=e,window.addEventListener("move",this.Ct),window.addEventListener("zoom",this.Dt),window.addEventListener("resize",this.kt),this.wt}async Nt(){return{whoami:this.name.replace(" ",""),scriptVersion:this.version,schemaVersion:this.bt,templates:{}}}async Bt(t,e,i){this.nt||(this.nt=await this.Nt()),this.o._(`Creating template at ${i.join(", ")}...`);const n=new f({displayName:e,F:0,j:a(this.gt||0,this.ft),file:t,coords:i}),{et:r,it:s}=await n.Z(this.A);n.R=r;const m=`${n.F} ${n.j}`;n.H=m,this.nt.templates[m]={name:n.displayName,coords:i.join(", "),enabled:!0,tiles:s,palette:n.X},this.rt=[],this.rt.push(n);const l=(new Intl.NumberFormat).format(n.J);this.o._(`Template created at ${i.join(", ")}! Total pixels: ${l}`);try{const t=document.querySelector("#bm-9");t&&(t.style.display=""),window.postMessage({source:"blue-marble",st:"bm-b"},"*")}catch(t){}await o(this,c,u).call(this)}It(){}async Lt(){this.nt||(this.nt=await this.Nt())}async Gt(t,e){if(!this.Mt)return t;const i=this.A*this.ot;e=e[0].toString().padStart(4,"0")+","+e[1].toString().padStart(4,"0");const n=this.rt;if(n.sort((t,e)=>t.F-e.F),!n.some(t=>!!t?.R&&(t.q&&t.q.size>0?t.q.has(e):Object.keys(t.R).some(t=>t.startsWith(e)))))return t;const o=n.map(t=>{const i=Object.keys(t.R).filter(t=>t.startsWith(e));if(0===i.length)return null;const n=i.map(e=>{const i=e.split(",");return{Wt:t.R[e],Pt:[i[0],i[1]],_t:[i[2],i[3]]}});return n?.[0]}).filter(Boolean),r=o?.length||0;let s=0,a=0,m=0;const l=await createImageBitmap(t),c=new OffscreenCanvas(i,i),u=c.getContext("2d");u.imageSmoothingEnabled=!1,u.beginPath(),u.rect(0,0,i,i),u.clip(),u.clearRect(0,0,i,i),u.drawImage(l,0,0,i,i);let d=null;try{d=u.getImageData(0,0,i,i).data}catch(t){}for(const t of o){if(d)try{const e=t.Wt.width,n=t.Wt.height,o=new OffscreenCanvas(e,n).getContext("2d",{tt:!0});o.imageSmoothingEnabled=!1,o.clearRect(0,0,e,n),o.drawImage(t.Wt,0,0);const r=o.getImageData(0,0,e,n).data,l=Number(t._t[0])*this.ot,c=Number(t._t[1])*this.ot;for(let t=0;t=i||u>=i)continue;const h=4*(t*e+n),b=r[h],p=r[h+1],g=r[h+2];if(r[h+3]<64){try{const t=this.rt?.[0],e=4*(u*i+o),n=d[e],r=d[e+1],s=d[e+2],m=d[e+3],l=t.Y.has(`${n},${r},${s}`)?`${n},${r},${s}`:"other",c=!!t?.Y&&t.Y.has(l);m>=64&&c&&a++}catch(t){}continue}m++;const f=4*(u*i+o),w=d[f],y=d[f+1],v=d[f+2];d[f+3]<64||(w===b&&y===p&&v===g?s++:a++)}}catch(t){}try{const e=this.rt?.[0],i=e?.X||{};if(Object.values(i).some(t=>!1===t?.enabled)){const n=t.Wt.width,o=t.Wt.height,r=new OffscreenCanvas(n,o),s=r.getContext("2d",{tt:!0});s.imageSmoothingEnabled=!1,s.clearRect(0,0,n,o),s.drawImage(t.Wt,0,0);const a=s.getImageData(0,0,n,o),m=a.data;for(let t=0;t0){const t=e;this.Ot.set(t,{Et:s,required:m,Ft:a});let i=0,n=0,o=0;for(const t of this.Ot.values())i+=t.Et||0,n+=t.required||0,o+=t.Ft||0;const l=this.rt.reduce((t,e)=>t+(e.U||e.J||0),0),c=l>0?l:n,u=(new Intl.NumberFormat).format(i),d=(new Intl.NumberFormat).format(c),h=(new Intl.NumberFormat).format(c-i);this.o._(`Displaying ${r} template${1==r?"":"s"}.\nPainted ${u} / ${d} • Wrong ${h}`)}else this.o._(`Displaying ${r} templates.`);return await c.convertToBlob({type:"image/png"})}jt(t){"BlueMarble"==t?.whoami&&o(this,c,d).call(this,t)}Rt(t){this.Mt=t}}(w,y,$)),M=new class{constructor(t){n(this,h),this.At=t,this.Jt=!1,this.Ut=[],this.Vt=[]}Xt(t){window.addEventListener("message",async e=>{const i=e.data,n=i.jsonData;if(!i||"blue-marble"!==i.source)return;if(!i.endpoint)return;const o=i.endpoint?.split("?")[0].split("/").filter(t=>t&&isNaN(Number(t))).filter(t=>t&&!t.includes(".")).pop();switch(o){case"me":if(n.status&&"2"!=n.status?.toString()[0])return void t.P("You are not logged in!\nCould not fetch userdata.");const e=Math.ceil(Math.pow(Math.floor(n.level)*Math.pow(30,.65),1/.65)-n.pixelsPainted);n.id||n.id,this.At.gt=n.id,t.B("bm-u",`Username: ${function(t){const e=document.createElement("div");return e.textContent=t,e.innerHTML}(n.name)}`),t.B("bm-p",`Droplets: ${(new Intl.NumberFormat).format(n.droplets)}`),t.B("bm-i",`Next level in ${(new Intl.NumberFormat).format(e)} pixel${1==e?"":"s"}`);break;case"pixel":const o=i.endpoint.split("?")[0].split("/").filter(t=>t&&!isNaN(Number(t))),a=new URLSearchParams(i.endpoint.split("?")[1]),m=[a.get("x"),a.get("y")];if(this.Ut.length&&(!o.length||!m.length))return void t.P("Coordinates are malformed!\nDid you try clicking the canvas first?");this.Ut=[...o,...m];const l=(r=o,s=m,[parseInt(r[0])%4*1e3+parseInt(s[0]),parseInt(r[1])%4*1e3+parseInt(s[1])]),c=document.querySelectorAll("span");for(const t of c)if(t.textContent.trim().includes(`${l[0]}, ${l[1]}`)){let e=document.querySelector("#bm-h");const i=`(Tl X: ${o[0]}, Tl Y: ${o[1]}, Px X: ${m[0]}, Px Y: ${m[1]})`;e?e.textContent=i:(e=document.createElement("span"),e.id="bm-h",e.textContent=i,e.style="margin-left: calc(var(--spacing)*3); font-size: small;",t.parentNode.parentNode.insertAdjacentElement("afterend",e))}break;case"tiles":let u=i.endpoint.split("/");u=[parseInt(u[u.length-2]),parseInt(u[u.length-1].replace(".png",""))];const d=i.blobID,h=i.blobData,b=await this.At.Gt(h,u);window.postMessage({source:"blue-marble",blobID:d,blobData:b,blink:i.blink});break;case"robots":this.Jt="false"==n.userscript?.toString().toLowerCase()}var r,s})}async qt(t){let e=GM_getValue("bmUserSettings","{}");if(e=JSON.parse(e),!e||!e.telemetry||!e.uuid)return;const i=navigator.userAgent;let n=await o(this,h,b).call(this,i),r=o(this,h,p).call(this,i);GM_xmlhttpRequest({method:"POST",url:"https://telemetry.thebluecorner.net/heartbeat",headers:{"Content-Type":"application/json"},data:JSON.stringify({uuid:e.uuid,version:t,browser:n,os:r}),onload:t=>{200!==t.status&&s("Failed to send heartbeat:",t.statusText)},onerror:t=>{s("Error sending heartbeat:",t)}})}}(S);$.u(M);var O=JSON.parse(GM_getValue("bmTemplates","{}"));S.jt(O);var T=JSON.parse(GM_getValue("bmUserSettings","{}"));if(0==Object.keys(T).length){const t=crypto.randomUUID();GM.setValue("bmUserSettings",JSON.stringify({uuid:t}))}if(setInterval(()=>M.qt(y),18e5),null==T?.telemetry||T?.telemetry>1){const t=new r(w,y);t.u(M),t.v({id:"bm-d",style:"top: 0px; left: 0px; width: 100vw; max-width: 100vw; height: 100vh; max-height: 100vh; z-index: 9999;"}).v({id:"bm-7",style:"display: flex; flex-direction: column; align-items: center;"}).v({id:"bm-1",style:"margin-top: 10%;"}).O(1,{textContent:`${w} Telemetry`}).h().h().v({id:"bm-e",style:"max-width: 50%; overflow-y: auto; max-height: 80vh;"}).T().h().C().h().v({style:"width: fit-content; margin: auto; text-align: center;"}).k({id:"bm-8",textContent:"More Information"},(t,e)=>{e.onclick=()=>{window.open("https://github.com/SwingTheVine/Wplace-TelemetryServer#telemetry-data","_blank","noopener noreferrer")}}).h().h().C().h().v({style:"width: fit-content; margin: auto; text-align: center;"}).k({id:"bm-5",textContent:"Enable Telemetry",style:"margin-right: 2ch;"},(t,e)=>{e.onclick=()=>{const t=JSON.parse(GM_getValue("bmUserSettings","{}"));t.telemetry=1,GM.setValue("bmUserSettings",JSON.stringify(t));const e=document.getElementById("bm-d");e&&(e.style.display="none")}}).h().k({id:"bm-2",textContent:"Disable Telemetry"},(t,e)=>{e.onclick=()=>{const t=JSON.parse(GM_getValue("bmUserSettings","{}"));t.telemetry=0,GM.setValue("bmUserSettings",JSON.stringify(t));const e=document.getElementById("bm-d");e&&(e.style.display="none")}}).h().h().C().h().$({textContent:"We collect anonymous telemetry data such as your browser, OS, and script version to make the experience better for everyone. The data is never shared personally. The data is never sold. You can turn this off by pressing the 'Disable' button, but keeping it on helps us improve features and reliability faster. Thank you for supporting the Blue Marble!"}).h().$({textContent:'You can disable telemetry by pressing the "Disable" button below.'}).h().h().h().p(document.body)}!function(){let t=!1,e={};try{e=JSON.parse(GM_getValue("bmCoords","{}"))||{}}catch(t){e={}}const i=()=>{try{const t=Number(document.querySelector("#bm-v")?.value||""),e=Number(document.querySelector("#bm-w")?.value||""),i={Ht:t,Yt:e,px:Number(document.querySelector("#bm-x")?.value||""),zt:Number(document.querySelector("#bm-y")?.value||"")};GM.setValue("bmCoords",JSON.stringify(i))}catch(t){}};$.v({id:"bm-A",style:"top: 10px; right: 75px;"}).v({id:"bm-j"}).v({id:"bm-z"}).h().M({alt:"Blue Marble Icon - Click to minimize/maximize",src:"https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/assets/Favicon.png",style:"cursor: pointer;"},(e,i)=>{i.addEventListener("click",()=>{t=!t;const n=document.querySelector("#bm-A"),o=document.querySelector("#bm-j"),r=document.querySelector("#bm-z"),s=document.querySelector("#bm-k"),a=document.querySelector("#bm-q"),m=document.querySelector("#bm-r"),l=document.querySelector("#bm-s"),c=document.querySelector("#bm-l"),u=document.querySelectorAll("#bm-k input");t||(n.style.width="auto",n.style.maxWidth="300px",n.style.minWidth="200px",n.style.padding="10px"),["#bm-A h1","#bm-f","#bm-A hr","#bm-c > *:not(#bm-k)","#bm-a","#bm-6",`#${e.i}`,"#bm-9"].forEach(e=>{document.querySelectorAll(e).forEach(e=>{e.style.display=t?"none":""})}),t?(s&&(s.style.display="none"),a&&(a.style.display="none"),m&&(m.style.display="none"),l&&(l.style.display="none"),c&&(c.style.display="none"),u.forEach(t=>{t.style.display="none"}),n.style.width="60px",n.style.height="76px",n.style.maxWidth="60px",n.style.minWidth="60px",n.style.padding="8px",i.style.marginLeft="3px",o.style.textAlign="center",o.style.margin="0",o.style.marginBottom="0",r&&(r.style.display="",r.style.marginBottom="0.25em")):(s&&(s.style.display="",s.style.flexDirection="",s.style.justifyContent="",s.style.alignItems="",s.style.gap="",s.style.textAlign="",s.style.margin=""),a&&(a.style.display=""),m&&(m.style.display="",m.style.marginTop=""),l&&(l.style.display="",l.style.marginTop=""),c&&(c.style.display="",c.style.marginTop=""),u.forEach(t=>{t.style.display=""}),i.style.marginLeft="",n.style.padding="10px",o.style.textAlign="",o.style.margin="",o.style.marginBottom="",r&&(r.style.marginBottom="0.5em"),n.style.width="",n.style.height=""),i.alt=t?"Blue Marble Icon - Minimized (Click to maximize)":"Blue Marble Icon - Maximized (Click to minimize)"})}).h().O(1,{textContent:w}).h().h().T().h().v({id:"bm-f"}).$({id:"bm-u",textContent:"Username:"}).h().$({id:"bm-p",textContent:"Droplets:"}).h().$({id:"bm-i",textContent:"Next level in..."}).h().h().T().h().v({id:"bm-c"}).v({id:"bm-k"}).k({id:"bm-q",className:"bm-D",style:"margin-top: 0;",innerHTML:''},(t,e)=>{e.onclick=()=>{const e=t.t?.Ut;e?.[0]?(t.B("bm-v",e?.[0]||""),t.B("bm-w",e?.[1]||""),t.B("bm-x",e?.[2]||""),t.B("bm-y",e?.[3]||""),i()):t.P("Coordinates are malformed! Did you try clicking on the canvas first?")}}).h().I({type:"number",id:"bm-v",placeholder:"Tl X",min:0,max:2047,step:1,required:!0,value:e.Ht??""},(t,e)=>{e.addEventListener("paste",t=>{let e=(t.clipboardData||window.clipboardData).getData("text").split(" ").filter(t=>t).map(Number).filter(t=>!isNaN(t));if(4!==e.length)return;let i=(n=document,coords=[],coords.push(n.querySelector("#bm-v")),coords.push(n.querySelector("#bm-w")),coords.push(n.querySelector("#bm-x")),coords.push(n.querySelector("#bm-y")),coords);var n;for(let t=0;ti();e.addEventListener("input",n),e.addEventListener("change",n)}).h().I({type:"number",id:"bm-w",placeholder:"Tl Y",min:0,max:2047,step:1,required:!0,value:e.Yt??""},(t,e)=>{const n=()=>i();e.addEventListener("input",n),e.addEventListener("change",n)}).h().I({type:"number",id:"bm-x",placeholder:"Px X",min:0,max:2047,step:1,required:!0,value:e.px??""},(t,e)=>{const n=()=>i();e.addEventListener("input",n),e.addEventListener("change",n)}).h().I({type:"number",id:"bm-y",placeholder:"Px Y",min:0,max:2047,step:1,required:!0,value:e.zt??""},(t,e)=>{const n=()=>i();e.addEventListener("input",n),e.addEventListener("change",n)}).h().h().v({id:"bm-9",style:"max-height: 140px; overflow: auto; border: 1px solid rgba(255,255,255,0.1); padding: 4px; border-radius: 4px; display: none;"}).v({style:"display: flex; gap: 6px; margin-bottom: 6px;"}).k({id:"bm-3",textContent:"Enable All"},(t,e)=>{e.onclick=()=>{const e=S.rt[0];e?.X&&(Object.values(e.X).forEach(t=>t.enabled=!0),buildColorFilterList(),t._("Enabled all colors"))}}).h().k({id:"bm-0",textContent:"Disable All"},(t,e)=>{e.onclick=()=>{const e=S.rt[0];e?.X&&(Object.values(e.X).forEach(t=>t.enabled=!1),buildColorFilterList(),t._("Disabled all colors"))}}).h().h().v({id:"bm-g"}).h().h().L({id:"bm-a",textContent:"Upload Template",accept:"image/png, image/jpeg, image/webp, image/bmp, image/gif"}).h().v({id:"bm-4"}).k({id:"bm-s",textContent:"Enable"},(t,e)=>{e.onclick=()=>{t.t?.At?.Rt(!0),t._("Enabled templates!")}}).h().k({id:"bm-r",textContent:"Create"},(t,e)=>{e.onclick=()=>{const e=document.querySelector("#bm-a"),i=document.querySelector("#bm-v");if(!i.checkValidity())return i.reportValidity(),void t.P("Coordinates are malformed! Did you try clicking on the canvas first?");const n=document.querySelector("#bm-w");if(!n.checkValidity())return n.reportValidity(),void t.P("Coordinates are malformed! Did you try clicking on the canvas first?");const o=document.querySelector("#bm-x");if(!o.checkValidity())return o.reportValidity(),void t.P("Coordinates are malformed! Did you try clicking on the canvas first?");const r=document.querySelector("#bm-y");if(!r.checkValidity())return r.reportValidity(),void t.P("Coordinates are malformed! Did you try clicking on the canvas first?");e?.files[0]?(S.Bt(e.files[0],e.files[0]?.name.replace(/\.[^/.]+$/,""),[Number(i.value),Number(n.value),Number(o.value),Number(r.value)]),t._("Drew to canvas!")):t.P("No file selected!")}}).h().k({id:"bm-l",textContent:"Disable"},(t,e)=>{e.onclick=()=>{t.t?.At?.Rt(!1),t._("Disabled templates!")}}).h().h().G({id:$.i,placeholder:`Status: Sleeping...\nVersion: ${y}`,readOnly:!0}).h().v({id:"bm-6"}).v().k({id:"bm-m",className:"bm-D",innerHTML:"🎨",title:"Template Color Converter"},(t,e)=>{e.addEventListener("click",()=>{window.open("https://pepoafonso.github.io/color_converter_wplace/","_blank","noopener noreferrer")})}).h().k({id:"bm-n",className:"bm-D",innerHTML:"🌐",title:"Official Blue Marble Website"},(t,e)=>{e.addEventListener("click",()=>{window.open("https://bluemarble.lol/","_blank","noopener noreferrer")})}).h().h().S({textContent:"Made by SwingTheVine",style:"margin-top: auto;"}).h().h().h().p(document.body),window.buildColorFilterList=function(){const t=document.querySelector("#bm-g"),e=S.rt?.[0];if(!t||!e?.X)return void(t&&(t.innerHTML="No template colors to display."));t.innerHTML="";const i=Object.entries(e.X).sort((t,e)=>e[1].count-t[1].count);for(const[e,n]of i){let i=document.createElement("div");i.style.display="flex",i.style.alignItems="center",i.style.gap="8px",i.style.margin="4px 0";let o=document.createElement("div");o.style.width="14px",o.style.height="14px",o.style.border="1px solid rgba(255,255,255,0.5)";let r=document.createElement("span");r.style.fontSize="12px";let s=`${n.count.toLocaleString()}`;if("other"===e)o.style.background="#888",s=`Other • ${s}`;else if("#deface"===e)o.style.background="#deface",s=`Transparent • ${s}`;else{const[t,i,n]=e.split(",").map(Number);o.style.background=`rgb(${t},${i},${n})`;try{const o=S.rt?.[0]?.K?.get(e);if(o&&"number"==typeof o.id){const e=o?.name||`rgb(${t},${i},${n})`,r=o.premium?"★ ":"";s=`#${o.id} ${r}${e} • ${s}`}}catch(t){}}r.textContent=s;const a=document.createElement("input");a.type="checkbox",a.checked=!!n.enabled,a.addEventListener("change",()=>{n.enabled=a.checked,$._(`${a.checked?"Enabled":"Disabled"} ${e}`);try{const t=S.rt?.[0],e=t?.H;t&&e&&S.nt?.templates?.[e]&&(S.nt.templates[e].palette=t.X,GM.setValue("bmTemplates",JSON.stringify(S.nt)))}catch(t){}}),i.appendChild(a),i.appendChild(o),i.appendChild(r),t.appendChild(i)}},window.addEventListener("message",t=>{if("bm-b"===t?.data?.st)try{buildColorFilterList()}catch(t){}}),setTimeout(()=>{try{if(S.rt?.length>0){const t=document.querySelector("#bm-9");t&&(t.style.display=""),buildColorFilterList()}}catch(t){}},0)}(),$.W("#bm-A","#bm-z"),M.Xt($),new MutationObserver((t,e)=>{const i=document.querySelector("#color-1");if(!i)return;let n=document.querySelector("#bm-t");if(!n){n=document.createElement("button"),n.id="bm-t",n.textContent="Move ↑",n.className="btn btn-soft",n.onclick=function(){const t=this.parentNode.parentNode.parentNode.parentNode,e="Move ↑"==this.textContent;t.parentNode.className=t.parentNode.className.replace(e?"bottom":"top",e?"top":"bottom"),t.style.borderTopLeftRadius=e?"0px":"var(--radius-box)",t.style.borderTopRightRadius=e?"0px":"var(--radius-box)",t.style.borderBottomLeftRadius=e?"var(--radius-box)":"0px",t.style.borderBottomRightRadius=e?"var(--radius-box)":"0px",this.textContent=e?"Move ↓":"Move ↑"};const t=i.parentNode.parentNode.parentNode.parentNode.querySelector("h2");t.parentNode?.appendChild(n)}}).observe(document.body,{childList:!0,subtree:!0}),function(...t){(0,console.log)(...t)}(`%c${w}%c (${y}) userscript has loaded!`,"color: cornflowerblue;","")})(); \ No newline at end of file +(()=>{var t,e,n=t=>{throw TypeError(t)},i=(t,e,i)=>e.has(t)?n("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(t):e.set(t,i),o=(t,e,i)=>(((t,e)=>{e.has(t)||n("Cannot access private method")})(t,e),i),s=class{constructor(e,n){i(this,t),this.name=e,this.version=n,this.t=null,this.i="bm-o",this.o=null,this.l=null,this.m=[]}h(t){this.t=t}u(){return this.m.length>0&&(this.l=this.m.pop()),this}p(t){t?.appendChild(this.o),this.o=null,this.l=null,this.m=[]}v(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"div",{},n)),this}$(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"p",{},n)),this}S(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"small",{},n)),this}M(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"img",{},n)),this}T(n,i={},s=()=>{}){return s(this,o(this,t,e).call(this,"h"+n,{},i)),this}O(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"hr",{},n)),this}C(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"br",{},n)),this}N(n={},i=()=>{}){const s=o(this,t,e).call(this,"label",{textContent:n.textContent??""});delete n.textContent;const a=o(this,t,e).call(this,"input",{type:"checkbox"},n);return s.insertBefore(a,s.firstChild),this.u(),i(this,s,a),this}D(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"button",{},n)),this}k(n={},i=()=>{}){const s=n.title??n.textContent??"Help: No info";delete n.textContent,n.title=`Help: ${s}`;const a={textContent:"?",className:"bm-D",onclick:()=>{this.I(this.i,s)}};return i(this,o(this,t,e).call(this,"button",a,n)),this}B(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"input",{},n)),this}W(n={},i=()=>{}){const s=n.textContent??"";delete n.textContent;const a=o(this,t,e).call(this,"div"),r=o(this,t,e).call(this,"input",{type:"file",style:"display: none !important; visibility: hidden !important; position: absolute !important; left: -9999px !important; width: 0 !important; height: 0 !important; opacity: 0 !important;"},n);this.u();const l=o(this,t,e).call(this,"button",{textContent:s});return this.u(),this.u(),r.setAttribute("tabindex","-1"),r.setAttribute("aria-hidden","true"),l.addEventListener("click",()=>{r.click()}),r.addEventListener("change",()=>{l.style.maxWidth=`${l.offsetWidth}px`,r.files.length>0?l.textContent=r.files[0].name:l.textContent=s}),i(this,a,r,l),this}_(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"textarea",{},n)),this}I(t,e,n=!1){const i=document.getElementById(t.replace(/^#/,""));i&&(i instanceof HTMLInputElement?i.value=e:n?i.textContent=e:i.innerHTML=e)}P(t,e){let n,i=!1,o=0,s=null,a=0,r=0,l=0,c=0;if(t=document.querySelector("#"==t?.[0]?t:"#"+t),e=document.querySelector("#"==e?.[0]?e:"#"+e),!t||!e)return void this.X(`Can not drag! ${t?"":"moveMe"} ${t||e?"":"and "}${e?"":"iMoveThings "}was not found!`);const m=()=>{if(i){const e=Math.abs(a-l),n=Math.abs(r-c);(e>.5||n>.5)&&(a=l,r=c,t.style.transform=`translate(${a}px, ${r}px)`,t.style.left="0px",t.style.top="0px",t.style.right=""),s=requestAnimationFrame(m)}};let h=null;const u=(u,d)=>{i=!0,h=t.getBoundingClientRect(),n=u-h.left,o=d-h.top;const b=window.getComputedStyle(t).transform;if(b&&"none"!==b){const t=new DOMMatrix(b);a=t.m41,r=t.m42}else a=h.left,r=h.top;l=a,c=r,document.body.style.userSelect="none",e.classList.add("dragging"),s&&cancelAnimationFrame(s),m()},d=()=>{i=!1,s&&(cancelAnimationFrame(s),s=null),document.body.style.userSelect="",e.classList.remove("dragging")};e.addEventListener("mousedown",function(t){t.preventDefault(),u(t.clientX,t.clientY)}),e.addEventListener("touchstart",function(t){const e=t?.touches?.[0];e&&(u(e.clientX,e.clientY),t.preventDefault())},{passive:!1}),document.addEventListener("mousemove",function(t){i&&h&&(l=t.clientX-n,c=t.clientY-o)},{passive:!0}),document.addEventListener("touchmove",function(t){if(i&&h){const e=t?.touches?.[0];if(!e)return;l=e.clientX-n,c=e.clientY-o,t.preventDefault()}},{passive:!1}),document.addEventListener("mouseup",d),document.addEventListener("touchend",d),document.addEventListener("touchcancel",d)}G(t){(0,console.info)(`${this.name}: ${t}`),this.I(this.i,"Status: "+t,!0)}X(t){(0,console.error)(`${this.name}: ${t}`),this.I(this.i,"Error: "+t,!0)}};function a(...t){(0,console.error)(...t)}function r(t,e){if(0===t)return e[0];let n="";const i=e.length;for(;t>0;)n=e[t%i]+n,t=Math.floor(t/i);return n}function l(t){let e="";for(let n=0;n0)for(const t in e){const n=t,i=e[t];if(console.log(n),e.hasOwnProperty(t)){const t=n.split(" "),e=Number(t?.[0]),o=t?.[1]||"0",s=i.name||`Template ${e||""}`,a=i.tiles,r={};for(const t in a)if(console.log(t),a.hasOwnProperty(t)){const e=c(a[t]),n=new Blob([e],{type:"image/png"}),i=await createImageBitmap(n);r[t]=i}const l=new f({displayName:s,J:e||this.q?.length||0,U:o||""});l.j=r,this.q.push(l),console.log(this.q),console.log("^^^ This ^^^")}}},d=new WeakSet,b=async function(t=navigator.userAgent){return(t=t||"").includes("OPR/")||t.includes("Opera")?"Opera":t.includes("Edg/")?"Edge":t.includes("Vivaldi")?"Vivaldi":t.includes("YaBrowser")?"Yandex":t.includes("Kiwi")?"Kiwi":t.includes("Brave")?"Brave":t.includes("Firefox/")?"Firefox":t.includes("Chrome/")?"Chrome":t.includes("Safari/")?"Safari":navigator.brave&&"function"==typeof navigator.brave.isBrave&&await navigator.brave.isBrave()?"Brave":"Unknown"},p=function(t=navigator.userAgent){return/Windows NT 11/i.test(t=t||"")?"Windows 11":/Windows NT 10/i.test(t)?"Windows 10":/Windows NT 6\.3/i.test(t)?"Windows 8.1":/Windows NT 6\.2/i.test(t)?"Windows 8":/Windows NT 6\.1/i.test(t)?"Windows 7":/Windows NT 6\.0/i.test(t)?"Windows Vista":/Windows NT 5\.1|Windows XP/i.test(t)?"Windows XP":/Mac OS X 10[_\.]15/i.test(t)?"macOS Catalina":/Mac OS X 10[_\.]14/i.test(t)?"macOS Mojave":/Mac OS X 10[_\.]13/i.test(t)?"macOS High Sierra":/Mac OS X 10[_\.]12/i.test(t)?"macOS Sierra":/Mac OS X 10[_\.]11/i.test(t)?"OS X El Capitan":/Mac OS X 10[_\.]10/i.test(t)?"OS X Yosemite":/Mac OS X 10[_\.]/i.test(t)?"macOS":/Android/i.test(t)?"Android":/iPhone|iPad|iPod/i.test(t)?"iOS":/Linux/i.test(t)?"Linux":"Unknown"};var w=GM_info.script.name.toString(),g=GM_info.script.version.toString();!function(t){const e=document.createElement("script");e.setAttribute("bm-E",w),e.setAttribute("bm-B","color: cornflowerblue;"),e.textContent=`(${t})();`,document.documentElement?.appendChild(e),e.remove()}(()=>{const t=document.currentScript,e=t?.getAttribute("bm-E")||"Blue Marble",n=t?.getAttribute("bm-B")||"",i=new Map;window.addEventListener("message",t=>{const{source:o,endpoint:s,blobID:a,blobData:r,blink:l}=t.data,c=Date.now()-l;if(console.groupCollapsed(`%c${e}%c: ${i.size} Recieved IMAGE message about blob "${a}"`,n,""),console.log(`Blob fetch took %c${String(Math.floor(c/6e4)).padStart(2,"0")}:${String(Math.floor(c/1e3)%60).padStart(2,"0")}.${String(c%1e3).padStart(3,"0")}%c MM:SS.mmm`,n,""),console.log(i),console.groupEnd(),"blue-marble"==o&&a&&r&&!s){const t=i.get(a);"function"==typeof t?t(r):function(...t){(0,console.warn)(...t)}(`%c${e}%c: Attempted to retrieve a blob (%s) from queue, but the blobID was not a function! Skipping...`,n,"",a),i.delete(a)}});const o=window.fetch;window.fetch=async function(...t){const s=await o.apply(this,t),a=s.clone(),r=(t[0]instanceof Request?t[0]?.url:t[0])||"ignore",l=a.headers.get("content-type")||"";if(l.includes("application/json"))console.log(`%c${e}%c: Sending JSON message about endpoint "${r}"`,n,""),a.json().then(t=>{window.postMessage({source:"blue-marble",endpoint:r,jsonData:t},"*")}).catch(t=>{console.error(`%c${e}%c: Failed to parse JSON: `,n,"",t)});else if(l.includes("image/")&&!r.includes("openfreemap")&&!r.includes("maps")){const t=Date.now(),o=await a.blob();return console.log(`%c${e}%c: ${i.size} Sending IMAGE message about endpoint "${r}"`,n,""),new Promise(s=>{const l=crypto.randomUUID();i.set(l,t=>{s(new Response(t,{headers:a.headers,status:a.status,statusText:a.statusText})),console.log(`%c${e}%c: ${i.size} Processed blob "${l}"`,n,"")}),window.postMessage({source:"blue-marble",endpoint:r,blobID:l,blobData:o,blink:t})}).catch(o=>{const s=Date.now();console.error(`%c${e}%c: Failed to Promise blob!`,n,""),console.groupCollapsed(`%c${e}%c: Details of failed blob Promise:`,n,""),console.log(`Endpoint: ${r}\nThere are ${i.size} blobs processing...\nBlink: ${t.toLocaleString()}\nTime Since Blink: ${String(Math.floor(s/6e4)).padStart(2,"0")}:${String(Math.floor(s/1e3)%60).padStart(2,"0")}.${String(s%1e3).padStart(3,"0")} MM:SS.mmm`),console.error("Exception stack:",o),console.groupEnd()})}return s}});var y=GM_getResourceText("CSS-BM-File");GM_addStyle(y);var v=document.createElement("link");v.href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap",v.rel="preload",v.as="style",v.onload=function(){this.onload=null,this.rel="stylesheet"},document.head?.appendChild(v),new class{constructor(){this.K=null,this.Z=null,this.tt="#bm-h"}et(t){return this.Z=t,this.K=new MutationObserver(t=>{for(const e of t)for(const t of e.addedNodes)t instanceof HTMLElement&&t.matches?.(this.tt)}),this}nt(){return this.K}observe(t,e=!1,n=!1){t.observe(this.Z,{childList:e,subtree:n})}};var x=new s(w,g),$=(new s(w,g),new class{constructor(t,e,n){i(this,m),this.name=t,this.version=e,this.o=n,this.it="1.0.0",this.ot=null,this.st="!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~",this.H=1e3,this.rt=3,this.lt=null,this.ct=null,this.ht="bm-C",this.ut="div#map canvas.maplibregl-canvas",this.dt=null,this.bt="",this.q=[],this.R=null,this.ft=!0}wt(){if(document.body.contains(this.lt))return this.lt;document.getElementById(this.ht)?.remove();const t=document.querySelector(this.ut),e=document.createElement("canvas");return e.id=this.ht,e.className="maplibregl-canvas",e.style.position="absolute",e.style.top="0",e.style.left="0",e.style.height=t?.clientHeight*(window.devicePixelRatio||1)+"px",e.style.width=t?.clientWidth*(window.devicePixelRatio||1)+"px",e.height=t?.clientHeight*(window.devicePixelRatio||1),e.width=t?.clientWidth*(window.devicePixelRatio||1),e.style.zIndex="8999",e.style.pointerEvents="none",t?.parentElement?.appendChild(e),this.lt=e,window.addEventListener("move",this.gt),window.addEventListener("zoom",this.yt),window.addEventListener("resize",this.vt),this.lt}async xt(){return{whoami:this.name.replace(" ",""),scriptVersion:this.version,schemaVersion:this.it,templates:{}}}async $t(t,e,n){this.R||(this.R=await this.xt(),console.log("Creating JSON...")),this.o.G(`Creating template at ${n.join(", ")}...`);const i=new f({displayName:e,J:0,U:r(this.ot||0,this.st),file:t,coords:n}),{A:s,F:a}=await i.V(this.H);i.j=s,this.R.templates[`${i.J} ${i.U}`]={name:i.displayName,coords:n.join(", "),enabled:!0,tiles:a},this.q=[],this.q.push(i),this.o.G(`Template created at ${n.join(", ")}!`),console.log(Object.keys(this.R.templates).length),console.log(this.R),console.log(this.q),console.log(JSON.stringify(this.R)),await o(this,m,h).call(this)}St(){}async Mt(){this.R||(this.R=await this.xt(),console.log("Creating JSON..."))}async Tt(t,e){if(!this.ft)return t;const n=this.H*this.rt;e=e[0].toString().padStart(4,"0")+","+e[1].toString().padStart(4,"0"),console.log(`Searching for templates in tile: "${e}"`);const i=this.q;console.log(i),i.sort((t,e)=>t.J-e.J),console.log(i);const o=i.map(t=>{const n=Object.keys(t.j).filter(t=>t.startsWith(e));if(0===n.length)return null;const i=n.map(e=>{const n=e.split(",");return{Ot:t.j[e],Ct:[n[0],n[1]],Nt:[n[2],n[3]]}});return i?.[0]}).filter(Boolean);console.log(o);const s=o?.length||0;if(console.log(`templateCount = ${s}`),s>0){const t=i.filter(t=>Object.keys(t.j).filter(t=>t.startsWith(e)).length>0).reduce((t,e)=>t+(e.L||0),0),n=(new Intl.NumberFormat).format(t);this.o.G(`Displaying ${s} template${1==s?"":"s"}.\nTotal pixels: ${n}`)}else this.o.G(`Displaying ${s} templates.`);const a=await createImageBitmap(t),r=new OffscreenCanvas(n,n),l=r.getContext("2d");l.imageSmoothingEnabled=!1,l.beginPath(),l.rect(0,0,n,n),l.clip(),l.clearRect(0,0,n,n),l.drawImage(a,0,0,n,n);for(const t of o)return console.log("Template:"),console.log(t),l.drawImage(t.Ot,Number(t.Nt[0])*this.rt,Number(t.Nt[1])*this.rt),await r.convertToBlob({type:"image/png"})}Dt(t){console.log("Importing JSON..."),console.log(t),"BlueMarble"==t?.whoami&&o(this,m,u).call(this,t)}kt(t){this.ft=t}}(w,g,x)),S=new class{constructor(t){i(this,d),this.It=t,this.Bt=!1,this.Wt=[],this._t=[]}Pt(t){window.addEventListener("message",async e=>{const n=e.data,i=n.jsonData;if(!n||"blue-marble"!==n.source)return;if(!n.endpoint)return;const o=n.endpoint?.split("?")[0].split("/").filter(t=>t&&isNaN(Number(t))).filter(t=>t&&!t.includes(".")).pop();switch(console.log('%cBlue Marble%c: Recieved message about "%s"',"color: cornflowerblue;","",o),o){case"me":if(i.status&&"2"!=i.status?.toString()[0])return void t.X("You are not logged in!\nCould not fetch userdata.");const e=Math.ceil(Math.pow(Math.floor(i.level)*Math.pow(30,.65),1/.65)-i.pixelsPainted);console.log(i.id),(i.id||0===i.id)&&console.log(r(i.id,"!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~")),this.It.ot=i.id,t.I("bm-u",`Username: ${function(t){const e=document.createElement("div");return e.textContent=t,e.innerHTML}(i.name)}`),t.I("bm-p",`Droplets: ${(new Intl.NumberFormat).format(i.droplets)}`),t.I("bm-i",`Next level in ${(new Intl.NumberFormat).format(e)} pixel${1==e?"":"s"}`);break;case"pixel":const o=n.endpoint.split("?")[0].split("/").filter(t=>t&&!isNaN(Number(t))),l=new URLSearchParams(n.endpoint.split("?")[1]),c=[l.get("x"),l.get("y")];if(this.Wt.length&&(!o.length||!c.length))return void t.X("Coordinates are malformed!\nDid you try clicking the canvas first?");this.Wt=[...o,...c];const m=(s=o,a=c,[parseInt(s[0])%4*1e3+parseInt(a[0]),parseInt(s[1])%4*1e3+parseInt(a[1])]),h=document.querySelectorAll("span");for(const t of h)if(t.textContent.trim().includes(`${m[0]}, ${m[1]}`)){let e=document.querySelector("#bm-h");const n=`(Tl X: ${o[0]}, Tl Y: ${o[1]}, Px X: ${c[0]}, Px Y: ${c[1]})`;e?e.textContent=n:(e=document.createElement("span"),e.id="bm-h",e.textContent=n,e.style="margin-left: calc(var(--spacing)*3); font-size: small;",t.parentNode.parentNode.insertAdjacentElement("afterend",e))}break;case"tiles":let u=n.endpoint.split("/");u=[parseInt(u[u.length-2]),parseInt(u[u.length-1].replace(".png",""))];const d=n.blobID,b=n.blobData,p=await this.It.Tt(b,u);window.postMessage({source:"blue-marble",blobID:d,blobData:p,blink:n.blink});break;case"robots":this.Bt="false"==i.userscript?.toString().toLowerCase();break}var s,a})}async Xt(t){console.log("Sending heartbeat to telemetry server...");let e=GM_getValue("bmUserSettings","{}");if(e=JSON.parse(e),!e||!e.telemetry||!e.uuid)return void console.log("Telemetry is disabled, not sending heartbeat.");const n=navigator.userAgent;let i=await o(this,d,b).call(this,n),s=o(this,d,p).call(this,n);GM_xmlhttpRequest({method:"POST",url:"https://telemetry.thebluecorner.net/heartbeat",headers:{"Content-Type":"application/json"},data:JSON.stringify({uuid:e.uuid,version:t,browser:i,os:s}),onload:t=>{200!==t.status&&a("Failed to send heartbeat:",t.statusText)},onerror:t=>{a("Error sending heartbeat:",t)}})}}($);x.h(S);var M=JSON.parse(GM_getValue("bmTemplates","{}"));console.log(M),$.Dt(M);var T=JSON.parse(GM_getValue("bmUserSettings","{}"));if(console.log(T),console.log(Object.keys(T).length),0==Object.keys(T).length){const t=crypto.randomUUID();console.log(t),GM.setValue("bmUserSettings",JSON.stringify({uuid:t}))}if(setInterval(()=>S.Xt(g),18e5),console.log(`Telemetry is ${!(null==T?.telemetry)}`),null==T?.telemetry||T?.telemetry>1){const t=new s(w,g);t.h(S),t.v({id:"bm-d",style:"top: 0px; left: 0px; width: 100vw; max-width: 100vw; height: 100vh; max-height: 100vh; z-index: 9999;"}).v({id:"bm-7",style:"display: flex; flex-direction: column; align-items: center;"}).v({id:"bm-1",style:"margin-top: 10%;"}).T(1,{textContent:`${w} Telemetry`}).u().u().v({id:"bm-e",style:"max-width: 50%; overflow-y: auto; max-height: 80vh;"}).O().u().C().u().v({style:"width: fit-content; margin: auto; text-align: center;"}).D({id:"bm-8",textContent:"More Information"},(t,e)=>{e.onclick=()=>{window.open("https://github.com/SwingTheVine/Wplace-TelemetryServer#telemetry-data","_blank","noopener noreferrer")}}).u().u().C().u().v({style:"width: fit-content; margin: auto; text-align: center;"}).D({id:"bm-5",textContent:"Enable Telemetry",style:"margin-right: 2ch;"},(t,e)=>{e.onclick=()=>{const t=JSON.parse(GM_getValue("bmUserSettings","{}"));t.telemetry=1,GM.setValue("bmUserSettings",JSON.stringify(t));const e=document.getElementById("bm-d");e&&(e.style.display="none")}}).u().D({id:"bm-2",textContent:"Disable Telemetry"},(t,e)=>{e.onclick=()=>{const t=JSON.parse(GM_getValue("bmUserSettings","{}"));t.telemetry=0,GM.setValue("bmUserSettings",JSON.stringify(t));const e=document.getElementById("bm-d");e&&(e.style.display="none")}}).u().u().C().u().$({textContent:"We collect anonymous telemetry data such as your browser, OS, and script version to make the experience better for everyone. The data is never shared personally. The data is never sold. You can turn this off by pressing the 'Disable' button, but keeping it on helps us improve features and reliability faster. Thank you for supporting the Blue Marble!"}).u().$({textContent:'You can disable telemetry by pressing the "Disable" button below.'}).u().u().u().p(document.body)}!function(){let t=!1;x.v({id:"bm-A",style:"top: 10px; right: 75px;"}).v({id:"bm-j"}).v({id:"bm-z"}).u().M({alt:"Blue Marble Icon - Click to minimize/maximize",src:"https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/assets/Favicon.png",style:"cursor: pointer;"},(e,n)=>{n.addEventListener("click",()=>{t=!t;const i=document.querySelector("#bm-A"),o=document.querySelector("#bm-j"),s=document.querySelector("#bm-z"),a=document.querySelector("#bm-k"),r=document.querySelector("#bm-q"),l=document.querySelector("#bm-r"),c=document.querySelector("#bm-s"),m=document.querySelector("#bm-l"),h=document.querySelectorAll("#bm-k input");t||(i.style.width="auto",i.style.maxWidth="300px",i.style.minWidth="200px",i.style.padding="10px"),["#bm-A h1","#bm-f","#bm-A hr","#bm-c > *:not(#bm-k)","#bm-a","#bm-6",`#${e.i}`].forEach(e=>{document.querySelectorAll(e).forEach(e=>{e.style.display=t?"none":""})}),t?(a&&(a.style.display="none"),r&&(r.style.display="none"),l&&(l.style.display="none"),c&&(c.style.display="none"),m&&(m.style.display="none"),h.forEach(t=>{t.style.display="none"}),i.style.width="60px",i.style.height="76px",i.style.maxWidth="60px",i.style.minWidth="60px",i.style.padding="8px",n.style.marginLeft="3px",o.style.textAlign="center",o.style.margin="0",o.style.marginBottom="0",s&&(s.style.display="",s.style.marginBottom="0.25em")):(a&&(a.style.display="",a.style.flexDirection="",a.style.justifyContent="",a.style.alignItems="",a.style.gap="",a.style.textAlign="",a.style.margin=""),r&&(r.style.display=""),l&&(l.style.display="",l.style.marginTop=""),c&&(c.style.display="",c.style.marginTop=""),m&&(m.style.display="",m.style.marginTop=""),h.forEach(t=>{t.style.display=""}),n.style.marginLeft="",i.style.padding="10px",o.style.textAlign="",o.style.margin="",o.style.marginBottom="",s&&(s.style.marginBottom="0.5em"),i.style.width="",i.style.height=""),n.alt=t?"Blue Marble Icon - Minimized (Click to maximize)":"Blue Marble Icon - Maximized (Click to minimize)"})}).u().T(1,{textContent:w}).u().u().O().u().v({id:"bm-f"}).$({id:"bm-u",textContent:"Username:"}).u().$({id:"bm-p",textContent:"Droplets:"}).u().$({id:"bm-i",textContent:"Next level in..."}).u().u().O().u().v({id:"bm-c"}).v({id:"bm-k"}).D({id:"bm-q",className:"bm-D",style:"margin-top: 0;",innerHTML:''},(t,e)=>{e.onclick=()=>{const e=t.t?.Wt;e?.[0]?(t.I("bm-v",e?.[0]||""),t.I("bm-w",e?.[1]||""),t.I("bm-x",e?.[2]||""),t.I("bm-y",e?.[3]||"")):t.X("Coordinates are malformed! Did you try clicking on the canvas first?")}}).u().B({type:"number",id:"bm-v",placeholder:"Tl X",min:0,max:2047,step:1,required:!0},(t,e)=>{e.addEventListener("paste",t=>{let e=(t.clipboardData||window.clipboardData).getData("text").split(" ").filter(t=>t).map(Number).filter(t=>!isNaN(t));if(4!==e.length)return;let n=selectAllCoordinateInputs(document);for(let t=0;tpersistCoords();e.addEventListener("input",n),e.addEventListener("change",n)}).u().B({type:"number",id:"bm-w",placeholder:"Tl Y",min:0,max:2047,step:1,required:!0},(t,e)=>{const n=()=>persistCoords();e.addEventListener("input",n),e.addEventListener("change",n)}).u().B({type:"number",id:"bm-x",placeholder:"Px X",min:0,max:2047,step:1,required:!0},(t,e)=>{const n=()=>persistCoords();e.addEventListener("input",n),e.addEventListener("change",n)}).u().B({type:"number",id:"bm-y",placeholder:"Px Y",min:0,max:2047,step:1,required:!0},(t,e)=>{const n=()=>persistCoords();e.addEventListener("input",n),e.addEventListener("change",n)}).u().u().W({id:"bm-a",textContent:"Upload Template",accept:"image/png, image/jpeg, image/webp, image/bmp, image/gif"}).u().v({id:"bm-4"}).D({id:"bm-s",textContent:"Enable"},(t,e)=>{e.onclick=()=>{t.t?.It?.kt(!0),t.G("Enabled templates!")}}).u().D({id:"bm-r",textContent:"Create"},(t,e)=>{e.onclick=()=>{const e=document.querySelector("#bm-a"),n=document.querySelector("#bm-v");if(!n.checkValidity())return n.reportValidity(),void t.X("Coordinates are malformed! Did you try clicking on the canvas first?");const i=document.querySelector("#bm-w");if(!i.checkValidity())return i.reportValidity(),void t.X("Coordinates are malformed! Did you try clicking on the canvas first?");const o=document.querySelector("#bm-x");if(!o.checkValidity())return o.reportValidity(),void t.X("Coordinates are malformed! Did you try clicking on the canvas first?");const s=document.querySelector("#bm-y");if(!s.checkValidity())return s.reportValidity(),void t.X("Coordinates are malformed! Did you try clicking on the canvas first?");e?.files[0]?($.$t(e.files[0],e.files[0]?.name.replace(/\.[^/.]+$/,""),[Number(n.value),Number(i.value),Number(o.value),Number(s.value)]),t.G("Drew to canvas!")):t.X("No file selected!")}}).u().D({id:"bm-l",textContent:"Disable"},(t,e)=>{e.onclick=()=>{t.t?.It?.kt(!1),t.G("Disabled templates!")}}).u().u()._({id:x.i,placeholder:`Status: Sleeping...\nVersion: ${g}`,readOnly:!0}).u().v({id:"bm-6"}).v().D({id:"bm-m",className:"bm-D",innerHTML:"🎨",title:"Template Color Converter"},(t,e)=>{e.addEventListener("click",()=>{window.open("https://pepoafonso.github.io/color_converter_wplace/","_blank","noopener noreferrer")})}).u().D({id:"bm-n",className:"bm-D",innerHTML:"🌐",title:"Official Blue Marble Website"},(t,e)=>{e.addEventListener("click",()=>{window.open("https://bluemarble.lol/","_blank","noopener noreferrer")})}).u().u().S({textContent:"Made by SwingTheVine",style:"margin-top: auto;"}).u().u().u().p(document.body)}(),x.P("#bm-A","#bm-z"),S.Pt(x),new MutationObserver((t,e)=>{const n=document.querySelector("#color-1");if(!n)return;let i=document.querySelector("#bm-t");if(!i){i=document.createElement("button"),i.id="bm-t",i.textContent="Move ↑",i.className="btn btn-soft",i.onclick=function(){const t=this.parentNode.parentNode.parentNode.parentNode,e="Move ↑"==this.textContent;t.parentNode.className=t.parentNode.className.replace(e?"bottom":"top",e?"top":"bottom"),t.style.borderTopLeftRadius=e?"0px":"var(--radius-box)",t.style.borderTopRightRadius=e?"0px":"var(--radius-box)",t.style.borderBottomLeftRadius=e?"var(--radius-box)":"0px",t.style.borderBottomRightRadius=e?"var(--radius-box)":"0px",this.textContent=e?"Move ↓":"Move ↑"};const t=n.parentNode.parentNode.parentNode.parentNode.querySelector("h2");t.parentNode?.appendChild(i)}}).observe(document.body,{childList:!0,subtree:!0}),function(...t){(0,console.log)(...t)}(`%c${w}%c (${g}) userscript has loaded!`,"color: cornflowerblue;","")})(); \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index 4ee784b..1225e37 100644 --- a/docs/README.md +++ b/docs/README.md @@ -51,7 +51,7 @@ Contact Me Blue Marble Website WakaTime -Total Patches +Total Patches Total Lines of Code Total Comments Compression diff --git a/package-lock.json b/package-lock.json index 2eee195..c2cedbf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wplace-bluemarble", - "version": "0.88.0", + "version": "0.88.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "wplace-bluemarble", - "version": "0.88.0", + "version": "0.88.3", "devDependencies": { "esbuild": "^0.25.0", "jsdoc": "^4.0.5", diff --git a/package.json b/package.json index 09e44e8..9b101dd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wplace-bluemarble", - "version": "0.88.0", + "version": "0.88.3", "type": "module", "homepage": "https://bluemarble.lol/", "repository": { diff --git a/src/BlueMarble.meta.js b/src/BlueMarble.meta.js index a28c273..a9f6ea9 100644 --- a/src/BlueMarble.meta.js +++ b/src/BlueMarble.meta.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Blue Marble // @namespace https://github.com/SwingTheVine/ -// @version 0.88.0 +// @version 0.88.3 // @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. // @author SwingTheVine // @license MPL-2.0 diff --git a/src/main.js b/src/main.js index 21602f2..c51db6c 100644 --- a/src/main.js +++ b/src/main.js @@ -498,7 +498,7 @@ function buildOverlayMain() { } } ).buildElement() - .addInput({'type': 'number', 'id': 'bm-input-tx', 'placeholder': 'Tl X', 'min': 0, 'max': 2047, 'step': 1, 'required': true, 'value': (savedCoords.tx ?? '')}, (instance, input) => { + .addInput({'type': 'number', 'id': 'bm-input-tx', 'placeholder': 'Tl X', 'min': 0, 'max': 2047, 'step': 1, 'required': true}, (instance, input) => { //if a paste happens on tx, split and format it into other coordinates if possible input.addEventListener("paste", (event) => { let splitText = (event.clipboardData || window.clipboardData).getData("text").split(" ").filter(n => n).map(Number).filter(n => !isNaN(n)); //split and filter all Non Numbers @@ -519,17 +519,17 @@ function buildOverlayMain() { input.addEventListener('input', handler); input.addEventListener('change', handler); }).buildElement() - .addInput({'type': 'number', 'id': 'bm-input-ty', 'placeholder': 'Tl Y', 'min': 0, 'max': 2047, 'step': 1, 'required': true, 'value': (savedCoords.ty ?? '')}, (instance, input) => { + .addInput({'type': 'number', 'id': 'bm-input-ty', 'placeholder': 'Tl Y', 'min': 0, 'max': 2047, 'step': 1, 'required': true}, (instance, input) => { const handler = () => persistCoords(); input.addEventListener('input', handler); input.addEventListener('change', handler); }).buildElement() - .addInput({'type': 'number', 'id': 'bm-input-px', 'placeholder': 'Px X', 'min': 0, 'max': 2047, 'step': 1, 'required': true, 'value': (savedCoords.px ?? '')}, (instance, input) => { + .addInput({'type': 'number', 'id': 'bm-input-px', 'placeholder': 'Px X', 'min': 0, 'max': 2047, 'step': 1, 'required': true}, (instance, input) => { const handler = () => persistCoords(); input.addEventListener('input', handler); input.addEventListener('change', handler); }).buildElement() - .addInput({'type': 'number', 'id': 'bm-input-py', 'placeholder': 'Px Y', 'min': 0, 'max': 2047, 'step': 1, 'required': true, 'value': (savedCoords.py ?? '')}, (instance, input) => { + .addInput({'type': 'number', 'id': 'bm-input-py', 'placeholder': 'Px Y', 'min': 0, 'max': 2047, 'step': 1, 'required': true}, (instance, input) => { const handler = () => persistCoords(); input.addEventListener('input', handler); input.addEventListener('change', handler); diff --git a/src/templateManager.js b/src/templateManager.js index 9bdfc8e..616877f 100644 --- a/src/templateManager.js +++ b/src/templateManager.js @@ -61,7 +61,6 @@ export default class TemplateManager { this.templatesArray = []; // All Template instnaces currently loaded (Template) this.templatesJSON = null; // All templates currently loaded (JSON) this.templatesShouldBeDrawn = true; // Should ALL templates be drawn to the canvas? - this.tileProgress = new Map(); // Tracks per-tile progress stats {painted, required, wrong} } /** Retrieves the pixel art canvas. @@ -144,32 +143,17 @@ export default class TemplateManager { // Appends a child into the templates object // The child's name is the number of templates already in the list (sort order) plus the encoded player ID - const storageKey = `${template.sortID} ${template.authorID}`; - template.storageKey = storageKey; - this.templatesJSON.templates[storageKey] = { + this.templatesJSON.templates[`${template.sortID} ${template.authorID}`] = { "name": template.displayName, // Display name of template "coords": coords.join(', '), // The coords of the template "enabled": true, - "tiles": templateTilesBuffers, // Stores the chunked tile buffers - "palette": template.colorPalette // Persist palette and enabled flags + "tiles": templateTilesBuffers // Stores the chunked tile buffers }; this.templatesArray = []; // Remove this to enable multiple templates (2/2) this.templatesArray.push(template); // Pushes the Template object instance to the Template Array - // ==================== PIXEL COUNT DISPLAY SYSTEM ==================== - // Display pixel count statistics with internationalized number formatting - // This provides immediate feedback to users about template complexity and size - const pixelCountFormatted = new Intl.NumberFormat().format(template.pixelCount); - this.overlay.handleDisplayStatus(`Template created at ${coords.join(', ')}! Total pixels: ${pixelCountFormatted}`); - - // Ensure color filter UI is visible when a template is created - try { - const colorUI = document.querySelector('#bm-contain-colorfilter'); - if (colorUI) { colorUI.style.display = ''; } - // Deferred palette list rendering; actual DOM is built in main via helper - window.postMessage({ source: 'blue-marble', bmEvent: 'bm-rebuild-color-list' }, '*'); - } catch (_) { /* no-op */ } + this.overlay.handleDisplayStatus(`Template created at ${coords.join(', ')}!`); console.log(Object.keys(this.templatesJSON.templates).length); console.log(this.templatesJSON); @@ -235,18 +219,6 @@ export default class TemplateManager { console.log(templateArray); - // Early exit if none of the active templates touch this tile - const anyTouches = templateArray.some(t => { - if (!t?.chunked) { return false; } - // Fast path via recorded tile prefixes if available - if (t.tilePrefixes && t.tilePrefixes.size > 0) { - return t.tilePrefixes.has(tileCoords); - } - // Fallback: scan chunked keys - return Object.keys(t.chunked).some(k => k.startsWith(tileCoords)); - }); - if (!anyTouches) { return tileBlob; } - // Retrieves the relavent template tile blobs const templatesToDraw = templateArray .map(template => { @@ -277,10 +249,31 @@ export default class TemplateManager { const templateCount = templatesToDraw?.length || 0; // Number of templates to draw on this tile console.log(`templateCount = ${templateCount}`); - // We'll compute per-tile painted/wrong/required counts when templates exist for this tile - let paintedCount = 0; - let wrongCount = 0; - let requiredCount = 0; + if (templateCount > 0) { + + // Calculate total pixel count for templates actively being displayed in this tile + const totalPixels = templateArray + .filter(template => { + // Filter templates to include only those with tiles matching current coordinates + // This ensures we count pixels only for templates actually being rendered + const matchingTiles = Object.keys(template.chunked).filter(tile => + tile.startsWith(tileCoords) + ); + return matchingTiles.length > 0; + }) + .reduce((sum, template) => sum + (template.pixelCount || 0), 0); + + // Format pixel count with locale-appropriate thousands separators for better readability + // Examples: "1,234,567" (US), "1.234.567" (DE), "1 234 567" (FR) + const pixelCountFormatted = new Intl.NumberFormat().format(totalPixels); + + // Display status information about the templates being rendered + this.overlay.handleDisplayStatus( + `Displaying ${templateCount} template${templateCount == 1 ? '' : 's'}.\nTotal pixels: ${pixelCountFormatted}` + ); + } else { + this.overlay.handleDisplayStatus(`Displaying ${templateCount} templates.`); + } const tileBitmap = await createImageBitmap(tileBlob); @@ -297,234 +290,16 @@ export default class TemplateManager { context.clearRect(0, 0, drawSize, drawSize); // Draws transparent background context.drawImage(tileBitmap, 0, 0, drawSize, drawSize); - // Grab a snapshot of the tile pixels BEFORE we draw any template overlays - let tilePixels = null; - try { - tilePixels = context.getImageData(0, 0, drawSize, drawSize).data; - } catch (_) { - // If reading fails for any reason, we will skip stats - } - // For each template in this tile, draw them. for (const template of templatesToDraw) { console.log(`Template:`); console.log(template); - // Compute stats by sampling template center pixels against tile pixels, - // honoring color enable/disable from the active template's palette - if (tilePixels) { - try { - - const tempWidth = template.bitmap.width; - const tempHeight = template.bitmap.height; - const tempCanvas = new OffscreenCanvas(tempWidth, tempHeight); - const tempContext = tempCanvas.getContext('2d', { willReadFrequently: true }); - tempContext.imageSmoothingEnabled = false; - tempContext.clearRect(0, 0, tempWidth, tempHeight); - tempContext.drawImage(template.bitmap, 0, 0); - const tImg = tempContext.getImageData(0, 0, tempWidth, tempHeight); - const tData = tImg.data; // Tile Data, Template Data, or Temp Data???? + // Draws the each template on the tile based on it's relative position + context.drawImage(template.bitmap, Number(template.pixelCoords[0]) * this.drawMult, Number(template.pixelCoords[1]) * this.drawMult); - const offsetX = Number(template.pixelCoords[0]) * this.drawMult; - const offsetY = Number(template.pixelCoords[1]) * this.drawMult; - - // Loops over all pixels in the template - // Assigns each pixel a color (if center pixel) - for (let y = 0; y < tempHeight; y++) { - for (let x = 0; x < tempWidth; x++) { - // Purpose: Count which pixels are painted correctly??? - - // Only evaluate the center pixel of each shread block - // Skip if not the center pixel of the shread block - if ((x % this.drawMult) !== 1 || (y % this.drawMult) !== 1) { continue; } - - const gx = x + offsetX; - const gy = y + offsetY; - - // IF the pixel is out of bounds of the template, OR if the pixel is outside of the tile, then skip the pixel - if (gx < 0 || gy < 0 || gx >= drawSize || gy >= drawSize) { continue; } - - const templatePixelCenter = (y * tempWidth + x) * 4; // Shread block center pixel - const templatePixelCenterRed = tData[templatePixelCenter]; // Shread block's center pixel's RED value - const templatePixelCenterGreen = tData[templatePixelCenter + 1]; // Shread block's center pixel's GREEN value - const templatePixelCenterBlue = tData[templatePixelCenter + 2]; // Shread block's center pixel's BLUE value - const templatePixelCenterAlpha = tData[templatePixelCenter + 3]; // Shread block's center pixel's ALPHA value - - // Possibly needs to be removed - // Handle template transparent pixel (alpha < 64): wrong if board has any site palette color here - // If the alpha of the center pixel is less than 64... - if (templatePixelCenterAlpha < 64) { - try { - const activeTemplate = this.templatesArray?.[0]; - const tileIdx = (gy * drawSize + gx) * 4; - const pr = tilePixels[tileIdx]; - const pg = tilePixels[tileIdx + 1]; - const pb = tilePixels[tileIdx + 2]; - const pa = tilePixels[tileIdx + 3]; - - const key = activeTemplate.allowedColorsSet.has(`${pr},${pg},${pb}`) ? `${pr},${pg},${pb}` : 'other'; - - const isSiteColor = activeTemplate?.allowedColorsSet ? activeTemplate.allowedColorsSet.has(key) : false; - - // IF the alpha of the center pixel that is placed on the canvas is greater than or equal to 64, AND the pixel is a Wplace palette color, then it is incorrect. - if (pa >= 64 && isSiteColor) { - wrongCount++; - } - } catch (ignored) {} - - continue; // Continue to the next pixel - } - - // Treat #deface as Transparent palette color (required and paintable) - // Ignore non-palette colors (match against allowed set when available) for counting required template pixels - // try { - - // const activeTemplate = this.templatesArray?.[0]; // Get the first template - - // // IF the stored palette data exists, AND the pixel is not in the allowed palette - // if (activeTemplate?.allowedColorsSet && !activeTemplate.allowedColorsSet.has(`${templatePixelCenterRed},${templatePixelCenterGreen},${templatePixelCenterBlue}`)) { - - // continue; // Skip this pixel if it is not in the allowed palette - // } - // } catch (ignored) {} - - requiredCount++; - - // Strict center-pixel matching. Treat transparent tile pixels as unpainted (not wrong) - const realPixelCenter = (gy * drawSize + gx) * 4; - const realPixelRed = tilePixels[realPixelCenter]; - const realPixelCenterGreen = tilePixels[realPixelCenter + 1]; - const realPixelCenterBlue = tilePixels[realPixelCenter + 2]; - const realPixelCenterAlpha = tilePixels[realPixelCenter + 3]; - - // IF the alpha of the pixel is less than 64... - if (realPixelCenterAlpha < 64) { - // Unpainted -> neither painted nor wrong - - // ELSE IF the pixel matches the template center pixel color - } else if (realPixelRed === templatePixelCenterRed && realPixelCenterGreen === templatePixelCenterGreen && realPixelCenterBlue === templatePixelCenterBlue) { - paintedCount++; // ...the pixel is painted correctly - } else { - wrongCount++; // ...the pixel is NOT painted correctly - } - } - } - } catch (exception) { - console.warn('Failed to compute per-tile painted/wrong stats:', exception); - } - } - - // Draw the template overlay for visual guidance, honoring color filter - try { - - const activeTemplate = this.templatesArray?.[0]; // Get the first template - const palette = activeTemplate?.colorPalette || {}; // Obtain the color palette of the template - const hasDisabled = Object.values(palette).some(v => v?.enabled === false); // Check if any color is disabled - - // If none of the template colors are disabled, then draw the image normally - if (!hasDisabled) { - context.drawImage(template.bitmap, Number(template.pixelCoords[0]) * this.drawMult, Number(template.pixelCoords[1]) * this.drawMult); - } else { - // ELSE we need to apply the color filter - - console.log('Applying color filter...'); - - const tempW = template.bitmap.width; - const tempH = template.bitmap.height; - - const filterCanvas = new OffscreenCanvas(tempW, tempH); - const filterCtx = filterCanvas.getContext('2d', { willReadFrequently: true }); - filterCtx.imageSmoothingEnabled = false; // Nearest neighbor - filterCtx.clearRect(0, 0, tempW, tempH); - filterCtx.drawImage(template.bitmap, 0, 0); - - const img = filterCtx.getImageData(0, 0, tempW, tempH); - const data = img.data; - - // For every pixel... - for (let y = 0; y < tempH; y++) { - for (let x = 0; x < tempW; x++) { - - // If this pixel is NOT the center pixel, then skip the pixel - if ((x % this.drawMult) !== 1 || (y % this.drawMult) !== 1) { continue; } - - const idx = (y * tempW + x) * 4; - const r = data[idx]; - const g = data[idx + 1]; - const b = data[idx + 2]; - const a = data[idx + 3]; - - if (a < 1) { continue; } - - let key = activeTemplate.allowedColorsSet.has(`${r},${g},${b}`) ? `${r},${g},${b}` : 'other'; - - // Hide if color is not in allowed palette or explicitly disabled - const inWplacePalette = activeTemplate?.allowedColorsSet ? activeTemplate.allowedColorsSet.has(key) : true; - - // if (inWplacePalette) { - // key = 'other'; // Map all non-palette colors to "other" - // console.log('Added color to other'); - // } - - const isPaletteColorEnabled = palette?.[key]?.enabled !== false; - if (!inWplacePalette || !isPaletteColorEnabled) { - data[idx + 3] = 0; // hide disabled color center pixel - } - } - } - - // Draws the template with somes colors disabled - filterCtx.putImageData(img, 0, 0); - context.drawImage(filterCanvas, Number(template.pixelCoords[0]) * this.drawMult, Number(template.pixelCoords[1]) * this.drawMult); - } - } catch (exception) { - - // If filtering fails, we can log the error or handle it accordingly - console.warn('Failed to apply color filter:', exception); - - // Fallback to drawing raw bitmap if filtering fails - context.drawImage(template.bitmap, Number(template.pixelCoords[0]) * this.drawMult, Number(template.pixelCoords[1]) * this.drawMult); - } + return await canvas.convertToBlob({ type: 'image/png' }); } - - // Save per-tile stats and compute global aggregates across all processed tiles - if (templateCount > 0) { - const tileKey = tileCoords; // already padded string "xxxx,yyyy" - this.tileProgress.set(tileKey, { - painted: paintedCount, - required: requiredCount, - wrong: wrongCount, - }); - - // Aggregate painted/wrong across tiles we've processed - let aggPainted = 0; - let aggRequiredTiles = 0; - let aggWrong = 0; - for (const stats of this.tileProgress.values()) { - aggPainted += stats.painted || 0; - aggRequiredTiles += stats.required || 0; - aggWrong += stats.wrong || 0; - } - - // Determine total required across all templates - // Prefer precomputed per-template required counts; fall back to sum of processed tiles - const totalRequiredTemplates = this.templatesArray.reduce((sum, t) => - sum + (t.requiredPixelCount || t.pixelCount || 0), 0); - const totalRequired = totalRequiredTemplates > 0 ? totalRequiredTemplates : aggRequiredTiles; - - // Turns numbers into formatted number strings. E.g., 1234 -> 1,234 OR 1.234 based on location of user - const paintedStr = new Intl.NumberFormat().format(aggPainted); - const requiredStr = new Intl.NumberFormat().format(totalRequired); - const wrongStr = new Intl.NumberFormat().format(totalRequired - aggPainted); // Used to be aggWrong, but that is bugged - - this.overlay.handleDisplayStatus( - `Displaying ${templateCount} template${templateCount == 1 ? '' : 's'}.\nPainted ${paintedStr} / ${requiredStr} • Wrong ${wrongStr}` - ); - } else { - this.overlay.handleDisplayStatus(`Displaying ${templateCount} templates.`); - } - - return await canvas.convertToBlob({ type: 'image/png' }); } /** Imports the JSON object, and appends it to any JSON object already loaded @@ -570,8 +345,6 @@ export default class TemplateManager { //const coords = templateValue?.coords?.split(',').map(Number); // "1,2,3,4" -> [1, 2, 3, 4] const tilesbase64 = templateValue.tiles; const templateTiles = {}; // Stores the template bitmap tiles for each tile. - let requiredPixelCount = 0; // Global required pixel count for this imported template - const paletteMap = new Map(); // Accumulates color counts across tiles (center pixels only) for (const tile in tilesbase64) { console.log(tile); @@ -582,36 +355,6 @@ export default class TemplateManager { const templateBlob = new Blob([templateUint8Array], { type: "image/png" }); // Uint8Array -> Blob const templateBitmap = await createImageBitmap(templateBlob) // Blob -> Bitmap templateTiles[tile] = templateBitmap; - - // Count required pixels in this bitmap (center pixels with alpha >= 64 and not #deface) - try { - const w = templateBitmap.width; - const h = templateBitmap.height; - const c = new OffscreenCanvas(w, h); - const cx = c.getContext('2d', { willReadFrequently: true }); - cx.imageSmoothingEnabled = false; - cx.clearRect(0, 0, w, h); - cx.drawImage(templateBitmap, 0, 0); - const data = cx.getImageData(0, 0, w, h).data; - for (let y = 0; y < h; y++) { - for (let x = 0; x < w; x++) { - // Only count center pixels of 3x blocks - if ((x % this.drawMult) !== 1 || (y % this.drawMult) !== 1) { continue; } - const idx = (y * w + x) * 4; - const r = data[idx]; - const g = data[idx + 1]; - const b = data[idx + 2]; - const a = data[idx + 3]; - if (a < 64) { continue; } - if (r === 222 && g === 250 && b === 206) { continue; } - requiredPixelCount++; - const key = activeTemplate.allowedColorsSet.has(`${r},${g},${b}`) ? `${r},${g},${b}` : 'other'; - paletteMap.set(key, (paletteMap.get(key) || 0) + 1); - } - } - } catch (e) { - console.warn('Failed to count required pixels for imported tile', e); - } } } @@ -623,39 +366,12 @@ export default class TemplateManager { //coords: coords }); template.chunked = templateTiles; - template.requiredPixelCount = requiredPixelCount; - // Construct colorPalette from paletteMap - const paletteObj = {}; - for (const [key, count] of paletteMap.entries()) { paletteObj[key] = { count, enabled: true }; } - template.colorPalette = paletteObj; - // Populate tilePrefixes for fast-scoping - try { Object.keys(templateTiles).forEach(k => { template.tilePrefixes?.add(k.split(',').slice(0,2).join(',')); }); } catch (_) {} - // Merge persisted palette (enabled/disabled) if present - try { - const persisted = templates?.[templateKey]?.palette; - if (persisted) { - for (const [rgb, meta] of Object.entries(persisted)) { - if (!template.colorPalette[rgb]) { - template.colorPalette[rgb] = { count: meta?.count || 0, enabled: !!meta?.enabled }; - } else { - template.colorPalette[rgb].enabled = !!meta?.enabled; - } - } - } - } catch (_) {} - // Store storageKey for later writes - template.storageKey = templateKey; + this.templatesArray.push(template); console.log(this.templatesArray); console.log(`^^^ This ^^^`); } } - // After importing templates from storage, reveal color UI and request palette list build - try { - const colorUI = document.querySelector('#bm-contain-colorfilter'); - if (colorUI) { colorUI.style.display = ''; } - window.postMessage({ source: 'blue-marble', bmEvent: 'bm-rebuild-color-list' }, '*'); - } catch (_) { /* no-op */ } } }