omegga 1.8.0 → 1.9.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.
- package/CHANGELOG.md +21 -0
- package/README.md +3 -1
- package/dist/omegga/commands.js +53 -7
- package/dist/omegga/commands.js.map +1 -1
- package/dist/omegga/matchers/index.js +3 -1
- package/dist/omegga/matchers/index.js.map +1 -1
- package/dist/omegga/matchers/wire.js +27 -0
- package/dist/omegga/matchers/wire.js.map +1 -0
- package/dist/omegga/plugin/plugin_jsonrpc_stdio.js +38 -0
- package/dist/omegga/plugin/plugin_jsonrpc_stdio.js.map +1 -1
- package/dist/omegga/plugin/plugin_node_safe/proxyOmegga.js +26 -1
- package/dist/omegga/plugin/plugin_node_safe/proxyOmegga.js.map +1 -1
- package/dist/omegga/server.js +247 -42
- package/dist/omegga/server.js.map +1 -1
- package/dist/plugin.js.map +1 -1
- package/dist/util/brdb.js +38 -585
- package/dist/util/brdb.js.map +1 -1
- package/dist/util/index.js +50 -3
- package/dist/util/index.js.map +1 -1
- package/dist/util/uuid.js +2 -4
- package/dist/util/uuid.js.map +1 -1
- package/dist/webserver/backend/router/role.js +1 -1
- package/dist/webserver/backend/router/role.js.map +1 -1
- package/package.json +3 -3
- package/templates/safe-ts/omegga.d.ts +2572 -42
- package/.claude/settings.local.json +0 -53
package/dist/omegga/server.js
CHANGED
|
@@ -4,6 +4,7 @@ const logger = require("../logger.js");
|
|
|
4
4
|
const softconfig = require("../softconfig.js");
|
|
5
5
|
const version = require("../version.js");
|
|
6
6
|
const index$2 = require("../util/index.js");
|
|
7
|
+
const brdb = require("../util/brdb.js");
|
|
7
8
|
const file = require("../util/file.js");
|
|
8
9
|
const index = require("../webserver/backend/index.js");
|
|
9
10
|
const brs = require("brs-js");
|
|
@@ -17,6 +18,26 @@ const index$1 = require("./matchers/index.js");
|
|
|
17
18
|
const plugin = require("./plugin.js");
|
|
18
19
|
const wrapper = require("./wrapper.js");
|
|
19
20
|
const MISSING_CMD = '"Command not found. Type <color=\\"ffff00\\">/help</> for a list of commands or <color=\\"ffff00\\">/plugins</> for plugin information."';
|
|
21
|
+
const WHOLE_WORLD_EXTENT = 1e9;
|
|
22
|
+
function brsRemoved(version2) {
|
|
23
|
+
return version2 > 0 && version2 >= commands.PREFAB_VERSION;
|
|
24
|
+
}
|
|
25
|
+
function warnRemoved(version2, method, since, release, replacement) {
|
|
26
|
+
if (!(version2 > 0 && version2 >= since)) return false;
|
|
27
|
+
logger.default.warnp(
|
|
28
|
+
`omegga.${method}() uses a console command removed in Brickadia ${release}. This call has no effect - use ${replacement} instead.`
|
|
29
|
+
);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
function writeTempPrefab(omegga, saveData) {
|
|
33
|
+
const ref = omegga._tempSavePrefix + Date.now() + "_" + omegga._tempCounter.save++;
|
|
34
|
+
const file$1 = path.join(omegga.prefabPath, ref + ".brz");
|
|
35
|
+
if (!file$1.startsWith(omegga.prefabPath))
|
|
36
|
+
throw "prefab file not in Saved/Prefabs directory";
|
|
37
|
+
file.mkdir(omegga.prefabPath);
|
|
38
|
+
fs.writeFileSync(file$1, new Uint8Array(brs.writeBrzLegacy(saveData)));
|
|
39
|
+
return { ref, file: file$1 };
|
|
40
|
+
}
|
|
20
41
|
class Omegga extends wrapper.default {
|
|
21
42
|
/**
|
|
22
43
|
* Omegga instance
|
|
@@ -53,6 +74,7 @@ class Omegga extends wrapper.default {
|
|
|
53
74
|
const savedDir = cfg.server.savedDir ?? softconfig.CONFIG_SAVED_DIR;
|
|
54
75
|
this.savePath = path.join(this.path, softconfig.DATA_PATH, savedDir, "Builds");
|
|
55
76
|
this.worldPath = path.join(this.path, softconfig.DATA_PATH, savedDir, "Worlds");
|
|
77
|
+
this.prefabPath = path.join(this.path, softconfig.DATA_PATH, savedDir, "Prefabs");
|
|
56
78
|
this.presetPath = path.join(this.path, softconfig.DATA_PATH, savedDir, "Presets");
|
|
57
79
|
this.configPath = path.join(this.path, softconfig.DATA_PATH, savedDir, "Server");
|
|
58
80
|
logger.default.verbose("Creating directories");
|
|
@@ -455,22 +477,49 @@ class Omegga extends wrapper.default {
|
|
|
455
477
|
}
|
|
456
478
|
clearRegion(region, options) {
|
|
457
479
|
let target = "";
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
480
|
+
const rawTarget = options?.target;
|
|
481
|
+
if (rawTarget) {
|
|
482
|
+
if (typeof rawTarget === "object") target = rawTarget.id;
|
|
483
|
+
else if (index$2.uuid.match(rawTarget)) target = rawTarget;
|
|
484
|
+
else target = this.getPlayer(rawTarget)?.id ?? "";
|
|
485
|
+
}
|
|
486
|
+
const center = region.center.join(" ");
|
|
487
|
+
const extent = region.extent.join(" ");
|
|
488
|
+
if (brsRemoved(this.version)) {
|
|
489
|
+
const bricks = options?.bricks ?? true ? 1 : 0;
|
|
490
|
+
const entities = options?.entities ?? false ? 1 : 0;
|
|
491
|
+
this.writeln(
|
|
492
|
+
`${this.Console.World.ClearRegion} ${center} ${extent} ${bricks} ${entities}${target ? " " + target : ""}`
|
|
493
|
+
);
|
|
494
|
+
return;
|
|
463
495
|
}
|
|
464
496
|
this.writeln(
|
|
465
|
-
`${this.Console.Bricks.ClearRegion} ${
|
|
466
|
-
" "
|
|
467
|
-
)}${target}`
|
|
497
|
+
`${this.Console.Bricks.ClearRegion} ${center} ${extent}${target ? " " + target : ""}`
|
|
468
498
|
);
|
|
469
499
|
}
|
|
470
|
-
clearAllBricks(
|
|
500
|
+
clearAllBricks(options = {}) {
|
|
501
|
+
const {
|
|
502
|
+
quiet = false,
|
|
503
|
+
bricks = true,
|
|
504
|
+
entities = false
|
|
505
|
+
} = typeof options === "boolean" ? { quiet: options } : options;
|
|
506
|
+
if (brsRemoved(this.version)) {
|
|
507
|
+
this.writeln(
|
|
508
|
+
`${this.Console.World.ClearAll} ${bricks ? 1 : 0} ${entities ? 1 : 0} ${quiet ? 1 : 0}`
|
|
509
|
+
);
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
471
512
|
this.writeln(`${this.Console.Bricks.ClearAll} ${quiet ? 1 : ""}`);
|
|
472
513
|
}
|
|
473
514
|
saveBricks(saveName, region) {
|
|
515
|
+
if (warnRemoved(
|
|
516
|
+
this.version,
|
|
517
|
+
"saveBricks",
|
|
518
|
+
commands.PREFAB_VERSION,
|
|
519
|
+
"EA3",
|
|
520
|
+
"savePrefab"
|
|
521
|
+
))
|
|
522
|
+
return;
|
|
474
523
|
if (!saveName) return;
|
|
475
524
|
if (!(saveName.startsWith('"') && saveName.endsWith('"')))
|
|
476
525
|
saveName = `"${saveName}"`;
|
|
@@ -483,6 +532,14 @@ class Omegga extends wrapper.default {
|
|
|
483
532
|
else this.writeln(`${this.Console.Bricks.Save} ${saveName}`);
|
|
484
533
|
}
|
|
485
534
|
async saveBricksAsync(saveName, region) {
|
|
535
|
+
if (warnRemoved(
|
|
536
|
+
this.version,
|
|
537
|
+
"saveBricksAsync",
|
|
538
|
+
commands.PREFAB_VERSION,
|
|
539
|
+
"EA3",
|
|
540
|
+
"savePrefabAsync"
|
|
541
|
+
))
|
|
542
|
+
return;
|
|
486
543
|
if (!saveName) return;
|
|
487
544
|
let saveNameClean = saveName;
|
|
488
545
|
if (!(saveName.startsWith('"') && saveName.endsWith('"')))
|
|
@@ -509,6 +566,14 @@ class Omegga extends wrapper.default {
|
|
|
509
566
|
correctPalette = false,
|
|
510
567
|
correctCustom = false
|
|
511
568
|
} = {}) {
|
|
569
|
+
if (warnRemoved(
|
|
570
|
+
this.version,
|
|
571
|
+
"loadBricks",
|
|
572
|
+
commands.PREFAB_VERSION,
|
|
573
|
+
"EA3",
|
|
574
|
+
"loadPrefab"
|
|
575
|
+
))
|
|
576
|
+
return;
|
|
512
577
|
if (!(saveName.startsWith('"') && saveName.endsWith('"')))
|
|
513
578
|
saveName = `"${saveName}"`;
|
|
514
579
|
this.writeln(
|
|
@@ -522,6 +587,14 @@ class Omegga extends wrapper.default {
|
|
|
522
587
|
correctPalette = false,
|
|
523
588
|
correctCustom = false
|
|
524
589
|
} = {}) {
|
|
590
|
+
if (warnRemoved(
|
|
591
|
+
this.version,
|
|
592
|
+
"loadBricksOnPlayer",
|
|
593
|
+
commands.EA2_VERSION,
|
|
594
|
+
"EA2",
|
|
595
|
+
"loadPrefabOnPlayer"
|
|
596
|
+
))
|
|
597
|
+
return;
|
|
525
598
|
player = typeof player === "string" ? this.getPlayer(player) : player;
|
|
526
599
|
if (!player) return;
|
|
527
600
|
if (!(saveName.startsWith('"') && saveName.endsWith('"')))
|
|
@@ -536,6 +609,9 @@ class Omegga extends wrapper.default {
|
|
|
536
609
|
getWorlds() {
|
|
537
610
|
return fs.existsSync(this.worldPath) ? glob.sync(this.worldPath + "/**/*.brdb") : [];
|
|
538
611
|
}
|
|
612
|
+
getPrefabs() {
|
|
613
|
+
return fs.existsSync(this.prefabPath) ? glob.sync(this.prefabPath + "/**/*.brz") : [];
|
|
614
|
+
}
|
|
539
615
|
getSavePath(saveName) {
|
|
540
616
|
const file2 = path.join(
|
|
541
617
|
this.savePath,
|
|
@@ -551,42 +627,12 @@ class Omegga extends wrapper.default {
|
|
|
551
627
|
return fs.existsSync(file2) ? file2 : void 0;
|
|
552
628
|
}
|
|
553
629
|
async getWorldRevisions(worldName) {
|
|
554
|
-
if (!this.started || this.starting || this.stopping) {
|
|
555
|
-
throw new Error("Server is not started");
|
|
556
|
-
}
|
|
557
630
|
worldName = worldName.replace(/\.brdb$/i, "");
|
|
558
|
-
|
|
631
|
+
const path2 = this.getWorldPath(worldName);
|
|
632
|
+
if (!worldName || !path2) {
|
|
559
633
|
throw new Error(`World "${worldName}" does not exist`);
|
|
560
634
|
}
|
|
561
|
-
|
|
562
|
-
const revisionsRaw = await this.watchLogChunk(
|
|
563
|
-
`${this.Console.World.ListRevisions} "${worldName}"`,
|
|
564
|
-
/^LogBRBundleManager: (There are (?<numRevisions>\d+) revisions|Revision (?<revision>\d+) - (?<date>[\d.-]+): (?<note>.+))$/,
|
|
565
|
-
{
|
|
566
|
-
last: (match) => Number(match.groups.reverse) === numRevisions,
|
|
567
|
-
first: (match) => {
|
|
568
|
-
if (match.groups?.numRevisions !== void 0) {
|
|
569
|
-
numRevisions = Number(match.groups.numRevisions);
|
|
570
|
-
return true;
|
|
571
|
-
}
|
|
572
|
-
return false;
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
);
|
|
576
|
-
if (!revisionsRaw) return [];
|
|
577
|
-
return revisionsRaw.map((match) => {
|
|
578
|
-
if (match.groups?.numRevisions !== void 0) {
|
|
579
|
-
return null;
|
|
580
|
-
}
|
|
581
|
-
const revision = match.groups.revision ? Number(match.groups.revision) : 0;
|
|
582
|
-
const dateStr = match.groups.date.replace(
|
|
583
|
-
/(\d{4})\.(\d{2})\.(\d{2})-(\d{2})\.(\d{2})\.(\d{2})/,
|
|
584
|
-
"$1-$2-$3T$4:$5:$6Z"
|
|
585
|
-
);
|
|
586
|
-
const date = new Date(dateStr);
|
|
587
|
-
const note = match.groups.note || "";
|
|
588
|
-
return { index: revision, date, note };
|
|
589
|
-
}).filter(Boolean);
|
|
635
|
+
return brdb.readBrdbRevisions(path2) ?? [];
|
|
590
636
|
}
|
|
591
637
|
async loadWorld(worldName) {
|
|
592
638
|
worldName = worldName.replace(/\.brdb$/i, "");
|
|
@@ -733,6 +779,14 @@ class Omegga extends wrapper.default {
|
|
|
733
779
|
correctPalette = false,
|
|
734
780
|
correctCustom = false
|
|
735
781
|
} = {}) {
|
|
782
|
+
if (brsRemoved(this.version)) {
|
|
783
|
+
const { ref, file: file2 } = writeTempPrefab(this, saveData);
|
|
784
|
+
this.loadPrefab(ref, { offX, offY, offZ });
|
|
785
|
+
setTimeout(() => {
|
|
786
|
+
if (fs.existsSync(file2)) fs.unlinkSync(file2);
|
|
787
|
+
}, 5e3);
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
736
790
|
const saveFile = this._tempSavePrefix + Date.now() + "_" + this._tempCounter.save++;
|
|
737
791
|
this.writeSaveData(saveFile, saveData);
|
|
738
792
|
await this.watchLogChunk(
|
|
@@ -759,6 +813,22 @@ class Omegga extends wrapper.default {
|
|
|
759
813
|
} = {}) {
|
|
760
814
|
player = typeof player === "string" ? this.getPlayer(player) : player;
|
|
761
815
|
if (!player) return;
|
|
816
|
+
if (brsRemoved(this.version)) {
|
|
817
|
+
const { ref, file: file2 } = writeTempPrefab(this, saveData);
|
|
818
|
+
this.givePrefabToPlayer(ref, player);
|
|
819
|
+
setTimeout(() => {
|
|
820
|
+
if (fs.existsSync(file2)) fs.unlinkSync(file2);
|
|
821
|
+
}, 5e3);
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
if (warnRemoved(
|
|
825
|
+
this.version,
|
|
826
|
+
"loadSaveDataOnPlayer",
|
|
827
|
+
commands.EA2_VERSION,
|
|
828
|
+
"EA2",
|
|
829
|
+
"loadPrefabOnPlayer"
|
|
830
|
+
))
|
|
831
|
+
return;
|
|
762
832
|
const saveFile = this._tempSavePrefix + Date.now() + "_" + this._tempCounter.save++;
|
|
763
833
|
this.writeSaveData(saveFile, saveData);
|
|
764
834
|
await this.watchLogChunk(
|
|
@@ -777,6 +847,54 @@ class Omegga extends wrapper.default {
|
|
|
777
847
|
}
|
|
778
848
|
}
|
|
779
849
|
async getSaveData(region) {
|
|
850
|
+
if (brsRemoved(this.version)) {
|
|
851
|
+
const ref = this._tempSavePrefix + Date.now() + "_" + this._tempCounter.save++;
|
|
852
|
+
const file2 = await this.savePrefabAsync(ref, { region });
|
|
853
|
+
if (!file2) return void 0;
|
|
854
|
+
try {
|
|
855
|
+
const MAIN_GRID = 1;
|
|
856
|
+
const reader = brs.WorldReader.from(new Uint8Array(fs.readFileSync(file2)));
|
|
857
|
+
const bricks = [...reader.bricks(MAIN_GRID)].map((b) => ({
|
|
858
|
+
...b,
|
|
859
|
+
physical_index: 0,
|
|
860
|
+
components: {}
|
|
861
|
+
}));
|
|
862
|
+
let brickOffset = 0;
|
|
863
|
+
for (const chunk of reader.brickChunkIndex(MAIN_GRID)) {
|
|
864
|
+
if (chunk.numComponents > 0) {
|
|
865
|
+
const { components } = reader.componentChunk(
|
|
866
|
+
MAIN_GRID,
|
|
867
|
+
chunk.index
|
|
868
|
+
);
|
|
869
|
+
for (const c of components) {
|
|
870
|
+
const brick = bricks[brickOffset + c.brickIndex];
|
|
871
|
+
if (brick) brick.components[c.typeName] = c.data ?? {};
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
brickOffset += chunk.numBricks;
|
|
875
|
+
}
|
|
876
|
+
return {
|
|
877
|
+
version: 10,
|
|
878
|
+
map: this.currentMap ?? "Unknown",
|
|
879
|
+
author: { id: this.host?.id ?? "", name: this.host?.name ?? "" },
|
|
880
|
+
host: { id: this.host?.id ?? "", name: this.host?.name ?? "" },
|
|
881
|
+
description: "",
|
|
882
|
+
brick_count: bricks.length,
|
|
883
|
+
mods: [],
|
|
884
|
+
brick_assets: reader.brickAssets(),
|
|
885
|
+
colors: [],
|
|
886
|
+
materials: reader.materials(),
|
|
887
|
+
physical_materials: [],
|
|
888
|
+
brick_owners: reader.brickOwners(),
|
|
889
|
+
game_version: this.version,
|
|
890
|
+
save_time: new Uint8Array(),
|
|
891
|
+
bricks,
|
|
892
|
+
components: {}
|
|
893
|
+
};
|
|
894
|
+
} finally {
|
|
895
|
+
if (fs.existsSync(file2)) fs.unlinkSync(file2);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
780
898
|
const saveFile = this._tempSavePrefix + Date.now() + "_" + this._tempCounter.save++;
|
|
781
899
|
await this.saveBricksAsync(saveFile, region);
|
|
782
900
|
const savePath = this.getSavePath(saveFile);
|
|
@@ -787,6 +905,93 @@ class Omegga extends wrapper.default {
|
|
|
787
905
|
}
|
|
788
906
|
return void 0;
|
|
789
907
|
}
|
|
908
|
+
/**
|
|
909
|
+
* Load a prefab into the world (EA3). `path` is a bundle
|
|
910
|
+
* path ref such as `Prefabs/Uploads/<hash>.brz`.
|
|
911
|
+
* br.Prefab.Load <Path> [Offset X Y Z] [At Original Position] [Orientation]
|
|
912
|
+
* [Root Entity Persistent Index] [Mirror Axes] [Override User Id]
|
|
913
|
+
*/
|
|
914
|
+
loadPrefab(path2, {
|
|
915
|
+
offX = 0,
|
|
916
|
+
offY = 0,
|
|
917
|
+
offZ = 0,
|
|
918
|
+
atOriginalPosition = false,
|
|
919
|
+
orientation = 0,
|
|
920
|
+
rootEntityPersistentIndex = -1,
|
|
921
|
+
mirrorAxes = 0,
|
|
922
|
+
overrideUserId = ""
|
|
923
|
+
} = {}) {
|
|
924
|
+
if (!path2) return;
|
|
925
|
+
const rootPart = rootEntityPersistentIndex >= 0 ? ` ${rootEntityPersistentIndex} ${mirrorAxes}${overrideUserId ? ` "${overrideUserId}"` : ""}` : "";
|
|
926
|
+
this.writeln(
|
|
927
|
+
`${this.Console.Prefab.Load} "${path2}" ${offX} ${offY} ${offZ} ${atOriginalPosition ? 1 : 0} ${orientation}${rootPart}`
|
|
928
|
+
);
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Load a prefab onto a player (EA3). Replaces the
|
|
932
|
+
* removed {@link loadBricksOnPlayer}; backed by `br.Prefab.GiveToPlayer`.
|
|
933
|
+
* @param path prefab bundle path ref
|
|
934
|
+
* @param player player name/id or player object
|
|
935
|
+
* @param options give options (preserve ownership)
|
|
936
|
+
*/
|
|
937
|
+
loadPrefabOnPlayer(path2, player, { preserveOwnership = false } = {}) {
|
|
938
|
+
this.givePrefabToPlayer(path2, player, { preserveOwnership });
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Save the world (or a region of it) as a prefab (EA3).
|
|
942
|
+
* `path` is the destination bundle path ref (e.g. `Prefabs/MyPrefab.brz`).
|
|
943
|
+
* br.Prefab.SaveRegion <Path> <Center X Y Z> <Extent X Y Z> [Include Entities]
|
|
944
|
+
* [Root Entity Persistent Index] [Filter User Id]
|
|
945
|
+
* @param path destination prefab bundle path ref
|
|
946
|
+
* @param options save options; omit `region` to capture the whole world
|
|
947
|
+
*/
|
|
948
|
+
savePrefab(path2, {
|
|
949
|
+
region,
|
|
950
|
+
entities = true,
|
|
951
|
+
rootEntityPersistentIndex = -1,
|
|
952
|
+
userId = ""
|
|
953
|
+
} = {}) {
|
|
954
|
+
if (!path2) return;
|
|
955
|
+
const center = (region?.center ?? [0, 0, 0]).join(" ");
|
|
956
|
+
const extent = (region?.extent ?? [
|
|
957
|
+
WHOLE_WORLD_EXTENT,
|
|
958
|
+
WHOLE_WORLD_EXTENT,
|
|
959
|
+
WHOLE_WORLD_EXTENT
|
|
960
|
+
]).join(" ");
|
|
961
|
+
this.writeln(
|
|
962
|
+
`${this.Console.Prefab.SaveRegion} "${path2}" ${center} ${extent} ${entities ? 1 : 0} ${rootEntityPersistentIndex}${userId ? ` "${userId}"` : ""}`
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
/**
|
|
966
|
+
* Save a prefab and resolve once the prefab file has been written to disk.
|
|
967
|
+
* @param path destination prefab bundle path ref
|
|
968
|
+
* @param options same options as {@link savePrefab}
|
|
969
|
+
* @returns the absolute path to the written prefab, or null on timeout
|
|
970
|
+
*/
|
|
971
|
+
async savePrefabAsync(path$1, options) {
|
|
972
|
+
if (!path$1) return null;
|
|
973
|
+
this.savePrefab(path$1, options);
|
|
974
|
+
const name = path$1.replace(/^Prefabs[\\/]/i, "").replace(/\.brz$/i, "");
|
|
975
|
+
const file2 = path.join(this.prefabPath, name + ".brz");
|
|
976
|
+
const deadline = Date.now() + 3e4;
|
|
977
|
+
while (Date.now() < deadline) {
|
|
978
|
+
if (fs.existsSync(file2)) return file2;
|
|
979
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
980
|
+
}
|
|
981
|
+
return null;
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* Give a prefab to a player's inventory (EA3).
|
|
985
|
+
* br.Prefab.GiveToPlayer <Path> <Player Name or User Id> [Preserve Ownership]
|
|
986
|
+
*/
|
|
987
|
+
givePrefabToPlayer(path2, player, { preserveOwnership = false } = {}) {
|
|
988
|
+
if (!path2) return;
|
|
989
|
+
const target = typeof player === "object" ? player.id : player;
|
|
990
|
+
if (!target) return;
|
|
991
|
+
this.writeln(
|
|
992
|
+
`${this.Console.Prefab.GiveToPlayer} "${path2}" "${target}" ${preserveOwnership ? 1 : 0}`
|
|
993
|
+
);
|
|
994
|
+
}
|
|
790
995
|
// TODO: switch this to use worlds...
|
|
791
996
|
async changeMap(map) {
|
|
792
997
|
if (!map) return;
|