diff --git a/dist/BlueMarble.user.js b/dist/BlueMarble.user.js index 877ff96..f50f9a0 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.72.4 +// @version 0.72.21 // @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 @@ -14,10 +14,12 @@ // @match *://*.wplace.live/* // @grant GM_getResourceText // @grant GM_addStyle +// @grant GM.setValue +// @grant GM_getValue // @resource CSS-BM-File https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/6b6a45cba9943e5960c9887654aa704a90c02636/dist/BlueMarble.user.css // ==/UserScript== // Wplace --> https://wplace.live // License --> https://www.mozilla.org/en-US/MPL/2.0/ -(()=>{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);t=new WeakSet,e=function(t,e={},n={}){const i=document.createElement(t);this.t?(this.i.appendChild(i),this.o.push(this.i),this.i=i):(this.t=i,this.i=i);for(const[t,n]of Object.entries(e))i[t]=n;for(const[t,e]of Object.entries(n))i[t]=e;return i};var s,a=class{constructor({displayName:t="My template",l:e=0,h:n="",url:i="",file:o=null,coords:s=null,m:a=null,u:r=1e3}={}){this.displayName=t,this.l=e,this.h=n,this.url=i,this.file=o,this.coords=s,this.m=a,this.u=r,this.p=0}async $(){console.log("Template coordinates:",this.coords);const t=await createImageBitmap(this.file),e=t.width,n=t.height,i=e*n;console.log(`Template pixel analysis - Dimensions: ${e}×${n} = ${i.toLocaleString()} pixels`),this.p=i;const o={},s=new OffscreenCanvas(this.u,this.u),a=s.getContext("2d",{v:!0});for(let i=this.coords[3];iURL.revokeObjectURL(u),6e4);const d=a.getImageData(0,0,l,h);for(let t=0;t0;)n=e[t%i]+n,t=Math.floor(t/i);return n}s=new WeakSet;var c=GM_info.script.name.toString(),l=GM_info.script.version.toString();!function(t){const e=document.createElement("script");e.setAttribute("bm-o",c),e.setAttribute("bm-m","color: cornflowerblue;"),e.textContent=`(${t})();`,document.documentElement.appendChild(e),e.remove()}(()=>{const t=document.currentScript,e=t?.getAttribute("bm-o")||"Blue Marble",n=t?.getAttribute("bm-m")||"",i=new Map;window.addEventListener("message",t=>{const{source:o,endpoint:s,blobID:a,blobData:r,blink:c}=t.data,l=Date.now()-c;if(console.groupCollapsed(`%c${e}%c: ${i.size} Recieved IMAGE message about blob "${a}"`,n,""),console.log(`Blob fetch took %c${String(Math.floor(l/6e4)).padStart(2,"0")}:${String(Math.floor(l/1e3)%60).padStart(2,"0")}.${String(l%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",c=a.headers.get("content-type")||"";if(c.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(c.includes("image/")&&!r.includes("openfreemap")){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 c=crypto.randomUUID();i.set(c,t=>{s(new Response(t,{headers:a.headers,status:a.status,statusText:a.statusText})),console.log(`%c${e}%c: ${i.size} Processed blob "${c}"`,n,"")}),window.postMessage({source:"blue-marble",endpoint:r,blobID:c,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 h=GM_getResourceText("CSS-BM-File");GM_addStyle(h);var m=document.createElement("link");m.href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap",m.rel="preload",m.as="style",m.onload=function(){this.onload=null,this.rel="stylesheet"},document.head.appendChild(m),new class{constructor(){this.M=null,this.S=null,this.C="#bm-5"}D(t){return this.S=t,this.M=new MutationObserver(t=>{for(const e of t)for(const t of e.addedNodes)t instanceof HTMLElement&&t.matches?.(this.C)}),this}T(){return this.M}observe(t,e=!1,n=!1){t.observe(this.S,{childList:e,subtree:n})}};var u=new class{constructor(e,n){i(this,t),this.name=e,this.version=n,this.k=null,this.I="bm-a",this.t=null,this.i=null,this.o=[]}N(t){this.k=t}O(){return this.o.length>0&&(this.i=this.o.pop()),this}B(t){t.appendChild(this.t),this.t=null,this.i=null,this.o=[]}L(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"div",{},n)),this}P(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"p",{},n)),this}H(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"small",{},n)),this}R(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"img",{},n)),this}F(n,i={},s=()=>{}){return s(this,o(this,t,e).call(this,"h"+n,{},i)),this}Y(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"hr",{},n)),this}j(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"br",{},n)),this}q(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.O(),i(this,s,a),this}G(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"button",{},n)),this}X(n={},i=()=>{}){const s=n.title??n.textContent??"Help: No info";delete n.textContent,n.title=`Help: ${s}`;const a={textContent:"?",className:"bm-p",onclick:()=>{this._(this.I,s)}};return i(this,o(this,t,e).call(this,"button",a,n)),this}A(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"input",{},n)),this}J(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.O();const c=o(this,t,e).call(this,"button",{textContent:s});return this.O(),this.O(),r.setAttribute("tabindex","-1"),r.setAttribute("aria-hidden","true"),c.addEventListener("click",()=>{r.click()}),r.addEventListener("change",()=>{c.style.maxWidth=`${c.offsetWidth}px`,r.files.length>0?c.textContent=r.files[0].name:c.textContent=s}),i(this,a,r,c),this}U(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"textarea",{},n)),this}_(t,e,n=!1){const i=document.getElementById(t.replace(/^#/,""));i&&(i instanceof HTMLInputElement?i.value=e:n?i.textContent=e:i.innerHTML=e)}V(t,e){let n,i=!1,o=0,s=null,a=0,r=0,c=0,l=0;if(t=document.querySelector("#"==t?.[0]?t:"#"+t),e=document.querySelector("#"==e?.[0]?e:"#"+e),!t||!e)return void this.W(`Can not drag! ${t?"":"moveMe"} ${t||e?"":"and "}${e?"":"iMoveThings "}was not found!`);const h=()=>{if(i){const e=Math.abs(a-c),n=Math.abs(r-l);(e>.5||n>.5)&&(a=c,r=l,t.style.transform=`translate(${a}px, ${r}px)`,t.style.left="0px",t.style.top="0px",t.style.right=""),s=requestAnimationFrame(h)}};let m=null;const u=(u,d)=>{i=!0,m=t.getBoundingClientRect(),n=u-m.left,o=d-m.top;const p=window.getComputedStyle(t).transform;if(p&&"none"!==p){const t=new DOMMatrix(p);a=t.m41,r=t.m42}else a=m.left,r=m.top;c=a,l=r,document.body.style.userSelect="none",e.classList.add("dragging"),s&&cancelAnimationFrame(s),h()},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&&m&&(c=t.clientX-n,l=t.clientY-o)},{passive:!0}),document.addEventListener("touchmove",function(t){if(i&&m){const e=t?.touches?.[0];if(!e)return;c=e.clientX-n,l=e.clientY-o,t.preventDefault()}},{passive:!1}),document.addEventListener("mouseup",d),document.addEventListener("touchend",d),document.addEventListener("touchcancel",d)}Z(t){(0,console.info)(`${this.name}: ${t}`),this._(this.I,"Status: "+t,!0)}W(t){(0,console.error)(`${this.name}: ${t}`),this._(this.I,"Error: "+t,!0)}}(c,l),d=new class{constructor(t,e,n){i(this,s),this.name=t,this.version=e,this.t=n,this.K="1.0.0",this.tt=null,this.et="!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~",this.u=1e3,this.nt=3,this.it=null,this.ot=null,this.st="bm-n",this.rt="div#map canvas.maplibregl-canvas",this.ct=null,this.lt="",this.ht=[],this.ut=null}dt(){if(document.body.contains(this.it))return this.it;document.getElementById(this.st)?.remove();const t=document.querySelector(this.rt),e=document.createElement("canvas");return e.id=this.st,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.it=e,window.addEventListener("move",this.bt),window.addEventListener("zoom",this.ft),window.addEventListener("resize",this.wt),this.it}async $t(){return{whoami:this.name.replace(" ",""),scriptVersion:this.version,schemaVersion:this.K,templates:{}}}async gt(t,e,n){this.ut||(this.ut=await this.$t(),console.log("Creating JSON...")),this.t.Z(`Creating template at ${n.join(", ")}...`);const i=new a({displayName:e,l:0,h:r(this.tt||0,this.et),file:t,coords:n});i.m=await i.$(this.u),this.ut.templates[`${i.l} ${i.h}`]={name:i.displayName,enabled:!0,tiles:i.m},this.ht=[],this.ht.push(i);const o=(new Intl.NumberFormat).format(i.p);this.t.Z(`Template created at ${n.join(", ")}! Total pixels: ${o}`),console.log(Object.keys(this.ut.templates).length),console.log(this.ut),console.log(this.ht)}vt(){}async xt(){this.ut||(this.ut=await this.$t(),console.log("Creating JSON..."))}async yt(t,e){const n=this.u*this.nt;e=e[0].toString().padStart(4,"0")+","+e[1].toString().padStart(4,"0"),console.log(`Searching for templates in tile: "${e}"`);const i=this.ht;i.sort((t,e)=>t.l-e.l),console.log(i);const o=i.map(t=>{const n=Object.keys(t.m).filter(t=>t.startsWith(e));if(0===n.length)return null;const i=n.map(e=>{const n=e.split(",");return{Mt:t.m[e],St:[n[0],n[1]],Ct:[n[2],n[3]]}});return i?.[0]}).filter(Boolean);if(console.log(o),o.length>0){const t=i.filter(t=>Object.keys(t.m).filter(t=>t.startsWith(e)).length>0).reduce((t,e)=>t+(e.p||0),0),n=(new Intl.NumberFormat).format(t);this.t.Z(`Displaying ${o.length} template${1==o.length?"":"s"}. Total pixels: ${n}`)}const s=await createImageBitmap(t),a=new OffscreenCanvas(n,n),r=a.getContext("2d");r.imageSmoothingEnabled=!1,r.beginPath(),r.rect(0,0,n,n),r.clip(),r.clearRect(0,0,n,n),r.drawImage(s,0,0,n,n);for(const t of o)console.log("Template:"),console.log(t),console.log(`${Number(t.Ct[0])}, ${Number(t.Ct[1])}`),r.drawImage(t.Mt,Number(t.Ct[0])*this.nt,Number(t.Ct[1])*this.nt);return await a.convertToBlob({type:"image/png"})}Dt(){}}(c,l,u),p=new class{constructor(t){this.Tt=t,this.kt=!1,this.It=[],this.Nt=[]}Ot(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.W("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.Tt.tt=i.id,t._("bm-f",`Username: ${function(t){const e=document.createElement("div");return e.textContent=t,e.innerHTML}(i.name)}`),t._("bm-b",`Droplets: ${(new Intl.NumberFormat).format(i.droplets)}`),t._("bm-6",`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))),c=new URLSearchParams(n.endpoint.split("?")[1]),l=[c.get("x"),c.get("y")];if(this.It.length&&(!o.length||!l.length))return void t.W("Coordinates are malformed!\nDid you try clicking the canvas first?");this.It=[...o,...l];const h=(s=o,a=l,[parseInt(s[0])%4*1e3+parseInt(a[0]),parseInt(s[1])%4*1e3+parseInt(a[1])]),m=document.querySelectorAll("span");for(const t of m)if(t.textContent.trim().includes(`${h[0]}, ${h[1]}`)){let e=document.querySelector("#bm-5");const n=`(Tl X: ${o[0]}, Tl Y: ${o[1]}, Px X: ${l[0]}, Px Y: ${l[1]})`;e?e.textContent=n:(e=document.createElement("span"),e.id="bm-5",e.textContent=n,e.style="margin-left: calc(var(--spacing)*3); font-size: small;",t.parentNode.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,p=n.blobData,b=await this.Tt.yt(p,u);window.postMessage({source:"blue-marble",blobID:d,blobData:b,blink:n.blink});break;case"robots":this.kt="false"==i.userscript?.toString().toLowerCase();break}var s,a})}}(d);u.N(p),function(){let t=!1;u.L({id:"bm-l",style:"top: 10px; right: 75px;"}).L({id:"bm-7"}).L({id:"bm-g"}).O().R({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-l"),o=document.querySelector("#bm-7"),s=document.querySelector("#bm-g"),a=document.querySelector("#bm-8"),r=document.querySelector("#bm-c"),c=document.querySelector("#bm-d"),l=document.querySelectorAll("#bm-8 input");t||(i.style.width="auto",i.style.maxWidth="300px",i.style.minWidth="200px",i.style.padding="10px"),["#bm-l h1","#bm-4","#bm-l hr","#bm-3 > *:not(#bm-8)","#bm-2","#bm-1",`#${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"),c&&(c.style.display="none"),l.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=""),c&&(c.style.display="",c.style.marginTop=""),l.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)"})}).O().F(1,{textContent:c}).O().O().Y().O().L({id:"bm-4"}).P({id:"bm-f",textContent:"Username:"}).O().P({id:"bm-b",textContent:"Droplets:"}).O().P({id:"bm-6",textContent:"Next level in..."}).O().O().Y().O().L({id:"bm-3"}).L({id:"bm-8"}).G({id:"bm-c",className:"bm-p",style:"margin-top: 0;",innerHTML:''},(t,e)=>{e.onclick=()=>{const e=t.k?.It;e?.[0]?(t._("bm-h",e?.[0]||""),t._("bm-i",e?.[1]||""),t._("bm-j",e?.[2]||""),t._("bm-k",e?.[3]||"")):t.W("Coordinates are malformed! Did you try clicking on the canvas first?")}}).O().A({type:"number",id:"bm-h",placeholder:"Tl X",min:0,max:2047,step:1,required:!0}).O().A({type:"number",id:"bm-i",placeholder:"Tl Y",min:0,max:2047,step:1,required:!0}).O().A({type:"number",id:"bm-j",placeholder:"Px X",min:0,max:2047,step:1,required:!0}).O().A({type:"number",id:"bm-k",placeholder:"Px Y",min:0,max:2047,step:1,required:!0}).O().O().J({id:"bm-2",textContent:"Upload Template",accept:"image/png, image/jpeg, image/webp, image/bmp, image/gif"}).O().L({id:"bm-0"}).G({id:"bm-d",textContent:"Enable"},(t,e)=>{e.onclick=()=>{const e=document.querySelector("#bm-2"),n=document.querySelector("#bm-h");if(!n.checkValidity())return n.reportValidity(),void t.W("Coordinates are malformed! Did you try clicking on the canvas first?");const i=document.querySelector("#bm-i");if(!i.checkValidity())return i.reportValidity(),void t.W("Coordinates are malformed! Did you try clicking on the canvas first?");const o=document.querySelector("#bm-j");if(!o.checkValidity())return o.reportValidity(),void t.W("Coordinates are malformed! Did you try clicking on the canvas first?");const s=document.querySelector("#bm-k");if(!s.checkValidity())return s.reportValidity(),void t.W("Coordinates are malformed! Did you try clicking on the canvas first?");e?.files[0]?(d.gt(e.files[0],e.files[0]?.name.replace(/\.[^/.]+$/,""),[Number(n.value),Number(i.value),Number(o.value),Number(s.value)]),t.Z("Drew to canvas!")):t.W("No file selected!")}}).O().O().U({id:u.I,placeholder:`Status: Sleeping...\nVersion: ${l}`,readOnly:!0}).O().L({id:"bm-1"}).L().G({id:"bm-9",className:"bm-p",innerHTML:"🎨",title:"Template Color Converter"},(t,e)=>{e.addEventListener("click",()=>{window.open("https://pepoafonso.github.io/color_converter_wplace/","_blank","noopener noreferrer")})}).O().O().H({textContent:"Made by SwingTheVine",style:"margin-top: auto;"}).O().O().O().B(document.body)}(),u.V("#bm-l","#bm-g"),p.Ot(u),new MutationObserver((t,e)=>{const n=document.querySelector("#color-1");if(!n)return;let i=document.querySelector("#bm-e");i||(i=document.createElement("button"),i.id="bm-e",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 ↑"},n.parentNode.parentNode.parentNode.parentNode.querySelector("h2").parentNode.appendChild(i))}).observe(document.body,{childList:!0,subtree:!0}),function(...t){(0,console.log)(...t)}(`%c${c}%c (${l}) 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);function s(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 a(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?.coords?.split(",").map(Number),c=i.tiles,l={};for(const t in c)if(console.log(t),c.hasOwnProperty(t)){const e=r(c[t]),n=new Blob([e],{type:"image/png"}),i=await createImageBitmap(n);l[t]=i}const h=new m({displayName:s,l:e||this.D?.length||0,h:o||"",coords:a});h.m=l,this.D.push(h),console.log(this.D),console.log("^^^ This ^^^")}}};var u=GM_info.script.name.toString(),d=GM_info.script.version.toString();!function(t){const e=document.createElement("script");e.setAttribute("bm-o",u),e.setAttribute("bm-m","color: cornflowerblue;"),e.textContent=`(${t})();`,document.documentElement.appendChild(e),e.remove()}(()=>{const t=document.currentScript,e=t?.getAttribute("bm-o")||"Blue Marble",n=t?.getAttribute("bm-m")||"",i=new Map;window.addEventListener("message",t=>{const{source:o,endpoint:s,blobID:a,blobData:r,blink:c}=t.data,l=Date.now()-c;if(console.groupCollapsed(`%c${e}%c: ${i.size} Recieved IMAGE message about blob "${a}"`,n,""),console.log(`Blob fetch took %c${String(Math.floor(l/6e4)).padStart(2,"0")}:${String(Math.floor(l/1e3)%60).padStart(2,"0")}.${String(l%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",c=a.headers.get("content-type")||"";if(c.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(c.includes("image/")&&!r.includes("openfreemap")){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 c=crypto.randomUUID();i.set(c,t=>{s(new Response(t,{headers:a.headers,status:a.status,statusText:a.statusText})),console.log(`%c${e}%c: ${i.size} Processed blob "${c}"`,n,"")}),window.postMessage({source:"blue-marble",endpoint:r,blobID:c,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 p=GM_getResourceText("CSS-BM-File");GM_addStyle(p);var b=document.createElement("link");b.href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap",b.rel="preload",b.as="style",b.onload=function(){this.onload=null,this.rel="stylesheet"},document.head.appendChild(b),new class{constructor(){this.C=null,this.I=null,this.N="#bm-5"}k(t){return this.I=t,this.C=new MutationObserver(t=>{for(const e of t)for(const t of e.addedNodes)t instanceof HTMLElement&&t.matches?.(this.N)}),this}B(){return this.C}observe(t,e=!1,n=!1){t.observe(this.I,{childList:e,subtree:n})}};var f=new class{constructor(e,n){i(this,t),this.name=e,this.version=n,this.O=null,this.P="bm-a",this.t=null,this.i=null,this.o=[]}H(t){this.O=t}L(){return this.o.length>0&&(this.i=this.o.pop()),this}j(t){t.appendChild(this.t),this.t=null,this.i=null,this.o=[]}A(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"div",{},n)),this}G(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"p",{},n)),this}J(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"small",{},n)),this}F(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"img",{},n)),this}R(n,i={},s=()=>{}){return s(this,o(this,t,e).call(this,"h"+n,{},i)),this}Y(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"hr",{},n)),this}q(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"br",{},n)),this}X(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.L(),i(this,s,a),this}_(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"button",{},n)),this}U(n={},i=()=>{}){const s=n.title??n.textContent??"Help: No info";delete n.textContent,n.title=`Help: ${s}`;const a={textContent:"?",className:"bm-p",onclick:()=>{this.V(this.P,s)}};return i(this,o(this,t,e).call(this,"button",a,n)),this}W(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"input",{},n)),this}Z(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.L();const c=o(this,t,e).call(this,"button",{textContent:s});return this.L(),this.L(),r.setAttribute("tabindex","-1"),r.setAttribute("aria-hidden","true"),c.addEventListener("click",()=>{r.click()}),r.addEventListener("change",()=>{c.style.maxWidth=`${c.offsetWidth}px`,r.files.length>0?c.textContent=r.files[0].name:c.textContent=s}),i(this,a,r,c),this}K(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"textarea",{},n)),this}V(t,e,n=!1){const i=document.getElementById(t.replace(/^#/,""));i&&(i instanceof HTMLInputElement?i.value=e:n?i.textContent=e:i.innerHTML=e)}tt(t,e){let n,i=!1,o=0,s=null,a=0,r=0,c=0,l=0;if(t=document.querySelector("#"==t?.[0]?t:"#"+t),e=document.querySelector("#"==e?.[0]?e:"#"+e),!t||!e)return void this.et(`Can not drag! ${t?"":"moveMe"} ${t||e?"":"and "}${e?"":"iMoveThings "}was not found!`);const h=()=>{if(i){const e=Math.abs(a-c),n=Math.abs(r-l);(e>.5||n>.5)&&(a=c,r=l,t.style.transform=`translate(${a}px, ${r}px)`,t.style.left="0px",t.style.top="0px",t.style.right=""),s=requestAnimationFrame(h)}};let m=null;const u=(u,d)=>{i=!0,m=t.getBoundingClientRect(),n=u-m.left,o=d-m.top;const p=window.getComputedStyle(t).transform;if(p&&"none"!==p){const t=new DOMMatrix(p);a=t.m41,r=t.m42}else a=m.left,r=m.top;c=a,l=r,document.body.style.userSelect="none",e.classList.add("dragging"),s&&cancelAnimationFrame(s),h()},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&&m&&(c=t.clientX-n,l=t.clientY-o)},{passive:!0}),document.addEventListener("touchmove",function(t){if(i&&m){const e=t?.touches?.[0];if(!e)return;c=e.clientX-n,l=e.clientY-o,t.preventDefault()}},{passive:!1}),document.addEventListener("mouseup",d),document.addEventListener("touchend",d),document.addEventListener("touchcancel",d)}nt(t){(0,console.info)(`${this.name}: ${t}`),this.V(this.P,"Status: "+t,!0)}et(t){(0,console.error)(`${this.name}: ${t}`),this.V(this.P,"Error: "+t,!0)}}(u,d),w=new class{constructor(t,e,n){i(this,c),this.name=t,this.version=e,this.t=n,this.it="1.0.0",this.ot=null,this.st="!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~",this.u=1e3,this.rt=3,this.ct=null,this.lt=null,this.ht="bm-n",this.ut="div#map canvas.maplibregl-canvas",this.dt=null,this.bt="",this.D=[],this.T=null}ft(){if(document.body.contains(this.ct))return this.ct;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.ct=e,window.addEventListener("move",this.wt),window.addEventListener("zoom",this.gt),window.addEventListener("resize",this.$t),this.ct}async vt(){return{whoami:this.name.replace(" ",""),scriptVersion:this.version,schemaVersion:this.it,templates:{}}}async xt(t,e,n){this.T||(this.T=await this.vt(),console.log("Creating JSON...")),this.t.nt(`Creating template at ${n.join(", ")}...`);const i=new m({displayName:e,l:0,h:s(this.ot||0,this.st),file:t,coords:n}),{M:a,S:r}=await i.$(this.u);i.m=a,this.T.templates[`${i.l} ${i.h}`]={name:i.displayName,coords:n.join(", "),enabled:!0,tiles:r},this.D=[],this.D.push(i);const h=(new Intl.NumberFormat).format(i.p);this.t.nt(`Template created at ${n.join(", ")}! Total pixels: ${h}`),console.log(Object.keys(this.T.templates).length),console.log(this.T),console.log(this.D),console.log(JSON.stringify(this.T)),await o(this,c,l).call(this)}yt(){}async Mt(){this.T||(this.T=await this.vt(),console.log("Creating JSON..."))}async St(t,e){const n=this.u*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.D;console.log(i),i.sort((t,e)=>t.l-e.l),console.log(i);const o=i.map(t=>{const n=Object.keys(t.m).filter(t=>t.startsWith(e));if(0===n.length)return null;const i=n.map(e=>{const n=e.split(",");return{Tt:t.m[e],Dt:[n[0],n[1]],Ct:[n[2],n[3]]}});return i?.[0]}).filter(Boolean);if(console.log(o),o?.Tt?.length>0){const t=i.filter(t=>Object.keys(t.m).filter(t=>t.startsWith(e)).length>0).reduce((t,e)=>t+(e.p||0),0),n=(new Intl.NumberFormat).format(t);this.t.nt(`Displaying ${o.Tt.length} template${1==o.Tt.length?"":"s"}. Total pixels: ${n}`)}const s=await createImageBitmap(t),a=new OffscreenCanvas(n,n),r=a.getContext("2d");r.imageSmoothingEnabled=!1,r.beginPath(),r.rect(0,0,n,n),r.clip(),r.clearRect(0,0,n,n),r.drawImage(s,0,0,n,n);for(const t of o)console.log("Template:"),console.log(t),r.drawImage(t.Tt,Number(t.Ct[0])*this.rt,Number(t.Ct[1])*this.rt);return await a.convertToBlob({type:"image/png"})}It(t){console.log("Importing JSON..."),console.log(t),"BlueMarble"==t?.whoami&&o(this,c,h).call(this,t)}}(u,d,f),g=new class{constructor(t){this.Nt=t,this.kt=!1,this.Bt=[],this.Ot=[]}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.et("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(s(i.id,"!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~")),this.Nt.ot=i.id,t.V("bm-f",`Username: ${function(t){const e=document.createElement("div");return e.textContent=t,e.innerHTML}(i.name)}`),t.V("bm-b",`Droplets: ${(new Intl.NumberFormat).format(i.droplets)}`),t.V("bm-6",`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))),c=new URLSearchParams(n.endpoint.split("?")[1]),l=[c.get("x"),c.get("y")];if(this.Bt.length&&(!o.length||!l.length))return void t.et("Coordinates are malformed!\nDid you try clicking the canvas first?");this.Bt=[...o,...l];const h=(a=o,r=l,[parseInt(a[0])%4*1e3+parseInt(r[0]),parseInt(a[1])%4*1e3+parseInt(r[1])]),m=document.querySelectorAll("span");for(const t of m)if(t.textContent.trim().includes(`${h[0]}, ${h[1]}`)){let e=document.querySelector("#bm-5");const n=`(Tl X: ${o[0]}, Tl Y: ${o[1]}, Px X: ${l[0]}, Px Y: ${l[1]})`;e?e.textContent=n:(e=document.createElement("span"),e.id="bm-5",e.textContent=n,e.style="margin-left: calc(var(--spacing)*3); font-size: small;",t.parentNode.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,p=n.blobData,b=await this.Nt.St(p,u);window.postMessage({source:"blue-marble",blobID:d,blobData:b,blink:n.blink});break;case"robots":this.kt="false"==i.userscript?.toString().toLowerCase();break}var a,r})}}(w);f.H(g);var $=JSON.parse(GM_getValue("bmTemplates","{}"));console.log($),w.It($),function(){let t=!1;f.A({id:"bm-l",style:"top: 10px; right: 75px;"}).A({id:"bm-7"}).A({id:"bm-g"}).L().F({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-l"),o=document.querySelector("#bm-7"),s=document.querySelector("#bm-g"),a=document.querySelector("#bm-8"),r=document.querySelector("#bm-c"),c=document.querySelector("#bm-d"),l=document.querySelectorAll("#bm-8 input");t||(i.style.width="auto",i.style.maxWidth="300px",i.style.minWidth="200px",i.style.padding="10px"),["#bm-l h1","#bm-4","#bm-l hr","#bm-3 > *:not(#bm-8)","#bm-2","#bm-1",`#${e.P}`].forEach(e=>{document.querySelectorAll(e).forEach(e=>{e.style.display=t?"none":""})}),t?(a&&(a.style.display="none"),r&&(r.style.display="none"),c&&(c.style.display="none"),l.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=""),c&&(c.style.display="",c.style.marginTop=""),l.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)"})}).L().R(1,{textContent:u}).L().L().Y().L().A({id:"bm-4"}).G({id:"bm-f",textContent:"Username:"}).L().G({id:"bm-b",textContent:"Droplets:"}).L().G({id:"bm-6",textContent:"Next level in..."}).L().L().Y().L().A({id:"bm-3"}).A({id:"bm-8"})._({id:"bm-c",className:"bm-p",style:"margin-top: 0;",innerHTML:''},(t,e)=>{e.onclick=()=>{const e=t.O?.Bt;e?.[0]?(t.V("bm-h",e?.[0]||""),t.V("bm-i",e?.[1]||""),t.V("bm-j",e?.[2]||""),t.V("bm-k",e?.[3]||"")):t.et("Coordinates are malformed! Did you try clicking on the canvas first?")}}).L().W({type:"number",id:"bm-h",placeholder:"Tl X",min:0,max:2047,step:1,required:!0}).L().W({type:"number",id:"bm-i",placeholder:"Tl Y",min:0,max:2047,step:1,required:!0}).L().W({type:"number",id:"bm-j",placeholder:"Px X",min:0,max:2047,step:1,required:!0}).L().W({type:"number",id:"bm-k",placeholder:"Px Y",min:0,max:2047,step:1,required:!0}).L().L().Z({id:"bm-2",textContent:"Upload Template",accept:"image/png, image/jpeg, image/webp, image/bmp, image/gif"}).L().A({id:"bm-0"})._({id:"bm-d",textContent:"Enable"},(t,e)=>{e.onclick=()=>{const e=document.querySelector("#bm-2"),n=document.querySelector("#bm-h");if(!n.checkValidity())return n.reportValidity(),void t.et("Coordinates are malformed! Did you try clicking on the canvas first?");const i=document.querySelector("#bm-i");if(!i.checkValidity())return i.reportValidity(),void t.et("Coordinates are malformed! Did you try clicking on the canvas first?");const o=document.querySelector("#bm-j");if(!o.checkValidity())return o.reportValidity(),void t.et("Coordinates are malformed! Did you try clicking on the canvas first?");const s=document.querySelector("#bm-k");if(!s.checkValidity())return s.reportValidity(),void t.et("Coordinates are malformed! Did you try clicking on the canvas first?");e?.files[0]?(w.xt(e.files[0],e.files[0]?.name.replace(/\.[^/.]+$/,""),[Number(n.value),Number(i.value),Number(o.value),Number(s.value)]),t.nt("Drew to canvas!")):t.et("No file selected!")}}).L().L().K({id:f.P,placeholder:`Status: Sleeping...\nVersion: ${d}`,readOnly:!0}).L().A({id:"bm-1"}).A()._({id:"bm-9",className:"bm-p",innerHTML:"🎨",title:"Template Color Converter"},(t,e)=>{e.addEventListener("click",()=>{window.open("https://pepoafonso.github.io/color_converter_wplace/","_blank","noopener noreferrer")})}).L().L().J({textContent:"Made by SwingTheVine",style:"margin-top: auto;"}).L().L().L().j(document.body)}(),f.tt("#bm-l","#bm-g"),g.Pt(f),new MutationObserver((t,e)=>{const n=document.querySelector("#color-1");if(!n)return;let i=document.querySelector("#bm-e");i||(i=document.createElement("button"),i.id="bm-e",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 ↑"},n.parentNode.parentNode.parentNode.parentNode.querySelector("h2").parentNode.appendChild(i))}).observe(document.body,{childList:!0,subtree:!0}),function(...t){(0,console.log)(...t)}(`%c${u}%c (${d}) userscript has loaded!`,"color: cornflowerblue;","")})(); \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index 101dde0..399cc0d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -43,8 +43,8 @@ Latest Release Software License: MPL-2.0 Contact Me -WakaTime -Total Patches +WakaTime +Total Patches Total Lines of Code Total Comments Compression diff --git a/package-lock.json b/package-lock.json index 417762c..3a07927 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wplace-bluemarble", - "version": "0.72.4", + "version": "0.72.21", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "wplace-bluemarble", - "version": "0.72.4", + "version": "0.72.21", "devDependencies": { "esbuild": "^0.25.0", "terser": "^5.43.1" diff --git a/package.json b/package.json index 0a14808..cf5045b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wplace-bluemarble", - "version": "0.72.4", + "version": "0.72.21", "type": "module", "scripts": { "build": "node build/build.js", diff --git a/src/BlueMarble.meta.js b/src/BlueMarble.meta.js index fa8dafb..2894625 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.72.4 +// @version 0.72.21 // @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 @@ -14,6 +14,8 @@ // @match *://*.wplace.live/* // @grant GM_getResourceText // @grant GM_addStyle +// @grant GM.setValue +// @grant GM_getValue // @resource CSS-BM-File https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/6b6a45cba9943e5960c9887654aa704a90c02636/dist/BlueMarble.user.css // ==/UserScript== diff --git a/src/Template.js b/src/Template.js index a4915a6..9c1bc9b 100644 --- a/src/Template.js +++ b/src/Template.js @@ -1,3 +1,5 @@ +import { uint8ToBase64 } from "./utils"; + /** An instance of a template. * Handles all mathematics, manipulation, and analysis regarding a single template. * @since 0.65.2 @@ -40,7 +42,7 @@ export default class Template { /** Creates chunks of the template for each tile. * - * @returns {Object} Collection of template bitmaps organized by tile coordinates + * @returns {Object} Collection of template bitmaps & buffers organized by tile coordinates * @since 0.65.4 */ async createTemplateTiles() { @@ -52,15 +54,15 @@ export default class Template { const imageHeight = bitmap.height; // Calculate total pixel count using standard width × height formula - // This provides essential statistical information for the user interface + // TODO: Use non-transparent pixels instead of basic width times height const totalPixels = imageWidth * imageHeight; console.log(`Template pixel analysis - Dimensions: ${imageWidth}×${imageHeight} = ${totalPixels.toLocaleString()} pixels`); // Store pixel count in instance property for access by template manager and UI components - // This enables real-time statistics display and template comparison features this.pixelCount = totalPixels; const templateTiles = {}; // Holds the template tiles + const templateTilesBuffers = {}; // Holds the buffers of the template tiles const canvas = new OffscreenCanvas(this.tileSize, this.tileSize); const context = canvas.getContext('2d', { willReadFrequently: true }); @@ -145,15 +147,22 @@ export default class Template { console.log(`Shreaded pixels for ${pixelX}, ${pixelY}`, imageData); context.putImageData(imageData, 0, 0); - templateTiles[ - `${(this.coords[0] + Math.floor(pixelX / 1000)) - .toString() - .padStart(4, '0')},${(this.coords[1] + Math.floor(pixelY / 1000)) - .toString() - .padStart(4, '0')},${(pixelX % 1000) - .toString() - .padStart(3, '0')},${(pixelY % 1000).toString().padStart(3, '0')}` - ] = await createImageBitmap(canvas); + + // Creates the "0000,0000,000,000" key name + const templateTileName = `${(this.coords[0] + Math.floor(pixelX / 1000)) + .toString() + .padStart(4, '0')},${(this.coords[1] + Math.floor(pixelY / 1000)) + .toString() + .padStart(4, '0')},${(pixelX % 1000) + .toString() + .padStart(3, '0')},${(pixelY % 1000).toString().padStart(3, '0')}`; + + templateTiles[templateTileName] = await createImageBitmap(canvas); // Creates the bitmap + + const canvasBlob = await canvas.convertToBlob(); + const canvasBuffer = await canvasBlob.arrayBuffer(); + const canvasBufferBytes = Array.from(new Uint8Array(canvasBuffer)); + templateTilesBuffers[templateTileName] = uint8ToBase64(canvasBufferBytes); // Stores the buffer console.log(templateTiles); @@ -164,6 +173,7 @@ export default class Template { } console.log('Template Tiles: ', templateTiles); - return templateTiles; + console.log('Template Tiles Buffers: ', templateTilesBuffers); + return { templateTiles, templateTilesBuffers }; } } diff --git a/src/main.js b/src/main.js index 4fb878d..d219edb 100644 --- a/src/main.js +++ b/src/main.js @@ -180,6 +180,10 @@ const apiManager = new ApiManager(templateManager); // Constructs a new ApiManag overlay.setApiManager(apiManager); // Sets the API manager +const storageTemplates = JSON.parse(GM_getValue('bmTemplates', '{}')); +console.log(storageTemplates); +templateManager.importJSON(storageTemplates); // Loads the templates + buildOverlayMain(); // Builds the main overlay overlay.handleDrag('#bm-overlay', '#bm-bar-drag'); // Creates dragging capability on the drag bar for dragging the overlay diff --git a/src/templateManager.js b/src/templateManager.js index 2b6a503..5951ee3 100644 --- a/src/templateManager.js +++ b/src/templateManager.js @@ -1,5 +1,5 @@ import Template from "./Template"; -import { numberToEncoded } from "./utils"; +import { base64ToUint8, numberToEncoded } from "./utils"; /** Manages the template system. * This class handles all external requests for template modification, creation, and analysis. @@ -135,14 +135,17 @@ export default class TemplateManager { file: blob, coords: coords }); - template.chunked = await template.createTemplateTiles(this.tileSize); // Chunks the tiles - + //template.chunked = await template.createTemplateTiles(this.tileSize); // Chunks the tiles + const { templateTiles, templateTilesBuffers } = await template.createTemplateTiles(this.tileSize); // Chunks the tiles + template.chunked = templateTiles; // Stores the chunked tile bitmaps + // 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 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": template.chunked + "tiles": templateTilesBuffers // Stores the chunked tile buffers }; this.templatesArray = []; // Remove this to enable multiple templates (2/2) @@ -157,6 +160,9 @@ export default class TemplateManager { console.log(Object.keys(this.templatesJSON.templates).length); console.log(this.templatesJSON); console.log(this.templatesArray); + console.log(JSON.stringify(this.templatesJSON)); + + await this.#storeTemplates(); } /** Generates a {@link Template} class instance from the JSON object template @@ -165,6 +171,13 @@ export default class TemplateManager { } + /** Stores the JSON object of the loaded templates into TamperMonkey (GreaseMonkey) storage. + * @since 0.72.7 + */ + async #storeTemplates() { + GM.setValue('bmTemplates', JSON.stringify(this.templatesJSON)); + } + /** Deletes a template from the JSON object. * Also delete's the corrosponding {@link Template} class instance */ @@ -198,6 +211,7 @@ export default class TemplateManager { console.log(`Searching for templates in tile: "${tileCoords}"`); const templateArray = this.templatesArray; // Stores a copy for sorting + console.log(templateArray); // Sorts the array of Template class instances. 0 = first = lowest draw priority templateArray.sort((a, b) => {return a.sortID - b.sortID;}); @@ -234,7 +248,6 @@ export default class TemplateManager { if (templatesToDraw?.bitmap?.length > 0) { // Calculate total pixel count for templates actively being displayed in this tile - // This provides accurate statistics by counting only templates with content in the current viewport const totalPixels = templateArray .filter(template => { // Filter templates to include only those with tiles matching current coordinates @@ -250,8 +263,7 @@ export default class TemplateManager { // Examples: "1,234,567" (US), "1.234.567" (DE), "1 234 567" (FR) const pixelCountFormatted = new Intl.NumberFormat().format(totalPixels); - // Display comprehensive status information including both template count and pixel statistics - // This gives users immediate feedback about the complexity and scope of what's being rendered + // Display status information about the templates being rendered this.overlay.handleDisplayStatus( `Displaying ${templatesToDraw.bitmap.length} template${templatesToDraw.bitmap.length == 1 ? '' : 's'}. ` + `Total pixels: ${pixelCountFormatted}` @@ -286,15 +298,75 @@ export default class TemplateManager { } /** Imports the JSON object, and appends it to any JSON object already loaded + * @param {string} json - The JSON string to parse */ - importJSON() { + importJSON(json) { + console.log(`Importing JSON...`); + console.log(json); + + // If the passed in JSON is a Blue Marble template object... + if (json?.whoami == 'BlueMarble') { + this.#parseBlueMarble(json); // ...parse the template object as Blue Marble + } } /** Parses the Blue Marble JSON object + * @param {string} json - The JSON string to parse + * @since 0.72.13 */ - #parseBlueMarble() { + async #parseBlueMarble(json) { + console.log(`Parsing BlueMarble...`); + + const templates = json.templates; + + console.log(`BlueMarble length: ${Object.keys(templates).length}`); + + if (Object.keys(templates).length > 0) { + + for (const template in templates) { + + const templateKey = template; + const templateValue = templates[template]; + console.log(templateKey); + + if (templates.hasOwnProperty(template)) { + + const templateKeyArray = templateKey.split(' '); // E.g., "0 $Z" -> ["0", "$Z"] + const sortID = Number(templateKeyArray?.[0]); // Sort ID of the template + const authorID = templateKeyArray?.[1] || '0'; // User ID of the person who exported the template + const displayName = templateValue.name || `Template ${sortID || ''}`; // Display name of the template + //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. + + for (const tile in tilesbase64) { + console.log(tile); + if (tilesbase64.hasOwnProperty(tile)) { + const encodedTemplateBase64 = tilesbase64[tile]; + const templateUint8Array = base64ToUint8(encodedTemplateBase64); // Base 64 -> Uint8Array + + const templateBlob = new Blob([templateUint8Array], { type: "image/png" }); // Uint8Array -> Blob + const templateBitmap = await createImageBitmap(templateBlob) // Blob -> Bitmap + templateTiles[tile] = templateBitmap; + } + } + + // Creates a new Template class instance + const template = new Template({ + displayName: displayName, + sortID: sortID || this.templatesArray?.length || 0, + authorID: authorID || '', + //coords: coords + }); + template.chunked = templateTiles; + this.templatesArray.push(template); + console.log(this.templatesArray); + console.log(`^^^ This ^^^`); + } + } + } } /** Parses the OSU! Place JSON object diff --git a/src/utils.js b/src/utils.js index 7de0c89..b77f22d 100644 --- a/src/utils.js +++ b/src/utils.js @@ -98,4 +98,31 @@ export function numberToEncoded(number, encoding) { } return result; // The final encoded string +} + +/** Converts a Uint8 array to base64 using the browser's built-in binary to ASCII function + * @param {Uint8Array} uint8 - The Uint8Array to convert + * @returns {Uint8Array} The base64 encoded Uint8Array + * @since 0.72.9 + */ +export function uint8ToBase64(uint8) { + let binary = ''; + for (let i = 0; i < uint8.length; i++) { + binary += String.fromCharCode(uint8[i]); + } + return btoa(binary); // Binary to ASCII +} + +/** Decodes a base 64 encoded Uint8 array using the browser's built-in ASCII to binary function + * @param {Uint8Array} base64 - The base 64 encoded Uint8Array to convert + * @returns {Uint8Array} The decoded Uint8Array + * @since 0.72.9 + */ +export function base64ToUint8(base64) { + const binary = atob(base64); // ASCII to Binary + const array = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) { + array[i] = binary.charCodeAt(i); + } + return array; } \ No newline at end of file