mirror of
https://github.com/SwingTheVine/Wplace-BlueMarble.git
synced 2026-03-11 17:15:38 +00:00
Persistent template works (mostly)
This commit is contained in:
parent
663ac2950c
commit
5dcde7a9cd
9 changed files with 147 additions and 30 deletions
6
dist/BlueMarble.user.js
vendored
6
dist/BlueMarble.user.js
vendored
File diff suppressed because one or more lines are too long
|
|
@ -43,8 +43,8 @@
|
|||
<a href="https://github.com/SwingTheVine/Wplace-BlueMarble/releases" target="_blank" rel="noopener noreferrer"><img alt="Latest Release" src="https://img.shields.io/github/v/release/SwingTheVine/Wplace-BlueMarble?sort=semver&style=flat&label=Latest%20Release&color=blue"></a>
|
||||
<a href="https://github.com/SwingTheVine/Wplace-BlueMarble/blob/main/LICENSE.txt" target="_blank" rel="noopener noreferrer"><img alt="Software License: MPL-2.0" src="https://img.shields.io/badge/Software_License-MPL--2.0-slateblue?style=flat"></a>
|
||||
<a href="https://discord.gg/tpeBPy46hf" target="_blank" rel="noopener noreferrer"><img alt="Contact Me" src="https://img.shields.io/badge/Contact_Me-gray?style=flat&logo=Discord&logoColor=white&logoSize=auto&labelColor=cornflowerblue"></a>
|
||||
<a href="" target="_blank" rel="noopener noreferrer"><img alt="WakaTime" src="https://img.shields.io/badge/Coding_Time-87hrs_0mins-blue?style=flat&logo=wakatime&logoColor=black&logoSize=auto&labelColor=white"></a>
|
||||
<a href="" target="_blank" rel="noopener noreferrer"><img alt="Total Patches" src="https://img.shields.io/badge/Total_Patches-459-black?style=flat"></a>
|
||||
<a href="" target="_blank" rel="noopener noreferrer"><img alt="WakaTime" src="https://img.shields.io/badge/Coding_Time-91hrs_0mins-blue?style=flat&logo=wakatime&logoColor=black&logoSize=auto&labelColor=white"></a>
|
||||
<a href="" target="_blank" rel="noopener noreferrer"><img alt="Total Patches" src="https://img.shields.io/badge/Total_Patches-476-black?style=flat"></a>
|
||||
<a href="" target="_blank" rel="noopener noreferrer"><img alt="Total Lines of Code" src="https://tokei.rs/b1/github/SwingTheVine/Wplace-BlueMarble?category=code"></a>
|
||||
<a href="" target="_blank" rel="noopener noreferrer"><img alt="Total Comments" src="https://tokei.rs/b1/github/SwingTheVine/Wplace-BlueMarble?category=comments"></a>
|
||||
<a href="" target="_blank" rel="noopener noreferrer"><img alt="Compression" src="https://img.shields.io/badge/Compression-75.07%25-blue"></a>
|
||||
|
|
|
|||
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "wplace-bluemarble",
|
||||
"version": "0.72.4",
|
||||
"version": "0.72.21",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "node build/build.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==
|
||||
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
27
src/utils.js
27
src/utils.js
|
|
@ -99,3 +99,30 @@ 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;
|
||||
}
|
||||
Loading…
Reference in a new issue