Finished multi-tile templates

This commit is contained in:
SwingTheVine 2025-08-01 21:25:37 -04:00
parent 031f114433
commit 7c54997b73
8 changed files with 45 additions and 135 deletions

File diff suppressed because one or more lines are too long

View file

@ -35,7 +35,7 @@
<a href="https://github.com/SwingTheVine/Wplace-BlueMarble/blob/main/LICENSE.txt" target="_blank"><img alt="Software License: MPL-2.0" src="https://img.shields.io/badge/Software_License-MPL--2.0-brightgreen?style=flat"></a>
<a href="https://discord.gg/tpeBPy46hf" target="_blank"><img alt="Contact Me" src="https://img.shields.io/badge/Contact_Me-gray?style=flat&logo=Discord&logoColor=white&logoSize=auto&labelColor=cornflowerblue"></a>
<a href="" target="_blank"><img alt="WakaTime" src="https://img.shields.io/badge/Coding_Time-59hrs_0mins-blue?style=flat&logo=wakatime&logoColor=black&logoSize=auto&labelColor=white"></a>
<a href="" target="_blank"><img alt="Total Patches" src="https://img.shields.io/badge/Total_Patches-428-black?style=flat"></a>
<a href="" target="_blank"><img alt="Total Patches" src="https://img.shields.io/badge/Total_Patches-434-black?style=flat"></a>
<a href="" target="_blank"><img alt="Total Lines of Code" src="https://tokei.rs/b1/github/SwingTheVine/Wplace-BlueMarble?category=code"></a>
<a href="" target="_blank"><img alt="Total Comments" src="https://tokei.rs/b1/github/SwingTheVine/Wplace-BlueMarble?category=comments"></a>
<a href="" target="_blank"><img alt="Compression" src="https://img.shields.io/badge/Compression-73.03%25-blue"></a>

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "wplace-bluemarble",
"version": "0.65.74",
"version": "0.65.80",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "wplace-bluemarble",
"version": "0.65.74",
"version": "0.65.80",
"devDependencies": {
"esbuild": "^0.25.0",
"terser": "^5.43.1"

View file

@ -1,6 +1,6 @@
{
"name": "wplace-bluemarble",
"version": "0.65.74",
"version": "0.65.80",
"type": "module",
"scripts": {
"build": "node build/build.js",

View file

@ -1,7 +1,7 @@
// ==UserScript==
// @name Blue Marble
// @namespace https://github.com/SwingTheVine/
// @version 0.65.74
// @version 0.65.80
// @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

View file

@ -77,8 +77,8 @@ export default class Template {
console.log(`Draw Size X: ${drawSizeX}\nDraw Size Y: ${drawSizeY}`);
// Change the canvas size and wipe the canvas
const canvasWidth = (drawSizeX * shreadSize) + (this.coords[2] * shreadSize);
const canvasHeight = (drawSizeY * shreadSize) + (this.coords[3] * shreadSize);
const canvasWidth = (drawSizeX * shreadSize) + ((pixelX % this.tileSize) * shreadSize);
const canvasHeight = (drawSizeY * shreadSize) + ((pixelY % this.tileSize) * shreadSize);
canvas.width = canvasWidth;
canvas.height = canvasHeight;

View file

@ -165,7 +165,7 @@ document.head.appendChild(stylesheetLink);
// CONSTRUCTORS
const observers = new Observers(); // Constructs a new Observers object
const overlay = new Overlay(name, version); // Constructs a new Overlay object
const templateManager = new TemplateManager(name, version); // Constructs a new TemplateManager object
const templateManager = new TemplateManager(name, version, overlay); // Constructs a new TemplateManager object
const apiManager = new ApiManager(templateManager); // Constructs a new ApiManager object
overlay.setApiManager(apiManager); // Sets the API manager
@ -201,12 +201,12 @@ function buildOverlayMain() {
.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()
// .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': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 4 6"><circle cx="2" cy="2" r="2"></circle><path d="M2 6 L3.7 3 L0.3 3 Z"></path><circle cx="2" cy="2" r="0.7" fill="white"></circle></svg></svg>'},
(instance, button) => {
@ -256,15 +256,15 @@ function buildOverlayMain() {
instance.handleDisplayStatus(`Drew to canvas!`);
}
}).buildElement()
.addButton({'id': 'bm-button-disable', 'textContent': 'Disable'}).buildElement()
// .addButton({'id': 'bm-button-disable', 'textContent': 'Disable'}).buildElement()
.buildElement()
.addTextarea({'id': overlay.outputStatusId, 'placeholder': `Status: Sleeping...\nVersion: ${version}`, 'readOnly': true}).buildElement()
.addDiv({'id': 'bm-contain-buttons-action'})
.addDiv()
.addButton({'id': 'bm-button-teleport', 'className': 'bm-help', 'textContent': '✈'}).buildElement()
.addButton({'id': 'bm-button-favorite', 'className': 'bm-help', 'innerHTML': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><polygon points="10,2 12,7.5 18,7.5 13.5,11.5 15.5,18 10,14 4.5,18 6.5,11.5 2,7.5 8,7.5" fill="white"></polygon></svg>'}).buildElement()
.addButton({'id': 'bm-button-templates', 'className': 'bm-help', 'innerHTML': '🖌'}).buildElement()
.addButton({'id': 'bm-button-convert', 'className': 'bm-help', 'innerHTML': '🎨'},
// .addButton({'id': 'bm-button-teleport', 'className': 'bm-help', 'textContent': '✈'}).buildElement()
// .addButton({'id': 'bm-button-favorite', 'className': 'bm-help', 'innerHTML': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><polygon points="10,2 12,7.5 18,7.5 13.5,11.5 15.5,18 10,14 4.5,18 6.5,11.5 2,7.5 8,7.5" fill="white"></polygon></svg>'}).buildElement()
// .addButton({'id': 'bm-button-templates', 'className': 'bm-help', 'innerHTML': '🖌'}).buildElement()
.addButton({'id': 'bm-button-convert', 'className': 'bm-help', 'innerHTML': '🎨', 'title': 'Template Color Converter'},
(instance, button) => {
button.addEventListener('click', () => {
window.open('https://pepoafonso.github.io/color_converter_wplace/', '_blank', 'noopener noreferrer');

View file

@ -37,11 +37,12 @@ export default class TemplateManager {
/** The constructor for the {@link TemplateManager} class.
* @since 0.55.8
*/
constructor(name, version) {
constructor(name, version, overlay) {
// Meta
this.name = name; // Name of userscript
this.version = version; // Version of userscript
this.overlay = overlay; // The main instance of the Overlay class
this.templatesVersion = '1.0.0'; // Version of JSON schema
this.userID = null; // The ID of the current user
this.encodingBase = '!#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~'; // Characters to use for encoding/decoding
@ -116,18 +117,19 @@ export default class TemplateManager {
* @param {File} blob - The file blob to create a template from
* @param {string} name - The display name of the template
* @param {Array<number, number, number, number>} coords - The coordinates of the top left corner of the template
* @since 0.65.77
*/
async createTemplate(blob, name, coords) {
// Creates the JSON object if it does not already exist
if (!this.templatesJSON) {this.templatesJSON = await this.createJSON(); console.log(`Creating JSON...`);}
console.log(`Awaiting creation...`);
this.overlay.handleDisplayStatus(`Creating template at ${coords.join(', ')}...`);
// Creates a new template instance
const template = new Template({
displayName: name,
sortID: Object.keys(this.templatesJSON.templates).length || 0,
sortID: 0, // Object.keys(this.templatesJSON.templates).length || 0, // Uncomment this to enable multiple templates (1/2)
authorID: numberToEncoded(this.userID || 0, this.encodingBase),
file: blob,
coords: coords
@ -142,8 +144,11 @@ export default class TemplateManager {
"tiles": template.chunked
};
this.templatesArray = []; // Remove this to enable multiple templates (2/2)
this.templatesArray.push(template); // Pushes the Template object instance to the Template Array
this.overlay.handleDisplayStatus(`Template created at ${coords.join(', ')}!`);
console.log(Object.keys(this.templatesJSON.templates).length);
console.log(this.templatesJSON);
console.log(this.templatesArray);
@ -160,11 +165,22 @@ export default class TemplateManager {
*/
deleteTemplate() {
}
/** Disables the template from view
*/
async disableTemplate() {
// Creates the JSON object if it does not already exist
if (!this.templatesJSON) {this.templatesJSON = await this.createJSON(); console.log(`Creating JSON...`);}
}
/** Draws all templates on that tile
* @param {File} tileBlob - The pixels that are placed on a tile
* @param {[number, number]} tileCoords - The tile coordinates [x, y]
* @since 0.65.77
*/
async drawTemplateOnTile(tileBlob, tileCoords) {
@ -200,6 +216,10 @@ export default class TemplateManager {
.filter(Boolean);
console.log(templateBlobs);
if (templateBlobs.length > 0) {
this.overlay.handleDisplayStatus(`Displaying ${templateBlobs.length} template${templateBlobs.length == 1 ? '' : 's'}.`);
}
const tileBitmap = await createImageBitmap(tileBlob);
@ -243,114 +263,4 @@ export default class TemplateManager {
#parseOSU() {
}
/** Sets the template to the image passed in.
* @param {File} file - The file of the template image.
* @since 0.55.8
* @deprecated Since 0.65.43
*/
setTemplateImage(file) {
this.template = file;
this.templateState = 'file';
// const url = URL.createObjectURL(file); // Creates a blob URL
// window.open(url, '_blank'); // Opens a new tab with blob
// setTimeout(() => URL.revokeObjectURL(url), 10000); // Destroys the blob 10 seconds later
}
/** Draws the template on the tile.
* @param {File|Blob} tileBlob - The blob of the tile
* @param {Array<number, number, number, number>} [coordsTilePixel=[0,0,0,0]] - A number array of the four coordinates
* @returns {File|Blob} A image/png blob file
* @since 0.63.59
* @deprecated Since 0.65.43
*/
async drawTemplate(tileBlob, coordsTilePixel=[0, 0, 0, 0]) {
// Only continue if template state is NOT 'file' NOR 'template'
if (!((this.templateState == 'file') || (this.templateState == 'template'))) {return;}
const tileSize = 1000; // Pixels in a tile
const drawMult = 3; // Multiplier of draw size
const drawSize = tileSize * drawMult; // Draw multiplier
coordsTilePixel = !!coordsTilePixel?.length ? coordsTilePixel : [0, 0, 0, 0]; // Set to default if [] passed in
console.log(this.template);
// If the template has already been drawn, don't draw it again
const templateBitmap = this.templateState == 'template' ? this.template : await createImageBitmap(await this.shreadBlob(this.template));
const tileBitmap = await createImageBitmap(tileBlob);
const canvas = new OffscreenCanvas(drawSize, drawSize);
const context = canvas.getContext('2d');
context.imageSmoothingEnabled = false; // Nearest neighbor
// Tells the canvas to ignore anything outside of this area
context.beginPath();
context.rect(0, 0, drawSize, drawSize);
context.clip();
context.clearRect(0, 0, drawSize, drawSize); // Draws transparent background
context.drawImage(tileBitmap, 0, 0, drawSize, drawSize);
context.drawImage(templateBitmap, coordsTilePixel[2]*3, coordsTilePixel[3]*3);
const final = await canvas.convertToBlob({ type: 'image/png' });
// If the template is not drawn yet...
if (this.templateState != 'template') {
// (99% chance templateState is 'file')
this.template = templateBitmap; // Store the drawn template
this.templateState = 'template'; // Indicate that the template has been drawn
// const url = URL.createObjectURL(final); // Creates a blob URL
// window.open(url, '_blank'); // Opens a new tab with blob
// setTimeout(() => URL.revokeObjectURL(url), 10000); // Destroys the blob 10 seconds later
}
return final;
}
/** Shreads the blob so that every pixel is surrounded by adjacent transparent pixels
* @param {Blob|File} blob - The blob to manipulate
* @param {number} [shrinkFactor=3] - An odd number that will place each pixel in the center. Even numbers will be increased by 1
* @param {string} [fileType='image/png'] - The File type to output as. PNG is one of the few transparent types.
* @returns {Promise<File>} - A Promise that resolved to a image/png file blob
* @since 0.63.37
*/
async shreadBlob(blob, shreadSize = 3, fileType = 'image/png') {
const bitmap = await createImageBitmap(blob); // Creates a bitmap image
shreadSize |= 1; // Converts shreadSize to always be odd by forcing the right-most bit to be 1.
const width = bitmap.width * Math.round(shreadSize); // Width of the canvas based on shread size times blob
const height = bitmap.height * Math.round(shreadSize); // Height of the canvas based on shread size times blob
const canvas = document.createElement('canvas'); // Creates a canvas
canvas.width = width;
canvas.height = height;
const context = canvas.getContext('2d'); // Gets the context of the canvas
context.imageSmoothingEnabled = false; // Nearest Neighbor scaling
context.drawImage(bitmap, 0, 0, width, height); // Fills the canvas with the blob
const imageData = context.getImageData(0, 0, width, height); // Data of the image on the canvas
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
// For every pixel...
// ... Make it transparent unless it is the "center"
if ((x % shreadSize !== 1) || (y % shreadSize !== 1)) {
const pixelIndex = (y * width + x) * 4; // Find the pixel index in an array where every 4 indexes are 1 pixel
imageData.data[pixelIndex + 3] = 0; // Make the pixel transparent on the alpha channel
}
}
}
context.putImageData(imageData, 0, 0);
return new Promise((resolve) => {canvas.toBlob(resolve, fileType);});
}
}
}