diff --git a/mc_manifest.json b/mc_manifest.json index be3226666..53e06581a 100644 --- a/mc_manifest.json +++ b/mc_manifest.json @@ -9,12 +9,12 @@ "version": [ 0, 9, - 2 + 3 ], "min_engine_version": [ 1, 21, - 30 + 40 ] }, "bp_modules": [ @@ -58,7 +58,7 @@ "bp_dependencies": [ { "module_name": "@minecraft/server", - "version": "1.15.0-beta" + "version": "1.16.0-beta" }, { "module_name": "@minecraft/server-ui", diff --git a/package-lock.json b/package-lock.json index f16c169ae..8698e4a04 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,9 +7,9 @@ "name": "worldedit", "license": "GPL-3.0-or-later", "dependencies": { - "@minecraft/server": "1.15.0-beta.1.21.30-preview.24", + "@minecraft/server": "1.16.0-beta.1.21.40-preview.25", "@minecraft/server-admin": "1.0.0-beta.1.19.80-stable", - "@minecraft/server-ui": "1.4.0-beta.1.21.30-preview.24" + "@minecraft/server-ui": "1.4.0-beta.1.21.40-preview.25" }, "devDependencies": { "@typescript-eslint/eslint-plugin": "^7.1.0", @@ -167,9 +167,10 @@ "integrity": "sha512-stbUtINCXbcLNRlGNVX68xRC6ZYq3k3CYmfptwrCcPBEUjVOpVkSj3H4Y0qiSYB+1rVWv7DgiP7Uf9++50Ne5g==" }, "node_modules/@minecraft/server": { - "version": "1.15.0-beta.1.21.30-preview.24", - "resolved": "https://registry.npmjs.org/@minecraft/server/-/server-1.15.0-beta.1.21.30-preview.24.tgz", - "integrity": "sha512-ca/gcNMqddNAGBntFUM12FAP56Zen1Qsy3DrZKUge9ftuF85exdvBwtHoXwE+eLiK9QCapK97aNKkIjLnx+KFg==", + "version": "1.16.0-beta.1.21.40-preview.25", + "resolved": "https://registry.npmjs.org/@minecraft/server/-/server-1.16.0-beta.1.21.40-preview.25.tgz", + "integrity": "sha512-kLhhUbEY3H/obTArlc3tWp7bQgNAi7iy4DSIeaLfoPudg5tKYVBfqYxfdf3qa+S5HWKRYpXFK22/aMFUDow6aA==", + "license": "MIT", "dependencies": { "@minecraft/common": "^1.1.0" } @@ -180,9 +181,10 @@ "integrity": "sha512-HKyfnulfR853lsTlpzWwusDUo5S7KpOhL8w4PwObMeGK4t9ATl32dchZNZpT3N6WlgLxWpq/Z9F6s5th3cFG9A==" }, "node_modules/@minecraft/server-ui": { - "version": "1.4.0-beta.1.21.30-preview.24", - "resolved": "https://registry.npmjs.org/@minecraft/server-ui/-/server-ui-1.4.0-beta.1.21.30-preview.24.tgz", - "integrity": "sha512-bknRa9JcU1NgOrIakK+wbPlSwg5nf8GJPiLtRRYNfSS5dqcLhAoigWhm6p9LyBWa7HHYXzbi48IWddgYXvtv4Q==", + "version": "1.4.0-beta.1.21.40-preview.25", + "resolved": "https://registry.npmjs.org/@minecraft/server-ui/-/server-ui-1.4.0-beta.1.21.40-preview.25.tgz", + "integrity": "sha512-IOTwPGQ1geoGgZE/iZ5wcDJDf2H6nfFKncEMmPrhUMuajNgpk0pev9yGGRsRK3y8OFStXzduJu3aio/JurOOqQ==", + "license": "MIT", "dependencies": { "@minecraft/common": "^1.0.0", "@minecraft/server": "^1.8.0" @@ -1896,9 +1898,9 @@ "integrity": "sha512-stbUtINCXbcLNRlGNVX68xRC6ZYq3k3CYmfptwrCcPBEUjVOpVkSj3H4Y0qiSYB+1rVWv7DgiP7Uf9++50Ne5g==" }, "@minecraft/server": { - "version": "1.15.0-beta.1.21.30-preview.24", - "resolved": "https://registry.npmjs.org/@minecraft/server/-/server-1.15.0-beta.1.21.30-preview.24.tgz", - "integrity": "sha512-ca/gcNMqddNAGBntFUM12FAP56Zen1Qsy3DrZKUge9ftuF85exdvBwtHoXwE+eLiK9QCapK97aNKkIjLnx+KFg==", + "version": "1.16.0-beta.1.21.40-preview.25", + "resolved": "https://registry.npmjs.org/@minecraft/server/-/server-1.16.0-beta.1.21.40-preview.25.tgz", + "integrity": "sha512-kLhhUbEY3H/obTArlc3tWp7bQgNAi7iy4DSIeaLfoPudg5tKYVBfqYxfdf3qa+S5HWKRYpXFK22/aMFUDow6aA==", "requires": { "@minecraft/common": "^1.1.0" } @@ -1909,12 +1911,12 @@ "integrity": "sha512-HKyfnulfR853lsTlpzWwusDUo5S7KpOhL8w4PwObMeGK4t9ATl32dchZNZpT3N6WlgLxWpq/Z9F6s5th3cFG9A==" }, "@minecraft/server-ui": { - "version": "1.4.0-beta.1.21.30-preview.24", - "resolved": "https://registry.npmjs.org/@minecraft/server-ui/-/server-ui-1.4.0-beta.1.21.30-preview.24.tgz", - "integrity": "sha512-bknRa9JcU1NgOrIakK+wbPlSwg5nf8GJPiLtRRYNfSS5dqcLhAoigWhm6p9LyBWa7HHYXzbi48IWddgYXvtv4Q==", + "version": "1.4.0-beta.1.21.40-preview.25", + "resolved": "https://registry.npmjs.org/@minecraft/server-ui/-/server-ui-1.4.0-beta.1.21.40-preview.25.tgz", + "integrity": "sha512-IOTwPGQ1geoGgZE/iZ5wcDJDf2H6nfFKncEMmPrhUMuajNgpk0pev9yGGRsRK3y8OFStXzduJu3aio/JurOOqQ==", "requires": { "@minecraft/common": "^1.0.0", - "@minecraft/server": "1.15.0-beta.1.21.30-preview.24" + "@minecraft/server": "1.16.0-beta.1.21.40-preview.25" } }, "@nodelib/fs.scandir": { diff --git a/package.json b/package.json index 36d5a157f..e0f93db39 100644 --- a/package.json +++ b/package.json @@ -27,13 +27,13 @@ "typescript": "^4.7.4" }, "dependencies": { - "@minecraft/server": "1.15.0-beta.1.21.30-preview.24", + "@minecraft/server": "1.16.0-beta.1.21.40-preview.25", "@minecraft/server-admin": "1.0.0-beta.1.19.80-stable", - "@minecraft/server-ui": "1.4.0-beta.1.21.30-preview.24" + "@minecraft/server-ui": "1.4.0-beta.1.21.40-preview.25" }, "overrides": { "@minecraft/server-ui": { - "@minecraft/server": "1.15.0-beta.1.21.30-preview.24" + "@minecraft/server": "1.16.0-beta.1.21.40-preview.25" } } } \ No newline at end of file diff --git a/src/config.ts b/src/config.ts index c8732f6e4..2c9fc7a7a 100644 --- a/src/config.ts +++ b/src/config.ts @@ -70,4 +70,4 @@ export default { }; // WorldEdit version (do not change) -export const VERSION = "0.9.2"; +export const VERSION = "0.9.3"; diff --git a/src/library/@types/classes/databaseBuilder.d.ts b/src/library/@types/classes/databaseBuilder.d.ts index 36b7c1a72..0277ba3ff 100644 --- a/src/library/@types/classes/databaseBuilder.d.ts +++ b/src/library/@types/classes/databaseBuilder.d.ts @@ -1,66 +1,20 @@ // eslint-disable-next-line @typescript-eslint/no-explicit-any export interface Database { - /** - * Save a value or update a value in the Database under a key - * @param key The key you want to save the value as - * @param value The value you want to save - * @example Database.set('Test Key', 'Test Value'); - */ - set(key: S, value: T[S]): void; + /** Database's data. */ + data: T; - /** - * Get the value of the key - * @param key - * @returns value - * @example Database.get('Test Key'); - */ - get(key: S): T[S]; + /** Returns whether the database is a valid object that can have functions called and data read from. */ + isValid(): boolean; - /** - * Check if the key exists in the table - * @param key - * @returns Whether the key exists - * @example Database.has('Test Key'); - */ - has(key: keyof T): boolean; + /** Returns whether the database has loaded its data from its provider. */ + isLoaded(): boolean; - /** - * Delete the key from the table - * @param key - * @example Database.delete('Test Key'); - */ - delete(key: keyof T): void; - - /** - * Clear everything in the table - * @example Database.clear() - */ + /** Clears everything in the database. */ clear(): void; - /** - * Save all changes made in the database. - * @example Database.save() - */ + /** Saves all changes made in the database. */ save(): void; - /** - * Get all the keys in the database - * @returns Array of keys - * @example Database.keys(); - */ - keys(): (keyof T)[]; - - /** - * Get all the values in the database - * @returns Array of values - * @example Database.values(); - */ - values(): T[keyof T][]; - - /** - * Get all the keys and values in the database in pairs - * @returns Array of key/value pairs - * @example Database.entries(); - */ - entries(): [S, T[S]][]; + /** Deletes the database from the provider it was loaded from. */ + delete(): void; } diff --git a/src/library/Minecraft.ts b/src/library/Minecraft.ts index 9e67fa346..2396ce741 100644 --- a/src/library/Minecraft.ts +++ b/src/library/Minecraft.ts @@ -41,7 +41,7 @@ import { Block } from "./classes/blockBuilder.js"; export { CustomArgType, CommandPosition } from "./classes/commandBuilder.js"; export { commandSyntaxError, registerInformation as CommandInfo } from "./@types/classes/CommandBuilder"; export { StructureSaveOptions, StructureLoadOptions } from "./classes/structureBuilder.js"; -export { getDatabase, deleteDatabase } from "./classes/databaseBuilder.js"; +export { Databases } from "./classes/databaseBuilder.js"; export { configuration } from "./configurations.js"; class ServerBuild extends ServerBuilder { diff --git a/src/library/classes/databaseBuilder.ts b/src/library/classes/databaseBuilder.ts index 27faeb9b9..d1da7b297 100644 --- a/src/library/classes/databaseBuilder.ts +++ b/src/library/classes/databaseBuilder.ts @@ -1,96 +1,201 @@ /* eslint-disable @typescript-eslint/ban-types */ /* eslint-disable @typescript-eslint/no-explicit-any */ import { Entity, World, world } from "@minecraft/server"; -import { Server } from "./serverBuilder.js"; import { Database } from "../@types/classes/databaseBuilder"; -import { contentLog } from "@notbeer-api"; +import { contentLog, Server } from "@notbeer-api"; const objective = world.scoreboard.getObjective("GAMETEST_DB") ?? world.scoreboard.addObjective("GAMETEST_DB", ""); const databases: { [k: string]: DatabaseImpl } = {}; +const parsers: ((key: string, value: any, databaseName: string) => any)[] = []; -export function getDatabase(name: string, provider: World | Entity = world, reviver?: (key: string, value: any) => any, legacyStorage = false) { - const key = name + "//" + (provider instanceof Entity ? provider.id : "world"); - if (!databases[key]) databases[key] = new DatabaseImpl(name, provider, reviver, legacyStorage); - return databases[key] as DatabaseImpl; +function parseJSON(databaseName: string, json: string) { + return JSON.parse(json, (key, value) => { + for (const parser of parsers) value = parser(key, value, databaseName); + return value; + }); } -export function deleteDatabase(name: string, provider: World | Entity = world) { - const key = name + "//" + (provider instanceof Entity ? provider.id : "world"); - if (databases[key]) databases[key].clear(); +function getDatabaseKey(name: string, provider?: World | Entity) { + return name + "//" + (provider instanceof Entity ? provider.id : "world"); +} + +class DatabaseManager { + load(name: string, provider: World | Entity = world, legacyStorage = false) { + const key = getDatabaseKey(name, provider); + if (!databases[key]) { + databases[key] = new DatabaseImpl(name, provider, legacyStorage); + databases[key].load(); + } + return >databases[key]; + } + + delete(name: string, provider: World | Entity = world) { + const key = getDatabaseKey(name, provider); + const database = databases[key] ?? new DatabaseImpl(name, provider); + if (database.isValid()) database.delete(); + delete databases[key]; + } + + find(regexp: RegExp, provider: World | Entity = world) { + return provider.getDynamicPropertyIds().filter((name) => name.match(regexp)); + } + + getRawData(name: string, provider: World | Entity = world) { + const key = getDatabaseKey(name, provider); + const database = databases[key] ?? new DatabaseImpl(name, provider); + return database.rawData; + } - const scoreboardTable = DatabaseImpl.getScoreboardParticipant(DatabaseImpl.getScoreboardName(name, provider)); - if (scoreboardTable) objective.removeParticipant(scoreboardTable); - else provider.setDynamicProperty(name, undefined); + addParser(parser: (key: string, value: any, database: string) => any) { + parsers.push(parser); + } } +export const Databases = new DatabaseManager(); + class DatabaseImpl implements Database { - private data: T; + private _data: T = {}; + private loaded = false; + private valid = true; constructor( private name: string, private provider: World | Entity = world, - reviver?: (key: string, value: any) => any, private legacyStorage = false - ) { - const scoreboardName = DatabaseImpl.getScoreboardName(name, provider); - let table = DatabaseImpl.getScoreboardParticipant(scoreboardName); - try { - if (table) this.data = JSON.parse(JSON.parse(`"${table.displayName}"`), reviver)[1]; - else this.data = JSON.parse(provider.getDynamicProperty(name) ?? "{}", reviver); - - if (table && !legacyStorage) objective.removeParticipant(table); - } catch { - contentLog.error(`Failed to load database ${name} from ${provider instanceof Entity ? provider.nameTag ?? provider.id : "world"}`); - if (table) objective.removeParticipant(table), (table = undefined); - provider.setDynamicProperty(name, undefined); - this.data = {}; - } + ) {} + + get data() { + if (!this.valid) throw new Error(`Can't get data from invalid database "${this.name}".`); + if (!this.loaded) this.load(); + return this._data; } - set(key: S, value: T[S]): void { - this.data[key] = value; + set data(value: T) { + if (!this.valid) throw new Error(`Can't set data on invalid database "${this.name}".`); + this.loaded = true; + this._data = value; } - get(key: S): T[S] { - return this.data[key]; + + get rawData(): string | undefined { + const table = this.getScoreboardParticipant(); + let data: string | undefined; + if (table) { + data = (JSON.parse(`"${table.displayName}"`)).slice(`[\\"${this.getScoreboardName()}\\"`.length - 1, -1); + } else { + let data = this.provider.getDynamicProperty(this.name); + let page: string | undefined; + let i = 2; + while (data && (page = this.provider.getDynamicProperty(`__page${i++}__` + this.name))) data += page; + } + return data; } - has(key: keyof T): boolean { - return key in this.data; + + isLoaded() { + return this.loaded; } - delete(key: keyof T): void { - delete this.data[key]; + + isValid() { + return this.valid; } + clear(): void { - this.data = {} as T; + if (!this.valid) throw new Error(`Can't clear data from invalid database "${this.name}".`); + if (!this.loaded) this.load(); + this._data = {} as T; } + save(): void { + if (!this.valid) throw new Error(`Can't save data to invalid database "${this.name}".`); + + const table = this.getScoreboardParticipant(); if (this.legacyStorage) { - const scoreboardName = DatabaseImpl.getScoreboardName(this.name, this.provider); - const table = DatabaseImpl.getScoreboardParticipant(scoreboardName); + const scoreboardName = this.getScoreboardName(); if (table) Server.runCommand(`scoreboard players reset "${table.displayName}" GAMETEST_DB`); - Server.runCommand(`scoreboard players add ${JSON.stringify(JSON.stringify([scoreboardName, this.data]))} GAMETEST_DB 0`); - } else { - this.provider.setDynamicProperty(this.name, JSON.stringify(this.data)); + Server.runCommand(`scoreboard players add ${JSON.stringify(JSON.stringify([scoreboardName, this._data]))} GAMETEST_DB 0`); + return; + } else if (table && !this.legacyStorage) { + objective.removeParticipant(table); + } + + const data = JSON.stringify(this._data); + // Try smaller divisions of data until the right number of pages is found. + // 50 subdivions allow for a little more than 1.5 MB per database. + divisions: for (let i = 1; i <= 50; i++) { + let page: number | undefined = undefined; + const stepSize = Math.ceil(data.length / i); + for (let j = 0; j < data.length; j += stepSize) { + try { + this.provider.setDynamicProperty((page ? `__page${page}__` : "") + this.name, data.slice(j, j + stepSize)); + page = (page ?? 1) + 1; + } catch { + continue divisions; + } + } + // Remove unused pages + while (this.provider.getDynamicProperty(`__page${page}__` + this.name)) { + this.provider.setDynamicProperty(`__page${page!++}__` + this.name, undefined); + } + this.loaded = true; + return; } + + contentLog.error(`Failed to save database ${this.name} to ${this.provider instanceof Entity ? this.provider.nameTag ?? this.provider.id : "the world"}`); + contentLog.debug(contentLog.stack()); } - keys() { - return <(keyof T)[]>Object.keys(this.data); + + load() { + if (!this.valid) throw new Error(`Can't load data from invalid database "${this.name}".`); + if (this.loaded) return; + try { + this._data = parseJSON(this.name, this.rawData ?? "{}"); + } catch (err) { + contentLog.error(`Failed to load database ${this.name} from ${this.provider instanceof Entity ? this.provider.nameTag ?? this.provider.id : "the world"}`); + if (err) contentLog.debug(err); + return; + } + this.loaded = true; } - values() { - return Object.values(this.data); + + delete() { + if (!this.valid) throw new Error(`Can't delete invalid database "${this.name}".`); + const table = this.getScoreboardParticipant(); + if (table) { + objective.removeParticipant(table); + } else { + this.provider.setDynamicProperty(this.name, undefined); + let page = 2; + while (this.provider.getDynamicProperty(`__page${page}__` + this.name)) { + this.provider.setDynamicProperty(`__page${page++}__` + this.name, undefined); + } + } + this.valid = false; + Databases.delete(this.name, this.provider); } - entries() { - return <[S, T[S]][]>Object.entries(this.data); + + toJSON() { + const json: any = { __dbName__: this.name }; + if (this.provider instanceof Entity) json.__dbProvider__ = this.provider.id; + if (this.legacyStorage) json.__dbLegacy__ = true; + return json; } - static getScoreboardParticipant(scoreboardName: string) { + private getScoreboardParticipant() { for (const table of objective?.getParticipants() ?? []) { - if (table.displayName.startsWith(`[\\"${scoreboardName}\\"`)) return table; + if (table.displayName.startsWith(`[\\"${this.getScoreboardName()}\\"`)) return table; } } - static getScoreboardName(name: string, provider: World | Entity) { - name = "wedit:" + name; - if (provider instanceof Entity) name += provider.id; + private getScoreboardName() { + let name = "wedit:" + this.name; + if (this.provider instanceof Entity) name += this.provider.id; return name; } } + +Databases.addParser((_, value) => { + if (typeof value !== "object" || !("__dbName__" in value)) return value; + const provider = value.__dbProvider__ ? world.getEntity(value.__dbProvider__) : world; + const key = getDatabaseKey(value.__dbName__, provider); + if (!databases[key]) databases[key] = new DatabaseImpl(value.__dbName__, provider, value.__dbLegacy__); + return databases[key]; +}); diff --git a/src/server/modules/biome_data.ts b/src/server/modules/biome_data.ts index 9fd1c0143..f54d5a42a 100644 --- a/src/server/modules/biome_data.ts +++ b/src/server/modules/biome_data.ts @@ -1,5 +1,5 @@ import { Dimension, Vector3, world, Entity } from "@minecraft/server"; -import { commandSyntaxError, contentLog, CustomArgType, getDatabase, Vector } from "@notbeer-api"; +import { commandSyntaxError, contentLog, CustomArgType, Databases, Vector } from "@notbeer-api"; import { EventEmitter } from "library/classes/eventEmitter.js"; import { locToString, wrap } from "../util.js"; import { errorEventSym, PooledResource, readyEventSym, ResourcePool } from "./extern/resource_pools.js"; @@ -85,15 +85,15 @@ class BiomeChanges { flush() { for (const [chunk, data] of this.changes) { const tableName = `biome,${this.dimension.id},${chunk}`; - const database = getDatabase(tableName, world, undefined, true); + const database = Databases.load(tableName, world, true); let biomes: number[] = []; - if (!database.has("biomes")) { + if (!("biomes" in database)) { biomes.length = 4096; biomes = biomes.fill(-1); } else { - const palette: number[] = database.get("palette"); - biomes = (database.get("biomes") as number[]).map((idx) => (idx ? palette[idx - 1] : -1)); + const palette: number[] = database.data.palette; + biomes = (database.data.biomes as number[]).map((idx) => (idx ? palette[idx - 1] : -1)); } for (const [loc, biome] of data.entries()) { @@ -111,12 +111,8 @@ class BiomeChanges { newPalette.forEach((val, idx) => paletteMap.set(val, idx + 1)); paletteMap.set(-1, 0); - database.set( - "biomes", - biomes.map((biome) => paletteMap.get(biome)) - ); - database.set("palette", newPalette); - + database.data.biomes = biomes.map((biome) => paletteMap.get(biome)); + database.data.palette = newPalette; database.save(); } this.changes.clear(); diff --git a/src/server/sessions.ts b/src/server/sessions.ts index c98c5595d..c44ce6266 100644 --- a/src/server/sessions.ts +++ b/src/server/sessions.ts @@ -1,5 +1,5 @@ -import { Player, system } from "@minecraft/server"; -import { Server, Vector, setTickTimeout, contentLog, getDatabase, deleteDatabase } from "@notbeer-api"; +import { Player, system, world } from "@minecraft/server"; +import { Server, Vector, setTickTimeout, contentLog, Databases } from "@notbeer-api"; import { Tools } from "./tools/tool_manager.js"; import { History } from "@modules/history.js"; import { Mask } from "@modules/mask.js"; @@ -36,9 +36,22 @@ interface gradients { [id: string]: { dither: number; patterns: Pattern[] }; } +Databases.addParser((key, value, databaseName) => { + if (databaseName === "gradients" && typeof value === "object" && value.patterns) { + try { + value.patterns = (value.patterns).map((v) => new Pattern(v)); + return value; + } catch { + contentLog.error(`Failed to load gradient ${key}`); + } + } else { + return value; + } +}); + system.afterEvents.scriptEventReceive.subscribe(({ id, sourceEntity }) => { if (id !== "wedit:reset_gradients_database" || !sourceEntity) return; - deleteDatabase("gradients", sourceEntity); + Databases.delete("gradients", sourceEntity); }); /** @@ -121,10 +134,7 @@ export class PlayerSession { this.history = new History(this); this.selection = new Selection(player); this.drawOutlines = config.drawOutlines; - this.gradients = getDatabase("gradients", player, (k, v) => { - if (k === "patterns") return (v).map((v) => new Pattern(v)); - return v; - }); + this.gradients = Databases.load("gradients", player); if (!this.getTools().length) { this.bindTool("selection_wand", config.wandItem); @@ -286,27 +296,25 @@ export class PlayerSession { } public createGradient(id: string, dither: number, patterns: Pattern[]) { - this.gradients.set(id, { dither, patterns }); + this.gradients.data[id] = { dither, patterns }; this.gradients.save(); } public getGradient(id: string) { - return this.gradients.get(id); + return this.gradients.data[id]; } public getGradientNames() { - return this.gradients.keys() as string[]; + return Object.keys(this.gradients.data); } public deleteGradient(id: string) { - this.gradients.delete(id); + delete this.gradients.data[id]; this.gradients.save(); } delete() { - for (const region of this.regions.values()) { - region.deref(); - } + for (const region of this.regions.values()) region.deref(); this.regions.clear(); this.history.delete(); this.history = null; diff --git a/src/server/tools/tool_manager.ts b/src/server/tools/tool_manager.ts index cb9709aac..625703a18 100644 --- a/src/server/tools/tool_manager.ts +++ b/src/server/tools/tool_manager.ts @@ -1,5 +1,5 @@ import { Player, ItemStack, ItemUseBeforeEvent, world, PlayerBreakBlockBeforeEvent, EntityHitBlockAfterEvent, system } from "@minecraft/server"; -import { contentLog, deleteDatabase, getDatabase, Server, sleep, Thread, Vector } from "@notbeer-api"; +import { contentLog, Databases, Server, sleep, Thread, Vector } from "@notbeer-api"; import { Tool, ToolAction } from "./base_tool.js"; import { PlayerSession, getSession, hasSession } from "../sessions.js"; import { Database } from "library/@types/classes/databaseBuilder.js"; @@ -10,13 +10,29 @@ type toolCondition = (player: Player, session: PlayerSession) => boolean; // eslint-disable-next-line @typescript-eslint/no-explicit-any type toolObject = { [key: string]: any } & Tool; +const tools = new Map(); + +Databases.addParser((k, v, databaseName) => { + if (databaseName.startsWith("tools|") && v && typeof v === "object" && "toolType" in v) { + try { + const toolClass = tools.get(v.toolType); + const tool = new toolClass(...(toolClass as toolConstruct & typeof Tool).parseJSON(v)); + tool.type = v.toolType; + return tool; + } catch (err) { + contentLog.error(`Failed to load tool from '${JSON.stringify(v)}' for '${k}': ${err}`); + } + } else { + return v; + } +}); + system.afterEvents.scriptEventReceive.subscribe(({ id, sourceEntity }) => { if (id !== "wedit:reset_tools_database" || !sourceEntity) return; - deleteDatabase(`tools|${sourceEntity.id}`); + Databases.delete(`tools|${sourceEntity.id}`); }); class ToolBuilder { - private tools = new Map(); private bindings = new Map>(); private fixedBindings = new Map(); private prevHeldTool = new Map(); @@ -74,7 +90,7 @@ class ToolBuilder { } register(toolClass: toolConstruct, name: string, item?: string | string[], condition?: toolCondition) { - this.tools.set(name, toolClass); + tools.set(name, toolClass); if (typeof item == "string") { this.fixedBindings.set(item, new toolClass()); } else if (condition && Array.isArray(item)) { @@ -89,11 +105,11 @@ class ToolBuilder { bind(toolId: string, itemId: string, playerId: string, ...args: any[]) { this.unbind(itemId, playerId); if (itemId) { - const tool = new (this.tools.get(toolId))(...args); + const tool = new (tools.get(toolId))(...args); tool.type = toolId; this.createPlayerBindingMap(playerId); - this.bindings.get(playerId).get(itemId)?.delete(); - this.bindings.get(playerId).set(itemId, tool); + this.bindings.get(playerId).data[itemId]?.delete(); + this.bindings.get(playerId).data[itemId] = tool; this.bindings.get(playerId).save(); return tool; } else { @@ -107,8 +123,8 @@ class ToolBuilder { throw "worldedit.tool.fixedBind"; } this.createPlayerBindingMap(playerId); - this.bindings.get(playerId).get(itemId)?.delete(); - this.bindings.get(playerId).delete(itemId); + this.bindings.get(playerId).data[itemId]?.delete(); + delete this.bindings.get(playerId).data[itemId]; this.bindings.get(playerId).save(); } else { throw "worldedit.tool.noItem"; @@ -117,7 +133,7 @@ class ToolBuilder { hasBinding(itemId: string, playerId: string) { if (itemId) { - return this.bindings.get(playerId)?.has(itemId) || this.fixedBindings.has(itemId); + return !!this.bindings.get(playerId)?.data[itemId] || this.fixedBindings.has(itemId); } else { return false; } @@ -125,7 +141,7 @@ class ToolBuilder { getBindingType(itemId: string, playerId: string) { if (itemId) { - const tool = this.bindings.get(playerId)?.get(itemId) || this.fixedBindings.get(itemId); + const tool = this.bindings.get(playerId)?.data[itemId] || this.fixedBindings.get(itemId); return tool?.type ?? ""; } else { return ""; @@ -134,9 +150,9 @@ class ToolBuilder { getBoundItems(playerId: string, type?: RegExp | string) { this.createPlayerBindingMap(playerId); - const tools = this.bindings.get(playerId); + const tools = this.bindings.get(playerId).data; return tools - ? Array.from(tools.entries()) + ? Array.from(Object.entries(tools)) .filter((binding) => !type || (typeof type == "string" ? binding[1].type == type : type.test(binding[1].type))) .map((binding) => binding[0] as string) : ([] as string[]); @@ -144,7 +160,7 @@ class ToolBuilder { setProperty(itemId: string, playerId: string, prop: string, value: T) { if (itemId) { - const tool: toolObject = this.bindings.get(playerId).get(itemId); + const tool: toolObject = this.bindings.get(playerId).data[itemId]; if (tool && prop in tool) { tool[prop] = value; this.bindings.get(playerId).save(); @@ -156,7 +172,7 @@ class ToolBuilder { getProperty(itemId: string, playerId: string, prop: string) { if (itemId) { - const tool: toolObject = this.bindings.get(playerId).get(itemId); + const tool: toolObject = this.bindings.get(playerId).data[itemId]; if (tool && prop in tool) { return tool[prop] as T; } @@ -166,7 +182,7 @@ class ToolBuilder { hasProperty(itemId: string, playerId: string, prop: string) { if (itemId) { - const tool: toolObject = this.bindings.get(playerId).get(itemId); + const tool: toolObject = this.bindings.get(playerId).data[itemId]; if (tool && prop in tool) { return true; } @@ -187,8 +203,8 @@ class ToolBuilder { const key = item.typeId; let tool: Tool; - if (this.bindings.get(player.id)?.has(key)) { - tool = this.bindings.get(player.id).get(key); + if (this.bindings.get(player.id)?.data[key]) { + tool = this.bindings.get(player.id).data[key]; } else if (this.fixedBindings.has(key)) { tool = this.fixedBindings.get(key); } else { @@ -209,8 +225,8 @@ class ToolBuilder { const key = item.typeId; let tool: Tool; - if (this.bindings.get(player.id)?.has(key)) { - tool = this.bindings.get(player.id).get(key); + if (this.bindings.get(player.id)?.data[key]) { + tool = this.bindings.get(player.id).data[key]; } else if (this.fixedBindings.has(key)) { tool = this.fixedBindings.get(key); } else if (this.conditionalBindings.get(key)?.condition(player, getSession(player))) { @@ -228,8 +244,8 @@ class ToolBuilder { const key = item.typeId; let tool: Tool; - if (this.bindings.get(player.id)?.has(key)) { - tool = this.bindings.get(player.id).get(key); + if (this.bindings.get(player.id)?.data[key]) { + tool = this.bindings.get(player.id).data[key]; } else if (this.fixedBindings.has(key)) { tool = this.fixedBindings.get(key); } else if (this.conditionalBindings.get(key)?.condition(player, getSession(player))) { @@ -247,8 +263,8 @@ class ToolBuilder { const key = item.typeId; let tool: Tool; - if (this.bindings.get(player.id)?.has(key)) { - tool = this.bindings.get(player.id).get(key); + if (this.bindings.get(player.id)?.data[key]) { + tool = this.bindings.get(player.id).data[key]; } else if (this.fixedBindings.has(key)) { tool = this.fixedBindings.get(key); } else if (this.conditionalBindings.get(key)?.condition(player, getSession(player))) { @@ -266,8 +282,8 @@ class ToolBuilder { const key = item.typeId; let tool: Tool; - if (this.bindings.get(player.id)?.has(key)) { - tool = this.bindings.get(player.id).get(key); + if (this.bindings.get(player.id)?.data[key]) { + tool = this.bindings.get(player.id).data[key]; } else if (this.fixedBindings.has(key)) { tool = this.fixedBindings.get(key); } else if (this.conditionalBindings.get(key)?.condition(player, getSession(player))) { @@ -280,28 +296,14 @@ class ToolBuilder { private createPlayerBindingMap(playerId: string) { if (this.bindings.has(playerId)) return; - const database = getDatabase<{ [id: string]: Tool }>(`tools|${playerId}`, world, (k, v) => { - if (v && typeof v === "object" && "toolType" in v) { - try { - const toolClass = this.tools.get(v.toolType); - const tool = new toolClass(...(toolClass as toolConstruct & typeof Tool).parseJSON(v)); - tool.type = v.toolType; - return tool; - } catch (err) { - contentLog.error(`Failed to load tool from '${JSON.stringify(v)}' for '${k}': ${err}`); - } - } else { - return v; - } - }); + const database = Databases.load<{ [id: string]: Tool }>(`tools|${playerId}`, world); this.bindings.set(playerId, database); } private stopHolding(player: Player) { - if (this.prevHeldTool.has(player)) { - this.prevHeldTool.get(player)?.process(getSession(player), ToolAction.STOP_HOLD); - this.prevHeldTool.delete(player); - } + if (!this.prevHeldTool.has(player)) return; + this.prevHeldTool.get(player)?.process(getSession(player), ToolAction.STOP_HOLD); + this.prevHeldTool.delete(player); } } export const Tools = new ToolBuilder(); diff --git a/src/server/util.ts b/src/server/util.ts index 8797ce57c..de1c26a16 100644 --- a/src/server/util.ts +++ b/src/server/util.ts @@ -67,10 +67,7 @@ export function blockHasNBTData(block: Block) { "minecraft:sign", "minecraft:piston", "minecraft:record_player", - "minecraft:waterContainer", - "minecraft:lavaContainer", - "minecraft:snowContainer", - "minecraft:potionContainer", + "minecraft:fluidContainer", ]; const nbt_blocks = [ "minecraft:bee_nest", diff --git a/tools/process_config.py b/tools/process_config.py index bd438e346..2d0c93764 100644 --- a/tools/process_config.py +++ b/tools/process_config.py @@ -141,7 +141,7 @@ def update(): class MyHandler(FileSystemEventHandler): def on_modified(self, ev): - if ev.src_path in [".\worldedit_settings.json", "BP\scripts\config.js"]: + if ev.src_path in ["./worldedit_settings.json", "BP/scripts/config.js"]: update() obsSettings = Observer() @@ -149,7 +149,7 @@ def on_modified(self, ev): obsSettings.start() obsConfigJS = Observer() - obsConfigJS.schedule(MyHandler(), path="BP\scripts") + obsConfigJS.schedule(MyHandler(), path="BP/scripts") obsConfigJS.start() try: diff --git a/tools/sync2com-mojang.py b/tools/sync2com-mojang.py index f8a9d7f4b..d18393000 100644 --- a/tools/sync2com-mojang.py +++ b/tools/sync2com-mojang.py @@ -124,8 +124,7 @@ def on_deleted(self, ev): sync_all() try: alert_watching() - while True: - time.sleep(1) + while True: time.sleep(1) except KeyboardInterrupt: observerBP.stop() observerRP.stop()