omegga 1.2.8 → 1.3.0

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.
Files changed (255) hide show
  1. package/README.md +20 -1
  2. package/dist/brickadia/config.js +26 -90
  3. package/dist/brickadia/config.js.map +1 -1
  4. package/dist/brickadia/server.js +294 -293
  5. package/dist/brickadia/server.js.map +1 -1
  6. package/dist/cli/auth.js +149 -177
  7. package/dist/cli/auth.js.map +1 -1
  8. package/dist/cli/config.js +64 -66
  9. package/dist/cli/config.js.map +1 -1
  10. package/dist/cli/index.js +14 -14
  11. package/dist/cli/index.js.map +1 -1
  12. package/dist/cli/installer.js +37 -43
  13. package/dist/cli/installer.js.map +1 -1
  14. package/dist/cli/plugin.js +651 -682
  15. package/dist/cli/plugin.js.map +1 -1
  16. package/dist/cli/terminal.js +811 -733
  17. package/dist/cli/terminal.js.map +1 -1
  18. package/dist/config/formats/format_js.js +6 -6
  19. package/dist/config/formats/format_js.js.map +1 -1
  20. package/dist/config/formats/format_yml.js +7 -10
  21. package/dist/config/formats/format_yml.js.map +1 -1
  22. package/dist/config/index.js +46 -68
  23. package/dist/config/index.js.map +1 -1
  24. package/dist/config/reader.js +16 -27
  25. package/dist/config/reader.js.map +1 -1
  26. package/dist/config/validator.js +23 -25
  27. package/dist/config/validator.js.map +1 -1
  28. package/dist/config/writer.js +11 -21
  29. package/dist/config/writer.js.map +1 -1
  30. package/dist/index.js +18 -64
  31. package/dist/index.js.map +1 -1
  32. package/dist/info/default_commands.json.js +39 -0
  33. package/dist/info/default_commands.json.js.map +1 -0
  34. package/dist/logger.js +61 -60
  35. package/dist/logger.js.map +1 -1
  36. package/dist/main.js +424 -471
  37. package/dist/main.js.map +1 -1
  38. package/dist/omegga/auth.js +101 -148
  39. package/dist/omegga/auth.js.map +1 -1
  40. package/dist/omegga/commandInjector.js +261 -251
  41. package/dist/omegga/commandInjector.js.map +1 -1
  42. package/dist/omegga/index.js +14 -52
  43. package/dist/omegga/index.js.map +1 -1
  44. package/dist/omegga/logWrangler.js +224 -275
  45. package/dist/omegga/logWrangler.js.map +1 -1
  46. package/dist/omegga/matchers/auth.js +36 -50
  47. package/dist/omegga/matchers/auth.js.map +1 -1
  48. package/dist/omegga/matchers/chat.js +49 -71
  49. package/dist/omegga/matchers/chat.js.map +1 -1
  50. package/dist/omegga/matchers/command.js +25 -36
  51. package/dist/omegga/matchers/command.js.map +1 -1
  52. package/dist/omegga/matchers/exit.js +15 -18
  53. package/dist/omegga/matchers/exit.js.map +1 -1
  54. package/dist/omegga/matchers/index.js +26 -40
  55. package/dist/omegga/matchers/index.js.map +1 -1
  56. package/dist/omegga/matchers/init.js +20 -23
  57. package/dist/omegga/matchers/init.js.map +1 -1
  58. package/dist/omegga/matchers/interact.js +58 -64
  59. package/dist/omegga/matchers/interact.js.map +1 -1
  60. package/dist/omegga/matchers/join.js +74 -95
  61. package/dist/omegga/matchers/join.js.map +1 -1
  62. package/dist/omegga/matchers/leave.js +43 -56
  63. package/dist/omegga/matchers/leave.js.map +1 -1
  64. package/dist/omegga/matchers/mapChange.js +26 -35
  65. package/dist/omegga/matchers/mapChange.js.map +1 -1
  66. package/dist/omegga/matchers/minigameJoin.js +24 -31
  67. package/dist/omegga/matchers/minigameJoin.js.map +1 -1
  68. package/dist/omegga/matchers/version.js +42 -49
  69. package/dist/omegga/matchers/version.js.map +1 -1
  70. package/dist/omegga/player.js +527 -527
  71. package/dist/omegga/player.js.map +1 -1
  72. package/dist/omegga/plugin/interface.js +91 -98
  73. package/dist/omegga/plugin/interface.js.map +1 -1
  74. package/dist/omegga/plugin/plugin_jsonrpc_stdio.js +524 -429
  75. package/dist/omegga/plugin/plugin_jsonrpc_stdio.js.map +1 -1
  76. package/dist/omegga/plugin/plugin_node_safe/proxyOmegga.js +282 -294
  77. package/dist/omegga/plugin/plugin_node_safe/proxyOmegga.js.map +1 -1
  78. package/dist/omegga/plugin/plugin_node_safe.js +365 -393
  79. package/dist/omegga/plugin/plugin_node_safe.js.map +1 -1
  80. package/dist/omegga/plugin/plugin_node_unsafe.js +119 -179
  81. package/dist/omegga/plugin/plugin_node_unsafe.js.map +1 -1
  82. package/dist/omegga/plugin.js +402 -347
  83. package/dist/omegga/plugin.js.map +1 -1
  84. package/dist/omegga/server.js +742 -774
  85. package/dist/omegga/server.js.map +1 -1
  86. package/dist/omegga/wrapper.js +64 -91
  87. package/dist/omegga/wrapper.js.map +1 -1
  88. package/dist/plugin.js +11 -31
  89. package/dist/plugin.js.map +1 -1
  90. package/dist/softconfig.js +121 -100
  91. package/dist/softconfig.js.map +1 -1
  92. package/dist/updater/steam.js +97 -112
  93. package/dist/updater/steam.js.map +1 -1
  94. package/dist/util/brdb.js +496 -528
  95. package/dist/util/brdb.js.map +1 -1
  96. package/dist/util/brick/checkBounds.js +11 -15
  97. package/dist/util/brick/checkBounds.js.map +1 -1
  98. package/dist/util/brick/constants.js +723 -174
  99. package/dist/util/brick/constants.js.map +1 -1
  100. package/dist/util/brick/displayName.js +204 -211
  101. package/dist/util/brick/displayName.js.map +1 -1
  102. package/dist/util/brick/getBounds.js +29 -41
  103. package/dist/util/brick/getBounds.js.map +1 -1
  104. package/dist/util/brick/getBrickSize.js +6 -7
  105. package/dist/util/brick/getBrickSize.js.map +1 -1
  106. package/dist/util/brick/getScaleAxis.js +24 -30
  107. package/dist/util/brick/getScaleAxis.js.map +1 -1
  108. package/dist/util/brick/index.js +8 -29
  109. package/dist/util/brick/index.js.map +1 -1
  110. package/dist/util/brick/rotate.js +29 -34
  111. package/dist/util/brick/rotate.js.map +1 -1
  112. package/dist/util/brick/setOwnership.js +13 -19
  113. package/dist/util/brick/setOwnership.js.map +1 -1
  114. package/dist/util/chat.js +53 -44
  115. package/dist/util/chat.js.map +1 -1
  116. package/dist/util/color.js +112 -124
  117. package/dist/util/color.js.map +1 -1
  118. package/dist/util/file.js +54 -98
  119. package/dist/util/file.js.map +1 -1
  120. package/dist/util/index.js +84 -106
  121. package/dist/util/index.js.map +1 -1
  122. package/dist/util/map.js +18 -20
  123. package/dist/util/map.js.map +1 -1
  124. package/dist/util/pattern.js +6 -9
  125. package/dist/util/pattern.js.map +1 -1
  126. package/dist/util/time.js +31 -34
  127. package/dist/util/time.js.map +1 -1
  128. package/dist/util/uuid.js +10 -8
  129. package/dist/util/uuid.js.map +1 -1
  130. package/dist/util/wsl.js +19 -25
  131. package/dist/util/wsl.js.map +1 -1
  132. package/dist/version.js +7 -9
  133. package/dist/version.js.map +1 -1
  134. package/dist/webserver/backend/api.js +784 -874
  135. package/dist/webserver/backend/api.js.map +1 -1
  136. package/dist/webserver/backend/calendar.js +14 -17
  137. package/dist/webserver/backend/calendar.js.map +1 -1
  138. package/dist/webserver/backend/database.js +563 -613
  139. package/dist/webserver/backend/database.js.map +1 -1
  140. package/dist/webserver/backend/index.js +153 -191
  141. package/dist/webserver/backend/index.js.map +1 -1
  142. package/dist/webserver/backend/metrics.js +219 -248
  143. package/dist/webserver/backend/metrics.js.map +1 -1
  144. package/dist/webserver/backend/util.js +47 -68
  145. package/dist/webserver/backend/util.js.map +1 -1
  146. package/dist/worker.js +322 -0
  147. package/dist/worker.js.map +1 -0
  148. package/package.json +14 -10
  149. package/templates/safe-ts/omegga.d.ts +2 -2
  150. package/vite.backend.config.mts +46 -0
  151. package/vite.frontend.config.mts +58 -0
  152. package/vitest.backend.config.mts +7 -0
  153. package/dist/brickadia/config.d.ts +0 -3
  154. package/dist/brickadia/presets.d.ts +0 -65
  155. package/dist/brickadia/presets.js +0 -3
  156. package/dist/brickadia/presets.js.map +0 -1
  157. package/dist/brickadia/server.d.ts +0 -36
  158. package/dist/brickadia/types.d.ts +0 -45
  159. package/dist/brickadia/types.js +0 -3
  160. package/dist/brickadia/types.js.map +0 -1
  161. package/dist/cli/auth.d.ts +0 -26
  162. package/dist/cli/config.d.ts +0 -10
  163. package/dist/cli/index.d.ts +0 -30
  164. package/dist/cli/installer.d.ts +0 -1
  165. package/dist/cli/plugin.d.ts +0 -22
  166. package/dist/cli/terminal.d.ts +0 -25
  167. package/dist/config/authorizer.d.ts +0 -2
  168. package/dist/config/authorizer.js +0 -38
  169. package/dist/config/authorizer.js.map +0 -1
  170. package/dist/config/formats/format_js.d.ts +0 -3
  171. package/dist/config/formats/format_yml.d.ts +0 -3
  172. package/dist/config/index.d.ts +0 -18
  173. package/dist/config/reader.d.ts +0 -3
  174. package/dist/config/types.d.ts +0 -50
  175. package/dist/config/types.js +0 -3
  176. package/dist/config/types.js.map +0 -1
  177. package/dist/config/validator.d.ts +0 -6
  178. package/dist/config/writer.d.ts +0 -3
  179. package/dist/index.d.ts +0 -22
  180. package/dist/info/default_commands.json +0 -29
  181. package/dist/logger.d.ts +0 -41
  182. package/dist/main.d.ts +0 -1
  183. package/dist/omegga/auth.d.ts +0 -9
  184. package/dist/omegga/commandInjector.d.ts +0 -35
  185. package/dist/omegga/index.d.ts +0 -7
  186. package/dist/omegga/logWrangler.d.ts +0 -32
  187. package/dist/omegga/matchers/auth.d.ts +0 -10
  188. package/dist/omegga/matchers/chat.d.ts +0 -10
  189. package/dist/omegga/matchers/command.d.ts +0 -7
  190. package/dist/omegga/matchers/exit.d.ts +0 -3
  191. package/dist/omegga/matchers/index.d.ts +0 -3
  192. package/dist/omegga/matchers/init.d.ts +0 -3
  193. package/dist/omegga/matchers/interact.d.ts +0 -4
  194. package/dist/omegga/matchers/join.d.ts +0 -4
  195. package/dist/omegga/matchers/leave.d.ts +0 -4
  196. package/dist/omegga/matchers/mapChange.d.ts +0 -3
  197. package/dist/omegga/matchers/minigameJoin.d.ts +0 -9
  198. package/dist/omegga/matchers/types.d.ts +0 -5
  199. package/dist/omegga/matchers/types.js +0 -3
  200. package/dist/omegga/matchers/types.js.map +0 -1
  201. package/dist/omegga/matchers/version.d.ts +0 -3
  202. package/dist/omegga/player.d.ts +0 -81
  203. package/dist/omegga/plugin/interface.d.ts +0 -30
  204. package/dist/omegga/plugin/plugin_jsonrpc_stdio.d.ts +0 -23
  205. package/dist/omegga/plugin/plugin_node_safe/proxyOmegga.d.ts +0 -145
  206. package/dist/omegga/plugin/plugin_node_safe/worker.d.ts +0 -1
  207. package/dist/omegga/plugin/plugin_node_safe/worker.js +0 -367
  208. package/dist/omegga/plugin/plugin_node_safe/worker.js.map +0 -1
  209. package/dist/omegga/plugin/plugin_node_safe.d.ts +0 -23
  210. package/dist/omegga/plugin/plugin_node_unsafe.d.ts +0 -13
  211. package/dist/omegga/plugin.d.ts +0 -63
  212. package/dist/omegga/server.d.ts +0 -166
  213. package/dist/omegga/types.d.ts +0 -49
  214. package/dist/omegga/types.js +0 -3
  215. package/dist/omegga/types.js.map +0 -1
  216. package/dist/omegga/wrapper.d.ts +0 -32
  217. package/dist/plugin.d.ts +0 -916
  218. package/dist/softconfig.d.ts +0 -67
  219. package/dist/tsconfig.tsbuildinfo +0 -1
  220. package/dist/updater/index.d.ts +0 -1
  221. package/dist/updater/index.js +0 -10
  222. package/dist/updater/index.js.map +0 -1
  223. package/dist/updater/steam.d.ts +0 -52
  224. package/dist/util/brdb.d.ts +0 -31
  225. package/dist/util/brick/checkBounds.d.ts +0 -8
  226. package/dist/util/brick/constants.d.ts +0 -18
  227. package/dist/util/brick/displayName.d.ts +0 -7
  228. package/dist/util/brick/getBounds.d.ts +0 -5
  229. package/dist/util/brick/getBrickSize.d.ts +0 -3
  230. package/dist/util/brick/getScaleAxis.d.ts +0 -3
  231. package/dist/util/brick/index.d.ts +0 -14
  232. package/dist/util/brick/rotate.d.ts +0 -16
  233. package/dist/util/brick/setOwnership.d.ts +0 -12
  234. package/dist/util/chat.d.ts +0 -23
  235. package/dist/util/color.d.ts +0 -13
  236. package/dist/util/file.d.ts +0 -5
  237. package/dist/util/index.d.ts +0 -183
  238. package/dist/util/map.d.ts +0 -6
  239. package/dist/util/pattern.d.ts +0 -1
  240. package/dist/util/time.d.ts +0 -39
  241. package/dist/util/uuid.d.ts +0 -3
  242. package/dist/util/wsl.d.ts +0 -2
  243. package/dist/version.d.ts +0 -2
  244. package/dist/webserver/backend/api.d.ts +0 -115
  245. package/dist/webserver/backend/calendar.d.ts +0 -5
  246. package/dist/webserver/backend/database.d.ts +0 -150
  247. package/dist/webserver/backend/index.d.ts +0 -30
  248. package/dist/webserver/backend/metrics.d.ts +0 -3
  249. package/dist/webserver/backend/types.d.ts +0 -114
  250. package/dist/webserver/backend/types.js +0 -3
  251. package/dist/webserver/backend/types.js.map +0 -1
  252. package/dist/webserver/backend/util.d.ts +0 -2
  253. package/dist/webserver/index.d.ts +0 -2
  254. package/dist/webserver/index.js +0 -9
  255. package/dist/webserver/index.js.map +0 -1
@@ -1,783 +1,751 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const default_commands_json_1 = __importDefault(require("../info/default_commands.json"));
7
- const logger_1 = __importDefault(require("../logger"));
8
- const softconfig_1 = require("../softconfig");
9
- const version_1 = require("../version");
10
- const _util_1 = require("../util");
11
- const file_1 = require("../util/file");
12
- const backend_1 = __importDefault(require("../webserver/backend"));
13
- const brs_js_1 = require("brs-js");
2
+ Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
3
+ const default_commands = require("../info/default_commands.json.js");
4
+ const logger = require("../logger.js");
5
+ const softconfig = require("../softconfig.js");
6
+ const version = require("../version.js");
7
+ const index$2 = require("../util/index.js");
8
+ const file = require("../util/file.js");
9
+ const index = require("../webserver/backend/index.js");
10
+ const brs = require("brs-js");
14
11
  require("colors");
15
- const fs_1 = require("fs");
16
- const glob_1 = require("glob");
17
- const path_1 = require("path");
18
- const commandInjector_1 = __importDefault(require("./commandInjector"));
19
- const matchers_1 = __importDefault(require("./matchers"));
20
- const plugin_1 = require("./plugin");
21
- const wrapper_1 = __importDefault(require("./wrapper"));
12
+ const glob = require("glob");
13
+ const fs = require("node:fs");
14
+ const path = require("path");
15
+ const commandInjector = require("./commandInjector.js");
16
+ const index$1 = require("./matchers/index.js");
17
+ const plugin = require("./plugin.js");
18
+ const wrapper = require("./wrapper.js");
22
19
  const MISSING_CMD = '"Command not found. Type <color=\\"ffff00\\">/help</> for a list of commands or <color=\\"ffff00\\">/plugins</> for plugin information."';
23
- // TODO: safe broadcast parsing
24
- class Omegga extends wrapper_1.default {
25
- /**
26
- * Omegga instance
27
- */
28
- constructor(serverPath, cfg, options = {}) {
29
- var _a;
30
- super(serverPath, cfg);
31
- /** The save counter prevents omegga from saving over the same file */
32
- this._tempCounter = { save: 0, environment: 0 };
33
- /** The save prefix is prepended to all temporary saves */
34
- this._tempSavePrefix = 'omegga_temp_';
35
- // pluginloader is not private so plugins can potentially add more formats
36
- this.pluginLoader = undefined;
37
- this.started = false;
38
- this.starting = false;
39
- this.stopping = false;
40
- this.verbose = logger_1.default.VERBOSE;
41
- logger_1.default.verbose('Running omegga', `v${version_1.VERSION}`.green);
42
- logger_1.default.verbose('Versions', process.versions);
43
- logger_1.default.verbose('Config', cfg);
44
- // inject commands
45
- logger_1.default.verbose('Setting up command injector');
46
- (0, commandInjector_1.default)(this, this.logWrangler);
47
- // launch options (disabling webserver)
48
- this.options = options;
49
- const savedDir = (_a = cfg.server.savedDir) !== null && _a !== void 0 ? _a : softconfig_1.CONFIG_SAVED_DIR;
50
- // path to save files
51
- this.savePath = (0, path_1.join)(this.path, softconfig_1.DATA_PATH, savedDir, 'Builds');
52
- this.worldPath = (0, path_1.join)(this.path, softconfig_1.DATA_PATH, savedDir, 'Worlds');
53
- this.presetPath = (0, path_1.join)(this.path, softconfig_1.DATA_PATH, savedDir, 'Presets');
54
- // path to config files
55
- this.configPath = (0, path_1.join)(this.path, softconfig_1.DATA_PATH, savedDir, 'Server');
56
- // create dir folders
57
- logger_1.default.verbose('Creating directories');
58
- (0, file_1.mkdir)(this.savePath);
59
- (0, file_1.mkdir)(this.configPath);
60
- // ignore auth file copy
61
- if (!options.noauth) {
62
- logger_1.default.verbose('Copying auth files');
63
- this.copyAuthFiles();
64
- }
65
- // create the webserver if it's enabled
66
- // the web interface provides access to server information while the server is running
67
- // and lets you view chat logs, disable plugins, etc
68
- if (!options.noweb) {
69
- logger_1.default.verbose('Creating webserver');
70
- this.webserver = new backend_1.default(cfg.omegga, this);
71
- }
72
- if (!options.noplugin) {
73
- logger_1.default.verbose('Creating plugin loader');
74
- // create the pluginloader
75
- this.pluginLoader = new plugin_1.PluginLoader(this.path, this);
76
- }
77
- /** @type {Array<Player>}list of online players */
78
- this.players = [];
79
- /** host player info `{id: uuid, name: player name}` */
80
- this.host = undefined;
81
- /** @type {String} current game version - may later be turned into CL#### versions */
82
- this.version = -1;
83
- /** @type {Boolean} whether server has started */
84
- this.started = false;
85
- /** @type {Boolean} whether server is starting up */
86
- this.starting = false;
87
- /** @type {String} current map */
88
- this.currentMap = '';
89
- // add all the matchers to the server
90
- logger_1.default.verbose('Adding matchers');
91
- for (const matcher of matchers_1.default) {
92
- const { pattern, callback } = matcher(this);
93
- this.addMatcher(pattern, callback);
94
- }
95
- process.on('uncaughtException', async (err) => {
96
- var _a, _b;
97
- logger_1.default.verbose('Uncaught exception', err);
98
- this.emit('error', err);
99
- // publish stop to database
100
- (_b = (_a = this.webserver) === null || _a === void 0 ? void 0 : _a.database) === null || _b === void 0 ? void 0 : _b.addChatLog('server', {}, 'Server error');
101
- try {
102
- await this.stop();
103
- }
104
- catch (e) {
105
- logger_1.default.error(e);
106
- }
107
- process.exit();
108
- });
109
- // when brickadia starts, mark the server as started
110
- this.on('start', ({ map }) => {
111
- this.started = true;
112
- this.starting = false;
113
- this.currentMap = map;
114
- this.writeln('Chat.MessageForUnknownCommands 0');
115
- this.restoreServer();
116
- });
117
- // when brickadia exits, stop omegga
118
- this.on('exit', () => {
119
- this.stop();
120
- });
121
- // when the process closes, emit the exit signal and stop
122
- this.on('closed', () => {
123
- if (this.started)
124
- this.emit('exit');
125
- if (!this.stopping)
126
- this.stop();
127
- });
128
- // detect when a missing command is sent
129
- this.on('cmd', (cmd, name) => {
130
- // if it's not in the default commands and it's not registered to a plugin,
131
- // it's okay to send the missing command message
132
- if (!default_commands_json_1.default.includes(cmd) &&
133
- (!this.pluginLoader || !this.pluginLoader.isCommand(cmd))) {
134
- this.whisper(name, MISSING_CMD);
135
- }
136
- });
137
- }
138
- /** attempt to save server state */
139
- async saveServer(config) {
140
- if (config.players && this.players.length > 0) {
141
- logger_1.default.logp('Getting player positions...');
142
- const players = await this.getAllPlayerPositions();
143
- logger_1.default.logp(`Saving ${players.length} player positions...`);
144
- const data = players
145
- .filter(p => !p.isDead && p.pos)
146
- .map(p => ({ position: p.pos, id: p.player.id }));
147
- if (players.length > 0)
148
- (0, fs_1.writeFileSync)((0, path_1.join)(this.path, softconfig_1.DATA_PATH, 'omegga_temp_players.json'), JSON.stringify(data));
149
- }
150
- if (config.saveWorld) {
151
- logger_1.default.logp('Saving world...');
152
- await this.saveWorld();
153
- }
154
- }
155
- async restartServer() {
156
- if (this.starting || this.stopping)
157
- return;
158
- if (!this.started)
159
- return await this.start();
160
- const nextWorld = this.getNextWorld();
161
- if (nextWorld) {
162
- logger_1.default.logp('Loading world', nextWorld.file.yellow);
163
- logger_1.default.verbose('Next world configured from', nextWorld.source.yellow);
164
- this.loadWorld(nextWorld.file);
165
- }
166
- else {
167
- this.changeMap(this.currentMap);
168
- }
169
- const res = await Promise.race([
170
- // wait for the map to change
171
- new Promise(resolve => this.once('mapchange', () => resolve('mapchange'))),
172
- // Timeout after 10 seconds
173
- new Promise(resolve => setTimeout(() => resolve('timeout'), 10000)),
174
- ]);
175
- logger_1.default.verbose('Restart result:', res);
176
- }
177
- /** attempt to restore the server's state */
178
- async restoreServer() {
179
- const tempPlayersFile = (0, path_1.join)(this.path, softconfig_1.DATA_PATH, 'omegga_temp_players.json');
180
- if (!(0, fs_1.existsSync)(tempPlayersFile))
181
- return;
182
- try {
183
- logger_1.default.logp('Loading previous player positions...');
184
- // player positions are an array to address multi-clienting
185
- const players = JSON.parse((0, fs_1.readFileSync)(tempPlayersFile).toString());
186
- // restore player position on join
187
- const callback = (player) => {
188
- const index = players.findIndex(p => p.id === player.id);
189
- if (index > -1) {
190
- const { position } = players[index];
191
- this.writeln(`Chat.Command /TP "${player.name}" ${position.join(' ')} 0`);
192
- // remove the entry
193
- players[index] = players[players.length - 1];
194
- players.pop();
195
- }
196
- };
197
- this.on('join', callback);
198
- let timeout = setTimeout(() => {
199
- try {
200
- this.off('join', callback);
201
- if ((0, fs_1.existsSync)(tempPlayersFile))
202
- (0, fs_1.unlinkSync)(tempPlayersFile);
203
- }
204
- catch (err) {
205
- logger_1.default.error('Error removing omegga_temp_players.json', err);
206
- }
207
- }, 10000);
208
- this.once('changemap', () => {
209
- clearTimeout(timeout);
210
- this.off('join', callback);
211
- });
212
- }
213
- catch (err) {
214
- logger_1.default.error('Error restoring previous server state', err);
215
- }
216
- }
217
- /**
218
- * start webserver, load plugins, start the brickadia server
219
- * this should not be called by a plugin
220
- */
221
- //
222
- async start() {
223
- this.starting = true;
224
- if (this.webserver)
225
- await this.webserver.start();
226
- if (this.pluginLoader) {
227
- // scan for plugins
228
- logger_1.default.verbose('Scanning for plugins');
229
- await this.pluginLoader.scan();
230
- // load the plugins
231
- logger_1.default.verbose('Loading plugins');
232
- await this.pluginLoader.reload();
233
- }
234
- logger_1.default.verbose('Starting Brickadia');
235
- super.start();
236
- this.emit('server:starting');
237
- }
238
- /**
239
- * unload plugins and stop the server
240
- * this should not be called by a plugin
241
- */
242
- async stop() {
243
- if (!this.started && !this.starting) {
244
- logger_1.default.verbose("Stop called while server wasn't started or was starting");
245
- return;
246
- }
247
- if (this.stopping) {
248
- logger_1.default.verbose('Stop called while server was starting');
249
- return;
250
- }
251
- this.stopping = true;
252
- this.emit('server:stopping');
253
- if (this.pluginLoader) {
254
- logger_1.default.verbose('Unloading plugins');
255
- await this.pluginLoader.unload();
256
- }
257
- logger_1.default.verbose('Stopping server');
258
- super.stop();
259
- const res = await Promise.race([
260
- new Promise(resolve => this.once('exit', () => resolve('exit'))),
261
- // Timeout after 10 seconds
262
- new Promise(resolve => setTimeout(() => resolve('timeout'), 10000)),
263
- ]);
264
- logger_1.default.verbose('Stop result:', res);
265
- if (this.stopping)
266
- this.emit('server:stopped');
267
- this.stopping = false;
268
- this.started = false;
269
- this.starting = false;
270
- this.players = [];
271
- }
272
- /**
273
- * Copies auth files from home config dir
274
- * this should never be called by a plugin
275
- */
276
- copyAuthFiles() {
277
- var _a, _b;
278
- const authDir = (_a = this.config.server.authDir) !== null && _a !== void 0 ? _a : softconfig_1.CONFIG_AUTH_DIR;
279
- const savedDir = (_b = this.config.server.savedDir) !== null && _b !== void 0 ? _b : softconfig_1.CONFIG_SAVED_DIR;
280
- const authPath = (0, path_1.join)(this.path, softconfig_1.DATA_PATH, savedDir, authDir);
281
- const homeAuthPath = (0, path_1.join)(softconfig_1.CONFIG_HOME, (savedDir !== softconfig_1.CONFIG_SAVED_DIR ? savedDir : '') + authDir);
282
- (0, file_1.copyFiles)(homeAuthPath, authPath, softconfig_1.BRICKADIA_AUTH_FILES);
283
- }
284
- // TODO: split messages that longer than 512 characters
285
- // TODO: delete characters that are known to crash the game
286
- broadcast(...messages) {
287
- messages
288
- .flatMap(m => m.toString().split('\n'))
289
- .filter(m => m.length < 512)
290
- .forEach(m => this.writeln(`Chat.Broadcast ${m}`));
291
- }
292
- whisper(target, ...messages) {
293
- // find the target player
294
- if (typeof target !== 'object')
295
- target = this.getPlayer(target);
296
- // whisper the messages to that player
297
- messages
298
- .flatMap(m => m.toString().split('\n'))
299
- .filter(m => m.length < 512)
300
- .forEach(m => this.writeln(`Chat.Whisper "${target.name}" ${m}`));
301
- }
302
- middlePrint(target, message) {
303
- // find the target player
304
- if (typeof target !== 'object')
305
- target = this.getPlayer(target);
306
- // whisper the messages to that player
307
- if (message.length > 512)
308
- return;
309
- this.writeln(`Chat.StatusMessage "${target.name}" ${message}`);
310
- }
311
- getPlayers() {
312
- return this.players.map(p => ({ ...p }));
313
- }
314
- getRoleSetup() {
315
- var _a;
316
- // Read RoleSetup2, fallback to old RoleSetup if it doesn't exist
317
- return ((_a = (0, file_1.readWatchedJSON)((0, path_1.join)(this.configPath, 'RoleSetup2.json'))) !== null && _a !== void 0 ? _a : (0, file_1.readWatchedJSON)((0, path_1.join)(this.configPath, 'RoleSetup.json')));
318
- }
319
- getRoleAssignments() {
320
- return (0, file_1.readWatchedJSON)((0, path_1.join)(this.configPath, 'RoleAssignments.json'));
321
- }
322
- getBanList() {
323
- return (0, file_1.readWatchedJSON)((0, path_1.join)(this.configPath, 'BanList.json'));
324
- }
325
- getNameCache() {
326
- return (0, file_1.readWatchedJSON)((0, path_1.join)(this.configPath, 'PlayerNameCache.json'));
327
- }
328
- getPlayer(target) {
329
- return this.players.find(p => p.name === target ||
330
- p.id === target ||
331
- p.controller === target ||
332
- p.state === target);
333
- }
334
- findPlayerByName(name) {
335
- name = name.toLowerCase();
336
- const exploded = _util_1.pattern.explode(name);
337
- return (this.players.find(p => p.name === name || p.displayName === name) || // find by exact match
338
- this.players.find(p => p.name.indexOf(name) > -1 || p.displayName.indexOf(name) > -1) || // find by rough match
339
- this.players.find(p => p.name.match(exploded) || p.displayName.match(exploded)) // find by exploded regex match (ck finds cake, tbp finds TheBlackParrot)
20
+ class Omegga extends wrapper.default {
21
+ /**
22
+ * Omegga instance
23
+ */
24
+ constructor(serverPath, cfg, options = {}) {
25
+ super(serverPath, cfg);
26
+ this._tempCounter = { save: 0, environment: 0 };
27
+ this._tempSavePrefix = "omegga_temp_";
28
+ this.pluginLoader = void 0;
29
+ this.started = false;
30
+ this.starting = false;
31
+ this.stopping = false;
32
+ this.verbose = logger.default.VERBOSE;
33
+ logger.default.verbose("Running omegga", `v${version.VERSION}`.green);
34
+ logger.default.verbose("Versions", process.versions);
35
+ logger.default.verbose("Config", cfg);
36
+ logger.default.verbose("Setting up command injector");
37
+ commandInjector.default(this, this.logWrangler);
38
+ this.options = options;
39
+ const savedDir = cfg.server.savedDir ?? softconfig.CONFIG_SAVED_DIR;
40
+ this.savePath = path.join(this.path, softconfig.DATA_PATH, savedDir, "Builds");
41
+ this.worldPath = path.join(this.path, softconfig.DATA_PATH, savedDir, "Worlds");
42
+ this.presetPath = path.join(this.path, softconfig.DATA_PATH, savedDir, "Presets");
43
+ this.configPath = path.join(this.path, softconfig.DATA_PATH, savedDir, "Server");
44
+ logger.default.verbose("Creating directories");
45
+ file.mkdir(this.savePath);
46
+ file.mkdir(this.configPath);
47
+ if (!options.noauth) {
48
+ logger.default.verbose("Copying auth files");
49
+ this.copyAuthFiles();
50
+ }
51
+ if (!options.noweb) {
52
+ logger.default.verbose("Creating webserver");
53
+ this.webserver = new index.default(cfg.omegga, this);
54
+ }
55
+ if (!options.noplugin) {
56
+ logger.default.verbose("Creating plugin loader");
57
+ this.pluginLoader = new plugin.PluginLoader(this.path, this);
58
+ }
59
+ this.players = [];
60
+ this.host = void 0;
61
+ this.version = -1;
62
+ this.started = false;
63
+ this.starting = false;
64
+ this.currentMap = "";
65
+ logger.default.verbose("Adding matchers");
66
+ for (const matcher of index$1.default) {
67
+ const { pattern: pattern2, callback } = matcher(this);
68
+ this.addMatcher(pattern2, callback);
69
+ }
70
+ process.on("uncaughtException", async (err) => {
71
+ logger.default.verbose("Uncaught exception", err);
72
+ this.emit("error", err);
73
+ this.webserver?.database?.addChatLog("server", {}, "Server error");
74
+ try {
75
+ await this.stop();
76
+ } catch (e) {
77
+ logger.default.error(e);
78
+ }
79
+ process.exit();
80
+ });
81
+ this.on("start", ({ map }) => {
82
+ this.started = true;
83
+ this.starting = false;
84
+ this.currentMap = map;
85
+ this.writeln("Chat.MessageForUnknownCommands 0");
86
+ this.restoreServer();
87
+ });
88
+ this.on("exit", () => {
89
+ this.stop();
90
+ });
91
+ this.on("closed", () => {
92
+ if (this.started) this.emit("exit");
93
+ if (!this.stopping) this.stop();
94
+ });
95
+ this.on("cmd", (cmd, name) => {
96
+ if (!default_commands.default.includes(cmd) && (!this.pluginLoader || !this.pluginLoader.isCommand(cmd))) {
97
+ this.whisper(name, MISSING_CMD);
98
+ }
99
+ });
100
+ }
101
+ /** attempt to save server state */
102
+ async saveServer(config) {
103
+ if (config.players && this.players.length > 0) {
104
+ logger.default.logp("Getting player positions...");
105
+ const players = await this.getAllPlayerPositions();
106
+ logger.default.logp(`Saving ${players.length} player positions...`);
107
+ const data = players.filter((p) => !p.isDead && p.pos).map((p) => ({ position: p.pos, id: p.player.id }));
108
+ if (players.length > 0)
109
+ fs.writeFileSync(
110
+ path.join(this.path, softconfig.DATA_PATH, "omegga_temp_players.json"),
111
+ JSON.stringify(data)
340
112
  );
341
113
  }
342
- getHostId() {
343
- var _a, _b;
344
- return (_b = (_a = this.host) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : '';
345
- }
346
- saveMinigame(index, name) {
347
- this.writeln(`Server.Minigames.SavePreset ${index} "${name}"`);
348
- }
349
- deleteMinigame(index) {
350
- this.writeln(`Server.Minigames.Delete ${index}`);
351
- }
352
- resetMinigame(index) {
353
- this.writeln(`Server.Minigames.Reset ${index}`);
354
- }
355
- nextRoundMinigame(index) {
356
- this.writeln(`Server.Minigames.NextRound ${index}`);
357
- }
358
- loadMinigame(presetName, owner = '') {
359
- this.writeln(`Server.Minigames.LoadPreset "${presetName}" ${owner ? `"${owner}"` : ''}`);
360
- }
361
- getMinigamePresets() {
362
- const presetPath = (0, path_1.join)(this.presetPath, 'Minigame');
363
- return (0, fs_1.existsSync)(presetPath)
364
- ? (0, glob_1.sync)(presetPath + '/**/*.bp').map(f => (0, path_1.basename)(f).replace(/\.bp$/, ''))
365
- : [];
366
- }
367
- resetEnvironment() {
368
- this.writeln(`Server.Environment.Reset`);
369
- }
370
- async saveEnvironment(presetName) {
371
- await this.addWatcher(/Environment preset saved.$/, {
372
- // request the pawn for this player's controller (should only be one)
373
- exec: () => this.writeln(`Server.Environment.SavePreset "${presetName}"`),
374
- timeoutDelay: 100,
375
- });
376
- }
377
- async getEnvironmentData() {
378
- const saveName = this._tempSavePrefix + Date.now() + '_' + this._tempCounter.environment++;
379
- await this.saveEnvironment(saveName);
380
- const data = this.readEnvironmentData(saveName);
381
- const file = (0, path_1.join)(this.presetPath, 'Environment', saveName + '.bp');
382
- if ((0, fs_1.existsSync)(file))
383
- (0, fs_1.unlinkSync)(file);
384
- return data;
385
- }
386
- readEnvironmentData(saveName) {
387
- if (typeof saveName !== 'string')
388
- throw 'expected name argument for readEnvironmentData';
389
- const file = (0, path_1.join)(this.presetPath, 'Environment', saveName + '.bp');
114
+ if (config.saveWorld) {
115
+ logger.default.logp("Saving world...");
116
+ await this.saveWorld();
117
+ }
118
+ }
119
+ async restartServer() {
120
+ if (this.starting || this.stopping) return;
121
+ if (!this.started) return await this.start();
122
+ const nextWorld = this.getNextWorld();
123
+ if (nextWorld) {
124
+ logger.default.logp("Loading world", nextWorld.file.yellow);
125
+ logger.default.verbose("Next world configured from", nextWorld.source.yellow);
126
+ this.loadWorld(nextWorld.file);
127
+ } else {
128
+ this.changeMap(this.currentMap);
129
+ }
130
+ const res = await Promise.race([
131
+ // wait for the map to change
132
+ new Promise(
133
+ (resolve) => this.once("mapchange", () => resolve("mapchange"))
134
+ ),
135
+ // Timeout after 10 seconds
136
+ new Promise((resolve) => setTimeout(() => resolve("timeout"), 1e4))
137
+ ]);
138
+ logger.default.verbose("Restart result:", res);
139
+ }
140
+ /** attempt to restore the server's state */
141
+ async restoreServer() {
142
+ const tempPlayersFile = path.join(
143
+ this.path,
144
+ softconfig.DATA_PATH,
145
+ "omegga_temp_players.json"
146
+ );
147
+ if (!fs.existsSync(tempPlayersFile)) return;
148
+ try {
149
+ logger.default.logp("Loading previous player positions...");
150
+ const players = JSON.parse(
151
+ fs.readFileSync(tempPlayersFile).toString()
152
+ );
153
+ const callback = (player) => {
154
+ const index2 = players.findIndex((p) => p.id === player.id);
155
+ if (index2 > -1) {
156
+ const { position } = players[index2];
157
+ this.writeln(
158
+ `Chat.Command /TP "${player.name}" ${position.join(" ")} 0`
159
+ );
160
+ players[index2] = players[players.length - 1];
161
+ players.pop();
162
+ }
163
+ };
164
+ this.on("join", callback);
165
+ let timeout = setTimeout(() => {
390
166
  try {
391
- if ((0, fs_1.existsSync)(file))
392
- return JSON.parse((0, fs_1.readFileSync)(file).toString());
393
- }
394
- catch (err) {
395
- logger_1.default.verbose('Error parsing save data in readEnvironmentData', err);
396
- }
167
+ this.off("join", callback);
168
+ if (fs.existsSync(tempPlayersFile)) fs.unlinkSync(tempPlayersFile);
169
+ } catch (err) {
170
+ logger.default.error("Error removing omegga_temp_players.json", err);
171
+ }
172
+ }, 1e4);
173
+ this.once("changemap", () => {
174
+ clearTimeout(timeout);
175
+ this.off("join", callback);
176
+ });
177
+ } catch (err) {
178
+ logger.default.error("Error restoring previous server state", err);
179
+ }
180
+ }
181
+ /**
182
+ * start webserver, load plugins, start the brickadia server
183
+ * this should not be called by a plugin
184
+ */
185
+ //
186
+ async start() {
187
+ this.starting = true;
188
+ if (this.webserver) await this.webserver.start();
189
+ if (this.pluginLoader) {
190
+ logger.default.verbose("Scanning for plugins");
191
+ await this.pluginLoader.scan();
192
+ logger.default.verbose("Loading plugins");
193
+ await this.pluginLoader.reload();
194
+ }
195
+ logger.default.verbose("Starting Brickadia");
196
+ super.start();
197
+ this.emit("server:starting");
198
+ }
199
+ /**
200
+ * unload plugins and stop the server
201
+ * this should not be called by a plugin
202
+ */
203
+ async stop() {
204
+ if (!this.started && !this.starting) {
205
+ logger.default.verbose("Stop called while server wasn't started or was starting");
206
+ return;
207
+ }
208
+ if (this.stopping) {
209
+ logger.default.verbose("Stop called while server was starting");
210
+ return;
211
+ }
212
+ this.stopping = true;
213
+ this.emit("server:stopping");
214
+ if (this.pluginLoader) {
215
+ logger.default.verbose("Unloading plugins");
216
+ await this.pluginLoader.unload();
217
+ }
218
+ logger.default.verbose("Stopping server");
219
+ super.stop();
220
+ const res = await Promise.race([
221
+ new Promise((resolve) => this.once("exit", () => resolve("exit"))),
222
+ // Timeout after 10 seconds
223
+ new Promise((resolve) => setTimeout(() => resolve("timeout"), 1e4))
224
+ ]);
225
+ logger.default.verbose("Stop result:", res);
226
+ if (this.stopping) this.emit("server:stopped");
227
+ this.stopping = false;
228
+ this.started = false;
229
+ this.starting = false;
230
+ this.players = [];
231
+ }
232
+ /**
233
+ * Copies auth files from home config dir
234
+ * this should never be called by a plugin
235
+ */
236
+ copyAuthFiles() {
237
+ const authDir = this.config.server.authDir ?? softconfig.CONFIG_AUTH_DIR;
238
+ const savedDir = this.config.server.savedDir ?? softconfig.CONFIG_SAVED_DIR;
239
+ const authPath = path.join(this.path, softconfig.DATA_PATH, savedDir, authDir);
240
+ const homeAuthPath = path.join(
241
+ softconfig.CONFIG_HOME,
242
+ (savedDir !== softconfig.CONFIG_SAVED_DIR ? savedDir : "") + authDir
243
+ );
244
+ file.copyFiles(homeAuthPath, authPath, softconfig.BRICKADIA_AUTH_FILES);
245
+ }
246
+ // TODO: split messages that longer than 512 characters
247
+ // TODO: delete characters that are known to crash the game
248
+ broadcast(...messages) {
249
+ messages.flatMap((m) => m.toString().split("\n")).filter((m) => m.length < 512).forEach((m) => this.writeln(`Chat.Broadcast ${m}`));
250
+ }
251
+ whisper(target, ...messages) {
252
+ if (typeof target !== "object") target = this.getPlayer(target);
253
+ messages.flatMap((m) => m.toString().split("\n")).filter((m) => m.length < 512).forEach(
254
+ (m) => this.writeln(
255
+ `Chat.Whisper "${target.name}" ${m}`
256
+ )
257
+ );
258
+ }
259
+ middlePrint(target, message) {
260
+ if (typeof target !== "object") target = this.getPlayer(target);
261
+ if (message.length > 512) return;
262
+ this.writeln(
263
+ `Chat.StatusMessage "${target.name}" ${message}`
264
+ );
265
+ }
266
+ getPlayers() {
267
+ return this.players.map((p) => ({ ...p }));
268
+ }
269
+ getRoleSetup() {
270
+ return file.readWatchedJSON(path.join(this.configPath, "RoleSetup2.json")) ?? file.readWatchedJSON(path.join(this.configPath, "RoleSetup.json"));
271
+ }
272
+ getRoleAssignments() {
273
+ return file.readWatchedJSON(
274
+ path.join(this.configPath, "RoleAssignments.json")
275
+ );
276
+ }
277
+ getBanList() {
278
+ return file.readWatchedJSON(path.join(this.configPath, "BanList.json"));
279
+ }
280
+ getNameCache() {
281
+ return file.readWatchedJSON(
282
+ path.join(this.configPath, "PlayerNameCache.json")
283
+ );
284
+ }
285
+ getPlayer(target) {
286
+ return this.players.find(
287
+ (p) => p.name === target || p.id === target || p.controller === target || p.state === target
288
+ );
289
+ }
290
+ findPlayerByName(name) {
291
+ name = name.toLowerCase();
292
+ const exploded = index$2.pattern.explode(name);
293
+ return this.players.find((p) => p.name === name || p.displayName === name) || // find by exact match
294
+ this.players.find(
295
+ (p) => p.name.indexOf(name) > -1 || p.displayName.indexOf(name) > -1
296
+ ) || // find by rough match
297
+ this.players.find(
298
+ (p) => p.name.match(exploded) || p.displayName.match(exploded)
299
+ );
300
+ }
301
+ getHostId() {
302
+ return this.host?.id ?? "";
303
+ }
304
+ saveMinigame(index2, name) {
305
+ this.writeln(`Server.Minigames.SavePreset ${index2} "${name}"`);
306
+ }
307
+ deleteMinigame(index2) {
308
+ this.writeln(`Server.Minigames.Delete ${index2}`);
309
+ }
310
+ resetMinigame(index2) {
311
+ this.writeln(`Server.Minigames.Reset ${index2}`);
312
+ }
313
+ nextRoundMinigame(index2) {
314
+ this.writeln(`Server.Minigames.NextRound ${index2}`);
315
+ }
316
+ loadMinigame(presetName, owner = "") {
317
+ this.writeln(
318
+ `Server.Minigames.LoadPreset "${presetName}" ${owner ? `"${owner}"` : ""}`
319
+ );
320
+ }
321
+ getMinigamePresets() {
322
+ const presetPath = path.join(this.presetPath, "Minigame");
323
+ return fs.existsSync(presetPath) ? glob.sync(presetPath + "/**/*.bp").map((f) => path.basename(f).replace(/\.bp$/, "")) : [];
324
+ }
325
+ resetEnvironment() {
326
+ this.writeln(`Server.Environment.Reset`);
327
+ }
328
+ async saveEnvironment(presetName) {
329
+ await this.addWatcher(/Environment preset saved.$/, {
330
+ // request the pawn for this player's controller (should only be one)
331
+ exec: () => this.writeln(`Server.Environment.SavePreset "${presetName}"`),
332
+ timeoutDelay: 100
333
+ });
334
+ }
335
+ async getEnvironmentData() {
336
+ const saveName = this._tempSavePrefix + Date.now() + "_" + this._tempCounter.environment++;
337
+ await this.saveEnvironment(saveName);
338
+ const data = this.readEnvironmentData(saveName);
339
+ const file2 = path.join(this.presetPath, "Environment", saveName + ".bp");
340
+ if (fs.existsSync(file2)) fs.unlinkSync(file2);
341
+ return data;
342
+ }
343
+ readEnvironmentData(saveName) {
344
+ if (typeof saveName !== "string")
345
+ throw "expected name argument for readEnvironmentData";
346
+ const file2 = path.join(this.presetPath, "Environment", saveName + ".bp");
347
+ try {
348
+ if (fs.existsSync(file2)) return JSON.parse(fs.readFileSync(file2).toString());
349
+ } catch (err) {
350
+ logger.default.verbose("Error parsing save data in readEnvironmentData", err);
351
+ }
352
+ return null;
353
+ }
354
+ loadEnvironment(presetName) {
355
+ this.writeln(`Server.Environment.LoadPreset ${presetName}`);
356
+ }
357
+ loadEnvironmentData(preset) {
358
+ if ("data" in preset) preset = preset.data.groups;
359
+ const saveFile = this._tempSavePrefix + Date.now() + "_" + this._tempCounter.environment++;
360
+ const path$1 = path.join(this.presetPath, "Environment", saveFile + ".bp");
361
+ fs.writeFileSync(
362
+ path$1,
363
+ JSON.stringify({
364
+ formatVersion: "1",
365
+ presetVersion: "1",
366
+ type: "Environment",
367
+ data: {
368
+ groups: {
369
+ ...preset
370
+ }
371
+ }
372
+ })
373
+ );
374
+ this.loadEnvironment(saveFile);
375
+ setTimeout(() => fs.unlinkSync(path$1), 5e3);
376
+ }
377
+ getEnvironmentPresets() {
378
+ const presetPath = path.join(this.presetPath, "Environment");
379
+ return fs.existsSync(presetPath) ? glob.sync(presetPath + "/**/*.bp").map((f) => path.basename(f).replace(/\.bp$/, "")) : [];
380
+ }
381
+ clearBricks(target, quiet = false) {
382
+ if (typeof target === "object" && target.id) target = target.id;
383
+ else if (typeof target === "string" && !index$2.uuid.match(target)) {
384
+ const player = this.getPlayer(target);
385
+ target = player && player.id;
386
+ }
387
+ if (!target) return;
388
+ this.writeln(`Bricks.Clear ${target} ${quiet ? 1 : ""}`);
389
+ }
390
+ clearRegion(region, options) {
391
+ let target = "";
392
+ if (options && typeof options.target === "object")
393
+ target = " " + options.target.id;
394
+ if (typeof target === "string" && !index$2.uuid.match(target)) {
395
+ const player = this.getPlayer(target);
396
+ if (player) target = " " + player.id;
397
+ }
398
+ this.writeln(
399
+ `Bricks.ClearRegion ${region.center.join(" ")} ${region.extent.join(
400
+ " "
401
+ )}${target}`
402
+ );
403
+ }
404
+ clearAllBricks(quiet = false) {
405
+ this.writeln(`Bricks.ClearAll ${quiet ? 1 : ""}`);
406
+ }
407
+ saveBricks(saveName, region) {
408
+ if (!saveName) return;
409
+ if (!(saveName.startsWith('"') && saveName.endsWith('"')))
410
+ saveName = `"${saveName}"`;
411
+ if (region?.center && region?.extent)
412
+ this.writeln(
413
+ `Bricks.SaveRegion ${saveName} ${region.center.join(
414
+ " "
415
+ )} ${region.extent.join(" ")}`
416
+ );
417
+ else this.writeln(`Bricks.Save ${saveName}`);
418
+ }
419
+ async saveBricksAsync(saveName, region) {
420
+ if (!saveName) return;
421
+ let saveNameClean = saveName;
422
+ if (!(saveName.startsWith('"') && saveName.endsWith('"')))
423
+ saveNameClean = `"${saveName}"`;
424
+ const command = region?.center && region?.extent ? `Bricks.SaveRegion ${saveNameClean} ${region.center.join(
425
+ " "
426
+ )} ${region.extent.join(" ")}` : `Bricks.Save ${saveNameClean}`;
427
+ await this.watchLogChunk(command, /^(LogBrickSerializer|LogTemp): (.+)$/, {
428
+ first: (match) => match[0].endsWith(saveName + ".brs..."),
429
+ last: (match) => Boolean(
430
+ match[2].match(
431
+ /Saved .+ bricks and .+ components from .+ owners|Error: No bricks in grid!|Error: No bricks selected to save!/
432
+ )
433
+ ),
434
+ afterMatchDelay: 0,
435
+ timeoutDelay: 3e4
436
+ });
437
+ }
438
+ loadBricks(saveName, {
439
+ offX = 0,
440
+ offY = 0,
441
+ offZ = 0,
442
+ quiet = false,
443
+ correctPalette = false,
444
+ correctCustom = false
445
+ } = {}) {
446
+ if (!(saveName.startsWith('"') && saveName.endsWith('"')))
447
+ saveName = `"${saveName}"`;
448
+ this.writeln(
449
+ `Bricks.Load ${saveName} ${offX} ${offY} ${offZ} ${quiet ? 1 : 0} ${correctPalette ? 1 : 0} ${correctCustom ? 1 : 0}`
450
+ );
451
+ }
452
+ loadBricksOnPlayer(saveName, player, {
453
+ offX = 0,
454
+ offY = 0,
455
+ offZ = 0,
456
+ correctPalette = false,
457
+ correctCustom = false
458
+ } = {}) {
459
+ player = typeof player === "string" ? this.getPlayer(player) : player;
460
+ if (!player) return;
461
+ if (!(saveName.startsWith('"') && saveName.endsWith('"')))
462
+ saveName = `"${saveName}"`;
463
+ this.writeln(
464
+ `Bricks.LoadTemplate ${saveName} ${offX} ${offY} ${offZ} ${correctPalette ? 1 : 0} ${correctCustom ? 1 : 0} "${player.name}"`
465
+ );
466
+ }
467
+ getSaves() {
468
+ return fs.existsSync(this.savePath) ? glob.sync(this.savePath + "/**/*.brs") : [];
469
+ }
470
+ getWorlds() {
471
+ return fs.existsSync(this.worldPath) ? glob.sync(this.worldPath + "/**/*.brdb") : [];
472
+ }
473
+ getSavePath(saveName) {
474
+ const file2 = path.join(
475
+ this.savePath,
476
+ saveName.endsWith(".brs") ? saveName : saveName + ".brs"
477
+ );
478
+ return fs.existsSync(file2) ? file2 : void 0;
479
+ }
480
+ getWorldPath(worldName) {
481
+ const file2 = path.join(
482
+ this.worldPath,
483
+ worldName.endsWith(".brdb") ? worldName : worldName + ".brdb"
484
+ );
485
+ return fs.existsSync(file2) ? file2 : void 0;
486
+ }
487
+ async getWorldRevisions(worldName) {
488
+ if (!this.started || this.starting || this.stopping) {
489
+ throw new Error("Server is not started");
490
+ }
491
+ worldName = worldName.replace(/\.brdb$/i, "");
492
+ if (!worldName || !this.getWorldPath(worldName)) {
493
+ throw new Error(`World "${worldName}" does not exist`);
494
+ }
495
+ let numRevisions = 0;
496
+ const revisionsRaw = await this.watchLogChunk(
497
+ `BR.World.ListRevisions "${worldName}"`,
498
+ /^LogBRBundleManager: (There are (?<numRevisions>\d+) revisions|Revision (?<revision>\d+) - (?<date>[\d.-]+): (?<note>.+))$/,
499
+ {
500
+ last: (match) => Number(match.groups.reverse) === numRevisions,
501
+ first: (match) => {
502
+ if (match.groups?.numRevisions !== void 0) {
503
+ numRevisions = Number(match.groups.numRevisions);
504
+ return true;
505
+ }
506
+ return false;
507
+ }
508
+ }
509
+ );
510
+ if (!revisionsRaw) return [];
511
+ return revisionsRaw.map((match) => {
512
+ if (match.groups?.numRevisions !== void 0) {
397
513
  return null;
398
- }
399
- loadEnvironment(presetName) {
400
- this.writeln(`Server.Environment.LoadPreset ${presetName}`);
401
- }
402
- loadEnvironmentData(preset) {
403
- if ('data' in preset)
404
- preset = preset.data.groups;
405
- const saveFile = this._tempSavePrefix + Date.now() + '_' + this._tempCounter.environment++;
406
- const path = (0, path_1.join)(this.presetPath, 'Environment', saveFile + '.bp');
407
- (0, fs_1.writeFileSync)(path, JSON.stringify({
408
- formatVersion: '1',
409
- presetVersion: '1',
410
- type: 'Environment',
411
- data: {
412
- groups: {
413
- ...preset,
414
- },
415
- },
416
- }));
417
- this.loadEnvironment(saveFile);
418
- // this is lazy, but environments should load much faster than builds
419
- // do, so it's not really worth keeping track of logs for this
420
- setTimeout(() => (0, fs_1.unlinkSync)(path), 5000);
421
- }
422
- getEnvironmentPresets() {
423
- const presetPath = (0, path_1.join)(this.presetPath, 'Environment');
424
- return (0, fs_1.existsSync)(presetPath)
425
- ? (0, glob_1.sync)(presetPath + '/**/*.bp').map(f => (0, path_1.basename)(f).replace(/\.bp$/, ''))
426
- : [];
427
- }
428
- clearBricks(target, quiet = false) {
429
- // target is a player object, just use that id
430
- if (typeof target === 'object' && target.id)
431
- target = target.id;
432
- // if the target isn't a uuid already, find the player by name or controller and use that uuid
433
- else if (typeof target === 'string' && !_util_1.uuid.match(target)) {
434
- // only set the target if the player exists
435
- const player = this.getPlayer(target);
436
- target = player && player.id;
437
- }
438
- if (!target)
439
- return;
440
- this.writeln(`Bricks.Clear ${target} ${quiet ? 1 : ''}`);
441
- }
442
- clearRegion(region, options) {
443
- let target = '';
444
- // target is a player object, just use that id
445
- if (options && typeof options.target === 'object')
446
- target = ' ' + options.target.id;
447
- // if the target isn't a uuid already, find the player by name or controller and use that uuid
448
- if (typeof target === 'string' && !_util_1.uuid.match(target)) {
449
- // only set the target if the player exists
450
- const player = this.getPlayer(target);
451
- if (player)
452
- target = ' ' + player.id;
453
- }
454
- this.writeln(`Bricks.ClearRegion ${region.center.join(' ')} ${region.extent.join(' ')}${target}`);
455
- }
456
- clearAllBricks(quiet = false) {
457
- this.writeln(`Bricks.ClearAll ${quiet ? 1 : ''}`);
458
- }
459
- saveBricks(saveName, region) {
460
- if (!saveName)
461
- return;
462
- // add quotes around the filename if it doesn't have them (backwards compat w/ plugins)
463
- if (!(saveName.startsWith('"') && saveName.endsWith('"')))
464
- saveName = `"${saveName}"`;
465
- if ((region === null || region === void 0 ? void 0 : region.center) && (region === null || region === void 0 ? void 0 : region.extent))
466
- this.writeln(`Bricks.SaveRegion ${saveName} ${region.center.join(' ')} ${region.extent.join(' ')}`);
467
- else
468
- this.writeln(`Bricks.Save ${saveName}`);
469
- }
470
- async saveBricksAsync(saveName, region) {
471
- if (!saveName)
472
- return;
473
- let saveNameClean = saveName;
474
- // add quotes around the filename if it doesn't have them (backwards compat w/ plugins)
475
- if (!(saveName.startsWith('"') && saveName.endsWith('"')))
476
- saveNameClean = `"${saveName}"`;
477
- const command = (region === null || region === void 0 ? void 0 : region.center) && (region === null || region === void 0 ? void 0 : region.extent)
478
- ? `Bricks.SaveRegion ${saveNameClean} ${region.center.join(' ')} ${region.extent.join(' ')}`
479
- : `Bricks.Save ${saveNameClean}`;
480
- // wait for the server to save the file
481
- await this.watchLogChunk(command, /^(LogBrickSerializer|LogTemp): (.+)$/, {
482
- first: match => match[0].endsWith(saveName + '.brs...'),
483
- last: match => Boolean(match[2].match(/Saved .+ bricks and .+ components from .+ owners|Error: No bricks in grid!|Error: No bricks selected to save!/)),
484
- afterMatchDelay: 0,
485
- timeoutDelay: 30000,
486
- });
487
- }
488
- loadBricks(saveName, { offX = 0, offY = 0, offZ = 0, quiet = false, correctPalette = false, correctCustom = false, } = {}) {
489
- // add quotes around the filename if it doesn't have them (backwards compat w/ plugins)
490
- if (!(saveName.startsWith('"') && saveName.endsWith('"')))
491
- saveName = `"${saveName}"`;
492
- this.writeln(`Bricks.Load ${saveName} ${offX} ${offY} ${offZ} ${quiet ? 1 : 0} ${correctPalette ? 1 : 0} ${correctCustom ? 1 : 0}`);
493
- }
494
- loadBricksOnPlayer(saveName, player, { offX = 0, offY = 0, offZ = 0, correctPalette = false, correctCustom = false, } = {}) {
495
- player = typeof player === 'string' ? this.getPlayer(player) : player;
496
- if (!player)
497
- return;
498
- // add quotes around the filename if it doesn't have them (backwards compat w/ plugins)
499
- if (!(saveName.startsWith('"') && saveName.endsWith('"')))
500
- saveName = `"${saveName}"`;
501
- this.writeln(`Bricks.LoadTemplate ${saveName} ${offX} ${offY} ${offZ} ${correctPalette ? 1 : 0} ${correctCustom ? 1 : 0} "${player.name}"`);
502
- }
503
- getSaves() {
504
- return (0, fs_1.existsSync)(this.savePath) ? (0, glob_1.sync)(this.savePath + '/**/*.brs') : [];
505
- }
506
- getWorlds() {
507
- return (0, fs_1.existsSync)(this.worldPath)
508
- ? (0, glob_1.sync)(this.worldPath + '/**/*.brdb')
509
- : [];
510
- }
511
- getSavePath(saveName) {
512
- const file = (0, path_1.join)(this.savePath, saveName.endsWith('.brs') ? saveName : saveName + '.brs');
513
- return (0, fs_1.existsSync)(file) ? file : undefined;
514
- }
515
- getWorldPath(worldName) {
516
- const file = (0, path_1.join)(this.worldPath, worldName.endsWith('.brdb') ? worldName : worldName + '.brdb');
517
- return (0, fs_1.existsSync)(file) ? file : undefined;
518
- }
519
- async getWorldRevisions(worldName) {
520
- if (!this.started || this.starting || this.stopping) {
521
- throw new Error('Server is not started');
522
- }
523
- worldName = worldName.replace(/\.brdb$/i, '');
524
- if (!worldName || !this.getWorldPath(worldName)) {
525
- throw new Error(`World "${worldName}" does not exist`);
526
- }
527
- /*
528
- LogBRBundleManager: There are 2 revisions
529
- LogBRBundleManager: Revision 1 - 2001.02.03-04.05.06: Initial Revision
530
- LogBRBundleManager: Revision 2 - 2001.02.03-04.05.06: Manual Save
531
- */
532
- let numRevisions = 0;
533
- const revisionsRaw = await this.watchLogChunk(`BR.World.ListRevisions "${worldName}"`, /^LogBRBundleManager: (There are (?<numRevisions>\d+) revisions|Revision (?<revision>\d+) - (?<date>[\d.-]+): (?<note>.+))$/, {
534
- last: match => Number(match.groups.reverse) === numRevisions,
535
- first: match => {
536
- var _a;
537
- if (((_a = match.groups) === null || _a === void 0 ? void 0 : _a.numRevisions) !== undefined) {
538
- numRevisions = Number(match.groups.numRevisions);
539
- return true;
540
- }
541
- return false;
542
- },
543
- });
544
- if (!revisionsRaw)
545
- return [];
546
- return revisionsRaw
547
- .map(match => {
548
- var _a;
549
- if (((_a = match.groups) === null || _a === void 0 ? void 0 : _a.numRevisions) !== undefined) {
550
- return null;
551
- }
552
- const revision = match.groups.revision
553
- ? Number(match.groups.revision)
554
- : 0;
555
- // parse date from YYYY.MM.DD-HH.MM.SS to YYYY-MM-DDTHH:MM:SSZ
556
- const dateStr = match.groups.date.replace(/(\d{4})\.(\d{2})\.(\d{2})-(\d{2})\.(\d{2})\.(\d{2})/, '$1-$2-$3T$4:$5:$6Z');
557
- const date = new Date(dateStr);
558
- const note = match.groups.note || '';
559
- return { index: revision, date, note };
560
- })
561
- .filter(Boolean);
562
- }
563
- async loadWorld(worldName) {
564
- worldName = worldName.replace(/\.brdb$/i, '');
565
- if (!worldName || !this.getWorldPath(worldName))
566
- return false;
567
- this.writeln(`BR.World.Load "${worldName}"`);
568
- const res = await Promise.race([
569
- // wait for the map to change
570
- new Promise(resolve => this.once('mapchange', () => resolve('mapchange'))),
571
- // Timeout after 10 seconds
572
- new Promise(resolve => setTimeout(() => resolve('timeout'), 10000)),
573
- ]);
574
- logger_1.default.verbose('LoadWorld', worldName, 'result:', res);
575
- return res === 'mapchange';
576
- }
577
- async loadWorldRevision(worldName, revision) {
578
- worldName = worldName.replace(/\.brdb$/i, '');
579
- if (!worldName || !this.getWorldPath(worldName))
580
- return false;
581
- if (typeof revision !== 'number' || revision < 1) {
582
- throw new Error(`Invalid revision number: ${revision}`);
583
- }
584
- this.writeln(`BR.World.LoadRevision "${worldName}" ${revision}`);
585
- const res = await Promise.race([
586
- // wait for the map to change
587
- new Promise(resolve => this.once('mapchange', () => resolve('mapchange'))),
588
- // Timeout after 10 seconds
589
- new Promise(resolve => setTimeout(() => resolve('timeout'), 10000)),
590
- ]);
591
- logger_1.default.verbose('LoadWorld', worldName, 'result:', res);
592
- return res === 'mapchange';
593
- }
594
- async saveWorldAs(worldName) {
595
- var _a, _b;
596
- if (!worldName)
597
- return false;
598
- if (this.stopping || this.starting || !this.started)
599
- return false;
600
- if (this.getWorldPath(worldName)) {
601
- return false;
602
- }
603
- worldName = worldName.replace(/\.brdb$/i, '');
604
- try {
605
- const match = await this.addWatcher((_line, match) => {
606
- var _a;
607
- if (((_a = match === null || match === void 0 ? void 0 : match.groups) === null || _a === void 0 ? void 0 : _a.generator) !== 'LogBRWorldManager')
608
- return;
609
- const ok = match.groups.data.match(/^World files saved after /);
610
- const err = !match.groups.data.startsWith('Error: Failed to capture minigame settings') &&
611
- match.groups.data.match(/^Error: (World already exists|Failed to create new world)?/);
612
- return ok ? { res: true } : err ? { res: false } : undefined;
613
- }, {
614
- exec: () => {
615
- this.writeln(`BR.World.SaveAs "${worldName}"`);
616
- },
617
- timeoutDelay: 2000,
618
- });
619
- return (_b = (_a = match === null || match === void 0 ? void 0 : match[0]) === null || _a === void 0 ? void 0 : _a['res']) !== null && _b !== void 0 ? _b : false;
620
- }
621
- catch (err) {
622
- return false;
623
- }
624
- }
625
- async saveWorld() {
626
- var _a, _b;
627
- // Don't allow saving while the server is starting or stopping
628
- if (this.stopping || this.starting || !this.started)
629
- return false;
630
- try {
631
- const match = await this.addWatcher((_line, match) => {
632
- var _a;
633
- if (((_a = match === null || match === void 0 ? void 0 : match.groups) === null || _a === void 0 ? void 0 : _a.generator) !== 'LogBRWorldManager')
634
- return;
635
- const ok = match.groups.data.match(/^World files saved after /);
636
- const err = !match.groups.data.startsWith('Error: Failed to capture minigame settings') &&
637
- match.groups.data.match(/^Error: (World has not been saved\.)?/);
638
- return ok ? { res: true } : err ? { res: false } : undefined;
639
- }, {
640
- exec: () => {
641
- this.writeln(`BR.World.Save 0`);
642
- },
643
- timeoutDelay: 2000,
644
- });
645
- return (_b = (_a = match === null || match === void 0 ? void 0 : match[0]) === null || _a === void 0 ? void 0 : _a['res']) !== null && _b !== void 0 ? _b : false;
646
- }
647
- catch (err) {
648
- return false;
649
- }
650
- }
651
- async createEmptyWorld(worldName, map = 'Plate') {
652
- var _a, _b;
653
- if (!worldName)
654
- return;
655
- worldName = worldName.replace(/\.brdb$/i, '');
656
- try {
657
- const match = await this.addWatcher((_line, match) => {
658
- var _a;
659
- if (((_a = match === null || match === void 0 ? void 0 : match.groups) === null || _a === void 0 ? void 0 : _a.generator) !== 'LogBRWorldManager')
660
- return;
661
- const ok = match.groups.data.match(/^World files saved after /);
662
- const err = match.groups.data.match(/^Error: (Invalid preset|World already exists|Failed to create new world)?/);
663
- return ok ? { res: true } : err ? { res: false } : undefined;
664
- }, {
665
- exec: () => {
666
- this.writeln(`BR.World.CreateEmpty "${worldName}" ${map}`);
667
- },
668
- timeoutDelay: 2000,
669
- });
670
- return (_b = (_a = match === null || match === void 0 ? void 0 : match[0]) === null || _a === void 0 ? void 0 : _a['res']) !== null && _b !== void 0 ? _b : false;
671
- }
672
- catch (err) {
673
- return false;
674
- }
675
- }
676
- writeSaveData(saveName, saveData) {
677
- if (typeof saveName !== 'string')
678
- throw 'expected name argument for writeSaveData';
679
- const file = (0, path_1.join)(this.savePath, saveName + '.brs');
680
- if (!file.startsWith(this.savePath))
681
- throw 'save file not in Saved/Builds directory';
682
- (0, fs_1.writeFileSync)(file, new Uint8Array((0, brs_js_1.write)(saveData)));
683
- }
684
- readSaveData(saveName, nobricks = false) {
685
- if (typeof saveName !== 'string')
686
- throw 'expected name argument for readSaveData';
687
- const file = this.getSavePath(saveName);
688
- if (!file || !file.startsWith(this.savePath))
689
- throw 'save file not in Saved/Builds directory';
690
- if (file)
691
- return (0, brs_js_1.read)((0, fs_1.readFileSync)(file), {
692
- preview: false,
693
- bricks: !nobricks,
694
- });
695
- }
696
- async loadSaveData(saveData, { offX = 0, offY = 0, offZ = 0, quiet = false, correctPalette = false, correctCustom = false, } = {}) {
697
- const saveFile = this._tempSavePrefix + Date.now() + '_' + this._tempCounter.save++;
698
- // write savedata to file
699
- this.writeSaveData(saveFile, saveData);
700
- // wait for the server to finish reading the save
701
- await this.watchLogChunk(`Bricks.Load "${saveFile}" ${offX} ${offY} ${offZ} ${quiet ? 1 : 0} ${correctPalette ? 1 : 0} ${correctCustom ? 1 : 0}`, /^LogBrickSerializer: (.+)$/, {
702
- first: match => match[0].endsWith(saveFile + '.brs...'),
703
- last: match => Boolean(match[1].match(/Read .+ bricks/)),
704
- afterMatchDelay: 0,
705
- timeoutDelay: 30000,
706
- });
707
- // delete the save file after we're done
708
- const savePath = this.getSavePath(saveFile);
709
- if (savePath) {
710
- (0, fs_1.unlinkSync)(savePath);
711
- }
712
- }
713
- async loadSaveDataOnPlayer(saveData, player, { offX = 0, offY = 0, offZ = 0, correctPalette = false, correctCustom = false, } = {}) {
714
- player = typeof player === 'string' ? this.getPlayer(player) : player;
715
- if (!player)
716
- return;
717
- const saveFile = this._tempSavePrefix + Date.now() + '_' + this._tempCounter.save++;
718
- // write savedata to file
719
- this.writeSaveData(saveFile, saveData);
720
- // wait for the server to finish reading the save
721
- await this.watchLogChunk(`Bricks.LoadTemplate "${saveFile}" ${offX} ${offY} ${offZ} ${correctPalette ? 1 : 0} ${correctCustom ? 1 : 0} "${player.name}"`, /^LogBrickSerializer: (.+)$/, {
722
- first: match => match[0].endsWith(saveFile + '.brs...'),
723
- last: match => Boolean(match[1].match(/Read .+ bricks/)),
724
- afterMatchDelay: 0,
725
- timeoutDelay: 30000,
726
- });
727
- // delete the save file after we're done
728
- const savePath = this.getSavePath(saveFile);
729
- if (savePath) {
730
- (0, fs_1.unlinkSync)(savePath);
731
- }
732
- }
733
- async getSaveData(region) {
734
- const saveFile = this._tempSavePrefix + Date.now() + '_' + this._tempCounter.save++;
735
- await this.saveBricksAsync(saveFile, region);
736
- // read the save file
737
- const savePath = this.getSavePath(saveFile);
738
- if (savePath) {
739
- // read and parse the save file
740
- const saveData = (0, brs_js_1.read)((0, fs_1.readFileSync)(savePath));
741
- // delete the save file after we're done reading it
742
- (0, fs_1.unlinkSync)(savePath);
743
- // return the parsed save
744
- return saveData;
745
- }
746
- return undefined;
747
- }
748
- // TODO: switch this to use worlds...
749
- async changeMap(map) {
750
- if (!map)
751
- return;
752
- // ServerTravel requires /Game/Maps/Plate/Plate instead of Plate
753
- const brName = _util_1.map.n2brn(map);
754
- // wait for the server to change maps
755
- const match = await this.addWatcher(/^.*(LogLoad: Took .+ seconds to LoadMap\((?<map>.+)\))|(ERROR: The map .+)$/, {
756
- timeoutDelay: 30000,
757
- exec: () => this.writeln(`ServerTravel ${brName}`),
758
- });
759
- const success = !!(match &&
760
- match[0] &&
761
- match[0].groups &&
762
- match[0].groups.map);
763
- return success;
764
- }
765
- async getPlugin(name) {
766
- const plugin = this.pluginLoader.plugins.find(p => p.getName() === name);
767
- if (plugin) {
768
- return {
769
- name,
770
- documentation: plugin.getDocumentation(),
771
- loaded: plugin.isLoaded(),
772
- emitPlugin: (event, ...args) => {
773
- return plugin.emitPlugin(event, 'unsafe', args);
774
- },
775
- };
776
- }
777
- else {
778
- return null;
779
- }
780
- }
514
+ }
515
+ const revision = match.groups.revision ? Number(match.groups.revision) : 0;
516
+ const dateStr = match.groups.date.replace(
517
+ /(\d{4})\.(\d{2})\.(\d{2})-(\d{2})\.(\d{2})\.(\d{2})/,
518
+ "$1-$2-$3T$4:$5:$6Z"
519
+ );
520
+ const date = new Date(dateStr);
521
+ const note = match.groups.note || "";
522
+ return { index: revision, date, note };
523
+ }).filter(Boolean);
524
+ }
525
+ async loadWorld(worldName) {
526
+ worldName = worldName.replace(/\.brdb$/i, "");
527
+ if (!worldName || !this.getWorldPath(worldName)) return false;
528
+ this.writeln(`BR.World.Load "${worldName}"`);
529
+ const res = await Promise.race([
530
+ // wait for the map to change
531
+ new Promise(
532
+ (resolve) => this.once("mapchange", () => resolve("mapchange"))
533
+ ),
534
+ // Timeout after 10 seconds
535
+ new Promise((resolve) => setTimeout(() => resolve("timeout"), 1e4))
536
+ ]);
537
+ logger.default.verbose("LoadWorld", worldName, "result:", res);
538
+ return res === "mapchange";
539
+ }
540
+ async loadWorldRevision(worldName, revision) {
541
+ worldName = worldName.replace(/\.brdb$/i, "");
542
+ if (!worldName || !this.getWorldPath(worldName)) return false;
543
+ if (typeof revision !== "number" || revision < 1) {
544
+ throw new Error(`Invalid revision number: ${revision}`);
545
+ }
546
+ this.writeln(`BR.World.LoadRevision "${worldName}" ${revision}`);
547
+ const res = await Promise.race([
548
+ // wait for the map to change
549
+ new Promise(
550
+ (resolve) => this.once("mapchange", () => resolve("mapchange"))
551
+ ),
552
+ // Timeout after 10 seconds
553
+ new Promise((resolve) => setTimeout(() => resolve("timeout"), 1e4))
554
+ ]);
555
+ logger.default.verbose("LoadWorld", worldName, "result:", res);
556
+ return res === "mapchange";
557
+ }
558
+ async saveWorldAs(worldName) {
559
+ if (!worldName) return false;
560
+ if (this.stopping || this.starting || !this.started) return false;
561
+ if (this.getWorldPath(worldName)) {
562
+ return false;
563
+ }
564
+ worldName = worldName.replace(/\.brdb$/i, "");
565
+ try {
566
+ const match = await this.addWatcher(
567
+ (_line, match2) => {
568
+ if (match2?.groups?.generator !== "LogBRWorldManager") return;
569
+ const ok = match2.groups.data.match(/^World files saved after /);
570
+ const err = !match2.groups.data.startsWith(
571
+ "Error: Failed to capture minigame settings"
572
+ ) && match2.groups.data.match(
573
+ /^Error: (World already exists|Failed to create new world)?/
574
+ );
575
+ return ok ? { res: true } : err ? { res: false } : void 0;
576
+ },
577
+ {
578
+ exec: () => {
579
+ this.writeln(`BR.World.SaveAs "${worldName}"`);
580
+ },
581
+ timeoutDelay: 2e3
582
+ }
583
+ );
584
+ return match?.[0]?.["res"] ?? false;
585
+ } catch (err) {
586
+ return false;
587
+ }
588
+ }
589
+ async saveWorld() {
590
+ if (this.stopping || this.starting || !this.started) return false;
591
+ try {
592
+ const match = await this.addWatcher(
593
+ (_line, match2) => {
594
+ if (match2?.groups?.generator !== "LogBRWorldManager") return;
595
+ const ok = match2.groups.data.match(/^World files saved after /);
596
+ const err = !match2.groups.data.startsWith(
597
+ "Error: Failed to capture minigame settings"
598
+ ) && match2.groups.data.match(/^Error: (World has not been saved\.)?/);
599
+ return ok ? { res: true } : err ? { res: false } : void 0;
600
+ },
601
+ {
602
+ exec: () => {
603
+ this.writeln(`BR.World.Save 0`);
604
+ },
605
+ timeoutDelay: 2e3
606
+ }
607
+ );
608
+ return match?.[0]?.["res"] ?? false;
609
+ } catch (err) {
610
+ return false;
611
+ }
612
+ }
613
+ async createEmptyWorld(worldName, map = "Plate") {
614
+ if (!worldName) return;
615
+ worldName = worldName.replace(/\.brdb$/i, "");
616
+ try {
617
+ const match = await this.addWatcher(
618
+ (_line, match2) => {
619
+ if (match2?.groups?.generator !== "LogBRWorldManager") return;
620
+ const ok = match2.groups.data.match(/^World files saved after /);
621
+ const err = match2.groups.data.match(
622
+ /^Error: (Invalid preset|World already exists|Failed to create new world)?/
623
+ );
624
+ return ok ? { res: true } : err ? { res: false } : void 0;
625
+ },
626
+ {
627
+ exec: () => {
628
+ this.writeln(`BR.World.CreateEmpty "${worldName}" ${map}`);
629
+ },
630
+ timeoutDelay: 2e3
631
+ }
632
+ );
633
+ return match?.[0]?.["res"] ?? false;
634
+ } catch (err) {
635
+ return false;
636
+ }
637
+ }
638
+ writeSaveData(saveName, saveData) {
639
+ if (typeof saveName !== "string")
640
+ throw "expected name argument for writeSaveData";
641
+ const file2 = path.join(this.savePath, saveName + ".brs");
642
+ if (!file2.startsWith(this.savePath))
643
+ throw "save file not in Saved/Builds directory";
644
+ fs.writeFileSync(file2, new Uint8Array(brs.write(saveData)));
645
+ }
646
+ readSaveData(saveName, nobricks = false) {
647
+ if (typeof saveName !== "string")
648
+ throw "expected name argument for readSaveData";
649
+ const file2 = this.getSavePath(saveName);
650
+ if (!file2 || !file2.startsWith(this.savePath))
651
+ throw "save file not in Saved/Builds directory";
652
+ if (file2)
653
+ return brs.read(fs.readFileSync(file2), {
654
+ preview: false,
655
+ bricks: !nobricks
656
+ });
657
+ }
658
+ async loadSaveData(saveData, {
659
+ offX = 0,
660
+ offY = 0,
661
+ offZ = 0,
662
+ quiet = false,
663
+ correctPalette = false,
664
+ correctCustom = false
665
+ } = {}) {
666
+ const saveFile = this._tempSavePrefix + Date.now() + "_" + this._tempCounter.save++;
667
+ this.writeSaveData(saveFile, saveData);
668
+ await this.watchLogChunk(
669
+ `Bricks.Load "${saveFile}" ${offX} ${offY} ${offZ} ${quiet ? 1 : 0} ${correctPalette ? 1 : 0} ${correctCustom ? 1 : 0}`,
670
+ /^LogBrickSerializer: (.+)$/,
671
+ {
672
+ first: (match) => match[0].endsWith(saveFile + ".brs..."),
673
+ last: (match) => Boolean(match[1].match(/Read .+ bricks/)),
674
+ afterMatchDelay: 0,
675
+ timeoutDelay: 3e4
676
+ }
677
+ );
678
+ const savePath = this.getSavePath(saveFile);
679
+ if (savePath) {
680
+ fs.unlinkSync(savePath);
681
+ }
682
+ }
683
+ async loadSaveDataOnPlayer(saveData, player, {
684
+ offX = 0,
685
+ offY = 0,
686
+ offZ = 0,
687
+ correctPalette = false,
688
+ correctCustom = false
689
+ } = {}) {
690
+ player = typeof player === "string" ? this.getPlayer(player) : player;
691
+ if (!player) return;
692
+ const saveFile = this._tempSavePrefix + Date.now() + "_" + this._tempCounter.save++;
693
+ this.writeSaveData(saveFile, saveData);
694
+ await this.watchLogChunk(
695
+ `Bricks.LoadTemplate "${saveFile}" ${offX} ${offY} ${offZ} ${correctPalette ? 1 : 0} ${correctCustom ? 1 : 0} "${player.name}"`,
696
+ /^LogBrickSerializer: (.+)$/,
697
+ {
698
+ first: (match) => match[0].endsWith(saveFile + ".brs..."),
699
+ last: (match) => Boolean(match[1].match(/Read .+ bricks/)),
700
+ afterMatchDelay: 0,
701
+ timeoutDelay: 3e4
702
+ }
703
+ );
704
+ const savePath = this.getSavePath(saveFile);
705
+ if (savePath) {
706
+ fs.unlinkSync(savePath);
707
+ }
708
+ }
709
+ async getSaveData(region) {
710
+ const saveFile = this._tempSavePrefix + Date.now() + "_" + this._tempCounter.save++;
711
+ await this.saveBricksAsync(saveFile, region);
712
+ const savePath = this.getSavePath(saveFile);
713
+ if (savePath) {
714
+ const saveData = brs.read(fs.readFileSync(savePath));
715
+ fs.unlinkSync(savePath);
716
+ return saveData;
717
+ }
718
+ return void 0;
719
+ }
720
+ // TODO: switch this to use worlds...
721
+ async changeMap(map) {
722
+ if (!map) return;
723
+ const brName = index$2.map.n2brn(map);
724
+ const match = await this.addWatcher(
725
+ /^.*(LogLoad: Took .+ seconds to LoadMap\((?<map>.+)\))|(ERROR: The map .+)$/,
726
+ {
727
+ timeoutDelay: 3e4,
728
+ exec: () => this.writeln(`ServerTravel ${brName}`)
729
+ }
730
+ );
731
+ const success = !!(match && match[0] && match[0].groups && match[0].groups.map);
732
+ return success;
733
+ }
734
+ async getPlugin(name) {
735
+ const plugin2 = this.pluginLoader.plugins.find((p) => p.getName() === name);
736
+ if (plugin2) {
737
+ return {
738
+ name,
739
+ documentation: plugin2.getDocumentation(),
740
+ loaded: plugin2.isLoaded(),
741
+ emitPlugin: (event, ...args) => {
742
+ return plugin2.emitPlugin(event, "unsafe", args);
743
+ }
744
+ };
745
+ } else {
746
+ return null;
747
+ }
748
+ }
781
749
  }
782
750
  exports.default = Omegga;
783
- //# sourceMappingURL=server.js.map
751
+ //# sourceMappingURL=server.js.map