diff --git a/dist/BlueMarble.user.css b/dist/BlueMarble.user.css
index 6813d3f..18d08ed 100644
--- a/dist/BlueMarble.user.css
+++ b/dist/BlueMarble.user.css
@@ -1 +1 @@
-#bm-overlay{position:fixed;background-color:#153063e6;color:#fff;padding:10px;border-radius:8px;z-index:9000}div#bm-overlay{font-family:Roboto Mono,Courier New,Monaco,DejaVu Sans Mono,monospace,Arial;letter-spacing:.05em}#bm-bar-drag{margin-bottom:.5em;background:url('data:image/svg+xml;utf8,') repeat;cursor:grab;width:100%;height:1em}#bm-bar-drag.dragging{cursor:grabbing}#bm-contain-header{margin-bottom:.5em}#bm-overlay img{display:inline-block;height:2.5em;margin-right:1ch;vertical-align:middle}#bm-overlay h1{display:inline-block;font-size:x-large;font-weight:700;vertical-align:middle}#bm-contain-automation input[type=checkbox]{vertical-align:middle;margin-right:.5ch}#bm-contain-automation label{margin-right:.5ch}.bm-help{border:white 1px solid;height:1.25em;width:1.25em;margin-top:2px;text-align:center;line-height:1.25em;padding:0!important}#bm-button-coords{vertical-align:middle}#bm-button-coords svg{width:50%;margin:0 auto;fill:#111}#bm-contain-coords input[type=number]{appearance:auto;-moz-appearance:textfield;width:5.5ch;margin-left:1ch;background-color:#0003;padding:0 .5ch;font-size:small}#bm-contain-coords input[type=number]::-webkit-outer-spin-button,#bm-contain-coords input[type=number]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}#bm-contain-buttons{display:flex;flex-direction:row;flex-wrap:wrap;align-content:center;justify-content:center;align-items:center;gap:1ch}div:has(>#bm-input-file)>button{width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}#bm-output-status{font-size:small;background-color:#0003;padding:0 .5ch;height:3.75em;width:100%}#bm-contain-userinfo,#bm-contain-automation,#bm-contain-coords,#bm-contain-buttons,div:has(>#bm-input-file),#bm-output-status{margin-top:.5em}#bm-overlay button{background-color:#144eb9;border-radius:1em;padding:0 .75ch}#bm-overlay button:hover,#bm-overlay button:focus-visible{background-color:#1061e5}#bm-overlay button:active,#bm-overlay button:disabled{background-color:#2e97ff}#bm-overlay button:disabled{text-decoration:line-through}
+#bm-overlay{position:fixed;background-color:#153063e6;color:#fff;padding:10px;border-radius:8px;z-index:9000}div#bm-overlay{font-family:Roboto Mono,Courier New,Monaco,DejaVu Sans Mono,monospace,Arial;letter-spacing:.05em}#bm-bar-drag{margin-bottom:.5em;background:url('data:image/svg+xml;utf8,') repeat;cursor:grab;width:100%;height:1em}#bm-bar-drag.dragging{cursor:grabbing}#bm-contain-header{margin-bottom:.5em}#bm-overlay img{display:inline-block;height:2.5em;margin-right:1ch;vertical-align:middle}#bm-overlay h1{display:inline-block;font-size:x-large;font-weight:700;vertical-align:middle}#bm-contain-automation input[type=checkbox]{vertical-align:middle;margin-right:.5ch}#bm-contain-automation label{margin-right:.5ch}.bm-help{border:white 1px solid;height:1.25em;width:1.25em;margin-top:2px;text-align:center;line-height:1.25em;padding:0!important}#bm-button-coords{vertical-align:middle}#bm-button-coords svg{width:50%;margin:0 auto;fill:#111}#bm-contain-coords input[type=number]{appearance:auto;-moz-appearance:textfield;width:5.5ch;margin-left:1ch;background-color:#0003;padding:0 .5ch;font-size:small}#bm-contain-coords input[type=number]::-webkit-outer-spin-button,#bm-contain-coords input[type=number]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}#bm-contain-buttons{display:flex;flex-direction:row;flex-wrap:wrap;align-content:center;justify-content:center;align-items:center;gap:1ch}div:has(>#bm-input-file-template)>button{width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}#bm-output-status{font-size:small;background-color:#0003;padding:0 .5ch;height:3.75em;width:100%}#bm-contain-userinfo,#bm-contain-automation,#bm-contain-coords,#bm-contain-buttons,div:has(>#bm-input-file-template),#bm-output-status{margin-top:.5em}#bm-overlay button{background-color:#144eb9;border-radius:1em;padding:0 .75ch}#bm-overlay button:hover,#bm-overlay button:focus-visible{background-color:#1061e5}#bm-overlay button:active,#bm-overlay button:disabled{background-color:#2e97ff}#bm-overlay button:disabled{text-decoration:line-through}
diff --git a/dist/BlueMarble.user.js b/dist/BlueMarble.user.js
index 1a6675e..528cbef 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.43.6
+// @version 0.43.40
// @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
@@ -20,5 +20,6 @@
// Wplace --> https://wplace.live
// License --> https://www.mozilla.org/en-US/MPL/2.0/
-(()=>{var g=class{constructor(e,s){this.name=e,this.version=s,this.apiHandler=null,this.outputStatusId="bm-output-status",this.overlay=null,this.currentParent=null,this.parentStack=[]}setApiHandler(e){this.apiHandler=e}createElement(e,s={},n={}){let t=document.createElement(e);this.overlay?(this.currentParent.appendChild(t),this.parentStack.push(this.currentParent),this.currentParent=t):(this.overlay=t,this.currentParent=t);for(let[i,o]of Object.entries(s))t[i]=o;for(let[i,o]of Object.entries(n))t[i]=o;return t}buildElement(){return this.parentStack.length>0&&(this.currentParent=this.parentStack.pop()),this}buildOverlay(e){e.appendChild(this.overlay)}addDiv(e={},s=()=>{}){let n={},t=this.createElement("div",n,e);return s(t),this}addImg(e={},s=()=>{}){let n={},t=this.createElement("img",n,e);return s(t),this}create(){let e=document.createElement("div");e.id="bm-overlay",e.style.top="10px",e.style.right="75px";let s=document.createElement("div");s.id="bm-contain-header";let n=document.createElement("div");n.id="bm-bar-drag",s.appendChild(n);let t=document.createElement("img");t.src="https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/src/assets/Favicon.png",t.alt="Blue Marble Icon",s.appendChild(t);let i=document.createElement("h1");i.textContent=this.name,s.appendChild(i);let o=document.createElement("div");o.id="bm-contain-userinfo";let r=document.createElement("p");r.id="bm-user-name",r.textContent="Username:",o.appendChild(r);let a=document.createElement("p");a.id="bm-user-droplets",a.textContent="Droplets:",o.appendChild(a);let m=document.createElement("p");m.id="bm-user-nextlevel",m.textContent="Next level in...",o.appendChild(m);let l=document.createElement("div");l.id="bm-contain-automation",l.appendChild(this.createInputCheckbox("Stealth","bm-input-stealth",!0)),l.appendChild(this.createButtonQuestion("bm-help-stealth","Help: Waits for the website to make requests, instead of sending requests.",this.outputStatusId)),l.appendChild(document.createElement("br")),l.appendChild(this.createInputCheckbox("Possessed","bm-input-possessed",!0)),l.appendChild(this.createButtonQuestion("bm-help-possessed","Help: Controls the website as if it were possessed.",this.outputStatusId)),l.appendChild(document.createElement("br"));let d=document.createElement("div");d.id="bm-contain-coords";let c=document.createElement("button");c.id="bm-button-coords",c.className="bm-help",c.style="margin-top: 0",c.innerHTML='',c.onclick=()=>{let h=this.apiHandler?.coordsTilePixel;if(!h?.[0]){this.handleDisplayError("Coordinates are malformed! Did you try clicking on the canvas first?");return}this.updateInnerHTML("bm-input-tx",h?.[0]||"0000"),this.updateInnerHTML("bm-input-ty",h?.[1]||"0000"),this.updateInnerHTML("bm-input-px",h?.[2]||"000"),this.updateInnerHTML("bm-input-py",h?.[3]||"000")},d.appendChild(c),d.appendChild(this.createInputNumber("bm-input-tx","Tl X","","0","2047","1")),d.appendChild(this.createInputNumber("bm-input-ty","Tl Y","","0","2047","1")),d.appendChild(this.createInputNumber("bm-input-px","Px X","","0","999","1")),d.appendChild(this.createInputNumber("bm-input-py","Px Y","","0","999","1")),l.appendChild(d),l.appendChild(this.createInputFile("bm-input-file"));let u=document.createElement("div");u.id="bm-contain-buttons",u.appendChild(this.createButton("bm-button-enable","Enable")),u.appendChild(this.createButton("bm-button-disable","Disable")),l.appendChild(u);let f=document.createElement("textarea");f.id=this.outputStatusId,f.readOnly=!0,f.placeholder=`Status: Sleeping...
-Version: ${this.version}`,l.appendChild(f),e.appendChild(s),e.appendChild(document.createElement("hr")),e.appendChild(o),e.appendChild(document.createElement("hr")),e.appendChild(l),document.body.appendChild(e),this.handleDrag(e,n)}updateInnerHTML(e,s,n=!1){let t=document.getElementById(e);if(t){if(t instanceof HTMLInputElement){t.value=s;return}n?t.textContent=s:t.innerHTML=s}}createButtonQuestion(e,s,n){let t=this.createButton(e,"?");return t.className="bm-help",t.title=s,t.onclick=()=>{this.updateInnerHTML(n,s)},t}createButton(e,s,n=!0){let t=document.createElement("button");return t.id=e,t.textContent=s,t.disabled=!n,t}createInputText(e,s="",n="",t="",i=!1){let o=document.createElement("input");return o.id=e,o.type="text",o.placeholder=s,o.value=n,o.readOnly=i,o.maxLength=t,o}createInputNumber(e,s="",n="",t="",i="",o="",r=!1){let a=this.createInputText(e,s,n,"",r);return a.type="number",a.min=t,a.max=i,a.step=o,a}createInputCheckbox(e,s,n=!1){let t=document.createElement("label");t.textContent=e;let i=document.createElement("input");return i.type="checkbox",i.id=s,i.checked=n,t.prepend(i),t}createInputFile(e){let s=document.createElement("div"),n=document.createElement("input");n.id=e,n.type="file",n.style="display: none";let t=document.createElement("button");return t.textContent="Upload File",t.addEventListener("click",()=>{n.click()}),n.addEventListener("change",()=>{t.style.maxWidth=`${t.offsetWidth}px`,n.files.length>0?t.textContent=n.files[0].name:t.textContent="Upload File"}),s.appendChild(n),s.appendChild(t),s}handleDrag(e,s){let n=!1,t,i=0;s.addEventListener("mousedown",function(o){n=!0,t=o.clientX-e.getBoundingClientRect().left,i=o.clientY-e.getBoundingClientRect().top,document.body.style.userSelect="none",s.classList.add("dragging")}),s.addEventListener("touchstart",function(o){n=!0;let r=o?.touches?.[0];r&&(t=r.clientX-e.getBoundingClientRect().left,i=r.clientY-e.getBoundingClientRect().top,document.body.style.userSelect="none",s.classList.add("dragging"))},{passive:!1}),document.addEventListener("mousemove",function(o){n&&(e.style.left=o.clientX-t+"px",e.style.top=o.clientY-i+"px",e.style.right="")}),document.addEventListener("touchmove",function(o){if(n){let r=o?.touches?.[0];if(!r)return;e.style.left=r.clientX-t+"px",e.style.top=r.clientY-i+"px",o.preventDefault()}},{passive:!1}),document.addEventListener("mouseup",function(){n=!1,document.body.style.userSelect="",s.classList.remove("dragging")}),document.addEventListener("touchend",function(){n=!1,document.body.style.userSelect="",s.classList.remove("dragging")}),document.addEventListener("touchcancel",function(){n=!1,document.body.style.userSelect="",s.classList.remove("dragging")})}handleDisplayError(e){console.error(`${this.name}: ${e}`),this.updateInnerHTML(this.outputStatusId,"Error: "+e,!0)}};var y=class{constructor(){this.observerBody=null,this.observerBodyTarget=null,this.targetDisplayCoords="#bm-display-coords"}createObserverBody(e){return this.observerBodyTarget=e,this.observerBody=new MutationObserver(s=>{for(let n of s)for(let t of n.addedNodes)t instanceof HTMLElement&&t.matches?.(this.targetDisplayCoords)}),this}getObserverBody(){return this.observerBody}observe(e,s=!1,n=!1){e.observe(this.observerBodyTarget,{childList:s,subtree:n})}};var v=class{serverTPtoDisplayTP(e,s){return[parseInt(e[0])%4*1e3+parseInt(s[0]),parseInt(e[1])%4*1e3+parseInt(s[1])]}};var x=class{constructor(e){this.coordsHandler=e,this.disableAll=!1,this.coordsTilePixel=[]}spontaneousResponseListener(e){window.addEventListener("message",s=>{let n=s.data;if(!(n&&n.source==="blue-marble"))return;let t=n.endpoint.split("?")[0].split("/").filter(i=>i&&isNaN(Number(i))).pop();switch(console.log(`Recieved message about "${t}"`),t){case"me":let i=Math.ceil(Math.pow(Math.floor(n.jsonData?.level)*Math.pow(30,.65),1.5384615384615383)-n.jsonData?.pixelsPainted);e.updateInnerHTML("bm-user-name",`Username: ${n.jsonData?.name}`),e.updateInnerHTML("bm-user-droplets",`Droplets: ${new Intl.NumberFormat().format(n.jsonData?.droplets)}`),e.updateInnerHTML("bm-user-nextlevel",`Next level in ${new Intl.NumberFormat().format(i)} pixel${i==1?"":"s"}`);break;case"pixel":let o=n.endpoint.split("?")[0].split("/").filter(d=>d&&!isNaN(Number(d))),r=new URLSearchParams(n.endpoint.split("?")[1]),a=[r.get("x"),r.get("y")];this.coordsTilePixel=[...o,...a];let m=this.coordsHandler.serverTPtoDisplayTP(o,a),l=document.querySelectorAll("span");for(let d of l)if(d.textContent.trim().includes(`${m[0]}, ${m[1]}`)){let c=document.querySelector("#bm-display-coords"),u=`(Tl X: ${o[0]}, Tl Y: ${o[1]}, Px X: ${a[0]}, Px Y: ${a[1]})`;c?c.textContent=u:(c=document.createElement("span"),c.id="bm-display-coords",c.textContent=u,c.style="margin-left: calc(var(--spacing)*3); font-size: small;",d.parentNode.parentNode.parentNode.insertAdjacentElement("afterend",c))}break;case"robots":this.disableAll=n.jsonData?.userscript?.toString().toLowerCase()=="false";break}})}};var E=GM_info.script.name.toString(),I=GM_info.script.version.toString();function T(p){let e=document.createElement("script");e.textContent=`(${p})();`,document.documentElement.appendChild(e),e.remove()}T(()=>{let p=window.fetch;window.fetch=async function(...e){let s=await p.apply(this,e),n=s.clone();if((n.headers.get("content-type")||"").includes("application/json")){let i=(e[0]instanceof Request?e[0]?.url:e[0])||"ignore";console.log(`Sending JSON message about endpoint "${i}"`),n.json().then(o=>{window.postMessage({source:"blue-marble",endpoint:i,jsonData:o},"*")}).catch(o=>{console.error("BM - Failed to parse JSON:",o)})}return s}});var w=GM_getResourceText("CSS-BM-File");GM_addStyle(w);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="this.onload=null;this.rel='stylesheet'";document.head.appendChild(b);var O=new y,C=new g(E,I),M=new v,L=new x(M);C.setApiHandler(L);C.addDiv({id:"bm-overlay",style:"top: 10px; right: 75px;"}).addDiv({id:"bm-contain-header"}).addDiv({id:"bm-bar-drag"}).buildElement().addImg({alt:"Blue Marble Icon",src:"https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/src/assets/Favicon.png"}).buildElement().buildOverlay(document.body);L.spontaneousResponseListener(C);console.log(`${E} (${I}) userscript has loaded!`);})();
+(()=>{var m=class{constructor(t,e){this.name=t,this.version=e,this.apiHandler=null,this.outputStatusId="bm-output-status",this.overlay=null,this.currentParent=null,this.parentStack=[]}setApiHandler(t){this.apiHandler=t}#t(t,e={},n={}){let s=document.createElement(t);this.overlay?(this.currentParent.appendChild(s),this.parentStack.push(this.currentParent),this.currentParent=s):(this.overlay=s,this.currentParent=s);for(let[r,o]of Object.entries(e))s[r]=o;for(let[r,o]of Object.entries(n))s[r]=o;return s}buildElement(){return this.parentStack.length>0&&(this.currentParent=this.parentStack.pop()),this}buildOverlay(t){t.appendChild(this.overlay),this.overlay=null,this.currentParent=null,this.parentStack=[]}addDiv(t={},e=()=>{}){let n={},s=this.#t("div",n,t);return e(this,s),this}addP(t={},e=()=>{}){let n={},s=this.#t("p",n,t);return e(this,s),this}addImg(t={},e=()=>{}){let n={},s=this.#t("img",n,t);return e(this,s),this}addHeader(t,e={},n=()=>{}){let s={},r=this.#t("h"+t,s,e);return n(this,r),this}addHr(t={},e=()=>{}){let n={},s=this.#t("hr",n,t);return e(this,s),this}addBr(t={},e=()=>{}){let n={},s=this.#t("br",n,t);return e(this,s),this}addCheckbox(t={},e=()=>{}){let n={type:"checkbox"},s=this.#t("label",{textContent:t.textContent??""});delete t.textContent;let r=this.#t("input",n,t);return s.insertBefore(r,s.firstChild),this.buildElement(),e(this,s,r),this}addButton(t={},e=()=>{}){let n={},s=this.#t("button",n,t);return e(this,s),this}addButtonHelp(t={},e=()=>{}){let n=t.title??t.textContent??"Help: No info";delete t.textContent,t.title=`Help: ${n}`;let s={textContent:"?",className:"bm-help",onclick:()=>{this.updateInnerHTML(this.outputStatusId,n)}},r=this.#t("button",s,t);return e(this,r),this}addInput(t={},e=()=>{}){let n={},s=this.#t("input",n,t);return e(this,s),this}addInputFile(t={},e=()=>{}){let n={type:"file",style:"display: none;"},s=t.textContent??"";delete t.textContent;let r=this.#t("div"),o=this.#t("input",n,t);this.buildElement();let i=this.#t("button",{textContent:s});return this.buildElement(),this.buildElement(),i.addEventListener("click",()=>{o.click()}),o.addEventListener("change",()=>{i.style.maxWidth=`${i.offsetWidth}px`,o.files.length>0?i.textContent=o.files[0].name:i.textContent=s}),e(this,r,o,i),this}addTextarea(t={},e=()=>{}){let n={},s=this.#t("textarea",n,t);return e(this,s),this}updateInnerHTML(t,e,n=!1){let s=document.getElementById(t.replace(/^#/,""));if(s){if(s instanceof HTMLInputElement){s.value=e;return}n?s.textContent=e:s.innerHTML=e}}handleDrag(t,e){let n=!1,s,r=0;if(t=document.querySelector(t?.[0]=="#"?t:"#"+t),e=document.querySelector(e?.[0]=="#"?e:"#"+e),!t||!e){this.handleDisplayError(`Can not drag! ${t?"":"moveMe"} ${!t&&!e?"and ":""}${e?"":"iMoveThings "}was not found!`);return}e.addEventListener("mousedown",function(o){n=!0,s=o.clientX-t.getBoundingClientRect().left,r=o.clientY-t.getBoundingClientRect().top,document.body.style.userSelect="none",e.classList.add("dragging")}),e.addEventListener("touchstart",function(o){n=!0;let i=o?.touches?.[0];i&&(s=i.clientX-t.getBoundingClientRect().left,r=i.clientY-t.getBoundingClientRect().top,document.body.style.userSelect="none",e.classList.add("dragging"))},{passive:!1}),document.addEventListener("mousemove",function(o){n&&(t.style.left=o.clientX-s+"px",t.style.top=o.clientY-r+"px",t.style.right="")}),document.addEventListener("touchmove",function(o){if(n){let i=o?.touches?.[0];if(!i)return;t.style.left=i.clientX-s+"px",t.style.top=i.clientY-r+"px",o.preventDefault()}},{passive:!1}),document.addEventListener("mouseup",function(){n=!1,document.body.style.userSelect="",e.classList.remove("dragging")}),document.addEventListener("touchend",function(){n=!1,document.body.style.userSelect="",e.classList.remove("dragging")}),document.addEventListener("touchcancel",function(){n=!1,document.body.style.userSelect="",e.classList.remove("dragging")})}handleDisplayError(t){console.error(`${this.name}: ${t}`),this.updateInnerHTML(this.outputStatusId,"Error: "+t,!0)}};var h=class{constructor(){this.observerBody=null,this.observerBodyTarget=null,this.targetDisplayCoords="#bm-display-coords"}createObserverBody(t){return this.observerBodyTarget=t,this.observerBody=new MutationObserver(e=>{for(let n of e)for(let s of n.addedNodes)s instanceof HTMLElement&&s.matches?.(this.targetDisplayCoords)}),this}getObserverBody(){return this.observerBody}observe(t,e=!1,n=!1){t.observe(this.observerBodyTarget,{childList:e,subtree:n})}};var b=class{serverTPtoDisplayTP(t,e){return[parseInt(t[0])%4*1e3+parseInt(e[0]),parseInt(t[1])%4*1e3+parseInt(e[1])]}};var f=class{constructor(t){this.coordsHandler=t,this.disableAll=!1,this.coordsTilePixel=[]}spontaneousResponseListener(t){window.addEventListener("message",e=>{let n=e.data;if(!(n&&n.source==="blue-marble"))return;let s=n.endpoint.split("?")[0].split("/").filter(r=>r&&isNaN(Number(r))).pop();switch(console.log(`Recieved message about "${s}"`),s){case"me":if(n.jsonData?.status&&n.jsonData?.status?.toString()[0]!="2"){t.handleDisplayError(`The game is down!
+Could not fetch userdata.`);return}let r=Math.ceil(Math.pow(Math.floor(n.jsonData?.level)*Math.pow(30,.65),1/.65)-n.jsonData?.pixelsPainted);t.updateInnerHTML("bm-user-name",`Username: ${n.jsonData?.name}`),t.updateInnerHTML("bm-user-droplets",`Droplets: ${new Intl.NumberFormat().format(n.jsonData?.droplets)}`),t.updateInnerHTML("bm-user-nextlevel",`Next level in ${new Intl.NumberFormat().format(r)} pixel${r==1?"":"s"}`);break;case"pixel":let o=n.endpoint.split("?")[0].split("/").filter(d=>d&&!isNaN(Number(d))),i=new URLSearchParams(n.endpoint.split("?")[1]),p=[i.get("x"),i.get("y")];this.coordsTilePixel=[...o,...p];let g=this.coordsHandler.serverTPtoDisplayTP(o,p),v=document.querySelectorAll("span");for(let d of v)if(d.textContent.trim().includes(`${g[0]}, ${g[1]}`)){let a=document.querySelector("#bm-display-coords"),E=`(Tl X: ${o[0]}, Tl Y: ${o[1]}, Px X: ${p[0]}, Px Y: ${p[1]})`;a?a.textContent=E:(a=document.createElement("span"),a.id="bm-display-coords",a.textContent=E,a.style="margin-left: calc(var(--spacing)*3); font-size: small;",d.parentNode.parentNode.parentNode.insertAdjacentElement("afterend",a))}break;case"robots":this.disableAll=n.jsonData?.userscript?.toString().toLowerCase()=="false";break}})}};var y=GM_info.script.name.toString(),x=GM_info.script.version.toString();function L(l){let t=document.createElement("script");t.textContent=`(${l})();`,document.documentElement.appendChild(t),t.remove()}L(()=>{let l=window.fetch;window.fetch=async function(...t){let e=await l.apply(this,t),n=e.clone();if((n.headers.get("content-type")||"").includes("application/json")){let r=(t[0]instanceof Request?t[0]?.url:t[0])||"ignore";console.log(`Sending JSON message about endpoint "${r}"`),n.json().then(o=>{window.postMessage({source:"blue-marble",endpoint:r,jsonData:o},"*")}).catch(o=>{console.error("BM - Failed to parse JSON:",o)})}return e}});var w=GM_getResourceText("CSS-BM-File");GM_addStyle(w);var u=document.createElement("link");u.href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap";u.rel="preload";u.as="style";u.onload=function(){this.onload=null,this.rel="stylesheet"};document.head.appendChild(u);var P=new h,c=new m(y,x),H=new b,C=new f(H);c.setApiHandler(C);c.addDiv({id:"bm-overlay",style:"top: 10px; right: 75px;"}).addDiv({id:"bm-contain-header"}).addDiv({id:"bm-bar-drag"}).buildElement().addImg({alt:"Blue Marble Icon",src:"https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/src/assets/Favicon.png"}).buildElement().addHeader(1,{textContent:y}).buildElement().buildElement().addHr().buildElement().addDiv({id:"bm-contain-userinfo"}).addP({id:"bm-user-name",textContent:"Username:"}).buildElement().addP({id:"bm-user-droplets",textContent:"Droplets:"}).buildElement().addP({id:"bm-user-nextlevel",textContent:"Next level in..."}).buildElement().buildElement().addHr().buildElement().addDiv({id:"bm-contain-automation"}).addCheckbox({id:"bm-input-stealth",textContent:"Stealth",checked:!0}).buildElement().addButtonHelp({title:"Waits for the website to make requests, instead of sending requests."}).buildElement().addBr().buildElement().addCheckbox({id:"bm-input-possessed",textContent:"Possessed",checked:!0}).buildElement().addButtonHelp({title:"Controls the website as if it were possessed."}).buildElement().addBr().buildElement().addDiv({id:"bm-contain-coords"}).addButton({id:"bm-button-coords",className:"bm-help",style:"margin-top: 0;",innerHTML:''},(l,t)=>{t.onclick=()=>{let e=l.apiHandler?.coordsTilePixel;if(!e?.[0]){l.handleDisplayError("Coordinates are malformed! Did you try clicking on the canvas first?");return}l.updateInnerHTML("bm-input-tx",e?.[0]||""),l.updateInnerHTML("bm-input-ty",e?.[1]||""),l.updateInnerHTML("bm-input-px",e?.[2]||""),l.updateInnerHTML("bm-input-py",e?.[3]||"")}}).buildElement().addInput({type:"number",id:"bm-input-tx",placeholder:"Tl X",min:0,max:2047,step:1}).buildElement().addInput({type:"number",id:"bm-input-ty",placeholder:"Tl Y",min:0,max:2047,step:1}).buildElement().addInput({type:"number",id:"bm-input-px",placeholder:"Px X",min:0,max:2047,step:1}).buildElement().addInput({type:"number",id:"bm-input-py",placeholder:"Px Y",min:0,max:2047,step:1}).buildElement().buildElement().addInputFile({id:"bm-input-file-template",textContent:"Upload Template"}).buildElement().addDiv({id:"bm-contain-buttons"}).addButton({id:"bm-button-enable",textContent:"Enable"}).buildElement().addButton({id:"bm-button-disable",textContent:"Disable"}).buildElement().buildElement().addTextarea({id:c.outputStatusId,placeholder:`Status: Sleeping...
+Version: ${x}`,readOnly:!0}).buildElement().buildElement().buildOverlay(document.body);c.handleDrag("#bm-overlay","#bm-bar-drag");C.spontaneousResponseListener(c);console.log(`${y} (${x}) userscript has loaded!`);})();
diff --git a/docs/README.md b/docs/README.md
index e935894..764280b 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -35,7 +35,7 @@
-
+
diff --git a/package-lock.json b/package-lock.json
index b0d6b1c..c218048 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7,7 +7,7 @@
"devDependencies": {
"esbuild": "^0.25.0"
},
- "version": "0.43.6"
+ "version": "0.43.40"
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.8",
@@ -467,5 +467,5 @@
}
}
},
- "version": "0.43.6"
+ "version": "0.43.40"
}
diff --git a/package.json b/package.json
index a04acc4..7edf40c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "wplace-bluemarble",
- "version": "0.43.6",
+ "version": "0.43.40",
"type": "module",
"scripts": {
"build": "node build/build.js",
diff --git a/src/BlueMarble.meta.js b/src/BlueMarble.meta.js
index c3b326d..aeff79b 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.43.6
+// @version 0.43.40
// @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/apiHandler.js b/src/apiHandler.js
index d1797f5..bf91af7 100644
--- a/src/apiHandler.js
+++ b/src/apiHandler.js
@@ -40,6 +40,15 @@ export class ApiHandler {
switch (endpointText) {
case 'me': // Request to retrieve user data
+
+ // If the game can not retrieve the userdata...
+ if (data.jsonData?.status && data.jsonData?.status?.toString()[0] != '2') {
+ // The server is probably down (NOT a 2xx status)
+
+ overlay.handleDisplayError(`The game is down!\nCould not fetch userdata.`);
+ return; // Kills itself before attempting to display null userdata
+ }
+
const nextLevelPixels = Math.ceil(Math.pow(Math.floor(data.jsonData?.level) * Math.pow(30, 0.65), (1/0.65)) - data.jsonData?.pixelsPainted); // Calculates pixels to the next level
overlay.updateInnerHTML('bm-user-name', `Username: ${data.jsonData?.name}`); // Updates the text content of the username field
diff --git a/src/main.js b/src/main.js
index 48a024a..93e45fd 100644
--- a/src/main.js
+++ b/src/main.js
@@ -70,7 +70,10 @@ var stylesheetLink = document.createElement('link');
stylesheetLink.href = 'https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap';
stylesheetLink.rel = 'preload';
stylesheetLink.as = 'style';
-stylesheetLink.onload = "this.onload=null;this.rel='stylesheet'";
+stylesheetLink.onload = function () {
+ this.onload = null;
+ this.rel = 'stylesheet';
+};
document.head.appendChild(stylesheetLink);
const observers = new Observers(); // Constructs a new Observers object
@@ -81,12 +84,63 @@ const apiHandler = new ApiHandler(coordsHandler); // Constructs a new ApiHandler
overlay.setApiHandler(apiHandler); // Sets the API handler
// Deploys the overlay to the page
+// Parent/child relationships in the DOM structure below are indicated by indentation
overlay.addDiv({'id': 'bm-overlay', 'style': 'top: 10px; right: 75px;'})
.addDiv({'id': 'bm-contain-header'})
.addDiv({'id': 'bm-bar-drag'}).buildElement()
.addImg({'alt': 'Blue Marble Icon', 'src': 'https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/src/assets/Favicon.png'}).buildElement()
+ .addHeader(1, {'textContent': name}).buildElement()
+ .buildElement()
+
+ .addHr().buildElement()
+
+ .addDiv({'id': 'bm-contain-userinfo'})
+ .addP({'id': 'bm-user-name', 'textContent': 'Username:'}).buildElement()
+ .addP({'id': 'bm-user-droplets', 'textContent': 'Droplets:'}).buildElement()
+ .addP({'id': 'bm-user-nextlevel', 'textContent': 'Next level in...'}).buildElement()
+ .buildElement()
+
+ .addHr().buildElement()
+
+ .addDiv({'id': 'bm-contain-automation'})
+ .addCheckbox({'id': 'bm-input-stealth', 'textContent': 'Stealth', 'checked': true}).buildElement()
+ .addButtonHelp({'title': 'Waits for the website to make requests, instead of sending requests.'}).buildElement()
+ .addBr().buildElement()
+ .addCheckbox({'id': 'bm-input-possessed', 'textContent': 'Possessed', 'checked': true}).buildElement()
+ .addButtonHelp({'title': 'Controls the website as if it were possessed.'}).buildElement()
+ .addBr().buildElement()
+ .addDiv({'id': 'bm-contain-coords'})
+ .addButton({'id': 'bm-button-coords', 'className': 'bm-help', 'style': 'margin-top: 0;', 'innerHTML': ''},
+ (instance, button) => {
+ button.onclick = () => {
+ const coords = instance.apiHandler?.coordsTilePixel; // Retrieves the coords from the API handler
+ if (!coords?.[0]) {
+ instance.handleDisplayError('Coordinates are malformed! Did you try clicking on the canvas first?');
+ return;
+ }
+ instance.updateInnerHTML('bm-input-tx', coords?.[0] || '');
+ instance.updateInnerHTML('bm-input-ty', coords?.[1] || '');
+ instance.updateInnerHTML('bm-input-px', coords?.[2] || '');
+ instance.updateInnerHTML('bm-input-py', coords?.[3] || '');
+ }
+ }
+ ).buildElement()
+ .addInput({'type': 'number', 'id': 'bm-input-tx', 'placeholder': 'Tl X', 'min': 0, 'max': 2047, 'step': 1}).buildElement()
+ .addInput({'type': 'number', 'id': 'bm-input-ty', 'placeholder': 'Tl Y', 'min': 0, 'max': 2047, 'step': 1}).buildElement()
+ .addInput({'type': 'number', 'id': 'bm-input-px', 'placeholder': 'Px X', 'min': 0, 'max': 2047, 'step': 1}).buildElement()
+ .addInput({'type': 'number', 'id': 'bm-input-py', 'placeholder': 'Px Y', 'min': 0, 'max': 2047, 'step': 1}).buildElement()
+ .buildElement()
+ .addInputFile({'id': 'bm-input-file-template', 'textContent': 'Upload Template'}).buildElement()
+ .addDiv({'id': 'bm-contain-buttons'})
+ .addButton({'id': 'bm-button-enable', 'textContent': 'Enable'}).buildElement()
+ .addButton({'id': 'bm-button-disable', 'textContent': 'Disable'}).buildElement()
+ .buildElement()
+ .addTextarea({'id': overlay.outputStatusId, 'placeholder': `Status: Sleeping...\nVersion: ${version}`, 'readOnly': true}).buildElement()
+ .buildElement()
.buildOverlay(document.body);
+overlay.handleDrag('#bm-overlay', '#bm-bar-drag'); // Creates dragging capability on the drag bar for dragging the overlay
+
apiHandler.spontaneousResponseListener(overlay); // Reads spontaneous fetch responces
console.log(`${name} (${version}) userscript has loaded!`);
\ No newline at end of file
diff --git a/src/overlay.css b/src/overlay.css
index 7348a21..8ebedf4 100644
--- a/src/overlay.css
+++ b/src/overlay.css
@@ -123,7 +123,7 @@ div#bm-overlay {
}
/* The template file upload button */
-div:has(> #bm-input-file) > button {
+div:has(> #bm-input-file-template) > button {
width: 100%;
white-space: nowrap;
overflow: hidden;
@@ -144,7 +144,7 @@ div:has(> #bm-input-file) > button {
#bm-contain-automation,
#bm-contain-coords,
#bm-contain-buttons,
-div:has(> #bm-input-file),
+div:has(> #bm-input-file-template),
#bm-output-status {
margin-top: 0.5em;
}
diff --git a/src/overlay.js b/src/overlay.js
index 9eb0f81..ce78e11 100644
--- a/src/overlay.js
+++ b/src/overlay.js
@@ -1,6 +1,21 @@
-/** The overlay for the Blue Marble script.
+/** The overlay builder for the Blue Marble script.
* @description This class handles the overlay UI for the Blue Marble script.
* @since 0.0.2
+ * @example
+ * const overlay = new Overlay();
+ * overlay.addDiv('overlay')
+ * .addHeader(1, {'textContent': 'Your Overlay'}).buildElement()
+ * .addP({'textContent': 'This is your overlay. It is versatile.'}).buildElement()
+ * .addHr().buildElement()
+ * .buildOverlay(document.body);
+ * // Output:
+ * // (Assume
This is your overlay. It is versatile.
+ *+ * .addHeader(1).buildElement() // Breaks out of the
* .buildElement() // Breaks out of the
` to the overlay. + * This `
` element will have properties shared between all `
` elements in the overlay.
+ * You can override the shared properties by using a callback.
+ * @param {Object. ` that are NOT shared between all overlay ` ` elements. These should be camelCase.
+ * @param {function(Overlay, HTMLParagraphElement):void} [callback=()=>{}] - Additional JS modification to the ` `.
+ * @returns {Overlay} Overlay class instance (this)
+ * @since 0.43.2
+ * @example
+ * // Assume all elements have a shared class (e.g. {'className': 'bar'})
+ * overlay.addP({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
+ * // Output:
+ * // (Assume DOM properties
+
+ const p = this.#createElement('p', properties, additionalProperties); // Creates the element
+ callback(this, p); // Runs any script passed in through the callback
return this;
}
@@ -125,157 +174,306 @@ export class Overlay {
* This `` element will have properties shared between all `
` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.
` that are NOT shared between all overlay `
` elements. These should be camelCase.
- * @param {function(HTMLElement):void} [callback=()=>{}] - Additional modification to the img.
+ * @param {function(Overlay, HTMLImageElement):void} [callback=()=>{}] - Additional JS modification to the `
`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.43.2
* @example
* // Assume all
elements have a shared class (e.g. {'className': 'bar'})
- * overlay.addimg({'id': 'foo'}).buildOverlay();
- * //
+ * overlay.addimg({'id': 'foo', 'src': './img.png'}).buildOverlay(document.body);
+ * // Output:
+ * // (Assume already exists in the webpage)
+ *
+ *
+ *
*/
addImg(additionalProperties = {}, callback = () => {}) {
- const properties = {}; // Shared DOM properties
- const img = this.createElement('img', properties, additionalProperties); // Creates the div element
- callback(img); // Runs (optionally) passed in script
+ const img = this.#createElement('img', properties, additionalProperties); // Creates the
element
+ callback(this, img); // Runs any script passed in through the callback
return this;
}
- /** Creates and deploys the overlay element
- * @since 0.0.2
+ /** Adds a header to the overlay.
+ * This header element will have properties shared between all header elements in the overlay.
+ * You can override the shared properties by using a callback.
+ * @param {number} level - The header level. Must be between 1 and 6 (inclusive)
+ * @param {Object.
Foobar.
+ *
*/
- create() {
+ addHeader(level, additionalProperties = {}, callback = () => {}) {
- const overlay = document.createElement('div'); // Creates a new
` to the overlay.
+ * This `
` element will have properties shared between all `
` elements in the overlay.
+ * You can override the shared properties by using a callback.
+ * @param {Object.
` that are NOT shared between all overlay `
` elements. These should be camelCase.
+ * @param {function(Overlay, HTMLHRElement):void} [callback=()=>{}] - Additional JS modification to the `
`.
+ * @returns {Overlay} Overlay class instance (this)
+ * @since 0.43.7
+ * @example
+ * // Assume all
elements have a shared class (e.g. {'className': 'bar'})
+ * overlay.addhr({'id': 'foo'}).buildOverlay(document.body);
+ * // Output:
+ * // (Assume already exists in the webpage)
+ *
+ *
+ *
+ */
+ addHr(additionalProperties = {}, callback = () => {}) {
- const barHeaderImage = document.createElement('img'); // Image in header
- barHeaderImage.src = 'https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/src/assets/Favicon.png';
- barHeaderImage.alt = 'Blue Marble Icon';
- containerOverlayHeader.appendChild(barHeaderImage); // Adds the header image to the overlay header container
+ const properties = {}; // Shared
DOM properties
- const barHeader = document.createElement('h1'); // Header bar for the overlay
- barHeader.textContent = this.name;
- containerOverlayHeader.appendChild(barHeader); // Adds the header to the overlay header container
+ const hr = this.#createElement('hr', properties, additionalProperties); // Creates the
element
+ callback(this, hr); // Runs any script passed in through the callback
+ return this;
+ }
- const containerUserInfo = document.createElement('div'); // User info container
- containerUserInfo.id = 'bm-contain-userinfo';
+ /** Adds a `
` to the overlay.
+ * This `
` element will have properties shared between all `
` elements in the overlay.
+ * You can override the shared properties by using a callback.
+ * @param {Object.
` that are NOT shared between all overlay `
` elements. These should be camelCase.
+ * @param {function(Overlay, HTMLBRElement):void} [callback=()=>{}] - Additional JS modification to the `
`.
+ * @returns {Overlay} Overlay class instance (this)
+ * @since 0.43.11
+ * @example
+ * // Assume all
elements have a shared class (e.g. {'className': 'bar'})
+ * overlay.addbr({'id': 'foo'}).buildOverlay(document.body);
+ * // Output:
+ * // (Assume already exists in the webpage)
+ *
+ *
+ *
+ */
+ addBr(additionalProperties = {}, callback = () => {}) {
- const userName = document.createElement('p'); // User name field
- userName.id = 'bm-user-name';
- userName.textContent = 'Username:';
- containerUserInfo.appendChild(userName); // Adds the username field to the user info container
+ const properties = {}; // Shared
DOM properties
- const userDroplets = document.createElement('p'); // User droplet field
- userDroplets.id = 'bm-user-droplets';
- userDroplets.textContent = 'Droplets:';
- containerUserInfo.appendChild(userDroplets); // Adds the droplet field to the user info container
+ const br = this.#createElement('br', properties, additionalProperties); // Creates the
element
+ callback(this, br); // Runs any script passed in through the callback
+ return this;
+ }
- const userNextLevel = document.createElement('p'); // Amount to next level
- userNextLevel.id = 'bm-user-nextlevel';
- userNextLevel.textContent = 'Next level in...';
- containerUserInfo.appendChild(userNextLevel); // Adds the "amount to next level" field to the user info container
+ /** Adds a checkbox to the overlay.
+ * This checkbox element will have properties shared between all checkbox elements in the overlay.
+ * You can override the shared properties by using a callback. Note: the checkbox element is inside a label element.
+ * @param {Object.