omegga 1.8.1 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,25 @@
4
4
 
5
5
  - Future features go here
6
6
 
7
+ ## 1.9.1 - 2026-07-07
8
+
9
+ Ban-history crash fix, web-ui ban/kick modal polish, and a brs-js bump.
10
+
11
+ - Database: Fix server crash when syncing bans
12
+ - Update brs-js to 3.2.4
13
+
14
+ ## 1.9.0 - 2026-07-05
15
+
16
+ Wire graph log events and a brs-js 3.0.0 upgrade for native brdb/brz reading.
17
+
18
+ - Plugins: Add `wirelog` event, emitted for each `[Wire Graph]` console log with the raw text
19
+ - Plugins: Add `wirecmd:command` event, emitted when a `[Wire Graph]` log starts with a command word (lowercased) followed by space-separated args
20
+ - Update brs-js to 3.2.1 and expose brdb/brz features on `OMEGGA_UTIL.brdb`
21
+ - World revisions are now read directly from the `.brdb` file instead of the game console, so `getWorldRevisions` no longer requires the server to be running
22
+ - Plugins: Add `OMEGGA_UTIL.Sqlite` for opening sqlite files (or brdb files)
23
+ - Plugins: `loadSaveData`/`loadSaveDataOnPlayer`/`getSaveData` are back using `brs-js` 3
24
+ - Fix `clearRegion`, `clearAllBricks`, `loadSaveData`, `getSaveData`, in safe plugins
25
+
7
26
  ## 1.8.1 - 2026-07-03
8
27
 
9
28
  Prefab support for EA3, replacing the removed `.brs` bricks commands.
package/README.md CHANGED
@@ -12,17 +12,18 @@ Omegga can do things like:
12
12
  - Read chat history with timestamps
13
13
  - See kick and ban history
14
14
  - Configure plugins from a web interface
15
+ - Manage permissions and multi-user role based access to the above features on a web ui
15
16
 
16
17
  Omegga plugins can do things like:
17
18
 
19
+ - Interface with in-game wires and react to in-game wire events
18
20
  - Add custom chat !commands and /commands
19
21
  - Respond to and send chat messages
20
- - Load bricks to player's clipboards
21
- - Load/Clear regions of bricks
22
+ - Load bricks onto a player's template
23
+ - Load/Clear regions of bricks, entities
22
24
  - Damage/heal players
23
25
  - Give/remove weapons to players
24
26
  - Change the environment
25
- - Create/delete minigames, and join/leave players from minigames
26
27
  - Teleport players, detect player's positions
27
28
  - Grant players roles
28
29
  - Detect when a brick with an interact component is clicked
@@ -891,7 +892,9 @@ Register custom `/commands` by returning `{registeredCommands: ['foo', 'bar']}`
891
892
  | `event:NAME` | [>player from click<, ...args] | Runs when an interact component has `event:NAME: arg1,arg2,arg\,3, | |
892
893
  | `mapchange` | \[{map}\] | Runs when the map changes | |
893
894
  | `autorestart` | [autorestart config] | Runs server has an autorestart scheduled | |
894
- | `minigamejoin` | {player: {name, id}; minigameName: string} | Runs when a player joins a minigame. Note that minigameName is not unique between minigames. minigameName will be null if player leaves all minigames. This will run before `join` | |
895
+ | `minigamejoin` | {player: {name, id}; minigameName: string} | **Deprecated as of EA3.** Runs when a player joins a minigame. Note that minigameName is not unique between minigames. minigameName will be null if player leaves all minigames. This will run before `join` | |
896
+ | `wirelog` | [raw string] | Runs when a `[Wire Graph]` log line is emitted. `raw` is the text after the `[Wire Graph] ` prefix | |
897
+ | `wirecmd:command` | [...args] | Runs when a `[Wire Graph]` log starts with `command args`. `command` is lowercased; args are the remaining space-separated words | |
895
898
 
896
899
  ### Folder Structure
897
900
 
@@ -247,8 +247,10 @@ async function importNedb(dataPath, mainSqlite, mainDb) {
247
247
  mainDb.insert(schema.banHistory).values({
248
248
  banned: b.banned ?? "",
249
249
  bannerId: b.bannerId ?? "",
250
- created: typeof b.created === "number" ? b.created : 0,
251
- expires: typeof b.expires === "number" ? b.expires : 0,
250
+ // Number.isFinite (not typeof) so a NaN timestamp becomes 0,
251
+ // binding NaN to a NOT NULL integer column stores NULL and throws
252
+ created: Number.isFinite(b.created) ? b.created : 0,
253
+ expires: Number.isFinite(b.expires) ? b.expires : 0,
252
254
  reason: b.reason ?? ""
253
255
  }).onConflictDoNothing().run();
254
256
  }
@@ -1 +1 @@
1
- {"version":3,"file":"nedbImport.js","sources":["../../src/db/nedbImport.ts"],"sourcesContent":["import Logger from '@/logger';\nimport soft from '@/softconfig';\nimport {\n decodePermissions,\n type StoredPermissionSet,\n} from '@webserver/backend/permissions';\nimport type BetterSqlite3 from 'better-sqlite3';\nimport { eq } from 'drizzle-orm';\nimport {\n drizzle,\n type BetterSQLite3Database,\n} from 'drizzle-orm/better-sqlite3';\nimport fs from 'fs';\nimport Datastore from 'nedb-promises';\nimport path from 'path';\nimport { openDb } from './connection';\nimport { runMigrations, runPluginMigrations } from './migrate';\nimport * as mainSchema from './schema';\nimport * as pluginSchema from './pluginSchema';\n\nconst MARKER_FILE = 'nedb-imported.marker';\nconst BATCH_SIZE = 500;\n\n// legacy NeDB database filenames; the one-time import is their only consumer\nexport const NEDB_FILES = {\n users: 'users.db',\n chat: 'chat.db',\n players: 'players.db',\n status: 'status.db',\n server: 'store.db',\n plugins: 'plugins.db',\n} as const;\n\n// Apply the NeDB data migrations that the old doMigrations() would have run.\n// Users v1: convert old NeDB permission format to PermissionSet.\n// Server v1: re-encode defaultPermissions from old format.\n// NeDB stored scope keys colon-encoded ('chat:send'); runtime lookups use\n// dotted keys, so every path below must run decodePermissions.\n\n// the v1 format used 'unset' sentinels and 'enabled'/'disabled' scope strings,\n// none of which are valid v2 values. detect by the presence of any sentinel so\n// an empty-scopes v1 doc (which a scopes-only check would miss) is still\n// normalized, while a valid v2 'none' domain is preserved.\nfunction isLegacyPermissions(p: any): boolean {\n if (p.root === 'unset') return true;\n for (const v of Object.values(p.domains ?? {}))\n if (v === 'unset') return true;\n for (const v of Object.values(p.scopes ?? {}))\n if (typeof v === 'string') return true;\n return false;\n}\n\n// normalize a v1 permission doc to a v2 StoredPermissionSet (still colon-keyed)\nfunction normalizeLegacyPermissions(p: any): StoredPermissionSet {\n const root = p.root === 'all' || p.root === 'read' ? p.root : 'off';\n const domains: Record<string, string> = {};\n for (const [k, v] of Object.entries(p.domains ?? {})) {\n if (v !== 'unset' && v !== 'none') domains[k] = v as string;\n }\n const scopes: Record<string, boolean> = {};\n for (const [k, v] of Object.entries(p.scopes ?? {})) {\n if (v === 'enabled' || v === true) scopes[k] = true;\n else if (v === 'disabled' || v === false) scopes[k] = false;\n }\n return { root, domains, scopes } as StoredPermissionSet;\n}\n\nfunction migrateUserPermissions(user: any): any {\n const p = user.permissions;\n if (!p || typeof p !== 'object') return user;\n const stored = isLegacyPermissions(p) ? normalizeLegacyPermissions(p) : p;\n return { ...user, permissions: decodePermissions(stored) };\n}\n\nfunction migrateDefaultPermissions(doc: any): any {\n if (!doc) return doc;\n const stored = isLegacyPermissions(doc)\n ? normalizeLegacyPermissions(doc)\n : doc;\n return { ...doc, ...decodePermissions(stored) };\n}\n\n// legacy nameHistory may predate the {name,displayName,date} object shape (older\n// docs stored bare strings) or be hand-edited; coerce to well-formed objects so\n// the json_each/REGEXP player search can't hit malformed JSON or null keys\nfunction sanitizeNameHistory(raw: any): {\n name: string;\n displayName: string;\n date: number;\n}[] {\n if (!Array.isArray(raw)) return [];\n return raw\n .filter(h => h && typeof h === 'object')\n .map(h => ({\n name: String(h.name ?? ''),\n displayName: String(h.displayName ?? ''),\n date: typeof h.date === 'number' ? h.date : 0,\n }));\n}\n\nasync function loadNedb<T = any>(filepath: string): Promise<T[]> {\n if (!fs.existsSync(filepath)) return [];\n const store = Datastore.create({ filename: filepath, autoload: true });\n return (await store.find({})) as T[];\n}\n\n// invoke fn on BATCH_SIZE-sized slices of docs (keeps multi-row inserts under\n// SQLite's bound-parameter limit). NOT wrapped in its own transaction — callers\n// run all batches inside one outer transaction for atomicity.\nfunction eachBatch(docs: any[], fn: (docs: any[]) => void) {\n for (let i = 0; i < docs.length; i += BATCH_SIZE) {\n fn(docs.slice(i, i + BATCH_SIZE));\n }\n}\n\n// key in server_config marking the main-DB import as committed. checked\n// in-band with the data so a crash between commit and marker-file write can't\n// cause a re-import (which would duplicate the append-only tables).\nconst IMPORT_FLAG_KEY = 'nedbImported';\n\n// coalesce concurrent import calls for the same data dir (the webserver and the\n// plugin loader both bootstrap it). entries are dropped once settled, so this is\n// purely a concurrency guard — repeat suppression is the marker file / DB flag.\nconst importsInFlight = new Map<string, Promise<void>>();\n\nexport function importNedbIfNeeded(\n dataPath: string,\n mainSqlite?: BetterSqlite3.Database,\n mainDb?: BetterSQLite3Database,\n): Promise<void> {\n const key = path.resolve(dataPath);\n let promise = importsInFlight.get(key);\n if (!promise) {\n promise = importNedb(dataPath, mainSqlite, mainDb).finally(() =>\n importsInFlight.delete(key),\n );\n importsInFlight.set(key, promise);\n }\n return promise;\n}\n\nasync function importNedb(\n dataPath: string,\n mainSqlite?: BetterSqlite3.Database,\n mainDb?: BetterSQLite3Database,\n) {\n const markerPath = path.join(dataPath, MARKER_FILE);\n if (fs.existsSync(markerPath)) return;\n\n const filePath = (f: string) => path.join(dataPath, f);\n const mainNedbFiles = [\n NEDB_FILES.users,\n NEDB_FILES.chat,\n NEDB_FILES.players,\n NEDB_FILES.status,\n NEDB_FILES.server,\n ];\n const hasMainNedb = mainNedbFiles.some(f => fs.existsSync(filePath(f)));\n const pluginPath = filePath(NEDB_FILES.plugins);\n const hasPluginNedb = fs.existsSync(pluginPath);\n if (!hasMainNedb && !hasPluginNedb) {\n fs.writeFileSync(markerPath, new Date().toISOString());\n return;\n }\n\n // open a dedicated main-db connection when the caller doesn't have one\n // (the plugin loader bootstraps the import without a webserver)\n let ownConnection = false;\n if (!mainSqlite || !mainDb) {\n ownConnection = true;\n mainSqlite = openDb(filePath(soft.MAIN_DB));\n mainDb = drizzle(mainSqlite);\n runMigrations(mainDb);\n }\n\n try {\n Logger.log('>>'.green, 'Importing NeDB data into SQLite...');\n const counts: Record<string, number> = {};\n\n // ---- Load phase: read every NeDB file up front (async). All writes happen\n // afterwards inside a single synchronous transaction so a partial failure\n // rolls back cleanly and a retry can't double-import append-only tables. ----\n const users = (await loadNedb(filePath(NEDB_FILES.users)))\n .filter((d: any) => d.type === 'user')\n .map(migrateUserPermissions);\n const chatMessages = (await loadNedb(filePath(NEDB_FILES.chat)))\n .filter((d: any) => d.type === 'chat')\n .sort((a: any, b: any) => (a.created ?? 0) - (b.created ?? 0));\n const playerDocs = await loadNedb(filePath(NEDB_FILES.players));\n const histories = playerDocs.filter((d: any) => d.type === 'userHistory');\n const bans = playerDocs.filter((d: any) => d.type === 'banHistory');\n const kicks = playerDocs.filter((d: any) => d.type === 'kickHistory');\n const notes = playerDocs.filter((d: any) => d.type === 'note');\n const statusDocs = await loadNedb(filePath(NEDB_FILES.status));\n const hbDocs = statusDocs\n .filter((d: any) => d.type === 'heartbeat')\n .sort((a: any, b: any) => (a.created ?? 0) - (b.created ?? 0));\n const pcDocs = statusDocs.filter((d: any) => d.type === 'punchcard');\n const serverDocs = await loadNedb(filePath(NEDB_FILES.server));\n const instances = serverDocs.filter((d: any) => d.type === 'app:start');\n const roles = serverDocs.filter((d: any) => d.type === 'webRole');\n const rawDefaultPerms = serverDocs.find(\n (d: any) => d.type === 'defaultPermissions',\n );\n const autoRestart = serverDocs.find(\n (d: any) => d.type === 'autoRestartConfig',\n );\n\n // warn about user rows that will be dropped by the new UNIQUE(username):\n // NeDB had no such constraint, so legacy data can hold duplicates\n const seenUsernames = new Set<string>();\n for (const u of users) {\n const name = u.username ?? '';\n if (seenUsernames.has(name))\n Logger.error(\n '!>'.red,\n `Duplicate username ${JSON.stringify(name)} in legacy data; user ${u._id} will not be imported`,\n );\n else seenUsernames.add(name);\n }\n\n // ---- Plugins: a separate connection/DB, imported first and idempotently\n // (onConflictDoNothing against real unique constraints), so re-running after\n // a main-DB failure is a safe no-op. ----\n if (hasPluginNedb) {\n const pluginSqlite = openDb(filePath(soft.PLUGINS_DB));\n try {\n const pluginDb = drizzle(pluginSqlite);\n runPluginMigrations(pluginDb);\n const pluginDocs = await loadNedb(pluginPath);\n // NeDB allowed store.set(key, null/undefined); the new value column is\n // NOT NULL, so skip those rows\n const storeDocs = pluginDocs.filter(\n (d: any) => d.type === 'store' && d.value != null,\n );\n const skipped =\n pluginDocs.filter((d: any) => d.type === 'store').length -\n storeDocs.length;\n if (skipped > 0)\n Logger.log(\n ':>'.yellow,\n `Skipping ${skipped} plugin store entr${skipped === 1 ? 'y' : 'ies'} with no value`,\n );\n const configDocs = pluginDocs.filter((d: any) => d.type === 'config');\n\n const pluginTx = pluginSqlite.transaction(() => {\n eachBatch(storeDocs, batch =>\n pluginDb\n .insert(pluginSchema.pluginStore)\n .values(\n batch.map((s: any) => ({\n plugin: s.plugin ?? '',\n key: s.key ?? '',\n value: s.value,\n })),\n )\n .onConflictDoNothing()\n .run(),\n );\n eachBatch(configDocs, batch =>\n pluginDb\n .insert(pluginSchema.pluginConfig)\n .values(\n batch.map((c: any) => ({\n plugin: c.plugin ?? '',\n value: c.value ?? {},\n })),\n )\n .onConflictDoNothing()\n .run(),\n );\n });\n pluginTx();\n if (storeDocs.length) counts.pluginStoreItems = storeDocs.length;\n if (configDocs.length) counts.pluginConfigs = configDocs.length;\n } finally {\n pluginSqlite.close();\n }\n }\n\n // ---- Main DB: one atomic transaction. The completion flag is written in\n // the same transaction as the data, so either both commit or neither does —\n // a crash before the marker file is written can't trigger a re-import. ----\n const alreadyImported = mainDb\n .select()\n .from(mainSchema.serverConfig)\n .where(eq(mainSchema.serverConfig.key, IMPORT_FLAG_KEY))\n .get();\n\n if (!alreadyImported && hasMainNedb) {\n const tx = mainSqlite.transaction(() => {\n eachBatch(users, batch =>\n mainDb\n .insert(mainSchema.users)\n .values(\n batch.map((u: any) => ({\n id: u._id,\n created: u.created ?? Date.now(),\n lastOnline: u.lastOnline ?? 0,\n username: u.username ?? '',\n hash: u.hash ?? '',\n isOwner: !!u.isOwner,\n roles: u.roles ?? [],\n playerId: u.playerId ?? '',\n isBanned: !!u.isBanned,\n permissions: u.permissions ?? null,\n totpSecret: u.totpSecret ?? null,\n totpEnabled: !!u.totpEnabled,\n passkeys: u.passkeys ?? [],\n recoveryCodes: u.recoveryCodes ?? [],\n })),\n )\n .onConflictDoNothing()\n .run(),\n );\n if (users.length) counts.users = users.length;\n\n eachBatch(chatMessages, batch =>\n mainDb\n .insert(mainSchema.chatLogs)\n .values(\n batch.map((c: any) => ({\n created: c.created ?? 0,\n instanceId: c.instanceId ?? '',\n action: c.action ?? 'msg',\n user: c.user ?? {},\n message: c.message ?? null,\n })),\n )\n .run(),\n );\n if (chatMessages.length) counts.chatMessages = chatMessages.length;\n\n eachBatch(histories, batch =>\n mainDb\n .insert(mainSchema.playerHistory)\n .values(\n batch.map((p: any) => ({\n id: p.id,\n name: p.name ?? '',\n displayName: p.displayName ?? '',\n nameHistory: sanitizeNameHistory(p.nameHistory),\n ips: Array.isArray(p.ips) ? p.ips : [],\n created: p.created ?? 0,\n lastSeen: p.lastSeen ?? 0,\n lastInstanceId: p.lastInstanceId ?? '',\n heartbeats: p.heartbeats ?? 0,\n sessions: p.sessions ?? 0,\n instances: p.instances ?? 0,\n })),\n )\n .onConflictDoNothing()\n .run(),\n );\n if (histories.length) counts.players = histories.length;\n\n for (const b of bans) {\n mainDb\n .insert(mainSchema.banHistory)\n .values({\n banned: b.banned ?? '',\n bannerId: b.bannerId ?? '',\n created: typeof b.created === 'number' ? b.created : 0,\n expires: typeof b.expires === 'number' ? b.expires : 0,\n reason: b.reason ?? '',\n })\n .onConflictDoNothing()\n .run();\n }\n if (bans.length) counts.bans = bans.length;\n\n for (const k of kicks) {\n mainDb\n .insert(mainSchema.kickHistory)\n .values({\n kicked: k.kicked ?? '',\n kickerId: k.kickerId ?? '',\n created: k.created ?? 0,\n reason: k.reason ?? '',\n })\n .onConflictDoNothing()\n .run();\n }\n if (kicks.length) counts.kicks = kicks.length;\n\n eachBatch(notes, batch =>\n mainDb\n .insert(mainSchema.playerNotes)\n .values(\n batch.map((n: any) => ({\n playerId: n.id ?? '',\n note: n.note ?? '',\n })),\n )\n .run(),\n );\n if (notes.length) counts.notes = notes.length;\n\n eachBatch(hbDocs, batch =>\n mainDb\n .insert(mainSchema.heartbeats)\n .values(\n batch.map((h: any) => ({\n created: h.created ?? 0,\n bricks: h.bricks ?? 0,\n players: Array.isArray(h.players) ? h.players : [],\n })),\n )\n .run(),\n );\n if (hbDocs.length) counts.heartbeats = hbDocs.length;\n\n for (const p of pcDocs) {\n mainDb\n .insert(mainSchema.punchcards)\n .values({\n kind: p.kind ?? 'playerCount',\n created: p.created ?? 0,\n updated: p.updated ?? 0,\n month: p.month ?? 0,\n year: p.year ?? 0,\n punchcard: p.punchcard ?? [],\n })\n .onConflictDoNothing()\n .run();\n }\n if (pcDocs.length) counts.punchcards = pcDocs.length;\n\n eachBatch(instances, batch =>\n mainDb\n .insert(mainSchema.serverInstances)\n .values(batch.map((i: any) => ({ id: i._id, date: i.date ?? 0 })))\n .onConflictDoNothing()\n .run(),\n );\n if (instances.length) counts.serverInstances = instances.length;\n\n eachBatch(roles, batch =>\n mainDb\n .insert(mainSchema.webRoles)\n .values(\n batch.map((r: any) => ({\n id: r._id,\n name: r.name ?? '',\n description: r.description ?? '',\n order: r.order ?? 0,\n permissions: decodePermissions(r.permissions),\n })),\n )\n .onConflictDoNothing()\n .run(),\n );\n if (roles.length) counts.roles = roles.length;\n\n const defaultPerms = migrateDefaultPermissions(rawDefaultPerms);\n if (defaultPerms) {\n mainDb\n .insert(mainSchema.serverConfig)\n .values({\n key: 'defaultPermissions',\n value: {\n root: defaultPerms.root,\n domains: defaultPerms.domains,\n scopes: defaultPerms.scopes,\n },\n })\n .onConflictDoNothing()\n .run();\n }\n\n if (autoRestart) {\n const { _id: _, type: __, ...config } = autoRestart;\n mainDb\n .insert(mainSchema.serverConfig)\n .values({ key: 'autoRestartConfig', value: config })\n .onConflictDoNothing()\n .run();\n }\n\n // commit the completion flag atomically with the data above\n mainDb\n .insert(mainSchema.serverConfig)\n .values({ key: IMPORT_FLAG_KEY, value: new Date().toISOString() })\n .onConflictDoNothing()\n .run();\n });\n tx();\n }\n\n // marker file enables the cheap fast-path skip on subsequent boots\n fs.writeFileSync(markerPath, new Date().toISOString());\n\n const parts = Object.entries(counts)\n .map(([k, v]) => `${v} ${k}`)\n .join(', ');\n Logger.log('>>'.green, `Imported ${parts || 'no records'}`);\n Logger.log('>>'.green, 'NeDB import complete. Original files preserved.');\n } finally {\n if (ownConnection) mainSqlite.close();\n }\n}\n"],"names":["decodePermissions","openDb","soft","drizzle","runMigrations","Logger","runPluginMigrations","pluginSchema.pluginStore","pluginSchema.pluginConfig","mainSchema.serverConfig","eq","mainSchema.users","mainSchema.chatLogs","mainSchema.playerHistory","mainSchema.banHistory","mainSchema.kickHistory","mainSchema.playerNotes","mainSchema.heartbeats","mainSchema.punchcards","mainSchema.serverInstances","mainSchema.webRoles"],"mappings":"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"}
1
+ {"version":3,"file":"nedbImport.js","sources":["../../src/db/nedbImport.ts"],"sourcesContent":["import Logger from '@/logger';\nimport soft from '@/softconfig';\nimport {\n decodePermissions,\n type StoredPermissionSet,\n} from '@webserver/backend/permissions';\nimport type BetterSqlite3 from 'better-sqlite3';\nimport { eq } from 'drizzle-orm';\nimport {\n drizzle,\n type BetterSQLite3Database,\n} from 'drizzle-orm/better-sqlite3';\nimport fs from 'fs';\nimport Datastore from 'nedb-promises';\nimport path from 'path';\nimport { openDb } from './connection';\nimport { runMigrations, runPluginMigrations } from './migrate';\nimport * as mainSchema from './schema';\nimport * as pluginSchema from './pluginSchema';\n\nconst MARKER_FILE = 'nedb-imported.marker';\nconst BATCH_SIZE = 500;\n\n// legacy NeDB database filenames; the one-time import is their only consumer\nexport const NEDB_FILES = {\n users: 'users.db',\n chat: 'chat.db',\n players: 'players.db',\n status: 'status.db',\n server: 'store.db',\n plugins: 'plugins.db',\n} as const;\n\n// Apply the NeDB data migrations that the old doMigrations() would have run.\n// Users v1: convert old NeDB permission format to PermissionSet.\n// Server v1: re-encode defaultPermissions from old format.\n// NeDB stored scope keys colon-encoded ('chat:send'); runtime lookups use\n// dotted keys, so every path below must run decodePermissions.\n\n// the v1 format used 'unset' sentinels and 'enabled'/'disabled' scope strings,\n// none of which are valid v2 values. detect by the presence of any sentinel so\n// an empty-scopes v1 doc (which a scopes-only check would miss) is still\n// normalized, while a valid v2 'none' domain is preserved.\nfunction isLegacyPermissions(p: any): boolean {\n if (p.root === 'unset') return true;\n for (const v of Object.values(p.domains ?? {}))\n if (v === 'unset') return true;\n for (const v of Object.values(p.scopes ?? {}))\n if (typeof v === 'string') return true;\n return false;\n}\n\n// normalize a v1 permission doc to a v2 StoredPermissionSet (still colon-keyed)\nfunction normalizeLegacyPermissions(p: any): StoredPermissionSet {\n const root = p.root === 'all' || p.root === 'read' ? p.root : 'off';\n const domains: Record<string, string> = {};\n for (const [k, v] of Object.entries(p.domains ?? {})) {\n if (v !== 'unset' && v !== 'none') domains[k] = v as string;\n }\n const scopes: Record<string, boolean> = {};\n for (const [k, v] of Object.entries(p.scopes ?? {})) {\n if (v === 'enabled' || v === true) scopes[k] = true;\n else if (v === 'disabled' || v === false) scopes[k] = false;\n }\n return { root, domains, scopes } as StoredPermissionSet;\n}\n\nfunction migrateUserPermissions(user: any): any {\n const p = user.permissions;\n if (!p || typeof p !== 'object') return user;\n const stored = isLegacyPermissions(p) ? normalizeLegacyPermissions(p) : p;\n return { ...user, permissions: decodePermissions(stored) };\n}\n\nfunction migrateDefaultPermissions(doc: any): any {\n if (!doc) return doc;\n const stored = isLegacyPermissions(doc)\n ? normalizeLegacyPermissions(doc)\n : doc;\n return { ...doc, ...decodePermissions(stored) };\n}\n\n// legacy nameHistory may predate the {name,displayName,date} object shape (older\n// docs stored bare strings) or be hand-edited; coerce to well-formed objects so\n// the json_each/REGEXP player search can't hit malformed JSON or null keys\nfunction sanitizeNameHistory(raw: any): {\n name: string;\n displayName: string;\n date: number;\n}[] {\n if (!Array.isArray(raw)) return [];\n return raw\n .filter(h => h && typeof h === 'object')\n .map(h => ({\n name: String(h.name ?? ''),\n displayName: String(h.displayName ?? ''),\n date: typeof h.date === 'number' ? h.date : 0,\n }));\n}\n\nasync function loadNedb<T = any>(filepath: string): Promise<T[]> {\n if (!fs.existsSync(filepath)) return [];\n const store = Datastore.create({ filename: filepath, autoload: true });\n return (await store.find({})) as T[];\n}\n\n// invoke fn on BATCH_SIZE-sized slices of docs (keeps multi-row inserts under\n// SQLite's bound-parameter limit). NOT wrapped in its own transaction — callers\n// run all batches inside one outer transaction for atomicity.\nfunction eachBatch(docs: any[], fn: (docs: any[]) => void) {\n for (let i = 0; i < docs.length; i += BATCH_SIZE) {\n fn(docs.slice(i, i + BATCH_SIZE));\n }\n}\n\n// key in server_config marking the main-DB import as committed. checked\n// in-band with the data so a crash between commit and marker-file write can't\n// cause a re-import (which would duplicate the append-only tables).\nconst IMPORT_FLAG_KEY = 'nedbImported';\n\n// coalesce concurrent import calls for the same data dir (the webserver and the\n// plugin loader both bootstrap it). entries are dropped once settled, so this is\n// purely a concurrency guard — repeat suppression is the marker file / DB flag.\nconst importsInFlight = new Map<string, Promise<void>>();\n\nexport function importNedbIfNeeded(\n dataPath: string,\n mainSqlite?: BetterSqlite3.Database,\n mainDb?: BetterSQLite3Database,\n): Promise<void> {\n const key = path.resolve(dataPath);\n let promise = importsInFlight.get(key);\n if (!promise) {\n promise = importNedb(dataPath, mainSqlite, mainDb).finally(() =>\n importsInFlight.delete(key),\n );\n importsInFlight.set(key, promise);\n }\n return promise;\n}\n\nasync function importNedb(\n dataPath: string,\n mainSqlite?: BetterSqlite3.Database,\n mainDb?: BetterSQLite3Database,\n) {\n const markerPath = path.join(dataPath, MARKER_FILE);\n if (fs.existsSync(markerPath)) return;\n\n const filePath = (f: string) => path.join(dataPath, f);\n const mainNedbFiles = [\n NEDB_FILES.users,\n NEDB_FILES.chat,\n NEDB_FILES.players,\n NEDB_FILES.status,\n NEDB_FILES.server,\n ];\n const hasMainNedb = mainNedbFiles.some(f => fs.existsSync(filePath(f)));\n const pluginPath = filePath(NEDB_FILES.plugins);\n const hasPluginNedb = fs.existsSync(pluginPath);\n if (!hasMainNedb && !hasPluginNedb) {\n fs.writeFileSync(markerPath, new Date().toISOString());\n return;\n }\n\n // open a dedicated main-db connection when the caller doesn't have one\n // (the plugin loader bootstraps the import without a webserver)\n let ownConnection = false;\n if (!mainSqlite || !mainDb) {\n ownConnection = true;\n mainSqlite = openDb(filePath(soft.MAIN_DB));\n mainDb = drizzle(mainSqlite);\n runMigrations(mainDb);\n }\n\n try {\n Logger.log('>>'.green, 'Importing NeDB data into SQLite...');\n const counts: Record<string, number> = {};\n\n // ---- Load phase: read every NeDB file up front (async). All writes happen\n // afterwards inside a single synchronous transaction so a partial failure\n // rolls back cleanly and a retry can't double-import append-only tables. ----\n const users = (await loadNedb(filePath(NEDB_FILES.users)))\n .filter((d: any) => d.type === 'user')\n .map(migrateUserPermissions);\n const chatMessages = (await loadNedb(filePath(NEDB_FILES.chat)))\n .filter((d: any) => d.type === 'chat')\n .sort((a: any, b: any) => (a.created ?? 0) - (b.created ?? 0));\n const playerDocs = await loadNedb(filePath(NEDB_FILES.players));\n const histories = playerDocs.filter((d: any) => d.type === 'userHistory');\n const bans = playerDocs.filter((d: any) => d.type === 'banHistory');\n const kicks = playerDocs.filter((d: any) => d.type === 'kickHistory');\n const notes = playerDocs.filter((d: any) => d.type === 'note');\n const statusDocs = await loadNedb(filePath(NEDB_FILES.status));\n const hbDocs = statusDocs\n .filter((d: any) => d.type === 'heartbeat')\n .sort((a: any, b: any) => (a.created ?? 0) - (b.created ?? 0));\n const pcDocs = statusDocs.filter((d: any) => d.type === 'punchcard');\n const serverDocs = await loadNedb(filePath(NEDB_FILES.server));\n const instances = serverDocs.filter((d: any) => d.type === 'app:start');\n const roles = serverDocs.filter((d: any) => d.type === 'webRole');\n const rawDefaultPerms = serverDocs.find(\n (d: any) => d.type === 'defaultPermissions',\n );\n const autoRestart = serverDocs.find(\n (d: any) => d.type === 'autoRestartConfig',\n );\n\n // warn about user rows that will be dropped by the new UNIQUE(username):\n // NeDB had no such constraint, so legacy data can hold duplicates\n const seenUsernames = new Set<string>();\n for (const u of users) {\n const name = u.username ?? '';\n if (seenUsernames.has(name))\n Logger.error(\n '!>'.red,\n `Duplicate username ${JSON.stringify(name)} in legacy data; user ${u._id} will not be imported`,\n );\n else seenUsernames.add(name);\n }\n\n // ---- Plugins: a separate connection/DB, imported first and idempotently\n // (onConflictDoNothing against real unique constraints), so re-running after\n // a main-DB failure is a safe no-op. ----\n if (hasPluginNedb) {\n const pluginSqlite = openDb(filePath(soft.PLUGINS_DB));\n try {\n const pluginDb = drizzle(pluginSqlite);\n runPluginMigrations(pluginDb);\n const pluginDocs = await loadNedb(pluginPath);\n // NeDB allowed store.set(key, null/undefined); the new value column is\n // NOT NULL, so skip those rows\n const storeDocs = pluginDocs.filter(\n (d: any) => d.type === 'store' && d.value != null,\n );\n const skipped =\n pluginDocs.filter((d: any) => d.type === 'store').length -\n storeDocs.length;\n if (skipped > 0)\n Logger.log(\n ':>'.yellow,\n `Skipping ${skipped} plugin store entr${skipped === 1 ? 'y' : 'ies'} with no value`,\n );\n const configDocs = pluginDocs.filter((d: any) => d.type === 'config');\n\n const pluginTx = pluginSqlite.transaction(() => {\n eachBatch(storeDocs, batch =>\n pluginDb\n .insert(pluginSchema.pluginStore)\n .values(\n batch.map((s: any) => ({\n plugin: s.plugin ?? '',\n key: s.key ?? '',\n value: s.value,\n })),\n )\n .onConflictDoNothing()\n .run(),\n );\n eachBatch(configDocs, batch =>\n pluginDb\n .insert(pluginSchema.pluginConfig)\n .values(\n batch.map((c: any) => ({\n plugin: c.plugin ?? '',\n value: c.value ?? {},\n })),\n )\n .onConflictDoNothing()\n .run(),\n );\n });\n pluginTx();\n if (storeDocs.length) counts.pluginStoreItems = storeDocs.length;\n if (configDocs.length) counts.pluginConfigs = configDocs.length;\n } finally {\n pluginSqlite.close();\n }\n }\n\n // ---- Main DB: one atomic transaction. The completion flag is written in\n // the same transaction as the data, so either both commit or neither does —\n // a crash before the marker file is written can't trigger a re-import. ----\n const alreadyImported = mainDb\n .select()\n .from(mainSchema.serverConfig)\n .where(eq(mainSchema.serverConfig.key, IMPORT_FLAG_KEY))\n .get();\n\n if (!alreadyImported && hasMainNedb) {\n const tx = mainSqlite.transaction(() => {\n eachBatch(users, batch =>\n mainDb\n .insert(mainSchema.users)\n .values(\n batch.map((u: any) => ({\n id: u._id,\n created: u.created ?? Date.now(),\n lastOnline: u.lastOnline ?? 0,\n username: u.username ?? '',\n hash: u.hash ?? '',\n isOwner: !!u.isOwner,\n roles: u.roles ?? [],\n playerId: u.playerId ?? '',\n isBanned: !!u.isBanned,\n permissions: u.permissions ?? null,\n totpSecret: u.totpSecret ?? null,\n totpEnabled: !!u.totpEnabled,\n passkeys: u.passkeys ?? [],\n recoveryCodes: u.recoveryCodes ?? [],\n })),\n )\n .onConflictDoNothing()\n .run(),\n );\n if (users.length) counts.users = users.length;\n\n eachBatch(chatMessages, batch =>\n mainDb\n .insert(mainSchema.chatLogs)\n .values(\n batch.map((c: any) => ({\n created: c.created ?? 0,\n instanceId: c.instanceId ?? '',\n action: c.action ?? 'msg',\n user: c.user ?? {},\n message: c.message ?? null,\n })),\n )\n .run(),\n );\n if (chatMessages.length) counts.chatMessages = chatMessages.length;\n\n eachBatch(histories, batch =>\n mainDb\n .insert(mainSchema.playerHistory)\n .values(\n batch.map((p: any) => ({\n id: p.id,\n name: p.name ?? '',\n displayName: p.displayName ?? '',\n nameHistory: sanitizeNameHistory(p.nameHistory),\n ips: Array.isArray(p.ips) ? p.ips : [],\n created: p.created ?? 0,\n lastSeen: p.lastSeen ?? 0,\n lastInstanceId: p.lastInstanceId ?? '',\n heartbeats: p.heartbeats ?? 0,\n sessions: p.sessions ?? 0,\n instances: p.instances ?? 0,\n })),\n )\n .onConflictDoNothing()\n .run(),\n );\n if (histories.length) counts.players = histories.length;\n\n for (const b of bans) {\n mainDb\n .insert(mainSchema.banHistory)\n .values({\n banned: b.banned ?? '',\n bannerId: b.bannerId ?? '',\n // Number.isFinite (not typeof) so a NaN timestamp becomes 0,\n // binding NaN to a NOT NULL integer column stores NULL and throws\n created: Number.isFinite(b.created) ? b.created : 0,\n expires: Number.isFinite(b.expires) ? b.expires : 0,\n reason: b.reason ?? '',\n })\n .onConflictDoNothing()\n .run();\n }\n if (bans.length) counts.bans = bans.length;\n\n for (const k of kicks) {\n mainDb\n .insert(mainSchema.kickHistory)\n .values({\n kicked: k.kicked ?? '',\n kickerId: k.kickerId ?? '',\n created: k.created ?? 0,\n reason: k.reason ?? '',\n })\n .onConflictDoNothing()\n .run();\n }\n if (kicks.length) counts.kicks = kicks.length;\n\n eachBatch(notes, batch =>\n mainDb\n .insert(mainSchema.playerNotes)\n .values(\n batch.map((n: any) => ({\n playerId: n.id ?? '',\n note: n.note ?? '',\n })),\n )\n .run(),\n );\n if (notes.length) counts.notes = notes.length;\n\n eachBatch(hbDocs, batch =>\n mainDb\n .insert(mainSchema.heartbeats)\n .values(\n batch.map((h: any) => ({\n created: h.created ?? 0,\n bricks: h.bricks ?? 0,\n players: Array.isArray(h.players) ? h.players : [],\n })),\n )\n .run(),\n );\n if (hbDocs.length) counts.heartbeats = hbDocs.length;\n\n for (const p of pcDocs) {\n mainDb\n .insert(mainSchema.punchcards)\n .values({\n kind: p.kind ?? 'playerCount',\n created: p.created ?? 0,\n updated: p.updated ?? 0,\n month: p.month ?? 0,\n year: p.year ?? 0,\n punchcard: p.punchcard ?? [],\n })\n .onConflictDoNothing()\n .run();\n }\n if (pcDocs.length) counts.punchcards = pcDocs.length;\n\n eachBatch(instances, batch =>\n mainDb\n .insert(mainSchema.serverInstances)\n .values(batch.map((i: any) => ({ id: i._id, date: i.date ?? 0 })))\n .onConflictDoNothing()\n .run(),\n );\n if (instances.length) counts.serverInstances = instances.length;\n\n eachBatch(roles, batch =>\n mainDb\n .insert(mainSchema.webRoles)\n .values(\n batch.map((r: any) => ({\n id: r._id,\n name: r.name ?? '',\n description: r.description ?? '',\n order: r.order ?? 0,\n permissions: decodePermissions(r.permissions),\n })),\n )\n .onConflictDoNothing()\n .run(),\n );\n if (roles.length) counts.roles = roles.length;\n\n const defaultPerms = migrateDefaultPermissions(rawDefaultPerms);\n if (defaultPerms) {\n mainDb\n .insert(mainSchema.serverConfig)\n .values({\n key: 'defaultPermissions',\n value: {\n root: defaultPerms.root,\n domains: defaultPerms.domains,\n scopes: defaultPerms.scopes,\n },\n })\n .onConflictDoNothing()\n .run();\n }\n\n if (autoRestart) {\n const { _id: _, type: __, ...config } = autoRestart;\n mainDb\n .insert(mainSchema.serverConfig)\n .values({ key: 'autoRestartConfig', value: config })\n .onConflictDoNothing()\n .run();\n }\n\n // commit the completion flag atomically with the data above\n mainDb\n .insert(mainSchema.serverConfig)\n .values({ key: IMPORT_FLAG_KEY, value: new Date().toISOString() })\n .onConflictDoNothing()\n .run();\n });\n tx();\n }\n\n // marker file enables the cheap fast-path skip on subsequent boots\n fs.writeFileSync(markerPath, new Date().toISOString());\n\n const parts = Object.entries(counts)\n .map(([k, v]) => `${v} ${k}`)\n .join(', ');\n Logger.log('>>'.green, `Imported ${parts || 'no records'}`);\n Logger.log('>>'.green, 'NeDB import complete. Original files preserved.');\n } finally {\n if (ownConnection) mainSqlite.close();\n }\n}\n"],"names":["decodePermissions","openDb","soft","drizzle","runMigrations","Logger","runPluginMigrations","pluginSchema.pluginStore","pluginSchema.pluginConfig","mainSchema.serverConfig","eq","mainSchema.users","mainSchema.chatLogs","mainSchema.playerHistory","mainSchema.banHistory","mainSchema.kickHistory","mainSchema.playerNotes","mainSchema.heartbeats","mainSchema.punchcards","mainSchema.serverInstances","mainSchema.webRoles"],"mappings":";;;;;;;;;;;;;;AAoBA,MAAM,cAAc;AACpB,MAAM,aAAa;AAGZ,MAAM,aAAa;AAAA,EACxB,OAAO;AAAA,EACP,MAAM;AAAA,EACN,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AACX;AAYA,SAAS,oBAAoB,GAAiB;AAC5C,MAAI,EAAE,SAAS,QAAS,QAAO;AAC/B,aAAW,KAAK,OAAO,OAAO,EAAE,WAAW,EAAE;AAC3C,QAAI,MAAM,QAAS,QAAO;AAC5B,aAAW,KAAK,OAAO,OAAO,EAAE,UAAU,EAAE;AAC1C,QAAI,OAAO,MAAM,SAAU,QAAO;AACpC,SAAO;AACT;AAGA,SAAS,2BAA2B,GAA6B;AAC/D,QAAM,OAAO,EAAE,SAAS,SAAS,EAAE,SAAS,SAAS,EAAE,OAAO;AAC9D,QAAM,UAAkC,CAAA;AACxC,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,EAAE,WAAW,CAAA,CAAE,GAAG;AACpD,QAAI,MAAM,WAAW,MAAM,OAAQ,SAAQ,CAAC,IAAI;AAAA,EAClD;AACA,QAAM,SAAkC,CAAA;AACxC,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,EAAE,UAAU,CAAA,CAAE,GAAG;AACnD,QAAI,MAAM,aAAa,MAAM,KAAM,QAAO,CAAC,IAAI;AAAA,aACtC,MAAM,cAAc,MAAM,MAAO,QAAO,CAAC,IAAI;AAAA,EACxD;AACA,SAAO,EAAE,MAAM,SAAS,OAAA;AAC1B;AAEA,SAAS,uBAAuB,MAAgB;AAC9C,QAAM,IAAI,KAAK;AACf,MAAI,CAAC,KAAK,OAAO,MAAM,SAAU,QAAO;AACxC,QAAM,SAAS,oBAAoB,CAAC,IAAI,2BAA2B,CAAC,IAAI;AACxE,SAAO,EAAE,GAAG,MAAM,aAAaA,YAAAA,kBAAkB,MAAM,EAAA;AACzD;AAEA,SAAS,0BAA0B,KAAe;AAChD,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,SAAS,oBAAoB,GAAG,IAClC,2BAA2B,GAAG,IAC9B;AACJ,SAAO,EAAE,GAAG,KAAK,GAAGA,YAAAA,kBAAkB,MAAM,EAAA;AAC9C;AAKA,SAAS,oBAAoB,KAIzB;AACF,MAAI,CAAC,MAAM,QAAQ,GAAG,UAAU,CAAA;AAChC,SAAO,IACJ,OAAO,CAAA,MAAK,KAAK,OAAO,MAAM,QAAQ,EACtC,IAAI,CAAA,OAAM;AAAA,IACT,MAAM,OAAO,EAAE,QAAQ,EAAE;AAAA,IACzB,aAAa,OAAO,EAAE,eAAe,EAAE;AAAA,IACvC,MAAM,OAAO,EAAE,SAAS,WAAW,EAAE,OAAO;AAAA,EAAA,EAC5C;AACN;AAEA,eAAe,SAAkB,UAAgC;AAC/D,MAAI,CAAC,GAAG,WAAW,QAAQ,UAAU,CAAA;AACrC,QAAM,QAAQ,UAAU,OAAO,EAAE,UAAU,UAAU,UAAU,MAAM;AACrE,SAAQ,MAAM,MAAM,KAAK,EAAE;AAC7B;AAKA,SAAS,UAAU,MAAa,IAA2B;AACzD,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,YAAY;AAChD,OAAG,KAAK,MAAM,GAAG,IAAI,UAAU,CAAC;AAAA,EAClC;AACF;AAKA,MAAM,kBAAkB;AAKxB,MAAM,sCAAsB,IAAA;AAErB,SAAS,mBACd,UACA,YACA,QACe;AACf,QAAM,MAAM,KAAK,QAAQ,QAAQ;AACjC,MAAI,UAAU,gBAAgB,IAAI,GAAG;AACrC,MAAI,CAAC,SAAS;AACZ,cAAU,WAAW,UAAU,YAAY,MAAM,EAAE;AAAA,MAAQ,MACzD,gBAAgB,OAAO,GAAG;AAAA,IAAA;AAE5B,oBAAgB,IAAI,KAAK,OAAO;AAAA,EAClC;AACA,SAAO;AACT;AAEA,eAAe,WACb,UACA,YACA,QACA;AACA,QAAM,aAAa,KAAK,KAAK,UAAU,WAAW;AAClD,MAAI,GAAG,WAAW,UAAU,EAAG;AAE/B,QAAM,WAAW,CAAC,MAAc,KAAK,KAAK,UAAU,CAAC;AACrD,QAAM,gBAAgB;AAAA,IACpB,WAAW;AAAA,IACX,WAAW;AAAA,IACX,WAAW;AAAA,IACX,WAAW;AAAA,IACX,WAAW;AAAA,EAAA;AAEb,QAAM,cAAc,cAAc,KAAK,CAAA,MAAK,GAAG,WAAW,SAAS,CAAC,CAAC,CAAC;AACtE,QAAM,aAAa,SAAS,WAAW,OAAO;AAC9C,QAAM,gBAAgB,GAAG,WAAW,UAAU;AAC9C,MAAI,CAAC,eAAe,CAAC,eAAe;AAClC,OAAG,cAAc,aAAY,oBAAI,KAAA,GAAO,aAAa;AACrD;AAAA,EACF;AAIA,MAAI,gBAAgB;AACpB,MAAI,CAAC,cAAc,CAAC,QAAQ;AAC1B,oBAAgB;AAChB,iBAAaC,WAAAA,OAAO,SAASC,WAAAA,QAAK,OAAO,CAAC;AAC1C,aAASC,cAAAA,QAAQ,UAAU;AAC3BC,YAAAA,cAAc,MAAM;AAAA,EACtB;AAEA,MAAI;AACFC,WAAAA,QAAO,IAAI,KAAK,OAAO,oCAAoC;AAC3D,UAAM,SAAiC,CAAA;AAKvC,UAAM,SAAS,MAAM,SAAS,SAAS,WAAW,KAAK,CAAC,GACrD,OAAO,CAAC,MAAW,EAAE,SAAS,MAAM,EACpC,IAAI,sBAAsB;AAC7B,UAAM,gBAAgB,MAAM,SAAS,SAAS,WAAW,IAAI,CAAC,GAC3D,OAAO,CAAC,MAAW,EAAE,SAAS,MAAM,EACpC,KAAK,CAAC,GAAQ,OAAY,EAAE,WAAW,MAAM,EAAE,WAAW,EAAE;AAC/D,UAAM,aAAa,MAAM,SAAS,SAAS,WAAW,OAAO,CAAC;AAC9D,UAAM,YAAY,WAAW,OAAO,CAAC,MAAW,EAAE,SAAS,aAAa;AACxE,UAAM,OAAO,WAAW,OAAO,CAAC,MAAW,EAAE,SAAS,YAAY;AAClE,UAAM,QAAQ,WAAW,OAAO,CAAC,MAAW,EAAE,SAAS,aAAa;AACpE,UAAM,QAAQ,WAAW,OAAO,CAAC,MAAW,EAAE,SAAS,MAAM;AAC7D,UAAM,aAAa,MAAM,SAAS,SAAS,WAAW,MAAM,CAAC;AAC7D,UAAM,SAAS,WACZ,OAAO,CAAC,MAAW,EAAE,SAAS,WAAW,EACzC,KAAK,CAAC,GAAQ,OAAY,EAAE,WAAW,MAAM,EAAE,WAAW,EAAE;AAC/D,UAAM,SAAS,WAAW,OAAO,CAAC,MAAW,EAAE,SAAS,WAAW;AACnE,UAAM,aAAa,MAAM,SAAS,SAAS,WAAW,MAAM,CAAC;AAC7D,UAAM,YAAY,WAAW,OAAO,CAAC,MAAW,EAAE,SAAS,WAAW;AACtE,UAAM,QAAQ,WAAW,OAAO,CAAC,MAAW,EAAE,SAAS,SAAS;AAChE,UAAM,kBAAkB,WAAW;AAAA,MACjC,CAAC,MAAW,EAAE,SAAS;AAAA,IAAA;AAEzB,UAAM,cAAc,WAAW;AAAA,MAC7B,CAAC,MAAW,EAAE,SAAS;AAAA,IAAA;AAKzB,UAAM,oCAAoB,IAAA;AAC1B,eAAW,KAAK,OAAO;AACrB,YAAM,OAAO,EAAE,YAAY;AAC3B,UAAI,cAAc,IAAI,IAAI;AACxBA,eAAAA,QAAO;AAAA,UACL,KAAK;AAAA,UACL,sBAAsB,KAAK,UAAU,IAAI,CAAC,yBAAyB,EAAE,GAAG;AAAA,QAAA;AAAA,UAEvE,eAAc,IAAI,IAAI;AAAA,IAC7B;AAKA,QAAI,eAAe;AACjB,YAAM,eAAeJ,WAAAA,OAAO,SAASC,WAAAA,QAAK,UAAU,CAAC;AACrD,UAAI;AACF,cAAM,WAAWC,cAAAA,QAAQ,YAAY;AACrCG,gBAAAA,oBAAoB,QAAQ;AAC5B,cAAM,aAAa,MAAM,SAAS,UAAU;AAG5C,cAAM,YAAY,WAAW;AAAA,UAC3B,CAAC,MAAW,EAAE,SAAS,WAAW,EAAE,SAAS;AAAA,QAAA;AAE/C,cAAM,UACJ,WAAW,OAAO,CAAC,MAAW,EAAE,SAAS,OAAO,EAAE,SAClD,UAAU;AACZ,YAAI,UAAU;AACZD,iBAAAA,QAAO;AAAA,YACL,KAAK;AAAA,YACL,YAAY,OAAO,qBAAqB,YAAY,IAAI,MAAM,KAAK;AAAA,UAAA;AAEvE,cAAM,aAAa,WAAW,OAAO,CAAC,MAAW,EAAE,SAAS,QAAQ;AAEpE,cAAM,WAAW,aAAa,YAAY,MAAM;AAC9C;AAAA,YAAU;AAAA,YAAW,CAAA,UACnB,SACG,OAAOE,aAAAA,WAAwB,EAC/B;AAAA,cACC,MAAM,IAAI,CAAC,OAAY;AAAA,gBACrB,QAAQ,EAAE,UAAU;AAAA,gBACpB,KAAK,EAAE,OAAO;AAAA,gBACd,OAAO,EAAE;AAAA,cAAA,EACT;AAAA,YAAA,EAEH,oBAAA,EACA,IAAA;AAAA,UAAI;AAET;AAAA,YAAU;AAAA,YAAY,CAAA,UACpB,SACG,OAAOC,aAAAA,YAAyB,EAChC;AAAA,cACC,MAAM,IAAI,CAAC,OAAY;AAAA,gBACrB,QAAQ,EAAE,UAAU;AAAA,gBACpB,OAAO,EAAE,SAAS,CAAA;AAAA,cAAC,EACnB;AAAA,YAAA,EAEH,oBAAA,EACA,IAAA;AAAA,UAAI;AAAA,QAEX,CAAC;AACD,iBAAA;AACA,YAAI,UAAU,OAAQ,QAAO,mBAAmB,UAAU;AAC1D,YAAI,WAAW,OAAQ,QAAO,gBAAgB,WAAW;AAAA,MAC3D,UAAA;AACE,qBAAa,MAAA;AAAA,MACf;AAAA,IACF;AAKA,UAAM,kBAAkB,OACrB,OAAA,EACA,KAAKC,OAAAA,YAAuB,EAC5B,MAAMC,WAAAA,GAAGD,OAAAA,aAAwB,KAAK,eAAe,CAAC,EACtD,IAAA;AAEH,QAAI,CAAC,mBAAmB,aAAa;AACnC,YAAM,KAAK,WAAW,YAAY,MAAM;AACtC;AAAA,UAAU;AAAA,UAAO,CAAA,UACf,OACG,OAAOE,OAAAA,KAAgB,EACvB;AAAA,YACC,MAAM,IAAI,CAAC,OAAY;AAAA,cACrB,IAAI,EAAE;AAAA,cACN,SAAS,EAAE,WAAW,KAAK,IAAA;AAAA,cAC3B,YAAY,EAAE,cAAc;AAAA,cAC5B,UAAU,EAAE,YAAY;AAAA,cACxB,MAAM,EAAE,QAAQ;AAAA,cAChB,SAAS,CAAC,CAAC,EAAE;AAAA,cACb,OAAO,EAAE,SAAS,CAAA;AAAA,cAClB,UAAU,EAAE,YAAY;AAAA,cACxB,UAAU,CAAC,CAAC,EAAE;AAAA,cACd,aAAa,EAAE,eAAe;AAAA,cAC9B,YAAY,EAAE,cAAc;AAAA,cAC5B,aAAa,CAAC,CAAC,EAAE;AAAA,cACjB,UAAU,EAAE,YAAY,CAAA;AAAA,cACxB,eAAe,EAAE,iBAAiB,CAAA;AAAA,YAAC,EACnC;AAAA,UAAA,EAEH,oBAAA,EACA,IAAA;AAAA,QAAI;AAET,YAAI,MAAM,OAAQ,QAAO,QAAQ,MAAM;AAEvC;AAAA,UAAU;AAAA,UAAc,CAAA,UACtB,OACG,OAAOC,OAAAA,QAAmB,EAC1B;AAAA,YACC,MAAM,IAAI,CAAC,OAAY;AAAA,cACrB,SAAS,EAAE,WAAW;AAAA,cACtB,YAAY,EAAE,cAAc;AAAA,cAC5B,QAAQ,EAAE,UAAU;AAAA,cACpB,MAAM,EAAE,QAAQ,CAAA;AAAA,cAChB,SAAS,EAAE,WAAW;AAAA,YAAA,EACtB;AAAA,UAAA,EAEH,IAAA;AAAA,QAAI;AAET,YAAI,aAAa,OAAQ,QAAO,eAAe,aAAa;AAE5D;AAAA,UAAU;AAAA,UAAW,CAAA,UACnB,OACG,OAAOC,OAAAA,aAAwB,EAC/B;AAAA,YACC,MAAM,IAAI,CAAC,OAAY;AAAA,cACrB,IAAI,EAAE;AAAA,cACN,MAAM,EAAE,QAAQ;AAAA,cAChB,aAAa,EAAE,eAAe;AAAA,cAC9B,aAAa,oBAAoB,EAAE,WAAW;AAAA,cAC9C,KAAK,MAAM,QAAQ,EAAE,GAAG,IAAI,EAAE,MAAM,CAAA;AAAA,cACpC,SAAS,EAAE,WAAW;AAAA,cACtB,UAAU,EAAE,YAAY;AAAA,cACxB,gBAAgB,EAAE,kBAAkB;AAAA,cACpC,YAAY,EAAE,cAAc;AAAA,cAC5B,UAAU,EAAE,YAAY;AAAA,cACxB,WAAW,EAAE,aAAa;AAAA,YAAA,EAC1B;AAAA,UAAA,EAEH,oBAAA,EACA,IAAA;AAAA,QAAI;AAET,YAAI,UAAU,OAAQ,QAAO,UAAU,UAAU;AAEjD,mBAAW,KAAK,MAAM;AACpB,iBACG,OAAOC,iBAAqB,EAC5B,OAAO;AAAA,YACN,QAAQ,EAAE,UAAU;AAAA,YACpB,UAAU,EAAE,YAAY;AAAA;AAAA;AAAA,YAGxB,SAAS,OAAO,SAAS,EAAE,OAAO,IAAI,EAAE,UAAU;AAAA,YAClD,SAAS,OAAO,SAAS,EAAE,OAAO,IAAI,EAAE,UAAU;AAAA,YAClD,QAAQ,EAAE,UAAU;AAAA,UAAA,CACrB,EACA,oBAAA,EACA,IAAA;AAAA,QACL;AACA,YAAI,KAAK,OAAQ,QAAO,OAAO,KAAK;AAEpC,mBAAW,KAAK,OAAO;AACrB,iBACG,OAAOC,kBAAsB,EAC7B,OAAO;AAAA,YACN,QAAQ,EAAE,UAAU;AAAA,YACpB,UAAU,EAAE,YAAY;AAAA,YACxB,SAAS,EAAE,WAAW;AAAA,YACtB,QAAQ,EAAE,UAAU;AAAA,UAAA,CACrB,EACA,oBAAA,EACA,IAAA;AAAA,QACL;AACA,YAAI,MAAM,OAAQ,QAAO,QAAQ,MAAM;AAEvC;AAAA,UAAU;AAAA,UAAO,CAAA,UACf,OACG,OAAOC,OAAAA,WAAsB,EAC7B;AAAA,YACC,MAAM,IAAI,CAAC,OAAY;AAAA,cACrB,UAAU,EAAE,MAAM;AAAA,cAClB,MAAM,EAAE,QAAQ;AAAA,YAAA,EAChB;AAAA,UAAA,EAEH,IAAA;AAAA,QAAI;AAET,YAAI,MAAM,OAAQ,QAAO,QAAQ,MAAM;AAEvC;AAAA,UAAU;AAAA,UAAQ,CAAA,UAChB,OACG,OAAOC,OAAAA,UAAqB,EAC5B;AAAA,YACC,MAAM,IAAI,CAAC,OAAY;AAAA,cACrB,SAAS,EAAE,WAAW;AAAA,cACtB,QAAQ,EAAE,UAAU;AAAA,cACpB,SAAS,MAAM,QAAQ,EAAE,OAAO,IAAI,EAAE,UAAU,CAAA;AAAA,YAAC,EACjD;AAAA,UAAA,EAEH,IAAA;AAAA,QAAI;AAET,YAAI,OAAO,OAAQ,QAAO,aAAa,OAAO;AAE9C,mBAAW,KAAK,QAAQ;AACtB,iBACG,OAAOC,iBAAqB,EAC5B,OAAO;AAAA,YACN,MAAM,EAAE,QAAQ;AAAA,YAChB,SAAS,EAAE,WAAW;AAAA,YACtB,SAAS,EAAE,WAAW;AAAA,YACtB,OAAO,EAAE,SAAS;AAAA,YAClB,MAAM,EAAE,QAAQ;AAAA,YAChB,WAAW,EAAE,aAAa,CAAA;AAAA,UAAC,CAC5B,EACA,oBAAA,EACA,IAAA;AAAA,QACL;AACA,YAAI,OAAO,OAAQ,QAAO,aAAa,OAAO;AAE9C;AAAA,UAAU;AAAA,UAAW,CAAA,UACnB,OACG,OAAOC,OAAAA,eAA0B,EACjC,OAAO,MAAM,IAAI,CAAC,OAAY,EAAE,IAAI,EAAE,KAAK,MAAM,EAAE,QAAQ,EAAA,EAAI,CAAC,EAChE,oBAAA,EACA,IAAA;AAAA,QAAI;AAET,YAAI,UAAU,OAAQ,QAAO,kBAAkB,UAAU;AAEzD;AAAA,UAAU;AAAA,UAAO,CAAA,UACf,OACG,OAAOC,OAAAA,QAAmB,EAC1B;AAAA,YACC,MAAM,IAAI,CAAC,OAAY;AAAA,cACrB,IAAI,EAAE;AAAA,cACN,MAAM,EAAE,QAAQ;AAAA,cAChB,aAAa,EAAE,eAAe;AAAA,cAC9B,OAAO,EAAE,SAAS;AAAA,cAClB,aAAapB,YAAAA,kBAAkB,EAAE,WAAW;AAAA,YAAA,EAC5C;AAAA,UAAA,EAEH,oBAAA,EACA,IAAA;AAAA,QAAI;AAET,YAAI,MAAM,OAAQ,QAAO,QAAQ,MAAM;AAEvC,cAAM,eAAe,0BAA0B,eAAe;AAC9D,YAAI,cAAc;AAChB,iBACG,OAAOS,mBAAuB,EAC9B,OAAO;AAAA,YACN,KAAK;AAAA,YACL,OAAO;AAAA,cACL,MAAM,aAAa;AAAA,cACnB,SAAS,aAAa;AAAA,cACtB,QAAQ,aAAa;AAAA,YAAA;AAAA,UACvB,CACD,EACA,oBAAA,EACA,IAAA;AAAA,QACL;AAEA,YAAI,aAAa;AACf,gBAAM,EAAE,KAAK,GAAG,MAAM,IAAI,GAAG,WAAW;AACxC,iBACG,OAAOA,OAAAA,YAAuB,EAC9B,OAAO,EAAE,KAAK,qBAAqB,OAAO,OAAA,CAAQ,EAClD,oBAAA,EACA,IAAA;AAAA,QACL;AAGA,eACG,OAAOA,OAAAA,YAAuB,EAC9B,OAAO,EAAE,KAAK,iBAAiB,QAAO,oBAAI,KAAA,GAAO,YAAA,EAAY,CAAG,EAChE,oBAAA,EACA,IAAA;AAAA,MACL,CAAC;AACD,SAAA;AAAA,IACF;AAGA,OAAG,cAAc,aAAY,oBAAI,KAAA,GAAO,aAAa;AAErD,UAAM,QAAQ,OAAO,QAAQ,MAAM,EAChC,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,EAAE,EAC3B,KAAK,IAAI;AACZJ,mBAAO,IAAI,KAAK,OAAO,YAAY,SAAS,YAAY,EAAE;AAC1DA,WAAAA,QAAO,IAAI,KAAK,OAAO,iDAAiD;AAAA,EAC1E,UAAA;AACE,QAAI,0BAA0B,MAAA;AAAA,EAChC;AACF;;;"}
@@ -11,6 +11,7 @@ const init = require("./init.js");
11
11
  const mapChange = require("./mapChange.js");
12
12
  const interact = require("./interact.js");
13
13
  const minigameJoin = require("./minigameJoin.js");
14
+ const wire = require("./wire.js");
14
15
  const MATCHERS = [
15
16
  join.default,
16
17
  leave.default,
@@ -22,7 +23,8 @@ const MATCHERS = [
22
23
  init.default,
23
24
  mapChange.default,
24
25
  interact.default,
25
- minigameJoin.default
26
+ minigameJoin.default,
27
+ wire.default
26
28
  ];
27
29
  exports.default = MATCHERS;
28
30
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../../src/omegga/matchers/index.ts"],"sourcesContent":["import { MatchGenerator } from './types';\n\nimport join from './join';\n// 'join' event => { name, id, state, controller }\n\nimport leave from './leave';\n// 'leave' event => { name, id, state, controller }\n\nimport chat from './chat';\n// 'chat' event => name, message; 'chatcmd:command' event => name, [...args]\n// 'kick' event => name, kicker, reason\n\nimport command from './command';\n// 'cmd:command' event => name, args (string)\n\nimport auth from './auth';\n// assigns host, 'host' event, 'start' event, 'unauthorized' event\n\nimport exit from './exit';\n// 'exit' event\n\nimport version from './version';\n// 'version' event, check game version\n\nimport init from './init';\n// watch loginit for any funny business\n\nimport mapChange from './mapChange';\n// 'mapchange' event\n\n// interaction event\nimport interact from './interact';\n\n// minigame join event\nimport minigameJoin from './minigameJoin';\n\nexport default [\n join,\n leave,\n chat,\n command,\n auth,\n exit,\n version,\n init,\n mapChange,\n interact,\n minigameJoin,\n] as MatchGenerator<any>[];\n"],"names":["join","leave","chat","command","auth","exit","version","init","mapChange","interact","minigameJoin"],"mappings":";;;;;;;;;;;;;AAoCA,MAAA,WAAe;AAAA,EACbA,KAAAA;AAAAA,EACAC,MAAAA;AAAAA,EACAC,KAAAA;AAAAA,EACAC,QAAAA;AAAAA,EACAC,KAAAA;AAAAA,EACAC,KAAAA;AAAAA,EACAC,QAAAA;AAAAA,EACAC,KAAAA;AAAAA,EACAC,UAAAA;AAAAA,EACAC,SAAAA;AAAAA,EACAC,aAAAA;AACF;;"}
1
+ {"version":3,"file":"index.js","sources":["../../../src/omegga/matchers/index.ts"],"sourcesContent":["import { MatchGenerator } from './types';\n\nimport join from './join';\n// 'join' event => { name, id, state, controller }\n\nimport leave from './leave';\n// 'leave' event => { name, id, state, controller }\n\nimport chat from './chat';\n// 'chat' event => name, message; 'chatcmd:command' event => name, [...args]\n// 'kick' event => name, kicker, reason\n\nimport command from './command';\n// 'cmd:command' event => name, args (string)\n\nimport auth from './auth';\n// assigns host, 'host' event, 'start' event, 'unauthorized' event\n\nimport exit from './exit';\n// 'exit' event\n\nimport version from './version';\n// 'version' event, check game version\n\nimport init from './init';\n// watch loginit for any funny business\n\nimport mapChange from './mapChange';\n// 'mapchange' event\n\n// interaction event\nimport interact from './interact';\n\n// minigame join event\nimport minigameJoin from './minigameJoin';\n\nimport wire from './wire';\n// 'wirelog' event => raw; 'wirecmd:command' event => [...args]\n\nexport default [\n join,\n leave,\n chat,\n command,\n auth,\n exit,\n version,\n init,\n mapChange,\n interact,\n minigameJoin,\n wire,\n] as MatchGenerator<any>[];\n"],"names":["join","leave","chat","command","auth","exit","version","init","mapChange","interact","minigameJoin","wire"],"mappings":";;;;;;;;;;;;;;AAuCA,MAAA,WAAe;AAAA,EACbA,KAAAA;AAAAA,EACAC,MAAAA;AAAAA,EACAC,KAAAA;AAAAA,EACAC,QAAAA;AAAAA,EACAC,KAAAA;AAAAA,EACAC,KAAAA;AAAAA,EACAC,QAAAA;AAAAA,EACAC,KAAAA;AAAAA,EACAC,UAAAA;AAAAA,EACAC,SAAAA;AAAAA,EACAC,aAAAA;AAAAA,EACAC,KAAAA;AACF;;"}
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
3
+ const wire = (omegga) => {
4
+ const wireRegExp = /^\[Wire Graph\] (?<raw>.*)$/;
5
+ return {
6
+ // listen for wire graph messages
7
+ pattern(_line, logMatch) {
8
+ if (!logMatch) return;
9
+ const { generator, data } = logMatch.groups;
10
+ if (generator !== "LogBrickadia") return;
11
+ const match = data.match(wireRegExp);
12
+ if (!match) return;
13
+ return { raw: match.groups.raw };
14
+ },
15
+ // when there's a match, emit the wire log event
16
+ callback({ raw }) {
17
+ omegga.emit("wirelog", raw);
18
+ const [cmd, ...args] = raw.split(" ");
19
+ if (cmd.length > 0) {
20
+ omegga.emit("wirecmd:" + cmd.toLowerCase(), ...args);
21
+ omegga.emit("wirecmd", cmd.toLowerCase(), ...args);
22
+ }
23
+ }
24
+ };
25
+ };
26
+ exports.default = wire;
27
+ //# sourceMappingURL=wire.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wire.js","sources":["../../../src/omegga/matchers/wire.ts"],"sourcesContent":["import { MatchGenerator } from './types';\n\nconst wire: MatchGenerator<{ raw: string }> = omegga => {\n // pattern to match wire graph log messages\n // e.g. `LogBrickadia: [Wire Graph] test`\n const wireRegExp = /^\\[Wire Graph\\] (?<raw>.*)$/;\n\n return {\n // listen for wire graph messages\n pattern(_line, logMatch) {\n // line is not generic console log\n if (!logMatch) return;\n\n const { generator, data } = logMatch.groups;\n // check if log is a brickadia log\n if (generator !== 'LogBrickadia') return;\n\n // match the wire graph log pattern\n const match = data.match(wireRegExp);\n if (!match) return;\n\n return { raw: match.groups.raw };\n },\n // when there's a match, emit the wire log event\n callback({ raw }) {\n omegga.emit('wirelog', raw);\n\n // wire command parsing, emit `wirecmd:test` when `test ...` is logged\n const [cmd, ...args] = raw.split(' ');\n if (cmd.length > 0) {\n omegga.emit('wirecmd:' + cmd.toLowerCase(), ...args);\n omegga.emit('wirecmd', cmd.toLowerCase(), ...args);\n }\n },\n };\n};\n\nexport default wire;\n"],"names":[],"mappings":";;AAEA,MAAM,OAAwC,CAAA,WAAU;AAGtD,QAAM,aAAa;AAEnB,SAAO;AAAA;AAAA,IAEL,QAAQ,OAAO,UAAU;AAEvB,UAAI,CAAC,SAAU;AAEf,YAAM,EAAE,WAAW,KAAA,IAAS,SAAS;AAErC,UAAI,cAAc,eAAgB;AAGlC,YAAM,QAAQ,KAAK,MAAM,UAAU;AACnC,UAAI,CAAC,MAAO;AAEZ,aAAO,EAAE,KAAK,MAAM,OAAO,IAAA;AAAA,IAC7B;AAAA;AAAA,IAEA,SAAS,EAAE,OAAO;AAChB,aAAO,KAAK,WAAW,GAAG;AAG1B,YAAM,CAAC,KAAK,GAAG,IAAI,IAAI,IAAI,MAAM,GAAG;AACpC,UAAI,IAAI,SAAS,GAAG;AAClB,eAAO,KAAK,aAAa,IAAI,YAAA,GAAe,GAAG,IAAI;AACnD,eAAO,KAAK,WAAW,IAAI,YAAA,GAAe,GAAG,IAAI;AAAA,MACnD;AAAA,IACF;AAAA,EAAA;AAEJ;;"}
@@ -4,6 +4,7 @@ const logger = require("../logger.js");
4
4
  const softconfig = require("../softconfig.js");
5
5
  const version = require("../version.js");
6
6
  const index$2 = require("../util/index.js");
7
+ const brdb = require("../util/brdb.js");
7
8
  const file = require("../util/file.js");
8
9
  const index = require("../webserver/backend/index.js");
9
10
  const brs = require("brs-js");
@@ -18,6 +19,25 @@ const plugin = require("./plugin.js");
18
19
  const wrapper = require("./wrapper.js");
19
20
  const MISSING_CMD = '"Command not found. Type <color=\\"ffff00\\">/help</> for a list of commands or <color=\\"ffff00\\">/plugins</> for plugin information."';
20
21
  const WHOLE_WORLD_EXTENT = 1e9;
22
+ function brsRemoved(version2) {
23
+ return version2 > 0 && version2 >= commands.PREFAB_VERSION;
24
+ }
25
+ function warnRemoved(version2, method, since, release, replacement) {
26
+ if (!(version2 > 0 && version2 >= since)) return false;
27
+ logger.default.warnp(
28
+ `omegga.${method}() uses a console command removed in Brickadia ${release}. This call has no effect - use ${replacement} instead.`
29
+ );
30
+ return true;
31
+ }
32
+ function writeTempPrefab(omegga, saveData) {
33
+ const ref = omegga._tempSavePrefix + Date.now() + "_" + omegga._tempCounter.save++;
34
+ const file$1 = path.join(omegga.prefabPath, ref + ".brz");
35
+ if (!file$1.startsWith(omegga.prefabPath))
36
+ throw "prefab file not in Saved/Prefabs directory";
37
+ file.mkdir(omegga.prefabPath);
38
+ fs.writeFileSync(file$1, new Uint8Array(brs.writeBrzLegacy(saveData)));
39
+ return { ref, file: file$1 };
40
+ }
21
41
  class Omegga extends wrapper.default {
22
42
  /**
23
43
  * Omegga instance
@@ -446,30 +466,6 @@ class Omegga extends wrapper.default {
446
466
  const presetPath = path.join(this.presetPath, "Environment");
447
467
  return fs.existsSync(presetPath) ? glob.sync(presetPath + "/**/*.bp").map((f) => path.basename(f).replace(/\.bp$/, "")) : [];
448
468
  }
449
- /**
450
- * Whether the running game version has removed the legacy .brs bricks console
451
- * commands (Bricks.Save/Load/ClearAll/ClearRegion, World.LoadAdditive) in
452
- * favor of the prefab (`br.Prefab.*`) and world (`br.World.Clear*`) commands.
453
- * Removed at Brickadia CL{@link PREFAB_VERSION}.
454
- */
455
- #brsRemoved() {
456
- return this.version > 0 && this.version >= commands.PREFAB_VERSION;
457
- }
458
- /**
459
- * Warn that a method backed by a removed console command is a no-op on the
460
- * running game version, and return whether the caller should bail.
461
- * @param method the omegga method being called (for the message)
462
- * @param since CL version the underlying command was removed at
463
- * @param release human release name for that version (e.g. `EA2`, `EA3`)
464
- * @param replacement suggested replacement method/API
465
- */
466
- #warnRemoved(method, since, release, replacement) {
467
- if (!(this.version > 0 && this.version >= since)) return false;
468
- logger.default.warnp(
469
- `omegga.${method}() uses a console command removed in Brickadia ${release}. This call has no effect - use ${replacement} instead.`
470
- );
471
- return true;
472
- }
473
469
  clearBricks(target, quiet = false) {
474
470
  if (typeof target === "object" && target.id) target = target.id;
475
471
  else if (typeof target === "string" && !index$2.uuid.match(target)) {
@@ -489,7 +485,7 @@ class Omegga extends wrapper.default {
489
485
  }
490
486
  const center = region.center.join(" ");
491
487
  const extent = region.extent.join(" ");
492
- if (this.#brsRemoved()) {
488
+ if (brsRemoved(this.version)) {
493
489
  const bricks = options?.bricks ?? true ? 1 : 0;
494
490
  const entities = options?.entities ?? false ? 1 : 0;
495
491
  this.writeln(
@@ -507,7 +503,7 @@ class Omegga extends wrapper.default {
507
503
  bricks = true,
508
504
  entities = false
509
505
  } = typeof options === "boolean" ? { quiet: options } : options;
510
- if (this.#brsRemoved()) {
506
+ if (brsRemoved(this.version)) {
511
507
  this.writeln(
512
508
  `${this.Console.World.ClearAll} ${bricks ? 1 : 0} ${entities ? 1 : 0} ${quiet ? 1 : 0}`
513
509
  );
@@ -516,7 +512,13 @@ class Omegga extends wrapper.default {
516
512
  this.writeln(`${this.Console.Bricks.ClearAll} ${quiet ? 1 : ""}`);
517
513
  }
518
514
  saveBricks(saveName, region) {
519
- if (this.#warnRemoved("saveBricks", commands.PREFAB_VERSION, "EA3", "savePrefab"))
515
+ if (warnRemoved(
516
+ this.version,
517
+ "saveBricks",
518
+ commands.PREFAB_VERSION,
519
+ "EA3",
520
+ "savePrefab"
521
+ ))
520
522
  return;
521
523
  if (!saveName) return;
522
524
  if (!(saveName.startsWith('"') && saveName.endsWith('"')))
@@ -530,7 +532,8 @@ class Omegga extends wrapper.default {
530
532
  else this.writeln(`${this.Console.Bricks.Save} ${saveName}`);
531
533
  }
532
534
  async saveBricksAsync(saveName, region) {
533
- if (this.#warnRemoved(
535
+ if (warnRemoved(
536
+ this.version,
534
537
  "saveBricksAsync",
535
538
  commands.PREFAB_VERSION,
536
539
  "EA3",
@@ -563,7 +566,13 @@ class Omegga extends wrapper.default {
563
566
  correctPalette = false,
564
567
  correctCustom = false
565
568
  } = {}) {
566
- if (this.#warnRemoved("loadBricks", commands.PREFAB_VERSION, "EA3", "loadPrefab"))
569
+ if (warnRemoved(
570
+ this.version,
571
+ "loadBricks",
572
+ commands.PREFAB_VERSION,
573
+ "EA3",
574
+ "loadPrefab"
575
+ ))
567
576
  return;
568
577
  if (!(saveName.startsWith('"') && saveName.endsWith('"')))
569
578
  saveName = `"${saveName}"`;
@@ -578,7 +587,8 @@ class Omegga extends wrapper.default {
578
587
  correctPalette = false,
579
588
  correctCustom = false
580
589
  } = {}) {
581
- if (this.#warnRemoved(
590
+ if (warnRemoved(
591
+ this.version,
582
592
  "loadBricksOnPlayer",
583
593
  commands.EA2_VERSION,
584
594
  "EA2",
@@ -617,42 +627,12 @@ class Omegga extends wrapper.default {
617
627
  return fs.existsSync(file2) ? file2 : void 0;
618
628
  }
619
629
  async getWorldRevisions(worldName) {
620
- if (!this.started || this.starting || this.stopping) {
621
- throw new Error("Server is not started");
622
- }
623
630
  worldName = worldName.replace(/\.brdb$/i, "");
624
- if (!worldName || !this.getWorldPath(worldName)) {
631
+ const path2 = this.getWorldPath(worldName);
632
+ if (!worldName || !path2) {
625
633
  throw new Error(`World "${worldName}" does not exist`);
626
634
  }
627
- let numRevisions = 0;
628
- const revisionsRaw = await this.watchLogChunk(
629
- `${this.Console.World.ListRevisions} "${worldName}"`,
630
- /^LogBRBundleManager: (There are (?<numRevisions>\d+) revisions|Revision (?<revision>\d+) - (?<date>[\d.-]+): (?<note>.+))$/,
631
- {
632
- last: (match) => Number(match.groups.reverse) === numRevisions,
633
- first: (match) => {
634
- if (match.groups?.numRevisions !== void 0) {
635
- numRevisions = Number(match.groups.numRevisions);
636
- return true;
637
- }
638
- return false;
639
- }
640
- }
641
- );
642
- if (!revisionsRaw) return [];
643
- return revisionsRaw.map((match) => {
644
- if (match.groups?.numRevisions !== void 0) {
645
- return null;
646
- }
647
- const revision = match.groups.revision ? Number(match.groups.revision) : 0;
648
- const dateStr = match.groups.date.replace(
649
- /(\d{4})\.(\d{2})\.(\d{2})-(\d{2})\.(\d{2})\.(\d{2})/,
650
- "$1-$2-$3T$4:$5:$6Z"
651
- );
652
- const date = new Date(dateStr);
653
- const note = match.groups.note || "";
654
- return { index: revision, date, note };
655
- }).filter(Boolean);
635
+ return brdb.readBrdbRevisions(path2) ?? [];
656
636
  }
657
637
  async loadWorld(worldName) {
658
638
  worldName = worldName.replace(/\.brdb$/i, "");
@@ -799,8 +779,14 @@ class Omegga extends wrapper.default {
799
779
  correctPalette = false,
800
780
  correctCustom = false
801
781
  } = {}) {
802
- if (this.#warnRemoved("loadSaveData", commands.PREFAB_VERSION, "EA3", "loadPrefab"))
782
+ if (brsRemoved(this.version)) {
783
+ const { ref, file: file2 } = writeTempPrefab(this, saveData);
784
+ this.loadPrefab(ref, { offX, offY, offZ });
785
+ setTimeout(() => {
786
+ if (fs.existsSync(file2)) fs.unlinkSync(file2);
787
+ }, 5e3);
803
788
  return;
789
+ }
804
790
  const saveFile = this._tempSavePrefix + Date.now() + "_" + this._tempCounter.save++;
805
791
  this.writeSaveData(saveFile, saveData);
806
792
  await this.watchLogChunk(
@@ -825,15 +811,24 @@ class Omegga extends wrapper.default {
825
811
  correctPalette = false,
826
812
  correctCustom = false
827
813
  } = {}) {
828
- if (this.#warnRemoved(
814
+ player = typeof player === "string" ? this.getPlayer(player) : player;
815
+ if (!player) return;
816
+ if (brsRemoved(this.version)) {
817
+ const { ref, file: file2 } = writeTempPrefab(this, saveData);
818
+ this.givePrefabToPlayer(ref, player);
819
+ setTimeout(() => {
820
+ if (fs.existsSync(file2)) fs.unlinkSync(file2);
821
+ }, 5e3);
822
+ return;
823
+ }
824
+ if (warnRemoved(
825
+ this.version,
829
826
  "loadSaveDataOnPlayer",
830
827
  commands.EA2_VERSION,
831
828
  "EA2",
832
829
  "loadPrefabOnPlayer"
833
830
  ))
834
831
  return;
835
- player = typeof player === "string" ? this.getPlayer(player) : player;
836
- if (!player) return;
837
832
  const saveFile = this._tempSavePrefix + Date.now() + "_" + this._tempCounter.save++;
838
833
  this.writeSaveData(saveFile, saveData);
839
834
  await this.watchLogChunk(
@@ -852,8 +847,54 @@ class Omegga extends wrapper.default {
852
847
  }
853
848
  }
854
849
  async getSaveData(region) {
855
- if (this.#warnRemoved("getSaveData", commands.PREFAB_VERSION, "EA3", "the prefab API"))
856
- return void 0;
850
+ if (brsRemoved(this.version)) {
851
+ const ref = this._tempSavePrefix + Date.now() + "_" + this._tempCounter.save++;
852
+ const file2 = await this.savePrefabAsync(ref, { region });
853
+ if (!file2) return void 0;
854
+ try {
855
+ const MAIN_GRID = 1;
856
+ const reader = brs.WorldReader.from(new Uint8Array(fs.readFileSync(file2)));
857
+ const bricks = [...reader.bricks(MAIN_GRID)].map((b) => ({
858
+ ...b,
859
+ physical_index: 0,
860
+ components: {}
861
+ }));
862
+ let brickOffset = 0;
863
+ for (const chunk of reader.brickChunkIndex(MAIN_GRID)) {
864
+ if (chunk.numComponents > 0) {
865
+ const { components } = reader.componentChunk(
866
+ MAIN_GRID,
867
+ chunk.index
868
+ );
869
+ for (const c of components) {
870
+ const brick = bricks[brickOffset + c.brickIndex];
871
+ if (brick) brick.components[c.typeName] = c.data ?? {};
872
+ }
873
+ }
874
+ brickOffset += chunk.numBricks;
875
+ }
876
+ return {
877
+ version: 10,
878
+ map: this.currentMap ?? "Unknown",
879
+ author: { id: this.host?.id ?? "", name: this.host?.name ?? "" },
880
+ host: { id: this.host?.id ?? "", name: this.host?.name ?? "" },
881
+ description: "",
882
+ brick_count: bricks.length,
883
+ mods: [],
884
+ brick_assets: reader.brickAssets(),
885
+ colors: [],
886
+ materials: reader.materials(),
887
+ physical_materials: [],
888
+ brick_owners: reader.brickOwners(),
889
+ game_version: this.version,
890
+ save_time: new Uint8Array(),
891
+ bricks,
892
+ components: {}
893
+ };
894
+ } finally {
895
+ if (fs.existsSync(file2)) fs.unlinkSync(file2);
896
+ }
897
+ }
857
898
  const saveFile = this._tempSavePrefix + Date.now() + "_" + this._tempCounter.save++;
858
899
  await this.saveBricksAsync(saveFile, region);
859
900
  const savePath = this.getSavePath(saveFile);
@@ -881,8 +922,9 @@ class Omegga extends wrapper.default {
881
922
  overrideUserId = ""
882
923
  } = {}) {
883
924
  if (!path2) return;
925
+ const rootPart = rootEntityPersistentIndex >= 0 ? ` ${rootEntityPersistentIndex} ${mirrorAxes}${overrideUserId ? ` "${overrideUserId}"` : ""}` : "";
884
926
  this.writeln(
885
- `${this.Console.Prefab.Load} "${path2}" ${offX} ${offY} ${offZ} ${atOriginalPosition ? 1 : 0} ${orientation} ${rootEntityPersistentIndex} ${mirrorAxes}${overrideUserId ? ` "${overrideUserId}"` : ""}`
927
+ `${this.Console.Prefab.Load} "${path2}" ${offX} ${offY} ${offZ} ${atOriginalPosition ? 1 : 0} ${orientation}${rootPart}`
886
928
  );
887
929
  }
888
930
  /**