omegga 1.9.0 → 1.9.2

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,20 @@
4
4
 
5
5
  - Future features go here
6
6
 
7
+ ## 1.9.2 - 2026-07-12
8
+
9
+ Fix a matcher error when a player leaves before an unknown-command reply can be sent.
10
+
11
+ - Fix `TypeError: Cannot read properties of undefined (reading 'name')` in the command matcher when whispering the "missing command" message to a player who already left
12
+ - Plugins: `whisper`/`middlePrint` now no-op instead of throwing when the target player can't be resolved
13
+
14
+ ## 1.9.1 - 2026-07-07
15
+
16
+ Ban-history crash fix, web-ui ban/kick modal polish, and a brs-js bump.
17
+
18
+ - Database: Fix server crash when syncing bans
19
+ - Update brs-js to 3.2.4
20
+
7
21
  ## 1.9.0 - 2026-07-05
8
22
 
9
23
  Wire graph log events and a brs-js 3.0.0 upgrade for native brdb/brz reading.
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
@@ -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;;;"}
@@ -334,6 +334,7 @@ class Omegga extends wrapper.default {
334
334
  }
335
335
  whisper(target, ...messages) {
336
336
  if (typeof target !== "object") target = this.getPlayer(target);
337
+ if (!target) return;
337
338
  messages.flatMap((m) => m.toString().split("\n")).filter((m) => m.length < 512).forEach(
338
339
  (m) => this.writeln(
339
340
  `${this.Console.Chat.Whisper} "${target.name}" ${m}`
@@ -342,6 +343,7 @@ class Omegga extends wrapper.default {
342
343
  }
343
344
  middlePrint(target, message) {
344
345
  if (typeof target !== "object") target = this.getPlayer(target);
346
+ if (!target) return;
345
347
  if (message.length > 512) return;
346
348
  this.writeln(
347
349
  `${this.Console.Chat.StatusMessage} "${target.name}" ${message}`
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sources":["../../src/omegga/server.ts"],"sourcesContent":["import Logger from '@/logger';\nimport { OmeggaLike, OmeggaPlayer, PluginInterop } from '@/plugin';\nimport {\n BRICKADIA_AUTH_FILES,\n CONFIG_AUTH_DIR,\n CONFIG_HOME,\n CONFIG_SAVED_DIR,\n DATA_PATH,\n} from '@/softconfig';\nimport { VERSION } from '@/version';\nimport { EnvironmentPreset } from '@brickadia/presets';\nimport {\n BRBanList,\n BRPlayerNameCache,\n BRRoleAssignments,\n BRRoleSetup,\n} from '@brickadia/types';\nimport { IConfig } from '@config/types';\nimport { map as mapUtils, pattern, uuid } from '@util';\nimport { readBrdbRevisions } from '@util/brdb';\nimport { copyFiles, mkdir, readWatchedJSON } from '@util/file';\nimport Webserver from '@webserver/backend';\nimport brs, {\n WorldReader,\n writeBrzLegacy,\n type ReadSaveObject,\n type WriteSaveObject,\n} from 'brs-js';\nimport 'colors';\nimport glob from 'glob';\nimport { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';\nimport { basename, join } from 'path';\nimport { AutoRestartConfig } from '..';\nimport commandInjector from './commandInjector';\nimport {\n ConsoleCommands,\n EA2_VERSION,\n PREFAB_VERSION,\n resolveConsoleCommands,\n} from './commands';\nimport MATCHERS from './matchers';\nimport Player from './player';\nimport { PluginLoader } from './plugin';\nimport {\n IGamemode,\n ILogMinigame,\n IMinigameList,\n IOmeggaOptions,\n IPlayerPositions,\n IServerStatus,\n} from './types';\nimport OmeggaWrapper from './wrapper';\n\nconst MISSING_CMD =\n '\"Command not found. Type <color=\\\\\"ffff00\\\\\">/help</> for a list of commands or <color=\\\\\"ffff00\\\\\">/plugins</> for plugin information.\"';\n\n// Prefab.SaveRegion requires a region; when saving the whole world we pass a\n// maximal extent centered on the origin to capture everything.\nconst WHOLE_WORLD_EXTENT = 1_000_000_000;\n\n// These helpers are module-level (not class methods) on purpose: safe plugins\n// call these methods through a ProxyOmegga whose prototype steals Omegga's\n// implementations (see injectOmeggaPrototypes). A `#private` method would fail\n// the brand check when `this` is a ProxyOmegga (\"Receiver must be an instance\n// of class Omegga\"), so anything a stolen method depends on must not be\n// `#private`. Module functions keep working regardless of the receiver.\n\n/**\n * Whether the running game version has removed the legacy .brs bricks console\n * commands (Bricks.Save/Load/ClearAll/ClearRegion, World.LoadAdditive) in\n * favor of the prefab (`br.Prefab.*`) and world (`br.World.Clear*`) commands.\n * Removed at Brickadia CL{@link PREFAB_VERSION}.\n */\nfunction brsRemoved(version: number): boolean {\n return version > 0 && version >= PREFAB_VERSION;\n}\n\n/**\n * Warn that a method backed by a removed console command is a no-op on the\n * running game version, and return whether the caller should bail.\n * @param version the running game version\n * @param method the omegga method being called (for the message)\n * @param since CL version the underlying command was removed at\n * @param release human release name for that version (e.g. `EA2`, `EA3`)\n * @param replacement suggested replacement method/API\n */\nfunction warnRemoved(\n version: number,\n method: string,\n since: number,\n release: string,\n replacement: string,\n): boolean {\n if (!(version > 0 && version >= since)) return false;\n Logger.warnp(\n `omegga.${method}() uses a console command removed in Brickadia ` +\n `${release}. This call has no effect - use ${replacement} instead.`,\n );\n return true;\n}\n\n/**\n * Write a save to a temporary `.brz` prefab in the prefab directory (EA3).\n * @returns the bare path ref for `br.Prefab.*` commands (no `Prefabs/` prefix\n * or `.brz` extension) and the absolute file path for cleanup.\n */\nfunction writeTempPrefab(\n omegga: {\n prefabPath: string;\n _tempSavePrefix: string;\n _tempCounter: { save: number };\n },\n saveData: WriteSaveObject,\n): { ref: string; file: string } {\n const ref =\n omegga._tempSavePrefix + Date.now() + '_' + omegga._tempCounter.save++;\n const file = join(omegga.prefabPath, ref + '.brz');\n if (!file.startsWith(omegga.prefabPath))\n throw 'prefab file not in Saved/Prefabs directory';\n mkdir(omegga.prefabPath);\n writeFileSync(file, new Uint8Array(writeBrzLegacy(saveData)));\n return { ref, file };\n}\n\n// TODO: safe broadcast parsing\n\nexport default class Omegga extends OmeggaWrapper implements OmeggaLike {\n /** The save counter prevents omegga from saving over the same file */\n _tempCounter = { save: 0, environment: 0 };\n /** The save prefix is prepended to all temporary saves */\n _tempSavePrefix = 'omegga_temp_';\n\n // pluginloader is not private so plugins can potentially add more formats\n pluginLoader: PluginLoader = undefined;\n webserver: Webserver;\n\n verbose: boolean;\n savePath: string;\n worldPath: string;\n prefabPath: string;\n presetPath: string;\n configPath: string;\n options: IOmeggaOptions;\n\n version: number;\n\n /** memoized version-resolved console commands ({@link Console}) */\n #console: { version: number; commands: ConsoleCommands };\n\n /**\n * version-resolved Brickadia console command names, nested by namespace.\n * e.g. `omegga.Console.Bricks.Clear` -> \"Bricks.Clear\" or \"br.Bricks.Clear\"\n * depending on the running game version.\n */\n get Console(): ConsoleCommands {\n if (this.#console?.version !== this.version)\n this.#console = {\n version: this.version,\n commands: resolveConsoleCommands(this.version),\n };\n return this.#console.commands;\n }\n\n host?: { id: string; name: string };\n players: OmeggaPlayer[];\n\n started = false;\n starting = false;\n stopping = false;\n crashDetected = false;\n currentMap: string;\n\n getServerStatus: () => Promise<IServerStatus>;\n listMinigames: () => Promise<IMinigameList>;\n getAllPlayerPositions: () => Promise<IPlayerPositions>;\n getMinigames: () => Promise<ILogMinigame[]>;\n getGamemode: () => Promise<IGamemode | null>;\n\n /**\n * Omegga instance\n */\n constructor(serverPath: string, cfg: IConfig, options: IOmeggaOptions = {}) {\n super(serverPath, cfg);\n this.verbose = Logger.VERBOSE;\n\n Logger.verbose('Running omegga', `v${VERSION}`.green);\n Logger.verbose('Versions', process.versions);\n Logger.verbose('Config', {\n ...cfg,\n credentials: cfg.credentials\n ? Object.fromEntries(\n Object.entries(cfg.credentials).map(([k, v]) => [k, v ? '***' : v]),\n )\n : cfg.credentials,\n server: {\n ...cfg.server,\n ...(cfg.server.password && { password: '***' }),\n ...(cfg.server.steambetaPassword && { steambetaPassword: '***' }),\n ...(cfg.server.launchArgs && {\n launchArgs: cfg.server.launchArgs\n .replace(/-Cookie=\".*?\"/g, '-Cookie=\"<hidden>\"')\n .replace(/-Cookie=\\S+/g, '-Cookie=<hidden>'),\n }),\n },\n });\n\n // inject commands\n Logger.verbose('Setting up command injector');\n commandInjector(this, this.logWrangler);\n\n // launch options (disabling webserver)\n this.options = options;\n const savedDir = cfg.server.savedDir ?? CONFIG_SAVED_DIR;\n\n // path to save files\n this.savePath = join(this.path, DATA_PATH, savedDir, 'Builds');\n this.worldPath = join(this.path, DATA_PATH, savedDir, 'Worlds');\n this.prefabPath = join(this.path, DATA_PATH, savedDir, 'Prefabs');\n\n this.presetPath = join(this.path, DATA_PATH, savedDir, 'Presets');\n\n // path to config files\n this.configPath = join(this.path, DATA_PATH, savedDir, 'Server');\n\n // create dir folders\n Logger.verbose('Creating directories');\n mkdir(this.savePath);\n mkdir(this.configPath);\n\n // ignore auth file copy\n if (!options.noauth) {\n Logger.verbose('Copying auth files');\n this.copyAuthFiles();\n }\n\n // create the webserver if it's enabled\n // the web interface provides access to server information while the server is running\n // and lets you view chat logs, disable plugins, etc\n if (!options.noweb) {\n Logger.verbose('Creating webserver');\n this.webserver = new Webserver(cfg.omegga, this);\n }\n\n if (!options.noplugin) {\n Logger.verbose('Creating plugin loader');\n this.pluginLoader = new PluginLoader(this.path, this);\n }\n\n /** @type {Array<Player>}list of online players */\n this.players = [];\n\n /** host player info `{id: uuid, name: player name}` */\n this.host = undefined;\n\n /** @type {String} current game version - may later be turned into CL#### versions */\n this.version = -1;\n\n /** @type {Boolean} whether server has started */\n this.started = false;\n /** @type {Boolean} whether server is starting up */\n this.starting = false;\n\n /** @type {String} current map */\n this.currentMap = '';\n\n // add all the matchers to the server\n Logger.verbose('Adding matchers');\n for (const matcher of MATCHERS) {\n const { pattern, callback } = matcher(this);\n this.addMatcher(pattern, callback);\n }\n\n process.on('uncaughtException', async err => {\n Logger.verbose('Uncaught exception', err);\n this.emit('error', err);\n\n // publish stop to database\n this.webserver?.database?.addChatLog('server', {}, 'Server error');\n\n try {\n await this.stop();\n } catch (e) {\n Logger.error(e);\n }\n process.exit();\n });\n\n // when brickadia starts, mark the server as started\n this.on('start', ({ map }) => {\n this.started = true;\n this.starting = false;\n this.currentMap = map;\n this.writeln(`${this.Console.Chat.MessageForUnknownCommands} 0`);\n\n this.restoreServer();\n });\n\n // detect engine crash from stderr or stdout\n const crashHandler = (line: string) => {\n if (\n !this.crashDetected &&\n (/Engine crash handling finished; re-raising signal \\d+ for the default handler\\. Good bye\\./.test(\n line,\n ) ||\n /LogCore: === Critical error: ===/.test(line))\n ) {\n Logger.error('Engine crash detected!');\n this.crashDetected = true;\n }\n };\n this.on('err', crashHandler);\n this.on('line', crashHandler);\n\n // when brickadia exits, stop omegga\n this.on('exit', () => {\n this.stop();\n });\n\n // when the process closes, emit the exit signal and stop\n this.on('closed', () => {\n // capture crash state before 'exit' handler triggers stop()\n const wasCrash = this.crashDetected;\n this.crashDetected = false;\n if (this.started) this.emit('exit');\n const doRestart = async () => {\n if (!wasCrash) return;\n try {\n const config = await this.webserver?.database?.getAutoRestartConfig();\n if (config?.crashRestartEnabled) {\n Logger.logp('Restarting server after crash...');\n this.webserver?.database?.addChatLog(\n 'server',\n {},\n 'Server crashed, restarting...',\n );\n await this.start();\n }\n } catch (err) {\n Logger.error('Error restarting after crash', err);\n }\n };\n if (!this.stopping) {\n this.stop().then(doRestart);\n } else {\n // stop() already in progress from 'exit' handler - wait for it to finish\n this.once('server:stopped', () => doRestart());\n }\n });\n\n // detect when the game reports a command does not exist\n this.on('unknownCommand', (name: string, cmd: string) => {\n // if it's not registered to a plugin, send the missing command message\n if (!this.pluginLoader || !this.pluginLoader.isCommand(cmd)) {\n this.whisper(name, MISSING_CMD);\n }\n });\n }\n\n /** attempt to save server state */\n async saveServer(config: AutoRestartConfig) {\n if (config.players && this.players.length > 0) {\n Logger.logp('Getting player positions...');\n const players = await this.getAllPlayerPositions();\n Logger.logp(`Saving ${players.length} player positions...`);\n const data = players\n .filter(p => !p.isDead && p.pos)\n .map(p => ({ position: p.pos, id: p.player.id }));\n if (players.length > 0)\n writeFileSync(\n join(this.path, DATA_PATH, 'omegga_temp_players.json'),\n JSON.stringify(data),\n );\n }\n\n if (config.saveWorld) {\n Logger.logp('Saving world...');\n await this.saveWorld();\n }\n }\n\n async restartServer() {\n if (this.starting || this.stopping) return;\n if (!this.started) return await this.start();\n\n const nextWorld = this.getNextWorld();\n if (nextWorld) {\n Logger.logp('Loading world', nextWorld.file.yellow);\n Logger.verbose('Next world configured from', nextWorld.source.yellow);\n this.loadWorld(nextWorld.file);\n } else {\n this.changeMap(this.currentMap);\n }\n\n const res = await Promise.race([\n // wait for the map to change\n new Promise(resolve =>\n this.once('mapchange', () => resolve('mapchange')),\n ),\n // Timeout after 10 seconds\n new Promise(resolve => setTimeout(() => resolve('timeout'), 10000)),\n ]);\n Logger.verbose('Restart result:', res);\n }\n\n /** attempt to restore the server's state */\n async restoreServer() {\n const tempPlayersFile = join(\n this.path,\n DATA_PATH,\n 'omegga_temp_players.json',\n );\n if (!existsSync(tempPlayersFile)) return;\n\n try {\n Logger.logp('Loading previous player positions...');\n\n // player positions are an array to address multi-clienting\n const players: { position: number[]; id: string }[] = JSON.parse(\n readFileSync(tempPlayersFile).toString(),\n );\n\n // restore player position on join\n const callback = (player: OmeggaPlayer) => {\n const index = players.findIndex(p => p.id === player.id);\n if (index > -1) {\n const { position } = players[index];\n this.writeln(\n `${this.Console.Chat.Command} /TP \"${player.name}\" ${position.join(' ')} 0`,\n );\n\n // remove the entry\n players[index] = players[players.length - 1];\n players.pop();\n }\n };\n this.on('join', callback);\n\n let timeout = setTimeout(() => {\n try {\n this.off('join', callback);\n if (existsSync(tempPlayersFile)) unlinkSync(tempPlayersFile);\n } catch (err) {\n Logger.error('Error removing omegga_temp_players.json', err);\n }\n }, 10000);\n this.once('changemap', () => {\n clearTimeout(timeout);\n this.off('join', callback);\n });\n } catch (err) {\n Logger.error('Error restoring previous server state', err);\n }\n }\n\n /**\n * start webserver, load plugins, start the brickadia server\n * this should not be called by a plugin\n */\n //\n async start(): Promise<any> {\n this.starting = true;\n if (this.webserver) await this.webserver.start();\n if (this.pluginLoader) {\n // scan for plugins\n Logger.verbose('Scanning for plugins');\n await this.pluginLoader.scan();\n\n // load the plugins\n Logger.verbose('Loading plugins');\n await this.pluginLoader.reload();\n }\n\n Logger.verbose('Starting Brickadia');\n super.start();\n this.emit('server:starting');\n }\n\n /**\n * unload plugins and stop the server\n * this should not be called by a plugin\n */\n async stop() {\n if (!this.started && !this.starting) {\n Logger.verbose(\"Stop called while server wasn't started or was starting\");\n return;\n }\n\n if (this.stopping) {\n Logger.verbose('Stop called while server was starting');\n return;\n }\n\n this.stopping = true;\n this.emit('server:stopping');\n if (this.pluginLoader) {\n Logger.verbose('Unloading plugins');\n await this.pluginLoader.unload();\n }\n Logger.verbose('Stopping server');\n super.stop();\n\n const res = await Promise.race([\n new Promise(resolve => this.once('exit', () => resolve('exit'))),\n // Timeout after 10 seconds\n new Promise(resolve => setTimeout(() => resolve('timeout'), 10000)),\n ]);\n\n Logger.verbose('Stop result:', res);\n if (this.stopping) this.emit('server:stopped');\n this.stopping = false;\n this.started = false;\n this.starting = false;\n this.players = [];\n }\n\n /**\n * Copies auth files from home config dir\n * this should never be called by a plugin\n */\n copyAuthFiles() {\n const authDir = this.config.server.authDir ?? CONFIG_AUTH_DIR;\n const savedDir = this.config.server.savedDir ?? CONFIG_SAVED_DIR;\n const authPath = join(this.path, DATA_PATH, savedDir, authDir);\n const homeAuthPath = join(\n CONFIG_HOME,\n (savedDir !== CONFIG_SAVED_DIR ? savedDir : '') + authDir,\n );\n\n copyFiles(homeAuthPath, authPath, BRICKADIA_AUTH_FILES);\n }\n\n // TODO: split messages that longer than 512 characters\n // TODO: delete characters that are known to crash the game\n broadcast(...messages: string[]) {\n messages\n .flatMap(m => m.toString().split('\\n'))\n .filter(m => m.length < 512)\n .forEach(m => this.writeln(`${this.Console.Chat.Broadcast} ${m}`));\n }\n\n whisper(target: string | OmeggaPlayer, ...messages: string[]) {\n // find the target player\n if (typeof target !== 'object') target = this.getPlayer(target);\n\n // whisper the messages to that player\n messages\n .flatMap(m => m.toString().split('\\n'))\n .filter(m => m.length < 512)\n .forEach(m =>\n this.writeln(\n `${this.Console.Chat.Whisper} \"${(target as { name: string }).name}\" ${m}`,\n ),\n );\n }\n\n middlePrint(target: string | OmeggaPlayer, message: string) {\n // find the target player\n if (typeof target !== 'object') target = this.getPlayer(target);\n\n // whisper the messages to that player\n if (message.length > 512) return;\n this.writeln(\n `${this.Console.Chat.StatusMessage} \"${(target as { name: string }).name}\" ${message}`,\n );\n }\n\n getPlayers(): {\n id: string;\n name: string;\n displayName: string;\n controller: string;\n state: string;\n }[] {\n return this.players.map(p => ({ ...p }));\n }\n\n getRoleSetup(): BRRoleSetup {\n // Read RoleSetup2, fallback to old RoleSetup if it doesn't exist\n return (readWatchedJSON(join(this.configPath, 'RoleSetup2.json')) ??\n readWatchedJSON(join(this.configPath, 'RoleSetup.json'))) as BRRoleSetup;\n }\n\n getRoleAssignments(): BRRoleAssignments {\n return readWatchedJSON(\n join(this.configPath, 'RoleAssignments.json'),\n ) as BRRoleAssignments;\n }\n\n getBanList(): BRBanList {\n return readWatchedJSON(join(this.configPath, 'BanList.json')) as BRBanList;\n }\n\n getNameCache(): BRPlayerNameCache {\n return readWatchedJSON(\n join(this.configPath, 'PlayerNameCache.json'),\n ) as BRPlayerNameCache;\n }\n\n getPlayer(target: string): OmeggaPlayer {\n return this.players.find(\n p =>\n p.name === target ||\n p.id === target ||\n p.controller === target ||\n p.state === target,\n );\n }\n\n findPlayerByName(name: string): OmeggaPlayer {\n name = name.toLowerCase();\n const exploded = pattern.explode(name);\n return (\n this.players.find(p => p.name === name || p.displayName === name) || // find by exact match\n this.players.find(\n p => p.name.indexOf(name) > -1 || p.displayName.indexOf(name) > -1,\n ) || // find by rough match\n this.players.find(\n p => p.name.match(exploded) || p.displayName.match(exploded),\n ) // find by exploded regex match (ck finds cake, tbp finds TheBlackParrot)\n );\n }\n\n getHostId(): string {\n return this.host?.id ?? '';\n }\n\n saveMinigame(index: number, name: string) {\n this.writeln(\n `${this.Console.Server.Minigames.SavePreset} ${index} \"${name}\"`,\n );\n }\n\n deleteMinigame(index: number) {\n this.writeln(`${this.Console.Server.Minigames.Delete} ${index}`);\n }\n\n resetMinigame(index: number) {\n this.writeln(`${this.Console.Server.Minigames.Reset} ${index}`);\n }\n\n nextRoundMinigame(index: number) {\n this.writeln(`${this.Console.Server.Minigames.NextRound} ${index}`);\n }\n\n loadMinigame(presetName: string, owner = '') {\n this.writeln(\n `${this.Console.Server.Minigames.LoadPreset} \"${presetName}\" ${owner ? `\"${owner}\"` : ''}`,\n );\n }\n\n getMinigamePresets(): string[] {\n const presetPath = join(this.presetPath, 'Minigame');\n return existsSync(presetPath)\n ? glob\n .sync(presetPath + '/**/*.bp')\n .map(f => basename(f).replace(/\\.bp$/, ''))\n : [];\n }\n\n resetEnvironment() {\n this.writeln(`${this.Console.Server.Environment.Reset}`);\n }\n\n async saveEnvironment(presetName: string): Promise<void> {\n await this.addWatcher(/Environment preset saved.$/, {\n // request the pawn for this player's controller (should only be one)\n exec: () =>\n this.writeln(\n `${this.Console.Server.Environment.SavePreset} \"${presetName}\"`,\n ),\n timeoutDelay: 100,\n });\n }\n\n async getEnvironmentData(): Promise<EnvironmentPreset> {\n const saveName =\n this._tempSavePrefix + Date.now() + '_' + this._tempCounter.environment++;\n\n await this.saveEnvironment(saveName);\n const data = this.readEnvironmentData(saveName);\n const file = join(this.presetPath, 'Environment', saveName + '.bp');\n if (existsSync(file)) unlinkSync(file);\n\n return data;\n }\n\n readEnvironmentData(saveName: string): EnvironmentPreset {\n if (typeof saveName !== 'string')\n throw 'expected name argument for readEnvironmentData';\n\n const file = join(this.presetPath, 'Environment', saveName + '.bp');\n try {\n if (existsSync(file)) return JSON.parse(readFileSync(file).toString());\n } catch (err) {\n Logger.verbose('Error parsing save data in readEnvironmentData', err);\n }\n return null;\n }\n\n loadEnvironment(presetName: string) {\n this.writeln(`${this.Console.Server.Environment.LoadPreset} ${presetName}`);\n }\n\n loadEnvironmentData(\n preset: EnvironmentPreset | EnvironmentPreset['data']['groups'],\n ) {\n if ('data' in preset) preset = preset.data.groups;\n\n const saveFile =\n this._tempSavePrefix + Date.now() + '_' + this._tempCounter.environment++;\n\n const path = join(this.presetPath, 'Environment', saveFile + '.bp');\n\n writeFileSync(\n path,\n JSON.stringify({\n formatVersion: '1',\n presetVersion: '1',\n type: 'Environment',\n data: {\n groups: {\n ...preset,\n },\n },\n }),\n );\n\n this.loadEnvironment(saveFile);\n\n // this is lazy, but environments should load much faster than builds\n // do, so it's not really worth keeping track of logs for this\n setTimeout(() => unlinkSync(path), 5000);\n }\n\n getEnvironmentPresets(): string[] {\n const presetPath = join(this.presetPath, 'Environment');\n return existsSync(presetPath)\n ? glob\n .sync(presetPath + '/**/*.bp')\n .map(f => basename(f).replace(/\\.bp$/, ''))\n : [];\n }\n\n clearBricks(target: string | { id: string }, quiet = false) {\n // target is a player object, just use that id\n if (typeof target === 'object' && target.id) target = target.id;\n // if the target isn't a uuid already, find the player by name or controller and use that uuid\n else if (typeof target === 'string' && !uuid.match(target)) {\n // only set the target if the player exists\n const player = this.getPlayer(target);\n target = player && player.id;\n }\n\n if (!target) return;\n\n this.writeln(`${this.Console.Bricks.Clear} ${target} ${quiet ? 1 : ''}`);\n }\n\n clearRegion(\n region: {\n center: [number, number, number];\n extent: [number, number, number];\n },\n options?: {\n target?: string | OmeggaPlayer;\n /** clear bricks in the region (default true) */\n bricks?: boolean;\n /** also clear entities in the region (default false, EA3 only) */\n entities?: boolean;\n },\n ) {\n // resolve the optional owner filter (player object, uuid, or name) to a uuid\n let target = '';\n const rawTarget = options?.target;\n if (rawTarget) {\n if (typeof rawTarget === 'object') target = rawTarget.id;\n else if (uuid.match(rawTarget)) target = rawTarget;\n else target = this.getPlayer(rawTarget)?.id ?? '';\n }\n\n const center = region.center.join(' ');\n const extent = region.extent.join(' ');\n\n if (brsRemoved(this.version)) {\n // br.World.ClearRegion <Center> <Extent> [ClearBricks] [ClearEntities] [FilterUserId]\n const bricks = (options?.bricks ?? true) ? 1 : 0;\n const entities = (options?.entities ?? false) ? 1 : 0;\n this.writeln(\n `${this.Console.World.ClearRegion} ${center} ${extent} ${bricks} ${entities}${\n target ? ' ' + target : ''\n }`,\n );\n return;\n }\n\n // legacy .brs region clear (bricks only)\n this.writeln(\n `${this.Console.Bricks.ClearRegion} ${center} ${extent}${\n target ? ' ' + target : ''\n }`,\n );\n }\n\n clearAllBricks(\n options:\n | boolean\n | { quiet?: boolean; bricks?: boolean; entities?: boolean } = {},\n ) {\n // backwards compat: a bare boolean is the legacy `quiet` argument\n const {\n quiet = false,\n bricks = true,\n entities = false,\n } = typeof options === 'boolean' ? { quiet: options } : options;\n\n if (brsRemoved(this.version)) {\n // br.World.ClearAll [ClearBricks] [ClearEntities] [Silent]\n this.writeln(\n `${this.Console.World.ClearAll} ${bricks ? 1 : 0} ${\n entities ? 1 : 0\n } ${quiet ? 1 : 0}`,\n );\n return;\n }\n // legacy Bricks.ClearAll only ever cleared bricks\n this.writeln(`${this.Console.Bricks.ClearAll} ${quiet ? 1 : ''}`);\n }\n\n saveBricks(\n saveName: string,\n region?: {\n center: [number, number, number];\n extent: [number, number, number];\n },\n ) {\n if (\n warnRemoved(\n this.version,\n 'saveBricks',\n PREFAB_VERSION,\n 'EA3',\n 'savePrefab',\n )\n )\n return;\n if (!saveName) return;\n\n // add quotes around the filename if it doesn't have them (backwards compat w/ plugins)\n if (!(saveName.startsWith('\"') && saveName.endsWith('\"')))\n saveName = `\"${saveName}\"`;\n\n if (region?.center && region?.extent)\n this.writeln(\n `${this.Console.Bricks.SaveRegion} ${saveName} ${region.center.join(\n ' ',\n )} ${region.extent.join(' ')}`,\n );\n else this.writeln(`${this.Console.Bricks.Save} ${saveName}`);\n }\n\n async saveBricksAsync(\n saveName: string,\n region?: {\n center: [number, number, number];\n extent: [number, number, number];\n },\n ): Promise<void> {\n if (\n warnRemoved(\n this.version,\n 'saveBricksAsync',\n PREFAB_VERSION,\n 'EA3',\n 'savePrefabAsync',\n )\n )\n return;\n if (!saveName) return;\n\n let saveNameClean = saveName;\n // add quotes around the filename if it doesn't have them (backwards compat w/ plugins)\n if (!(saveName.startsWith('\"') && saveName.endsWith('\"')))\n saveNameClean = `\"${saveName}\"`;\n\n const command =\n region?.center && region?.extent\n ? `${this.Console.Bricks.SaveRegion} ${saveNameClean} ${region.center.join(\n ' ',\n )} ${region.extent.join(' ')}`\n : `${this.Console.Bricks.Save} ${saveNameClean}`;\n\n // wait for the server to save the file\n await this.watchLogChunk(command, /^(LogBrickSerializer|LogTemp): (.+)$/, {\n first: match => match[0].endsWith(saveName + '.brs...'),\n last: match =>\n Boolean(\n match[2].match(\n /Saved .+ bricks and .+ components from .+ owners|Error: No bricks in grid!|Error: No bricks selected to save!/,\n ),\n ),\n afterMatchDelay: 0,\n timeoutDelay: 30000,\n });\n }\n\n loadBricks(\n saveName: string,\n {\n offX = 0,\n offY = 0,\n offZ = 0,\n quiet = false,\n correctPalette = false,\n correctCustom = false,\n } = {},\n ) {\n if (\n warnRemoved(\n this.version,\n 'loadBricks',\n PREFAB_VERSION,\n 'EA3',\n 'loadPrefab',\n )\n )\n return;\n // add quotes around the filename if it doesn't have them (backwards compat w/ plugins)\n if (!(saveName.startsWith('\"') && saveName.endsWith('\"')))\n saveName = `\"${saveName}\"`;\n\n this.writeln(\n `${this.Console.Bricks.Load} ${saveName} ${offX} ${offY} ${offZ} ${\n quiet ? 1 : 0\n } ${correctPalette ? 1 : 0} ${correctCustom ? 1 : 0}`,\n );\n }\n\n loadBricksOnPlayer(\n saveName: string,\n player: string | OmeggaPlayer,\n {\n offX = 0,\n offY = 0,\n offZ = 0,\n correctPalette = false,\n correctCustom = false,\n } = {},\n ) {\n if (\n warnRemoved(\n this.version,\n 'loadBricksOnPlayer',\n EA2_VERSION,\n 'EA2',\n 'loadPrefabOnPlayer',\n )\n )\n return;\n player = typeof player === 'string' ? this.getPlayer(player) : player;\n if (!player) return;\n\n // add quotes around the filename if it doesn't have them (backwards compat w/ plugins)\n if (!(saveName.startsWith('\"') && saveName.endsWith('\"')))\n saveName = `\"${saveName}\"`;\n\n this.writeln(\n `${this.Console.Bricks.LoadTemplate} ${saveName} ${offX} ${offY} ${offZ} ${\n correctPalette ? 1 : 0\n } ${correctCustom ? 1 : 0} \"${player.name}\"`,\n );\n }\n\n getSaves(): string[] {\n return existsSync(this.savePath)\n ? glob.sync(this.savePath + '/**/*.brs')\n : [];\n }\n\n getWorlds(): string[] {\n return existsSync(this.worldPath)\n ? glob.sync(this.worldPath + '/**/*.brdb')\n : [];\n }\n\n getPrefabs(): string[] {\n return existsSync(this.prefabPath)\n ? glob.sync(this.prefabPath + '/**/*.brz')\n : [];\n }\n\n getSavePath(saveName: string) {\n const file = join(\n this.savePath,\n saveName.endsWith('.brs') ? saveName : saveName + '.brs',\n );\n return existsSync(file) ? file : undefined;\n }\n\n getWorldPath(worldName: string) {\n const file = join(\n this.worldPath,\n worldName.endsWith('.brdb') ? worldName : worldName + '.brdb',\n );\n return existsSync(file) ? file : undefined;\n }\n\n async getWorldRevisions(worldName: string) {\n worldName = worldName.replace(/\\.brdb$/i, '');\n\n const path = this.getWorldPath(worldName);\n if (!worldName || !path) {\n throw new Error(`World \"${worldName}\" does not exist`);\n }\n\n // Revisions are read directly from the .brdb bundle (SQLite), so this no\n // longer requires the server to be running or a console round-trip. The\n // brdb revision indices/notes match the game's World.ListRevisions output.\n return readBrdbRevisions(path) ?? [];\n }\n\n async loadWorld(worldName: string): Promise<boolean> {\n worldName = worldName.replace(/\\.brdb$/i, '');\n if (!worldName || !this.getWorldPath(worldName)) return false;\n this.writeln(`${this.Console.World.Load} \"${worldName}\"`);\n const res = await Promise.race([\n // wait for the map to change\n new Promise(resolve =>\n this.once('mapchange', () => resolve('mapchange')),\n ),\n // Timeout after 10 seconds\n new Promise(resolve => setTimeout(() => resolve('timeout'), 10000)),\n ]);\n Logger.verbose('LoadWorld', worldName, 'result:', res);\n return res === 'mapchange';\n }\n\n async loadWorldRevision(\n worldName: string,\n revision: number,\n ): Promise<boolean> {\n worldName = worldName.replace(/\\.brdb$/i, '');\n if (!worldName || !this.getWorldPath(worldName)) return false;\n if (typeof revision !== 'number' || revision < 1) {\n throw new Error(`Invalid revision number: ${revision}`);\n }\n this.writeln(\n `${this.Console.World.LoadRevision} \"${worldName}\" ${revision}`,\n );\n const res = await Promise.race([\n // wait for the map to change\n new Promise(resolve =>\n this.once('mapchange', () => resolve('mapchange')),\n ),\n // Timeout after 10 seconds\n new Promise(resolve => setTimeout(() => resolve('timeout'), 10000)),\n ]);\n Logger.verbose('LoadWorld', worldName, 'result:', res);\n return res === 'mapchange';\n }\n\n async saveWorldAs(worldName: string) {\n if (!worldName) return false;\n if (this.stopping || this.starting || !this.started) return false;\n\n if (this.getWorldPath(worldName)) {\n return false;\n }\n worldName = worldName.replace(/\\.brdb$/i, '');\n\n try {\n const match = await this.addWatcher<{ res: boolean }>(\n (_line, match) => {\n if (match?.groups?.generator !== 'LogBRWorldManager') return;\n\n const ok = match.groups.data.match(/^World files saved after /);\n const err =\n !match.groups.data.startsWith(\n 'Error: Failed to capture minigame settings',\n ) &&\n match.groups.data.match(\n /^Error: (World already exists|Failed to create new world)?/,\n );\n return ok ? { res: true } : err ? { res: false } : undefined;\n },\n {\n exec: () => {\n this.writeln(`${this.Console.World.SaveAs} \"${worldName}\"`);\n },\n timeoutDelay: 2000,\n },\n );\n return match?.[0]?.['res'] ?? false;\n } catch (err) {\n return false;\n }\n }\n\n async saveWorld(): Promise<boolean> {\n // Don't allow saving while the server is starting or stopping\n if (this.stopping || this.starting || !this.started) return false;\n\n try {\n const match = await this.addWatcher<{ res: boolean }>(\n (_line, match) => {\n if (match?.groups?.generator !== 'LogBRWorldManager') return;\n\n const ok = match.groups.data.match(/^World files saved after /);\n const err =\n !match.groups.data.startsWith(\n 'Error: Failed to capture minigame settings',\n ) &&\n match.groups.data.match(/^Error: (World has not been saved\\.)?/);\n return ok ? { res: true } : err ? { res: false } : undefined;\n },\n {\n exec: () => {\n this.writeln(`${this.Console.World.Save} 0`);\n },\n timeoutDelay: 2000,\n },\n );\n return match?.[0]?.['res'] ?? false;\n } catch (err) {\n return false;\n }\n }\n\n async createEmptyWorld(\n worldName: string,\n map: 'Plate' | 'Space' | 'Studio' | 'Peaks' = 'Plate',\n ): Promise<boolean> {\n if (!worldName) return;\n worldName = worldName.replace(/\\.brdb$/i, '');\n\n try {\n const match = await this.addWatcher<{ res: boolean }>(\n (_line, match) => {\n if (match?.groups?.generator !== 'LogBRWorldManager') return;\n\n const ok = match.groups.data.match(/^World files saved after /);\n const err = match.groups.data.match(\n /^Error: (Invalid preset|World already exists|Failed to create new world)?/,\n );\n return ok ? { res: true } : err ? { res: false } : undefined;\n },\n {\n exec: () => {\n this.writeln(\n `${this.Console.World.CreateEmpty} \"${worldName}\" ${map}`,\n );\n },\n timeoutDelay: 2000,\n },\n );\n return match?.[0]?.['res'] ?? false;\n } catch (err) {\n return false;\n }\n }\n\n writeSaveData(saveName: string, saveData: WriteSaveObject) {\n if (typeof saveName !== 'string')\n throw 'expected name argument for writeSaveData';\n\n const file = join(this.savePath, saveName + '.brs');\n if (!file.startsWith(this.savePath))\n throw 'save file not in Saved/Builds directory';\n writeFileSync(file, new Uint8Array(brs.write(saveData)));\n }\n\n readSaveData(saveName: string, nobricks = false): ReadSaveObject {\n if (typeof saveName !== 'string')\n throw 'expected name argument for readSaveData';\n\n const file = this.getSavePath(saveName);\n if (!file || !file.startsWith(this.savePath))\n throw 'save file not in Saved/Builds directory';\n if (file)\n return brs.read(readFileSync(file), {\n preview: false,\n bricks: !nobricks,\n });\n }\n\n async loadSaveData(\n saveData: WriteSaveObject,\n {\n offX = 0,\n offY = 0,\n offZ = 0,\n quiet = false,\n correctPalette = false,\n correctCustom = false,\n } = {},\n ) {\n // EA3: the legacy Bricks.Load command was removed. Convert the save to a\n // .brz prefab and load it into the world via br.Prefab.Load. The palette\n // correction flags have no prefab equivalent and are ignored.\n if (brsRemoved(this.version)) {\n const { ref, file } = writeTempPrefab(this, saveData);\n this.loadPrefab(ref, { offX, offY, offZ });\n // the server reads the prefab synchronously and auto-closes the bundle a\n // couple seconds later; clean up the temp file lazily (cf. loadEnvironment)\n setTimeout(() => {\n if (existsSync(file)) unlinkSync(file);\n }, 5000);\n return;\n }\n\n const saveFile =\n this._tempSavePrefix + Date.now() + '_' + this._tempCounter.save++;\n // write savedata to file\n this.writeSaveData(saveFile, saveData);\n\n // wait for the server to finish reading the save\n await this.watchLogChunk(\n `${this.Console.Bricks.Load} \"${saveFile}\" ${offX} ${offY} ${offZ} ${quiet ? 1 : 0} ${\n correctPalette ? 1 : 0\n } ${correctCustom ? 1 : 0}`,\n /^LogBrickSerializer: (.+)$/,\n {\n first: match => match[0].endsWith(saveFile + '.brs...'),\n last: match => Boolean(match[1].match(/Read .+ bricks/)),\n afterMatchDelay: 0,\n timeoutDelay: 30000,\n },\n );\n\n // delete the save file after we're done\n const savePath = this.getSavePath(saveFile);\n if (savePath) {\n unlinkSync(savePath);\n }\n }\n\n async loadSaveDataOnPlayer(\n saveData: WriteSaveObject,\n player: string | OmeggaPlayer,\n {\n offX = 0,\n offY = 0,\n offZ = 0,\n correctPalette = false,\n correctCustom = false,\n } = {},\n ) {\n player = typeof player === 'string' ? this.getPlayer(player) : player;\n if (!player) return;\n\n // EA3: give the save to the player as a prefab (their inventory) via\n // br.Prefab.GiveToPlayer. Offsets have no equivalent and are ignored,\n // matching loadPrefabOnPlayer.\n if (brsRemoved(this.version)) {\n const { ref, file } = writeTempPrefab(this, saveData);\n this.givePrefabToPlayer(ref, player);\n setTimeout(() => {\n if (existsSync(file)) unlinkSync(file);\n }, 5000);\n return;\n }\n\n // The Bricks.LoadTemplate command was removed at EA2, before the prefab\n // commands existed; there is no working path on that intermediate version.\n if (\n warnRemoved(\n this.version,\n 'loadSaveDataOnPlayer',\n EA2_VERSION,\n 'EA2',\n 'loadPrefabOnPlayer',\n )\n )\n return;\n\n const saveFile =\n this._tempSavePrefix + Date.now() + '_' + this._tempCounter.save++;\n // write savedata to file\n this.writeSaveData(saveFile, saveData);\n\n // wait for the server to finish reading the save\n await this.watchLogChunk(\n `${this.Console.Bricks.LoadTemplate} \"${saveFile}\" ${offX} ${offY} ${offZ} ${\n correctPalette ? 1 : 0\n } ${correctCustom ? 1 : 0} \"${player.name}\"`,\n /^LogBrickSerializer: (.+)$/,\n {\n first: match => match[0].endsWith(saveFile + '.brs...'),\n last: match => Boolean(match[1].match(/Read .+ bricks/)),\n afterMatchDelay: 0,\n timeoutDelay: 30000,\n },\n );\n\n // delete the save file after we're done\n const savePath = this.getSavePath(saveFile);\n if (savePath) {\n unlinkSync(savePath);\n }\n }\n\n async getSaveData(region?: {\n center: [number, number, number];\n extent: [number, number, number];\n }) {\n // EA3: the legacy Bricks.Save command was removed. Save the world (or the\n // requested region) as a .brz prefab, then read it back into a legacy save\n // object. Brick geometry, ownership, assets, materials, colors, and\n // components are reconstructed; wires are not (the save-level `wires`\n // array is left empty). Component names/data are EA3-native (e.g.\n // `Component_Internal_Seat`), not the legacy `BCD_*` names.\n if (brsRemoved(this.version)) {\n const ref =\n this._tempSavePrefix + Date.now() + '_' + this._tempCounter.save++;\n const file = await this.savePrefabAsync(ref, { region });\n if (!file) return undefined;\n\n try {\n // gridId 1 is the world's main brick grid (MAIN_GRID); entity\n // sub-grids (>=2) are not captured, matching reader.bricks()'s default.\n const MAIN_GRID = 1;\n const reader = WorldReader.from(new Uint8Array(readFileSync(file)));\n const bricks = [...reader.bricks(MAIN_GRID)].map(b => ({\n ...b,\n physical_index: 0,\n components: {} as Record<string, unknown>,\n }));\n\n // Attach components to their bricks. Components are stored per chunk\n // with a chunk-local brick index; reader.bricks() yields bricks in the\n // same chunk order as brickChunkIndex(), so a running offset maps the\n // chunk-local index onto the flat bricks array.\n let brickOffset = 0;\n for (const chunk of reader.brickChunkIndex(MAIN_GRID)) {\n if (chunk.numComponents > 0) {\n const { components } = reader.componentChunk(\n MAIN_GRID,\n chunk.index,\n );\n for (const c of components) {\n const brick = bricks[brickOffset + c.brickIndex];\n if (brick) brick.components[c.typeName] = c.data ?? {};\n }\n }\n brickOffset += chunk.numBricks;\n }\n\n return {\n version: 10,\n map: this.currentMap ?? 'Unknown',\n author: { id: this.host?.id ?? '', name: this.host?.name ?? '' },\n host: { id: this.host?.id ?? '', name: this.host?.name ?? '' },\n description: '',\n brick_count: bricks.length,\n mods: [],\n brick_assets: reader.brickAssets(),\n colors: [],\n materials: reader.materials(),\n physical_materials: [],\n brick_owners: reader.brickOwners(),\n game_version: this.version,\n save_time: new Uint8Array(),\n bricks,\n components: {},\n } as ReadSaveObject;\n } finally {\n if (existsSync(file)) unlinkSync(file);\n }\n }\n\n const saveFile =\n this._tempSavePrefix + Date.now() + '_' + this._tempCounter.save++;\n\n await this.saveBricksAsync(saveFile, region);\n\n // read the save file\n const savePath = this.getSavePath(saveFile);\n if (savePath) {\n // read and parse the save file\n const saveData = brs.read(readFileSync(savePath));\n\n // delete the save file after we're done reading it\n unlinkSync(savePath);\n\n // return the parsed save\n return saveData;\n }\n\n return undefined;\n }\n\n /**\n * Load a prefab into the world (EA3). `path` is a bundle\n * path ref such as `Prefabs/Uploads/<hash>.brz`.\n * br.Prefab.Load <Path> [Offset X Y Z] [At Original Position] [Orientation]\n * [Root Entity Persistent Index] [Mirror Axes] [Override User Id]\n */\n loadPrefab(\n path: string,\n {\n offX = 0,\n offY = 0,\n offZ = 0,\n atOriginalPosition = false,\n orientation = 0,\n rootEntityPersistentIndex = -1,\n mirrorAxes = 0,\n overrideUserId = '',\n }: {\n offX?: number;\n offY?: number;\n offZ?: number;\n atOriginalPosition?: boolean;\n orientation?: number;\n rootEntityPersistentIndex?: number;\n /** bitmask: X=1 Y=2 Z=4 (e.g. 3 mirrors X and Y) */\n mirrorAxes?: number;\n overrideUserId?: string;\n } = {},\n ) {\n if (!path) return;\n // The root entity persistent index is looked up as a literal brick-grid\n // entity, so passing the -1 sentinel errors (\"No brick grid entity with\n // persistent index 4294967295\"). Omit it (and the positional args after\n // it) to load into the world grid; only include it when a real index is\n // given.\n const rootPart =\n rootEntityPersistentIndex >= 0\n ? ` ${rootEntityPersistentIndex} ${mirrorAxes}${\n overrideUserId ? ` \"${overrideUserId}\"` : ''\n }`\n : '';\n this.writeln(\n `${this.Console.Prefab.Load} \"${path}\" ${offX} ${offY} ${offZ} ${\n atOriginalPosition ? 1 : 0\n } ${orientation}${rootPart}`,\n );\n }\n\n /**\n * Load a prefab onto a player (EA3). Replaces the\n * removed {@link loadBricksOnPlayer}; backed by `br.Prefab.GiveToPlayer`.\n * @param path prefab bundle path ref\n * @param player player name/id or player object\n * @param options give options (preserve ownership)\n */\n loadPrefabOnPlayer(\n path: string,\n player: string | OmeggaPlayer,\n { preserveOwnership = false }: { preserveOwnership?: boolean } = {},\n ) {\n this.givePrefabToPlayer(path, player, { preserveOwnership });\n }\n\n /**\n * Save the world (or a region of it) as a prefab (EA3).\n * `path` is the destination bundle path ref (e.g. `Prefabs/MyPrefab.brz`).\n * br.Prefab.SaveRegion <Path> <Center X Y Z> <Extent X Y Z> [Include Entities]\n * [Root Entity Persistent Index] [Filter User Id]\n * @param path destination prefab bundle path ref\n * @param options save options; omit `region` to capture the whole world\n */\n savePrefab(\n path: string,\n {\n region,\n entities = true,\n rootEntityPersistentIndex = -1,\n userId = '',\n }: {\n region?: {\n center: [number, number, number];\n extent: [number, number, number];\n };\n entities?: boolean;\n rootEntityPersistentIndex?: number;\n userId?: string;\n } = {},\n ) {\n if (!path) return;\n // the command always takes a region; with none given, capture the whole\n // world from the origin with a maximal extent\n const center = (region?.center ?? [0, 0, 0]).join(' ');\n const extent = (\n region?.extent ?? [\n WHOLE_WORLD_EXTENT,\n WHOLE_WORLD_EXTENT,\n WHOLE_WORLD_EXTENT,\n ]\n ).join(' ');\n this.writeln(\n `${this.Console.Prefab.SaveRegion} \"${path}\" ${center} ${extent} ${\n entities ? 1 : 0\n } ${rootEntityPersistentIndex}${userId ? ` \"${userId}\"` : ''}`,\n );\n }\n\n /**\n * Save a prefab and resolve once the prefab file has been written to disk.\n * @param path destination prefab bundle path ref\n * @param options same options as {@link savePrefab}\n * @returns the absolute path to the written prefab, or null on timeout\n */\n async savePrefabAsync(\n path: string,\n options?: {\n region?: {\n center: [number, number, number];\n extent: [number, number, number];\n };\n entities?: boolean;\n rootEntityPersistentIndex?: number;\n userId?: string;\n },\n ): Promise<string | null> {\n if (!path) return null;\n this.savePrefab(path, options);\n\n // TODO: confirm the write via the prefab-saved log line once its exact\n // format is nailed down; for now poll for the written file on disk.\n const name = path.replace(/^Prefabs[\\\\/]/i, '').replace(/\\.brz$/i, '');\n const file = join(this.prefabPath, name + '.brz');\n const deadline = Date.now() + 30000;\n while (Date.now() < deadline) {\n if (existsSync(file)) return file;\n await new Promise(resolve => setTimeout(resolve, 100));\n }\n return null;\n }\n\n /**\n * Give a prefab to a player's inventory (EA3).\n * br.Prefab.GiveToPlayer <Path> <Player Name or User Id> [Preserve Ownership]\n */\n givePrefabToPlayer(\n path: string,\n player: string | OmeggaPlayer,\n { preserveOwnership = false }: { preserveOwnership?: boolean } = {},\n ) {\n if (!path) return;\n // the command accepts a player name or user id; prefer the resolved id\n const target = typeof player === 'object' ? player.id : player;\n if (!target) return;\n this.writeln(\n `${this.Console.Prefab.GiveToPlayer} \"${path}\" \"${target}\" ${\n preserveOwnership ? 1 : 0\n }`,\n );\n }\n\n // TODO: switch this to use worlds...\n async changeMap(map: string) {\n if (!map) return;\n\n // ServerTravel requires /Game/Maps/Plate/Plate instead of Plate\n const brName = mapUtils.n2brn(map);\n\n // wait for the server to change maps\n const match = await this.addWatcher(\n /^.*(LogLoad: Took .+ seconds to LoadMap\\((?<map>.+)\\))|(ERROR: The map .+)$/,\n {\n timeoutDelay: 30000,\n exec: () => this.writeln(`ServerTravel ${brName}`),\n },\n );\n const success = !!(\n match &&\n match[0] &&\n match[0].groups &&\n match[0].groups.map\n );\n return success;\n }\n\n async getPlugin(name: string): Promise<PluginInterop> {\n const plugin = this.pluginLoader.plugins.find(p => p.getName() === name);\n\n if (plugin) {\n return {\n name,\n documentation: plugin.getDocumentation(),\n loaded: plugin.isLoaded(),\n emitPlugin: (event: string, ...args: any[]) => {\n return plugin.emitPlugin(event, 'unsafe', args);\n },\n };\n } else {\n return null;\n }\n }\n}\n"],"names":["version","PREFAB_VERSION","Logger","file","join","mkdir","writeFileSync","writeBrzLegacy","OmeggaWrapper","VERSION","commandInjector","CONFIG_SAVED_DIR","DATA_PATH","Webserver","PluginLoader","MATCHERS","pattern","resolveConsoleCommands","existsSync","readFileSync","index","unlinkSync","CONFIG_AUTH_DIR","CONFIG_HOME","copyFiles","BRICKADIA_AUTH_FILES","readWatchedJSON","basename","path","uuid","EA2_VERSION","readBrdbRevisions","match","WorldReader","mapUtils","plugin"],"mappings":"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"}
1
+ {"version":3,"file":"server.js","sources":["../../src/omegga/server.ts"],"sourcesContent":["import Logger from '@/logger';\nimport { OmeggaLike, OmeggaPlayer, PluginInterop } from '@/plugin';\nimport {\n BRICKADIA_AUTH_FILES,\n CONFIG_AUTH_DIR,\n CONFIG_HOME,\n CONFIG_SAVED_DIR,\n DATA_PATH,\n} from '@/softconfig';\nimport { VERSION } from '@/version';\nimport { EnvironmentPreset } from '@brickadia/presets';\nimport {\n BRBanList,\n BRPlayerNameCache,\n BRRoleAssignments,\n BRRoleSetup,\n} from '@brickadia/types';\nimport { IConfig } from '@config/types';\nimport { map as mapUtils, pattern, uuid } from '@util';\nimport { readBrdbRevisions } from '@util/brdb';\nimport { copyFiles, mkdir, readWatchedJSON } from '@util/file';\nimport Webserver from '@webserver/backend';\nimport brs, {\n WorldReader,\n writeBrzLegacy,\n type ReadSaveObject,\n type WriteSaveObject,\n} from 'brs-js';\nimport 'colors';\nimport glob from 'glob';\nimport { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';\nimport { basename, join } from 'path';\nimport { AutoRestartConfig } from '..';\nimport commandInjector from './commandInjector';\nimport {\n ConsoleCommands,\n EA2_VERSION,\n PREFAB_VERSION,\n resolveConsoleCommands,\n} from './commands';\nimport MATCHERS from './matchers';\nimport Player from './player';\nimport { PluginLoader } from './plugin';\nimport {\n IGamemode,\n ILogMinigame,\n IMinigameList,\n IOmeggaOptions,\n IPlayerPositions,\n IServerStatus,\n} from './types';\nimport OmeggaWrapper from './wrapper';\n\nconst MISSING_CMD =\n '\"Command not found. Type <color=\\\\\"ffff00\\\\\">/help</> for a list of commands or <color=\\\\\"ffff00\\\\\">/plugins</> for plugin information.\"';\n\n// Prefab.SaveRegion requires a region; when saving the whole world we pass a\n// maximal extent centered on the origin to capture everything.\nconst WHOLE_WORLD_EXTENT = 1_000_000_000;\n\n// These helpers are module-level (not class methods) on purpose: safe plugins\n// call these methods through a ProxyOmegga whose prototype steals Omegga's\n// implementations (see injectOmeggaPrototypes). A `#private` method would fail\n// the brand check when `this` is a ProxyOmegga (\"Receiver must be an instance\n// of class Omegga\"), so anything a stolen method depends on must not be\n// `#private`. Module functions keep working regardless of the receiver.\n\n/**\n * Whether the running game version has removed the legacy .brs bricks console\n * commands (Bricks.Save/Load/ClearAll/ClearRegion, World.LoadAdditive) in\n * favor of the prefab (`br.Prefab.*`) and world (`br.World.Clear*`) commands.\n * Removed at Brickadia CL{@link PREFAB_VERSION}.\n */\nfunction brsRemoved(version: number): boolean {\n return version > 0 && version >= PREFAB_VERSION;\n}\n\n/**\n * Warn that a method backed by a removed console command is a no-op on the\n * running game version, and return whether the caller should bail.\n * @param version the running game version\n * @param method the omegga method being called (for the message)\n * @param since CL version the underlying command was removed at\n * @param release human release name for that version (e.g. `EA2`, `EA3`)\n * @param replacement suggested replacement method/API\n */\nfunction warnRemoved(\n version: number,\n method: string,\n since: number,\n release: string,\n replacement: string,\n): boolean {\n if (!(version > 0 && version >= since)) return false;\n Logger.warnp(\n `omegga.${method}() uses a console command removed in Brickadia ` +\n `${release}. This call has no effect - use ${replacement} instead.`,\n );\n return true;\n}\n\n/**\n * Write a save to a temporary `.brz` prefab in the prefab directory (EA3).\n * @returns the bare path ref for `br.Prefab.*` commands (no `Prefabs/` prefix\n * or `.brz` extension) and the absolute file path for cleanup.\n */\nfunction writeTempPrefab(\n omegga: {\n prefabPath: string;\n _tempSavePrefix: string;\n _tempCounter: { save: number };\n },\n saveData: WriteSaveObject,\n): { ref: string; file: string } {\n const ref =\n omegga._tempSavePrefix + Date.now() + '_' + omegga._tempCounter.save++;\n const file = join(omegga.prefabPath, ref + '.brz');\n if (!file.startsWith(omegga.prefabPath))\n throw 'prefab file not in Saved/Prefabs directory';\n mkdir(omegga.prefabPath);\n writeFileSync(file, new Uint8Array(writeBrzLegacy(saveData)));\n return { ref, file };\n}\n\n// TODO: safe broadcast parsing\n\nexport default class Omegga extends OmeggaWrapper implements OmeggaLike {\n /** The save counter prevents omegga from saving over the same file */\n _tempCounter = { save: 0, environment: 0 };\n /** The save prefix is prepended to all temporary saves */\n _tempSavePrefix = 'omegga_temp_';\n\n // pluginloader is not private so plugins can potentially add more formats\n pluginLoader: PluginLoader = undefined;\n webserver: Webserver;\n\n verbose: boolean;\n savePath: string;\n worldPath: string;\n prefabPath: string;\n presetPath: string;\n configPath: string;\n options: IOmeggaOptions;\n\n version: number;\n\n /** memoized version-resolved console commands ({@link Console}) */\n #console: { version: number; commands: ConsoleCommands };\n\n /**\n * version-resolved Brickadia console command names, nested by namespace.\n * e.g. `omegga.Console.Bricks.Clear` -> \"Bricks.Clear\" or \"br.Bricks.Clear\"\n * depending on the running game version.\n */\n get Console(): ConsoleCommands {\n if (this.#console?.version !== this.version)\n this.#console = {\n version: this.version,\n commands: resolveConsoleCommands(this.version),\n };\n return this.#console.commands;\n }\n\n host?: { id: string; name: string };\n players: OmeggaPlayer[];\n\n started = false;\n starting = false;\n stopping = false;\n crashDetected = false;\n currentMap: string;\n\n getServerStatus: () => Promise<IServerStatus>;\n listMinigames: () => Promise<IMinigameList>;\n getAllPlayerPositions: () => Promise<IPlayerPositions>;\n getMinigames: () => Promise<ILogMinigame[]>;\n getGamemode: () => Promise<IGamemode | null>;\n\n /**\n * Omegga instance\n */\n constructor(serverPath: string, cfg: IConfig, options: IOmeggaOptions = {}) {\n super(serverPath, cfg);\n this.verbose = Logger.VERBOSE;\n\n Logger.verbose('Running omegga', `v${VERSION}`.green);\n Logger.verbose('Versions', process.versions);\n Logger.verbose('Config', {\n ...cfg,\n credentials: cfg.credentials\n ? Object.fromEntries(\n Object.entries(cfg.credentials).map(([k, v]) => [k, v ? '***' : v]),\n )\n : cfg.credentials,\n server: {\n ...cfg.server,\n ...(cfg.server.password && { password: '***' }),\n ...(cfg.server.steambetaPassword && { steambetaPassword: '***' }),\n ...(cfg.server.launchArgs && {\n launchArgs: cfg.server.launchArgs\n .replace(/-Cookie=\".*?\"/g, '-Cookie=\"<hidden>\"')\n .replace(/-Cookie=\\S+/g, '-Cookie=<hidden>'),\n }),\n },\n });\n\n // inject commands\n Logger.verbose('Setting up command injector');\n commandInjector(this, this.logWrangler);\n\n // launch options (disabling webserver)\n this.options = options;\n const savedDir = cfg.server.savedDir ?? CONFIG_SAVED_DIR;\n\n // path to save files\n this.savePath = join(this.path, DATA_PATH, savedDir, 'Builds');\n this.worldPath = join(this.path, DATA_PATH, savedDir, 'Worlds');\n this.prefabPath = join(this.path, DATA_PATH, savedDir, 'Prefabs');\n\n this.presetPath = join(this.path, DATA_PATH, savedDir, 'Presets');\n\n // path to config files\n this.configPath = join(this.path, DATA_PATH, savedDir, 'Server');\n\n // create dir folders\n Logger.verbose('Creating directories');\n mkdir(this.savePath);\n mkdir(this.configPath);\n\n // ignore auth file copy\n if (!options.noauth) {\n Logger.verbose('Copying auth files');\n this.copyAuthFiles();\n }\n\n // create the webserver if it's enabled\n // the web interface provides access to server information while the server is running\n // and lets you view chat logs, disable plugins, etc\n if (!options.noweb) {\n Logger.verbose('Creating webserver');\n this.webserver = new Webserver(cfg.omegga, this);\n }\n\n if (!options.noplugin) {\n Logger.verbose('Creating plugin loader');\n this.pluginLoader = new PluginLoader(this.path, this);\n }\n\n /** @type {Array<Player>}list of online players */\n this.players = [];\n\n /** host player info `{id: uuid, name: player name}` */\n this.host = undefined;\n\n /** @type {String} current game version - may later be turned into CL#### versions */\n this.version = -1;\n\n /** @type {Boolean} whether server has started */\n this.started = false;\n /** @type {Boolean} whether server is starting up */\n this.starting = false;\n\n /** @type {String} current map */\n this.currentMap = '';\n\n // add all the matchers to the server\n Logger.verbose('Adding matchers');\n for (const matcher of MATCHERS) {\n const { pattern, callback } = matcher(this);\n this.addMatcher(pattern, callback);\n }\n\n process.on('uncaughtException', async err => {\n Logger.verbose('Uncaught exception', err);\n this.emit('error', err);\n\n // publish stop to database\n this.webserver?.database?.addChatLog('server', {}, 'Server error');\n\n try {\n await this.stop();\n } catch (e) {\n Logger.error(e);\n }\n process.exit();\n });\n\n // when brickadia starts, mark the server as started\n this.on('start', ({ map }) => {\n this.started = true;\n this.starting = false;\n this.currentMap = map;\n this.writeln(`${this.Console.Chat.MessageForUnknownCommands} 0`);\n\n this.restoreServer();\n });\n\n // detect engine crash from stderr or stdout\n const crashHandler = (line: string) => {\n if (\n !this.crashDetected &&\n (/Engine crash handling finished; re-raising signal \\d+ for the default handler\\. Good bye\\./.test(\n line,\n ) ||\n /LogCore: === Critical error: ===/.test(line))\n ) {\n Logger.error('Engine crash detected!');\n this.crashDetected = true;\n }\n };\n this.on('err', crashHandler);\n this.on('line', crashHandler);\n\n // when brickadia exits, stop omegga\n this.on('exit', () => {\n this.stop();\n });\n\n // when the process closes, emit the exit signal and stop\n this.on('closed', () => {\n // capture crash state before 'exit' handler triggers stop()\n const wasCrash = this.crashDetected;\n this.crashDetected = false;\n if (this.started) this.emit('exit');\n const doRestart = async () => {\n if (!wasCrash) return;\n try {\n const config = await this.webserver?.database?.getAutoRestartConfig();\n if (config?.crashRestartEnabled) {\n Logger.logp('Restarting server after crash...');\n this.webserver?.database?.addChatLog(\n 'server',\n {},\n 'Server crashed, restarting...',\n );\n await this.start();\n }\n } catch (err) {\n Logger.error('Error restarting after crash', err);\n }\n };\n if (!this.stopping) {\n this.stop().then(doRestart);\n } else {\n // stop() already in progress from 'exit' handler - wait for it to finish\n this.once('server:stopped', () => doRestart());\n }\n });\n\n // detect when the game reports a command does not exist\n this.on('unknownCommand', (name: string, cmd: string) => {\n // if it's not registered to a plugin, send the missing command message\n if (!this.pluginLoader || !this.pluginLoader.isCommand(cmd)) {\n this.whisper(name, MISSING_CMD);\n }\n });\n }\n\n /** attempt to save server state */\n async saveServer(config: AutoRestartConfig) {\n if (config.players && this.players.length > 0) {\n Logger.logp('Getting player positions...');\n const players = await this.getAllPlayerPositions();\n Logger.logp(`Saving ${players.length} player positions...`);\n const data = players\n .filter(p => !p.isDead && p.pos)\n .map(p => ({ position: p.pos, id: p.player.id }));\n if (players.length > 0)\n writeFileSync(\n join(this.path, DATA_PATH, 'omegga_temp_players.json'),\n JSON.stringify(data),\n );\n }\n\n if (config.saveWorld) {\n Logger.logp('Saving world...');\n await this.saveWorld();\n }\n }\n\n async restartServer() {\n if (this.starting || this.stopping) return;\n if (!this.started) return await this.start();\n\n const nextWorld = this.getNextWorld();\n if (nextWorld) {\n Logger.logp('Loading world', nextWorld.file.yellow);\n Logger.verbose('Next world configured from', nextWorld.source.yellow);\n this.loadWorld(nextWorld.file);\n } else {\n this.changeMap(this.currentMap);\n }\n\n const res = await Promise.race([\n // wait for the map to change\n new Promise(resolve =>\n this.once('mapchange', () => resolve('mapchange')),\n ),\n // Timeout after 10 seconds\n new Promise(resolve => setTimeout(() => resolve('timeout'), 10000)),\n ]);\n Logger.verbose('Restart result:', res);\n }\n\n /** attempt to restore the server's state */\n async restoreServer() {\n const tempPlayersFile = join(\n this.path,\n DATA_PATH,\n 'omegga_temp_players.json',\n );\n if (!existsSync(tempPlayersFile)) return;\n\n try {\n Logger.logp('Loading previous player positions...');\n\n // player positions are an array to address multi-clienting\n const players: { position: number[]; id: string }[] = JSON.parse(\n readFileSync(tempPlayersFile).toString(),\n );\n\n // restore player position on join\n const callback = (player: OmeggaPlayer) => {\n const index = players.findIndex(p => p.id === player.id);\n if (index > -1) {\n const { position } = players[index];\n this.writeln(\n `${this.Console.Chat.Command} /TP \"${player.name}\" ${position.join(' ')} 0`,\n );\n\n // remove the entry\n players[index] = players[players.length - 1];\n players.pop();\n }\n };\n this.on('join', callback);\n\n let timeout = setTimeout(() => {\n try {\n this.off('join', callback);\n if (existsSync(tempPlayersFile)) unlinkSync(tempPlayersFile);\n } catch (err) {\n Logger.error('Error removing omegga_temp_players.json', err);\n }\n }, 10000);\n this.once('changemap', () => {\n clearTimeout(timeout);\n this.off('join', callback);\n });\n } catch (err) {\n Logger.error('Error restoring previous server state', err);\n }\n }\n\n /**\n * start webserver, load plugins, start the brickadia server\n * this should not be called by a plugin\n */\n //\n async start(): Promise<any> {\n this.starting = true;\n if (this.webserver) await this.webserver.start();\n if (this.pluginLoader) {\n // scan for plugins\n Logger.verbose('Scanning for plugins');\n await this.pluginLoader.scan();\n\n // load the plugins\n Logger.verbose('Loading plugins');\n await this.pluginLoader.reload();\n }\n\n Logger.verbose('Starting Brickadia');\n super.start();\n this.emit('server:starting');\n }\n\n /**\n * unload plugins and stop the server\n * this should not be called by a plugin\n */\n async stop() {\n if (!this.started && !this.starting) {\n Logger.verbose(\"Stop called while server wasn't started or was starting\");\n return;\n }\n\n if (this.stopping) {\n Logger.verbose('Stop called while server was starting');\n return;\n }\n\n this.stopping = true;\n this.emit('server:stopping');\n if (this.pluginLoader) {\n Logger.verbose('Unloading plugins');\n await this.pluginLoader.unload();\n }\n Logger.verbose('Stopping server');\n super.stop();\n\n const res = await Promise.race([\n new Promise(resolve => this.once('exit', () => resolve('exit'))),\n // Timeout after 10 seconds\n new Promise(resolve => setTimeout(() => resolve('timeout'), 10000)),\n ]);\n\n Logger.verbose('Stop result:', res);\n if (this.stopping) this.emit('server:stopped');\n this.stopping = false;\n this.started = false;\n this.starting = false;\n this.players = [];\n }\n\n /**\n * Copies auth files from home config dir\n * this should never be called by a plugin\n */\n copyAuthFiles() {\n const authDir = this.config.server.authDir ?? CONFIG_AUTH_DIR;\n const savedDir = this.config.server.savedDir ?? CONFIG_SAVED_DIR;\n const authPath = join(this.path, DATA_PATH, savedDir, authDir);\n const homeAuthPath = join(\n CONFIG_HOME,\n (savedDir !== CONFIG_SAVED_DIR ? savedDir : '') + authDir,\n );\n\n copyFiles(homeAuthPath, authPath, BRICKADIA_AUTH_FILES);\n }\n\n // TODO: split messages that longer than 512 characters\n // TODO: delete characters that are known to crash the game\n broadcast(...messages: string[]) {\n messages\n .flatMap(m => m.toString().split('\\n'))\n .filter(m => m.length < 512)\n .forEach(m => this.writeln(`${this.Console.Chat.Broadcast} ${m}`));\n }\n\n whisper(target: string | OmeggaPlayer, ...messages: string[]) {\n // find the target player\n if (typeof target !== 'object') target = this.getPlayer(target);\n\n // player may have left before the message could be sent\n if (!target) return;\n\n // whisper the messages to that player\n messages\n .flatMap(m => m.toString().split('\\n'))\n .filter(m => m.length < 512)\n .forEach(m =>\n this.writeln(\n `${this.Console.Chat.Whisper} \"${(target as { name: string }).name}\" ${m}`,\n ),\n );\n }\n\n middlePrint(target: string | OmeggaPlayer, message: string) {\n // find the target player\n if (typeof target !== 'object') target = this.getPlayer(target);\n\n // player may have left before the message could be sent\n if (!target) return;\n\n // whisper the messages to that player\n if (message.length > 512) return;\n this.writeln(\n `${this.Console.Chat.StatusMessage} \"${(target as { name: string }).name}\" ${message}`,\n );\n }\n\n getPlayers(): {\n id: string;\n name: string;\n displayName: string;\n controller: string;\n state: string;\n }[] {\n return this.players.map(p => ({ ...p }));\n }\n\n getRoleSetup(): BRRoleSetup {\n // Read RoleSetup2, fallback to old RoleSetup if it doesn't exist\n return (readWatchedJSON(join(this.configPath, 'RoleSetup2.json')) ??\n readWatchedJSON(join(this.configPath, 'RoleSetup.json'))) as BRRoleSetup;\n }\n\n getRoleAssignments(): BRRoleAssignments {\n return readWatchedJSON(\n join(this.configPath, 'RoleAssignments.json'),\n ) as BRRoleAssignments;\n }\n\n getBanList(): BRBanList {\n return readWatchedJSON(join(this.configPath, 'BanList.json')) as BRBanList;\n }\n\n getNameCache(): BRPlayerNameCache {\n return readWatchedJSON(\n join(this.configPath, 'PlayerNameCache.json'),\n ) as BRPlayerNameCache;\n }\n\n getPlayer(target: string): OmeggaPlayer {\n return this.players.find(\n p =>\n p.name === target ||\n p.id === target ||\n p.controller === target ||\n p.state === target,\n );\n }\n\n findPlayerByName(name: string): OmeggaPlayer {\n name = name.toLowerCase();\n const exploded = pattern.explode(name);\n return (\n this.players.find(p => p.name === name || p.displayName === name) || // find by exact match\n this.players.find(\n p => p.name.indexOf(name) > -1 || p.displayName.indexOf(name) > -1,\n ) || // find by rough match\n this.players.find(\n p => p.name.match(exploded) || p.displayName.match(exploded),\n ) // find by exploded regex match (ck finds cake, tbp finds TheBlackParrot)\n );\n }\n\n getHostId(): string {\n return this.host?.id ?? '';\n }\n\n saveMinigame(index: number, name: string) {\n this.writeln(\n `${this.Console.Server.Minigames.SavePreset} ${index} \"${name}\"`,\n );\n }\n\n deleteMinigame(index: number) {\n this.writeln(`${this.Console.Server.Minigames.Delete} ${index}`);\n }\n\n resetMinigame(index: number) {\n this.writeln(`${this.Console.Server.Minigames.Reset} ${index}`);\n }\n\n nextRoundMinigame(index: number) {\n this.writeln(`${this.Console.Server.Minigames.NextRound} ${index}`);\n }\n\n loadMinigame(presetName: string, owner = '') {\n this.writeln(\n `${this.Console.Server.Minigames.LoadPreset} \"${presetName}\" ${owner ? `\"${owner}\"` : ''}`,\n );\n }\n\n getMinigamePresets(): string[] {\n const presetPath = join(this.presetPath, 'Minigame');\n return existsSync(presetPath)\n ? glob\n .sync(presetPath + '/**/*.bp')\n .map(f => basename(f).replace(/\\.bp$/, ''))\n : [];\n }\n\n resetEnvironment() {\n this.writeln(`${this.Console.Server.Environment.Reset}`);\n }\n\n async saveEnvironment(presetName: string): Promise<void> {\n await this.addWatcher(/Environment preset saved.$/, {\n // request the pawn for this player's controller (should only be one)\n exec: () =>\n this.writeln(\n `${this.Console.Server.Environment.SavePreset} \"${presetName}\"`,\n ),\n timeoutDelay: 100,\n });\n }\n\n async getEnvironmentData(): Promise<EnvironmentPreset> {\n const saveName =\n this._tempSavePrefix + Date.now() + '_' + this._tempCounter.environment++;\n\n await this.saveEnvironment(saveName);\n const data = this.readEnvironmentData(saveName);\n const file = join(this.presetPath, 'Environment', saveName + '.bp');\n if (existsSync(file)) unlinkSync(file);\n\n return data;\n }\n\n readEnvironmentData(saveName: string): EnvironmentPreset {\n if (typeof saveName !== 'string')\n throw 'expected name argument for readEnvironmentData';\n\n const file = join(this.presetPath, 'Environment', saveName + '.bp');\n try {\n if (existsSync(file)) return JSON.parse(readFileSync(file).toString());\n } catch (err) {\n Logger.verbose('Error parsing save data in readEnvironmentData', err);\n }\n return null;\n }\n\n loadEnvironment(presetName: string) {\n this.writeln(`${this.Console.Server.Environment.LoadPreset} ${presetName}`);\n }\n\n loadEnvironmentData(\n preset: EnvironmentPreset | EnvironmentPreset['data']['groups'],\n ) {\n if ('data' in preset) preset = preset.data.groups;\n\n const saveFile =\n this._tempSavePrefix + Date.now() + '_' + this._tempCounter.environment++;\n\n const path = join(this.presetPath, 'Environment', saveFile + '.bp');\n\n writeFileSync(\n path,\n JSON.stringify({\n formatVersion: '1',\n presetVersion: '1',\n type: 'Environment',\n data: {\n groups: {\n ...preset,\n },\n },\n }),\n );\n\n this.loadEnvironment(saveFile);\n\n // this is lazy, but environments should load much faster than builds\n // do, so it's not really worth keeping track of logs for this\n setTimeout(() => unlinkSync(path), 5000);\n }\n\n getEnvironmentPresets(): string[] {\n const presetPath = join(this.presetPath, 'Environment');\n return existsSync(presetPath)\n ? glob\n .sync(presetPath + '/**/*.bp')\n .map(f => basename(f).replace(/\\.bp$/, ''))\n : [];\n }\n\n clearBricks(target: string | { id: string }, quiet = false) {\n // target is a player object, just use that id\n if (typeof target === 'object' && target.id) target = target.id;\n // if the target isn't a uuid already, find the player by name or controller and use that uuid\n else if (typeof target === 'string' && !uuid.match(target)) {\n // only set the target if the player exists\n const player = this.getPlayer(target);\n target = player && player.id;\n }\n\n if (!target) return;\n\n this.writeln(`${this.Console.Bricks.Clear} ${target} ${quiet ? 1 : ''}`);\n }\n\n clearRegion(\n region: {\n center: [number, number, number];\n extent: [number, number, number];\n },\n options?: {\n target?: string | OmeggaPlayer;\n /** clear bricks in the region (default true) */\n bricks?: boolean;\n /** also clear entities in the region (default false, EA3 only) */\n entities?: boolean;\n },\n ) {\n // resolve the optional owner filter (player object, uuid, or name) to a uuid\n let target = '';\n const rawTarget = options?.target;\n if (rawTarget) {\n if (typeof rawTarget === 'object') target = rawTarget.id;\n else if (uuid.match(rawTarget)) target = rawTarget;\n else target = this.getPlayer(rawTarget)?.id ?? '';\n }\n\n const center = region.center.join(' ');\n const extent = region.extent.join(' ');\n\n if (brsRemoved(this.version)) {\n // br.World.ClearRegion <Center> <Extent> [ClearBricks] [ClearEntities] [FilterUserId]\n const bricks = (options?.bricks ?? true) ? 1 : 0;\n const entities = (options?.entities ?? false) ? 1 : 0;\n this.writeln(\n `${this.Console.World.ClearRegion} ${center} ${extent} ${bricks} ${entities}${\n target ? ' ' + target : ''\n }`,\n );\n return;\n }\n\n // legacy .brs region clear (bricks only)\n this.writeln(\n `${this.Console.Bricks.ClearRegion} ${center} ${extent}${\n target ? ' ' + target : ''\n }`,\n );\n }\n\n clearAllBricks(\n options:\n | boolean\n | { quiet?: boolean; bricks?: boolean; entities?: boolean } = {},\n ) {\n // backwards compat: a bare boolean is the legacy `quiet` argument\n const {\n quiet = false,\n bricks = true,\n entities = false,\n } = typeof options === 'boolean' ? { quiet: options } : options;\n\n if (brsRemoved(this.version)) {\n // br.World.ClearAll [ClearBricks] [ClearEntities] [Silent]\n this.writeln(\n `${this.Console.World.ClearAll} ${bricks ? 1 : 0} ${\n entities ? 1 : 0\n } ${quiet ? 1 : 0}`,\n );\n return;\n }\n // legacy Bricks.ClearAll only ever cleared bricks\n this.writeln(`${this.Console.Bricks.ClearAll} ${quiet ? 1 : ''}`);\n }\n\n saveBricks(\n saveName: string,\n region?: {\n center: [number, number, number];\n extent: [number, number, number];\n },\n ) {\n if (\n warnRemoved(\n this.version,\n 'saveBricks',\n PREFAB_VERSION,\n 'EA3',\n 'savePrefab',\n )\n )\n return;\n if (!saveName) return;\n\n // add quotes around the filename if it doesn't have them (backwards compat w/ plugins)\n if (!(saveName.startsWith('\"') && saveName.endsWith('\"')))\n saveName = `\"${saveName}\"`;\n\n if (region?.center && region?.extent)\n this.writeln(\n `${this.Console.Bricks.SaveRegion} ${saveName} ${region.center.join(\n ' ',\n )} ${region.extent.join(' ')}`,\n );\n else this.writeln(`${this.Console.Bricks.Save} ${saveName}`);\n }\n\n async saveBricksAsync(\n saveName: string,\n region?: {\n center: [number, number, number];\n extent: [number, number, number];\n },\n ): Promise<void> {\n if (\n warnRemoved(\n this.version,\n 'saveBricksAsync',\n PREFAB_VERSION,\n 'EA3',\n 'savePrefabAsync',\n )\n )\n return;\n if (!saveName) return;\n\n let saveNameClean = saveName;\n // add quotes around the filename if it doesn't have them (backwards compat w/ plugins)\n if (!(saveName.startsWith('\"') && saveName.endsWith('\"')))\n saveNameClean = `\"${saveName}\"`;\n\n const command =\n region?.center && region?.extent\n ? `${this.Console.Bricks.SaveRegion} ${saveNameClean} ${region.center.join(\n ' ',\n )} ${region.extent.join(' ')}`\n : `${this.Console.Bricks.Save} ${saveNameClean}`;\n\n // wait for the server to save the file\n await this.watchLogChunk(command, /^(LogBrickSerializer|LogTemp): (.+)$/, {\n first: match => match[0].endsWith(saveName + '.brs...'),\n last: match =>\n Boolean(\n match[2].match(\n /Saved .+ bricks and .+ components from .+ owners|Error: No bricks in grid!|Error: No bricks selected to save!/,\n ),\n ),\n afterMatchDelay: 0,\n timeoutDelay: 30000,\n });\n }\n\n loadBricks(\n saveName: string,\n {\n offX = 0,\n offY = 0,\n offZ = 0,\n quiet = false,\n correctPalette = false,\n correctCustom = false,\n } = {},\n ) {\n if (\n warnRemoved(\n this.version,\n 'loadBricks',\n PREFAB_VERSION,\n 'EA3',\n 'loadPrefab',\n )\n )\n return;\n // add quotes around the filename if it doesn't have them (backwards compat w/ plugins)\n if (!(saveName.startsWith('\"') && saveName.endsWith('\"')))\n saveName = `\"${saveName}\"`;\n\n this.writeln(\n `${this.Console.Bricks.Load} ${saveName} ${offX} ${offY} ${offZ} ${\n quiet ? 1 : 0\n } ${correctPalette ? 1 : 0} ${correctCustom ? 1 : 0}`,\n );\n }\n\n loadBricksOnPlayer(\n saveName: string,\n player: string | OmeggaPlayer,\n {\n offX = 0,\n offY = 0,\n offZ = 0,\n correctPalette = false,\n correctCustom = false,\n } = {},\n ) {\n if (\n warnRemoved(\n this.version,\n 'loadBricksOnPlayer',\n EA2_VERSION,\n 'EA2',\n 'loadPrefabOnPlayer',\n )\n )\n return;\n player = typeof player === 'string' ? this.getPlayer(player) : player;\n if (!player) return;\n\n // add quotes around the filename if it doesn't have them (backwards compat w/ plugins)\n if (!(saveName.startsWith('\"') && saveName.endsWith('\"')))\n saveName = `\"${saveName}\"`;\n\n this.writeln(\n `${this.Console.Bricks.LoadTemplate} ${saveName} ${offX} ${offY} ${offZ} ${\n correctPalette ? 1 : 0\n } ${correctCustom ? 1 : 0} \"${player.name}\"`,\n );\n }\n\n getSaves(): string[] {\n return existsSync(this.savePath)\n ? glob.sync(this.savePath + '/**/*.brs')\n : [];\n }\n\n getWorlds(): string[] {\n return existsSync(this.worldPath)\n ? glob.sync(this.worldPath + '/**/*.brdb')\n : [];\n }\n\n getPrefabs(): string[] {\n return existsSync(this.prefabPath)\n ? glob.sync(this.prefabPath + '/**/*.brz')\n : [];\n }\n\n getSavePath(saveName: string) {\n const file = join(\n this.savePath,\n saveName.endsWith('.brs') ? saveName : saveName + '.brs',\n );\n return existsSync(file) ? file : undefined;\n }\n\n getWorldPath(worldName: string) {\n const file = join(\n this.worldPath,\n worldName.endsWith('.brdb') ? worldName : worldName + '.brdb',\n );\n return existsSync(file) ? file : undefined;\n }\n\n async getWorldRevisions(worldName: string) {\n worldName = worldName.replace(/\\.brdb$/i, '');\n\n const path = this.getWorldPath(worldName);\n if (!worldName || !path) {\n throw new Error(`World \"${worldName}\" does not exist`);\n }\n\n // Revisions are read directly from the .brdb bundle (SQLite), so this no\n // longer requires the server to be running or a console round-trip. The\n // brdb revision indices/notes match the game's World.ListRevisions output.\n return readBrdbRevisions(path) ?? [];\n }\n\n async loadWorld(worldName: string): Promise<boolean> {\n worldName = worldName.replace(/\\.brdb$/i, '');\n if (!worldName || !this.getWorldPath(worldName)) return false;\n this.writeln(`${this.Console.World.Load} \"${worldName}\"`);\n const res = await Promise.race([\n // wait for the map to change\n new Promise(resolve =>\n this.once('mapchange', () => resolve('mapchange')),\n ),\n // Timeout after 10 seconds\n new Promise(resolve => setTimeout(() => resolve('timeout'), 10000)),\n ]);\n Logger.verbose('LoadWorld', worldName, 'result:', res);\n return res === 'mapchange';\n }\n\n async loadWorldRevision(\n worldName: string,\n revision: number,\n ): Promise<boolean> {\n worldName = worldName.replace(/\\.brdb$/i, '');\n if (!worldName || !this.getWorldPath(worldName)) return false;\n if (typeof revision !== 'number' || revision < 1) {\n throw new Error(`Invalid revision number: ${revision}`);\n }\n this.writeln(\n `${this.Console.World.LoadRevision} \"${worldName}\" ${revision}`,\n );\n const res = await Promise.race([\n // wait for the map to change\n new Promise(resolve =>\n this.once('mapchange', () => resolve('mapchange')),\n ),\n // Timeout after 10 seconds\n new Promise(resolve => setTimeout(() => resolve('timeout'), 10000)),\n ]);\n Logger.verbose('LoadWorld', worldName, 'result:', res);\n return res === 'mapchange';\n }\n\n async saveWorldAs(worldName: string) {\n if (!worldName) return false;\n if (this.stopping || this.starting || !this.started) return false;\n\n if (this.getWorldPath(worldName)) {\n return false;\n }\n worldName = worldName.replace(/\\.brdb$/i, '');\n\n try {\n const match = await this.addWatcher<{ res: boolean }>(\n (_line, match) => {\n if (match?.groups?.generator !== 'LogBRWorldManager') return;\n\n const ok = match.groups.data.match(/^World files saved after /);\n const err =\n !match.groups.data.startsWith(\n 'Error: Failed to capture minigame settings',\n ) &&\n match.groups.data.match(\n /^Error: (World already exists|Failed to create new world)?/,\n );\n return ok ? { res: true } : err ? { res: false } : undefined;\n },\n {\n exec: () => {\n this.writeln(`${this.Console.World.SaveAs} \"${worldName}\"`);\n },\n timeoutDelay: 2000,\n },\n );\n return match?.[0]?.['res'] ?? false;\n } catch (err) {\n return false;\n }\n }\n\n async saveWorld(): Promise<boolean> {\n // Don't allow saving while the server is starting or stopping\n if (this.stopping || this.starting || !this.started) return false;\n\n try {\n const match = await this.addWatcher<{ res: boolean }>(\n (_line, match) => {\n if (match?.groups?.generator !== 'LogBRWorldManager') return;\n\n const ok = match.groups.data.match(/^World files saved after /);\n const err =\n !match.groups.data.startsWith(\n 'Error: Failed to capture minigame settings',\n ) &&\n match.groups.data.match(/^Error: (World has not been saved\\.)?/);\n return ok ? { res: true } : err ? { res: false } : undefined;\n },\n {\n exec: () => {\n this.writeln(`${this.Console.World.Save} 0`);\n },\n timeoutDelay: 2000,\n },\n );\n return match?.[0]?.['res'] ?? false;\n } catch (err) {\n return false;\n }\n }\n\n async createEmptyWorld(\n worldName: string,\n map: 'Plate' | 'Space' | 'Studio' | 'Peaks' = 'Plate',\n ): Promise<boolean> {\n if (!worldName) return;\n worldName = worldName.replace(/\\.brdb$/i, '');\n\n try {\n const match = await this.addWatcher<{ res: boolean }>(\n (_line, match) => {\n if (match?.groups?.generator !== 'LogBRWorldManager') return;\n\n const ok = match.groups.data.match(/^World files saved after /);\n const err = match.groups.data.match(\n /^Error: (Invalid preset|World already exists|Failed to create new world)?/,\n );\n return ok ? { res: true } : err ? { res: false } : undefined;\n },\n {\n exec: () => {\n this.writeln(\n `${this.Console.World.CreateEmpty} \"${worldName}\" ${map}`,\n );\n },\n timeoutDelay: 2000,\n },\n );\n return match?.[0]?.['res'] ?? false;\n } catch (err) {\n return false;\n }\n }\n\n writeSaveData(saveName: string, saveData: WriteSaveObject) {\n if (typeof saveName !== 'string')\n throw 'expected name argument for writeSaveData';\n\n const file = join(this.savePath, saveName + '.brs');\n if (!file.startsWith(this.savePath))\n throw 'save file not in Saved/Builds directory';\n writeFileSync(file, new Uint8Array(brs.write(saveData)));\n }\n\n readSaveData(saveName: string, nobricks = false): ReadSaveObject {\n if (typeof saveName !== 'string')\n throw 'expected name argument for readSaveData';\n\n const file = this.getSavePath(saveName);\n if (!file || !file.startsWith(this.savePath))\n throw 'save file not in Saved/Builds directory';\n if (file)\n return brs.read(readFileSync(file), {\n preview: false,\n bricks: !nobricks,\n });\n }\n\n async loadSaveData(\n saveData: WriteSaveObject,\n {\n offX = 0,\n offY = 0,\n offZ = 0,\n quiet = false,\n correctPalette = false,\n correctCustom = false,\n } = {},\n ) {\n // EA3: the legacy Bricks.Load command was removed. Convert the save to a\n // .brz prefab and load it into the world via br.Prefab.Load. The palette\n // correction flags have no prefab equivalent and are ignored.\n if (brsRemoved(this.version)) {\n const { ref, file } = writeTempPrefab(this, saveData);\n this.loadPrefab(ref, { offX, offY, offZ });\n // the server reads the prefab synchronously and auto-closes the bundle a\n // couple seconds later; clean up the temp file lazily (cf. loadEnvironment)\n setTimeout(() => {\n if (existsSync(file)) unlinkSync(file);\n }, 5000);\n return;\n }\n\n const saveFile =\n this._tempSavePrefix + Date.now() + '_' + this._tempCounter.save++;\n // write savedata to file\n this.writeSaveData(saveFile, saveData);\n\n // wait for the server to finish reading the save\n await this.watchLogChunk(\n `${this.Console.Bricks.Load} \"${saveFile}\" ${offX} ${offY} ${offZ} ${quiet ? 1 : 0} ${\n correctPalette ? 1 : 0\n } ${correctCustom ? 1 : 0}`,\n /^LogBrickSerializer: (.+)$/,\n {\n first: match => match[0].endsWith(saveFile + '.brs...'),\n last: match => Boolean(match[1].match(/Read .+ bricks/)),\n afterMatchDelay: 0,\n timeoutDelay: 30000,\n },\n );\n\n // delete the save file after we're done\n const savePath = this.getSavePath(saveFile);\n if (savePath) {\n unlinkSync(savePath);\n }\n }\n\n async loadSaveDataOnPlayer(\n saveData: WriteSaveObject,\n player: string | OmeggaPlayer,\n {\n offX = 0,\n offY = 0,\n offZ = 0,\n correctPalette = false,\n correctCustom = false,\n } = {},\n ) {\n player = typeof player === 'string' ? this.getPlayer(player) : player;\n if (!player) return;\n\n // EA3: give the save to the player as a prefab (their inventory) via\n // br.Prefab.GiveToPlayer. Offsets have no equivalent and are ignored,\n // matching loadPrefabOnPlayer.\n if (brsRemoved(this.version)) {\n const { ref, file } = writeTempPrefab(this, saveData);\n this.givePrefabToPlayer(ref, player);\n setTimeout(() => {\n if (existsSync(file)) unlinkSync(file);\n }, 5000);\n return;\n }\n\n // The Bricks.LoadTemplate command was removed at EA2, before the prefab\n // commands existed; there is no working path on that intermediate version.\n if (\n warnRemoved(\n this.version,\n 'loadSaveDataOnPlayer',\n EA2_VERSION,\n 'EA2',\n 'loadPrefabOnPlayer',\n )\n )\n return;\n\n const saveFile =\n this._tempSavePrefix + Date.now() + '_' + this._tempCounter.save++;\n // write savedata to file\n this.writeSaveData(saveFile, saveData);\n\n // wait for the server to finish reading the save\n await this.watchLogChunk(\n `${this.Console.Bricks.LoadTemplate} \"${saveFile}\" ${offX} ${offY} ${offZ} ${\n correctPalette ? 1 : 0\n } ${correctCustom ? 1 : 0} \"${player.name}\"`,\n /^LogBrickSerializer: (.+)$/,\n {\n first: match => match[0].endsWith(saveFile + '.brs...'),\n last: match => Boolean(match[1].match(/Read .+ bricks/)),\n afterMatchDelay: 0,\n timeoutDelay: 30000,\n },\n );\n\n // delete the save file after we're done\n const savePath = this.getSavePath(saveFile);\n if (savePath) {\n unlinkSync(savePath);\n }\n }\n\n async getSaveData(region?: {\n center: [number, number, number];\n extent: [number, number, number];\n }) {\n // EA3: the legacy Bricks.Save command was removed. Save the world (or the\n // requested region) as a .brz prefab, then read it back into a legacy save\n // object. Brick geometry, ownership, assets, materials, colors, and\n // components are reconstructed; wires are not (the save-level `wires`\n // array is left empty). Component names/data are EA3-native (e.g.\n // `Component_Internal_Seat`), not the legacy `BCD_*` names.\n if (brsRemoved(this.version)) {\n const ref =\n this._tempSavePrefix + Date.now() + '_' + this._tempCounter.save++;\n const file = await this.savePrefabAsync(ref, { region });\n if (!file) return undefined;\n\n try {\n // gridId 1 is the world's main brick grid (MAIN_GRID); entity\n // sub-grids (>=2) are not captured, matching reader.bricks()'s default.\n const MAIN_GRID = 1;\n const reader = WorldReader.from(new Uint8Array(readFileSync(file)));\n const bricks = [...reader.bricks(MAIN_GRID)].map(b => ({\n ...b,\n physical_index: 0,\n components: {} as Record<string, unknown>,\n }));\n\n // Attach components to their bricks. Components are stored per chunk\n // with a chunk-local brick index; reader.bricks() yields bricks in the\n // same chunk order as brickChunkIndex(), so a running offset maps the\n // chunk-local index onto the flat bricks array.\n let brickOffset = 0;\n for (const chunk of reader.brickChunkIndex(MAIN_GRID)) {\n if (chunk.numComponents > 0) {\n const { components } = reader.componentChunk(\n MAIN_GRID,\n chunk.index,\n );\n for (const c of components) {\n const brick = bricks[brickOffset + c.brickIndex];\n if (brick) brick.components[c.typeName] = c.data ?? {};\n }\n }\n brickOffset += chunk.numBricks;\n }\n\n return {\n version: 10,\n map: this.currentMap ?? 'Unknown',\n author: { id: this.host?.id ?? '', name: this.host?.name ?? '' },\n host: { id: this.host?.id ?? '', name: this.host?.name ?? '' },\n description: '',\n brick_count: bricks.length,\n mods: [],\n brick_assets: reader.brickAssets(),\n colors: [],\n materials: reader.materials(),\n physical_materials: [],\n brick_owners: reader.brickOwners(),\n game_version: this.version,\n save_time: new Uint8Array(),\n bricks,\n components: {},\n } as ReadSaveObject;\n } finally {\n if (existsSync(file)) unlinkSync(file);\n }\n }\n\n const saveFile =\n this._tempSavePrefix + Date.now() + '_' + this._tempCounter.save++;\n\n await this.saveBricksAsync(saveFile, region);\n\n // read the save file\n const savePath = this.getSavePath(saveFile);\n if (savePath) {\n // read and parse the save file\n const saveData = brs.read(readFileSync(savePath));\n\n // delete the save file after we're done reading it\n unlinkSync(savePath);\n\n // return the parsed save\n return saveData;\n }\n\n return undefined;\n }\n\n /**\n * Load a prefab into the world (EA3). `path` is a bundle\n * path ref such as `Prefabs/Uploads/<hash>.brz`.\n * br.Prefab.Load <Path> [Offset X Y Z] [At Original Position] [Orientation]\n * [Root Entity Persistent Index] [Mirror Axes] [Override User Id]\n */\n loadPrefab(\n path: string,\n {\n offX = 0,\n offY = 0,\n offZ = 0,\n atOriginalPosition = false,\n orientation = 0,\n rootEntityPersistentIndex = -1,\n mirrorAxes = 0,\n overrideUserId = '',\n }: {\n offX?: number;\n offY?: number;\n offZ?: number;\n atOriginalPosition?: boolean;\n orientation?: number;\n rootEntityPersistentIndex?: number;\n /** bitmask: X=1 Y=2 Z=4 (e.g. 3 mirrors X and Y) */\n mirrorAxes?: number;\n overrideUserId?: string;\n } = {},\n ) {\n if (!path) return;\n // The root entity persistent index is looked up as a literal brick-grid\n // entity, so passing the -1 sentinel errors (\"No brick grid entity with\n // persistent index 4294967295\"). Omit it (and the positional args after\n // it) to load into the world grid; only include it when a real index is\n // given.\n const rootPart =\n rootEntityPersistentIndex >= 0\n ? ` ${rootEntityPersistentIndex} ${mirrorAxes}${\n overrideUserId ? ` \"${overrideUserId}\"` : ''\n }`\n : '';\n this.writeln(\n `${this.Console.Prefab.Load} \"${path}\" ${offX} ${offY} ${offZ} ${\n atOriginalPosition ? 1 : 0\n } ${orientation}${rootPart}`,\n );\n }\n\n /**\n * Load a prefab onto a player (EA3). Replaces the\n * removed {@link loadBricksOnPlayer}; backed by `br.Prefab.GiveToPlayer`.\n * @param path prefab bundle path ref\n * @param player player name/id or player object\n * @param options give options (preserve ownership)\n */\n loadPrefabOnPlayer(\n path: string,\n player: string | OmeggaPlayer,\n { preserveOwnership = false }: { preserveOwnership?: boolean } = {},\n ) {\n this.givePrefabToPlayer(path, player, { preserveOwnership });\n }\n\n /**\n * Save the world (or a region of it) as a prefab (EA3).\n * `path` is the destination bundle path ref (e.g. `Prefabs/MyPrefab.brz`).\n * br.Prefab.SaveRegion <Path> <Center X Y Z> <Extent X Y Z> [Include Entities]\n * [Root Entity Persistent Index] [Filter User Id]\n * @param path destination prefab bundle path ref\n * @param options save options; omit `region` to capture the whole world\n */\n savePrefab(\n path: string,\n {\n region,\n entities = true,\n rootEntityPersistentIndex = -1,\n userId = '',\n }: {\n region?: {\n center: [number, number, number];\n extent: [number, number, number];\n };\n entities?: boolean;\n rootEntityPersistentIndex?: number;\n userId?: string;\n } = {},\n ) {\n if (!path) return;\n // the command always takes a region; with none given, capture the whole\n // world from the origin with a maximal extent\n const center = (region?.center ?? [0, 0, 0]).join(' ');\n const extent = (\n region?.extent ?? [\n WHOLE_WORLD_EXTENT,\n WHOLE_WORLD_EXTENT,\n WHOLE_WORLD_EXTENT,\n ]\n ).join(' ');\n this.writeln(\n `${this.Console.Prefab.SaveRegion} \"${path}\" ${center} ${extent} ${\n entities ? 1 : 0\n } ${rootEntityPersistentIndex}${userId ? ` \"${userId}\"` : ''}`,\n );\n }\n\n /**\n * Save a prefab and resolve once the prefab file has been written to disk.\n * @param path destination prefab bundle path ref\n * @param options same options as {@link savePrefab}\n * @returns the absolute path to the written prefab, or null on timeout\n */\n async savePrefabAsync(\n path: string,\n options?: {\n region?: {\n center: [number, number, number];\n extent: [number, number, number];\n };\n entities?: boolean;\n rootEntityPersistentIndex?: number;\n userId?: string;\n },\n ): Promise<string | null> {\n if (!path) return null;\n this.savePrefab(path, options);\n\n // TODO: confirm the write via the prefab-saved log line once its exact\n // format is nailed down; for now poll for the written file on disk.\n const name = path.replace(/^Prefabs[\\\\/]/i, '').replace(/\\.brz$/i, '');\n const file = join(this.prefabPath, name + '.brz');\n const deadline = Date.now() + 30000;\n while (Date.now() < deadline) {\n if (existsSync(file)) return file;\n await new Promise(resolve => setTimeout(resolve, 100));\n }\n return null;\n }\n\n /**\n * Give a prefab to a player's inventory (EA3).\n * br.Prefab.GiveToPlayer <Path> <Player Name or User Id> [Preserve Ownership]\n */\n givePrefabToPlayer(\n path: string,\n player: string | OmeggaPlayer,\n { preserveOwnership = false }: { preserveOwnership?: boolean } = {},\n ) {\n if (!path) return;\n // the command accepts a player name or user id; prefer the resolved id\n const target = typeof player === 'object' ? player.id : player;\n if (!target) return;\n this.writeln(\n `${this.Console.Prefab.GiveToPlayer} \"${path}\" \"${target}\" ${\n preserveOwnership ? 1 : 0\n }`,\n );\n }\n\n // TODO: switch this to use worlds...\n async changeMap(map: string) {\n if (!map) return;\n\n // ServerTravel requires /Game/Maps/Plate/Plate instead of Plate\n const brName = mapUtils.n2brn(map);\n\n // wait for the server to change maps\n const match = await this.addWatcher(\n /^.*(LogLoad: Took .+ seconds to LoadMap\\((?<map>.+)\\))|(ERROR: The map .+)$/,\n {\n timeoutDelay: 30000,\n exec: () => this.writeln(`ServerTravel ${brName}`),\n },\n );\n const success = !!(\n match &&\n match[0] &&\n match[0].groups &&\n match[0].groups.map\n );\n return success;\n }\n\n async getPlugin(name: string): Promise<PluginInterop> {\n const plugin = this.pluginLoader.plugins.find(p => p.getName() === name);\n\n if (plugin) {\n return {\n name,\n documentation: plugin.getDocumentation(),\n loaded: plugin.isLoaded(),\n emitPlugin: (event: string, ...args: any[]) => {\n return plugin.emitPlugin(event, 'unsafe', args);\n },\n };\n } else {\n return null;\n }\n }\n}\n"],"names":["version","PREFAB_VERSION","Logger","file","join","mkdir","writeFileSync","writeBrzLegacy","OmeggaWrapper","VERSION","commandInjector","CONFIG_SAVED_DIR","DATA_PATH","Webserver","PluginLoader","MATCHERS","pattern","resolveConsoleCommands","existsSync","readFileSync","index","unlinkSync","CONFIG_AUTH_DIR","CONFIG_HOME","copyFiles","BRICKADIA_AUTH_FILES","readWatchedJSON","basename","path","uuid","EA2_VERSION","readBrdbRevisions","match","WorldReader","mapUtils","plugin"],"mappings":"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"}