dredless 0.2.0 → 0.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.
- package/README.md +65 -48
- package/dist/browser/dredless.global.js +12047 -0
- package/dist/browser/dredless.global.js.map +7 -0
- package/docs/API_REFERENCE.md +2481 -2481
- package/docs/BROWSER_API.md +98 -98
- package/docs/NODE_API.md +359 -359
- package/package.json +40 -35
- package/src/browser-global.js +3 -0
- package/src/browser.d.ts +212 -212
- package/src/browser.js +133 -133
- package/src/client/core.js +1202 -1202
- package/src/client/domains/index.js +1818 -1818
- package/src/client/index.d.ts +2 -2
- package/src/client/index.js +1 -1
- package/src/compression/lz4.d.ts +1 -1
- package/src/compression/lz4.js +112 -112
- package/src/constants.js +74 -74
- package/src/crypto/chacha-native.d.ts +9 -9
- package/src/crypto/chacha-native.js +48 -48
- package/src/crypto/chacha.d.ts +25 -25
- package/src/crypto/chacha.js +274 -274
- package/src/events.js +39 -39
- package/src/index.d.ts +2277 -2277
- package/src/index.js +125 -125
- package/src/network/browser-session.js +291 -291
- package/src/network/connection.d.ts +1 -1
- package/src/network/connection.js +31 -31
- package/src/network/cookies.js +85 -85
- package/src/network/http.js +34 -34
- package/src/network/index.d.ts +4 -4
- package/src/network/index.js +4 -4
- package/src/network/servers.d.ts +7 -7
- package/src/network/servers.js +133 -133
- package/src/network/session.d.ts +7 -7
- package/src/network/session.js +320 -320
- package/src/network/ships.d.ts +12 -12
- package/src/network/ships.js +73 -73
- package/src/node.d.ts +2 -2
- package/src/node.js +8 -8
- package/src/protocol/codec/binary.d.ts +3 -3
- package/src/protocol/codec/binary.js +52 -52
- package/src/protocol/codec/msgpack.d.ts +3 -3
- package/src/protocol/codec/msgpack.js +555 -555
- package/src/protocol/inbound/frame.d.ts +1 -1
- package/src/protocol/inbound/frame.js +8 -8
- package/src/protocol/inbound/world-payload.d.ts +3 -3
- package/src/protocol/inbound/world-payload.js +21 -21
- package/src/protocol/index.d.ts +11 -11
- package/src/protocol/index.js +19 -19
- package/src/protocol/outbound/blueprint.d.ts +22 -22
- package/src/protocol/outbound/blueprint.js +36 -36
- package/src/protocol/outbound/commands.d.ts +3 -3
- package/src/protocol/outbound/commands.js +149 -149
- package/src/protocol/outbound/comms.d.ts +28 -28
- package/src/protocol/outbound/comms.js +40 -40
- package/src/protocol/outbound/inventory.d.ts +64 -64
- package/src/protocol/outbound/inventory.js +147 -147
- package/src/protocol/outbound/ship-management.d.ts +84 -84
- package/src/protocol/outbound/ship-management.js +221 -221
- package/src/protocol/outbound/sign.d.ts +18 -18
- package/src/protocol/outbound/sign.js +45 -45
- package/src/protocol/outbound/ui-config.d.ts +157 -157
- package/src/protocol/outbound/ui-config.js +578 -578
- package/src/runtime.js +36 -36
- package/src/state/generator-maze.d.ts +32 -32
- package/src/state/generator-maze.js +370 -370
- package/src/state/index.d.ts +3 -3
- package/src/state/index.js +3 -3
- package/src/state/items.js +20 -20
- package/src/state/model/decoder.js +1554 -1554
- package/src/state/model/index.d.ts +33 -33
- package/src/state/model/index.js +2 -2
- package/src/state/model/loader-config.js +1020 -1020
- package/src/state/model/reader.js +69 -69
- package/src/state/model/state.js +1257 -1257
- package/src/state/overworld.js +72 -72
- package/src/state/tilesets.js +53 -53
- package/src/state/world/index.d.ts +29 -29
- package/src/state/world/index.js +180 -180
- package/src/state/world/state.js +297 -297
package/src/state/world/index.js
CHANGED
|
@@ -1,180 +1,180 @@
|
|
|
1
|
-
import {WorldState} from "./state.js";
|
|
2
|
-
import {ModelState} from "../model/index.js";
|
|
3
|
-
import {decodeModelPayload} from "../../protocol/inbound/world-payload.js";
|
|
4
|
-
|
|
5
|
-
export {WorldState};
|
|
6
|
-
|
|
7
|
-
// Retention limits for per-world history. These arrays previously grew without
|
|
8
|
-
// bound for the lifetime of a connection. Pass Infinity to restore that.
|
|
9
|
-
const DEFAULT_HISTORY_LIMITS = {
|
|
10
|
-
eventHistory: 200,
|
|
11
|
-
modelPacketHistory: 100,
|
|
12
|
-
chunkHistory: 64
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
function normalizeLimit(value, fallback) {
|
|
16
|
-
if (value === Infinity) return Infinity;
|
|
17
|
-
const limit = Number(value);
|
|
18
|
-
if (!Number.isFinite(limit) || limit < 0) return fallback;
|
|
19
|
-
return Math.floor(limit);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function pushBounded(target, value, limit) {
|
|
23
|
-
if (limit === 0) return;
|
|
24
|
-
target.push(value);
|
|
25
|
-
if (target.length > limit) target.splice(0, target.length - limit);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export class WorldStore {
|
|
29
|
-
#overworldId = null;
|
|
30
|
-
#shipWorldId = null;
|
|
31
|
-
|
|
32
|
-
constructor(limits = {}) {
|
|
33
|
-
this.currentWorldId = null;
|
|
34
|
-
this.worlds = new Map();
|
|
35
|
-
this.historyLimits = {
|
|
36
|
-
eventHistory: normalizeLimit(limits.eventHistory, DEFAULT_HISTORY_LIMITS.eventHistory),
|
|
37
|
-
modelPacketHistory: normalizeLimit(limits.modelPacketHistory, DEFAULT_HISTORY_LIMITS.modelPacketHistory),
|
|
38
|
-
chunkHistory: normalizeLimit(limits.chunkHistory, DEFAULT_HISTORY_LIMITS.chunkHistory)
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
get(id) {
|
|
43
|
-
const worldId = Number(id);
|
|
44
|
-
if (!this.worlds.has(worldId)) this.worlds.set(worldId, new WorldState(worldId, this.historyLimits));
|
|
45
|
-
return this.worlds.get(worldId);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
apply(packet) {
|
|
49
|
-
if (!packet || packet.world == null) return null;
|
|
50
|
-
if (packet.type === 22) return this.#applyMeta(packet);
|
|
51
|
-
if (packet.type === 23) return this.#applyTiles(packet);
|
|
52
|
-
if (packet.type === 20) return this.#applyModel(packet);
|
|
53
|
-
if (packet.type === 13) return this.#applyCommsBubble(packet);
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
snapshot({includeTiles = false, includeModel = false, includeBlocks = false} = {}) {
|
|
58
|
-
return [...this.worlds.values()].map((world) => world.snapshot({includeTiles, includeModel, includeBlocks}));
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
ids() {
|
|
62
|
-
return [...this.worlds.keys()];
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Both resolvers are called on nearly every public read, and each used to
|
|
66
|
-
// spread the whole world map. Cache the resolved id and re-scan only when the
|
|
67
|
-
// cached world no longer satisfies the predicate.
|
|
68
|
-
overworld() {
|
|
69
|
-
const cached = this.#overworldId != null ? this.worlds.get(this.#overworldId) : null;
|
|
70
|
-
if (cached && cached.isOverworld === true) return cached;
|
|
71
|
-
for (const world of this.worlds.values()) {
|
|
72
|
-
if (world.isOverworld === true) {
|
|
73
|
-
this.#overworldId = world.id;
|
|
74
|
-
return world;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
this.#overworldId = null;
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
shipWorld() {
|
|
82
|
-
if (this.currentWorldId != null) return this.worlds.get(Number(this.currentWorldId)) || null;
|
|
83
|
-
const cached = this.#shipWorldId != null ? this.worlds.get(this.#shipWorldId) : null;
|
|
84
|
-
if (cached && cached.isOverworld === false) return cached;
|
|
85
|
-
for (const world of this.worlds.values()) {
|
|
86
|
-
if (world.isOverworld === false) {
|
|
87
|
-
this.#shipWorldId = world.id;
|
|
88
|
-
return world;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
this.#shipWorldId = null;
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
currentShipEntity() {
|
|
96
|
-
const shipWorld = this.shipWorld();
|
|
97
|
-
if (!shipWorld || shipWorld.parentWorld == null || shipWorld.parentEntity == null) return null;
|
|
98
|
-
return this.worlds.get(Number(shipWorld.parentWorld))?.entity(shipWorld.parentEntity) || null;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
#applyMeta(packet) {
|
|
102
|
-
if (packet.removed) {
|
|
103
|
-
const world = this.worlds.get(Number(packet.world));
|
|
104
|
-
if (world) world.readMeta(packet);
|
|
105
|
-
this.worlds.delete(Number(packet.world));
|
|
106
|
-
if (this.currentWorldId === Number(packet.world)) this.currentWorldId = null;
|
|
107
|
-
return {type: "world-removed", world: world || null, packet};
|
|
108
|
-
}
|
|
109
|
-
const world = this.get(packet.world);
|
|
110
|
-
world.readMeta(packet);
|
|
111
|
-
if (this.currentWorldId == null && !world.isOverworld) this.currentWorldId = world.id;
|
|
112
|
-
return {type: "world", world};
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
#applyTiles(packet) {
|
|
116
|
-
const world = this.get(packet.world);
|
|
117
|
-
const decoded = world.decodeEncrypted(packet.data);
|
|
118
|
-
const updates = [];
|
|
119
|
-
const errors = [];
|
|
120
|
-
if (decoded && typeof decoded === "object") {
|
|
121
|
-
for (const [kind, value] of Object.entries(decoded)) {
|
|
122
|
-
try {
|
|
123
|
-
if (kind === "0") updates.push({kind, tiles: world.applyChunk(value)});
|
|
124
|
-
else if (kind === "1") updates.push({kind, tile: world.applyTile(value)});
|
|
125
|
-
else updates.push({kind, value});
|
|
126
|
-
} catch (error) {
|
|
127
|
-
errors.push(error);
|
|
128
|
-
updates.push({kind, value, error: error.message});
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
world.recordEvent({type: "tiles", packet, decoded, updates, errors});
|
|
133
|
-
return {type: "tiles", world, decoded, updates, errors};
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
#applyModel(packet) {
|
|
137
|
-
const world = this.get(packet.world);
|
|
138
|
-
const result = {
|
|
139
|
-
worldId: packet.world,
|
|
140
|
-
full: Boolean(packet.full),
|
|
141
|
-
events: Array.isArray(packet.events) ? packet.events : [],
|
|
142
|
-
modelData: packet.model_data || null,
|
|
143
|
-
decoded: null,
|
|
144
|
-
model: null,
|
|
145
|
-
commandNumber: typeof packet.command_number === "number" ? packet.command_number : null,
|
|
146
|
-
timing: {
|
|
147
|
-
roundTimeLeft: packet.round_time_left ?? null,
|
|
148
|
-
regenTimeLeft: packet.regen_time_left ?? null,
|
|
149
|
-
removeOnRegen: packet.remove_on_regen ?? null,
|
|
150
|
-
globalEventTime: packet.global_event_time ?? null,
|
|
151
|
-
tickTime: packet.tick_time ?? null,
|
|
152
|
-
tickQuota: packet.tick_quota ?? null,
|
|
153
|
-
cpuLoad: packet.cpu_load ?? null,
|
|
154
|
-
relayTime: packet.relay_time ?? null
|
|
155
|
-
}
|
|
156
|
-
};
|
|
157
|
-
if (packet.model_data && world.seed != null) {
|
|
158
|
-
const previousModel = world.model;
|
|
159
|
-
try {
|
|
160
|
-
if (packet.full) world.model = new ModelState({isOverworld: world.isOverworld});
|
|
161
|
-
result.decoded = decodeModelPayload(packet.model_data, packet.world, world.seed);
|
|
162
|
-
result.model = world.model.apply(result.decoded, {full: Boolean(packet.full)});
|
|
163
|
-
if (result.model?.error && packet.full && previousModel) world.model = previousModel;
|
|
164
|
-
} catch (error) {
|
|
165
|
-
if (packet.full && previousModel) world.model = previousModel;
|
|
166
|
-
result.error = error;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
pushBounded(world.modelPackets, result, world.historyLimits.modelPacketHistory);
|
|
170
|
-
world.recordEvent({type: "model", packet, result});
|
|
171
|
-
world.lastPacket = packet;
|
|
172
|
-
return {type: "model", world, result};
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
#applyCommsBubble(packet) {
|
|
176
|
-
const world = this.get(packet.world);
|
|
177
|
-
const bubble = world.addCommsBubble(packet);
|
|
178
|
-
return {type: "comms-bubble", world, packet, bubble};
|
|
179
|
-
}
|
|
180
|
-
}
|
|
1
|
+
import {WorldState} from "./state.js";
|
|
2
|
+
import {ModelState} from "../model/index.js";
|
|
3
|
+
import {decodeModelPayload} from "../../protocol/inbound/world-payload.js";
|
|
4
|
+
|
|
5
|
+
export {WorldState};
|
|
6
|
+
|
|
7
|
+
// Retention limits for per-world history. These arrays previously grew without
|
|
8
|
+
// bound for the lifetime of a connection. Pass Infinity to restore that.
|
|
9
|
+
const DEFAULT_HISTORY_LIMITS = {
|
|
10
|
+
eventHistory: 200,
|
|
11
|
+
modelPacketHistory: 100,
|
|
12
|
+
chunkHistory: 64
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function normalizeLimit(value, fallback) {
|
|
16
|
+
if (value === Infinity) return Infinity;
|
|
17
|
+
const limit = Number(value);
|
|
18
|
+
if (!Number.isFinite(limit) || limit < 0) return fallback;
|
|
19
|
+
return Math.floor(limit);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function pushBounded(target, value, limit) {
|
|
23
|
+
if (limit === 0) return;
|
|
24
|
+
target.push(value);
|
|
25
|
+
if (target.length > limit) target.splice(0, target.length - limit);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class WorldStore {
|
|
29
|
+
#overworldId = null;
|
|
30
|
+
#shipWorldId = null;
|
|
31
|
+
|
|
32
|
+
constructor(limits = {}) {
|
|
33
|
+
this.currentWorldId = null;
|
|
34
|
+
this.worlds = new Map();
|
|
35
|
+
this.historyLimits = {
|
|
36
|
+
eventHistory: normalizeLimit(limits.eventHistory, DEFAULT_HISTORY_LIMITS.eventHistory),
|
|
37
|
+
modelPacketHistory: normalizeLimit(limits.modelPacketHistory, DEFAULT_HISTORY_LIMITS.modelPacketHistory),
|
|
38
|
+
chunkHistory: normalizeLimit(limits.chunkHistory, DEFAULT_HISTORY_LIMITS.chunkHistory)
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get(id) {
|
|
43
|
+
const worldId = Number(id);
|
|
44
|
+
if (!this.worlds.has(worldId)) this.worlds.set(worldId, new WorldState(worldId, this.historyLimits));
|
|
45
|
+
return this.worlds.get(worldId);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
apply(packet) {
|
|
49
|
+
if (!packet || packet.world == null) return null;
|
|
50
|
+
if (packet.type === 22) return this.#applyMeta(packet);
|
|
51
|
+
if (packet.type === 23) return this.#applyTiles(packet);
|
|
52
|
+
if (packet.type === 20) return this.#applyModel(packet);
|
|
53
|
+
if (packet.type === 13) return this.#applyCommsBubble(packet);
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
snapshot({includeTiles = false, includeModel = false, includeBlocks = false} = {}) {
|
|
58
|
+
return [...this.worlds.values()].map((world) => world.snapshot({includeTiles, includeModel, includeBlocks}));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
ids() {
|
|
62
|
+
return [...this.worlds.keys()];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Both resolvers are called on nearly every public read, and each used to
|
|
66
|
+
// spread the whole world map. Cache the resolved id and re-scan only when the
|
|
67
|
+
// cached world no longer satisfies the predicate.
|
|
68
|
+
overworld() {
|
|
69
|
+
const cached = this.#overworldId != null ? this.worlds.get(this.#overworldId) : null;
|
|
70
|
+
if (cached && cached.isOverworld === true) return cached;
|
|
71
|
+
for (const world of this.worlds.values()) {
|
|
72
|
+
if (world.isOverworld === true) {
|
|
73
|
+
this.#overworldId = world.id;
|
|
74
|
+
return world;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
this.#overworldId = null;
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
shipWorld() {
|
|
82
|
+
if (this.currentWorldId != null) return this.worlds.get(Number(this.currentWorldId)) || null;
|
|
83
|
+
const cached = this.#shipWorldId != null ? this.worlds.get(this.#shipWorldId) : null;
|
|
84
|
+
if (cached && cached.isOverworld === false) return cached;
|
|
85
|
+
for (const world of this.worlds.values()) {
|
|
86
|
+
if (world.isOverworld === false) {
|
|
87
|
+
this.#shipWorldId = world.id;
|
|
88
|
+
return world;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
this.#shipWorldId = null;
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
currentShipEntity() {
|
|
96
|
+
const shipWorld = this.shipWorld();
|
|
97
|
+
if (!shipWorld || shipWorld.parentWorld == null || shipWorld.parentEntity == null) return null;
|
|
98
|
+
return this.worlds.get(Number(shipWorld.parentWorld))?.entity(shipWorld.parentEntity) || null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
#applyMeta(packet) {
|
|
102
|
+
if (packet.removed) {
|
|
103
|
+
const world = this.worlds.get(Number(packet.world));
|
|
104
|
+
if (world) world.readMeta(packet);
|
|
105
|
+
this.worlds.delete(Number(packet.world));
|
|
106
|
+
if (this.currentWorldId === Number(packet.world)) this.currentWorldId = null;
|
|
107
|
+
return {type: "world-removed", world: world || null, packet};
|
|
108
|
+
}
|
|
109
|
+
const world = this.get(packet.world);
|
|
110
|
+
world.readMeta(packet);
|
|
111
|
+
if (this.currentWorldId == null && !world.isOverworld) this.currentWorldId = world.id;
|
|
112
|
+
return {type: "world", world};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
#applyTiles(packet) {
|
|
116
|
+
const world = this.get(packet.world);
|
|
117
|
+
const decoded = world.decodeEncrypted(packet.data);
|
|
118
|
+
const updates = [];
|
|
119
|
+
const errors = [];
|
|
120
|
+
if (decoded && typeof decoded === "object") {
|
|
121
|
+
for (const [kind, value] of Object.entries(decoded)) {
|
|
122
|
+
try {
|
|
123
|
+
if (kind === "0") updates.push({kind, tiles: world.applyChunk(value)});
|
|
124
|
+
else if (kind === "1") updates.push({kind, tile: world.applyTile(value)});
|
|
125
|
+
else updates.push({kind, value});
|
|
126
|
+
} catch (error) {
|
|
127
|
+
errors.push(error);
|
|
128
|
+
updates.push({kind, value, error: error.message});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
world.recordEvent({type: "tiles", packet, decoded, updates, errors});
|
|
133
|
+
return {type: "tiles", world, decoded, updates, errors};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
#applyModel(packet) {
|
|
137
|
+
const world = this.get(packet.world);
|
|
138
|
+
const result = {
|
|
139
|
+
worldId: packet.world,
|
|
140
|
+
full: Boolean(packet.full),
|
|
141
|
+
events: Array.isArray(packet.events) ? packet.events : [],
|
|
142
|
+
modelData: packet.model_data || null,
|
|
143
|
+
decoded: null,
|
|
144
|
+
model: null,
|
|
145
|
+
commandNumber: typeof packet.command_number === "number" ? packet.command_number : null,
|
|
146
|
+
timing: {
|
|
147
|
+
roundTimeLeft: packet.round_time_left ?? null,
|
|
148
|
+
regenTimeLeft: packet.regen_time_left ?? null,
|
|
149
|
+
removeOnRegen: packet.remove_on_regen ?? null,
|
|
150
|
+
globalEventTime: packet.global_event_time ?? null,
|
|
151
|
+
tickTime: packet.tick_time ?? null,
|
|
152
|
+
tickQuota: packet.tick_quota ?? null,
|
|
153
|
+
cpuLoad: packet.cpu_load ?? null,
|
|
154
|
+
relayTime: packet.relay_time ?? null
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
if (packet.model_data && world.seed != null) {
|
|
158
|
+
const previousModel = world.model;
|
|
159
|
+
try {
|
|
160
|
+
if (packet.full) world.model = new ModelState({isOverworld: world.isOverworld});
|
|
161
|
+
result.decoded = decodeModelPayload(packet.model_data, packet.world, world.seed);
|
|
162
|
+
result.model = world.model.apply(result.decoded, {full: Boolean(packet.full)});
|
|
163
|
+
if (result.model?.error && packet.full && previousModel) world.model = previousModel;
|
|
164
|
+
} catch (error) {
|
|
165
|
+
if (packet.full && previousModel) world.model = previousModel;
|
|
166
|
+
result.error = error;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
pushBounded(world.modelPackets, result, world.historyLimits.modelPacketHistory);
|
|
170
|
+
world.recordEvent({type: "model", packet, result});
|
|
171
|
+
world.lastPacket = packet;
|
|
172
|
+
return {type: "model", world, result};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
#applyCommsBubble(packet) {
|
|
176
|
+
const world = this.get(packet.world);
|
|
177
|
+
const bubble = world.addCommsBubble(packet);
|
|
178
|
+
return {type: "comms-bubble", world, packet, bubble};
|
|
179
|
+
}
|
|
180
|
+
}
|