• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

BunnyNabbit / node-celaria-server / 21657032032

04 Feb 2026 03:16AM UTC coverage: 21.589% (+7.7%) from 13.856%
21657032032

push

github

web-flow
Merge pull request #26 from BunnyNabbit/use-typescript

Use ES modules

32 of 32 branches covered (100.0%)

Branch coverage included in aggregate %.

298 of 504 new or added lines in 16 files covered. (59.13%)

419 of 2057 relevant lines covered (20.37%)

0.41 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

5.96
/data/cmapLib.mjs
1
// @ts-check
2✔
2
import { SmartBuffer } from "smart-buffer"
2✔
3
/** @import {CelariaMap} from "./types.mjs" */
2✔
4

2✔
5
/**@todo Yet to be documented.
2✔
6
 *
2✔
7
 * @param {Buffer} buffer
2✔
8
 * @returns
2✔
9
 */
2✔
10
export function parseCelariaMap(buffer) {
2✔
NEW
11
        /** @type {CelariaMap} */
×
12
        const map = {}
×
NEW
13
        const smartBuffer = SmartBuffer.fromBuffer(buffer)
×
NEW
14
        const magic = smartBuffer.readString(11)
×
15
        if (magic === "celaria_map") {
×
NEW
16
                map.version = smartBuffer.readUInt8() // Version
×
17

×
NEW
18
                map.name = smartBuffer.readString(smartBuffer.readUInt8())
×
19

×
NEW
20
                if (map.version == 0) smartBuffer.readInt8() // unused byte
×
21

×
NEW
22
                map.mode = smartBuffer.readUInt8() // unused byte (is it really? it's used in validation inside the server code but not client)
×
23

×
NEW
24
                const numCheckpoints = smartBuffer.readInt8()
×
25

×
26
                map.medalTimes = []
×
27

×
28
                for (let i = 0; i < numCheckpoints; i++) {
×
29
                        map.medalTimes.push({
×
NEW
30
                                platin: smartBuffer.readUInt32LE(),
×
NEW
31
                                gold: smartBuffer.readUInt32LE(),
×
NEW
32
                                silver: smartBuffer.readUInt32LE(),
×
NEW
33
                                bronze: smartBuffer.readUInt32LE(),
×
34
                        })
×
35
                }
×
36

×
NEW
37
                map.sunRotationHorizontal = smartBuffer.readFloatLE()
×
NEW
38
                map.sunRotationVertical = smartBuffer.readFloatLE()
×
39

×
NEW
40
                map.previewCamFromX = smartBuffer.readDoubleLE()
×
NEW
41
                map.previewCamFromY = smartBuffer.readDoubleLE()
×
NEW
42
                map.previewCamFromZ = smartBuffer.readDoubleLE()
×
43

×
NEW
44
                map.previewCamToX = smartBuffer.readDoubleLE()
×
NEW
45
                map.previewCamToY = smartBuffer.readDoubleLE()
×
NEW
46
                map.previewCamToZ = smartBuffer.readDoubleLE()
×
47

×
NEW
48
                const instanceCount = smartBuffer.readUInt32LE()
×
49

×
50
                map.instances = []
×
51

×
NEW
52
                for (let i = 0; i < instanceCount; i++) {
×
NEW
53
                        const instance = {
×
NEW
54
                                instanceType: smartBuffer.readUInt8(),
×
NEW
55
                        }
×
56
                        switch (instance.instanceType) {
×
57
                                case 0: // block
×
NEW
58
                                        instance.blockType = smartBuffer.readUInt8()
×
NEW
59
                                        if (map.version == 0) smartBuffer.readUInt8() // unused byte
×
60

×
61
                                        if (map.version <= 1) {
×
62
                                                instance.position = {
×
NEW
63
                                                        x: smartBuffer.readInt32LE() / 10,
×
NEW
64
                                                        y: smartBuffer.readInt32LE() / 10,
×
NEW
65
                                                        z: smartBuffer.readUInt32LE() / 10,
×
66
                                                }
×
67

×
68
                                                instance.scale = {
×
NEW
69
                                                        x: smartBuffer.readUInt32LE() / 10,
×
NEW
70
                                                        y: smartBuffer.readUInt32LE() / 10,
×
NEW
71
                                                        z: smartBuffer.readUInt32LE() / 10,
×
72
                                                }
×
73
                                        } else {
×
74
                                                instance.position = {
×
NEW
75
                                                        x: smartBuffer.readDoubleLE(),
×
NEW
76
                                                        y: smartBuffer.readDoubleLE(),
×
NEW
77
                                                        z: smartBuffer.readDoubleLE(),
×
78
                                                }
×
79

×
80
                                                instance.scale = {
×
NEW
81
                                                        x: smartBuffer.readDoubleLE(),
×
NEW
82
                                                        y: smartBuffer.readDoubleLE(),
×
NEW
83
                                                        z: smartBuffer.readDoubleLE(),
×
84
                                                }
×
85
                                        }
×
86

×
87
                                        instance.rotation = {
×
88
                                                x: 0,
×
89
                                                y: 0,
×
NEW
90
                                                z: smartBuffer.readFloatLE(),
×
91
                                        }
×
92

×
NEW
93
                                        if (instance.blockType === 5) instance.checkpointId = smartBuffer.readUInt8()
×
94
                                        break
×
95

×
96
                                case 1: // Sphere/gem
×
97
                                        if (map.version <= 1) {
×
98
                                                instance.position = {}
×
NEW
99
                                                instance.position.x = smartBuffer.readInt32LE() / 10
×
NEW
100
                                                instance.position.y = smartBuffer.readInt32LE() / 10
×
101
                                                if (map.version == 0) {
×
NEW
102
                                                        instance.position.z = smartBuffer.readInt32LE() / 10
×
103
                                                } else {
×
NEW
104
                                                        instance.position.z = smartBuffer.readUInt32LE() / 10
×
105
                                                }
×
106
                                        } else {
×
107
                                                instance.position = {
×
NEW
108
                                                        x: smartBuffer.readDoubleLE(),
×
NEW
109
                                                        y: smartBuffer.readDoubleLE(),
×
NEW
110
                                                        z: smartBuffer.readDoubleLE(),
×
111
                                                }
×
112
                                        }
×
113
                                        break
×
114
                                case 2: // Player spawn
×
NEW
115
                                        smartBuffer.readUInt8() // unused byte
×
116

×
117
                                        if (map.version <= 1) {
×
118
                                                instance.position = {}
×
NEW
119
                                                instance.position.x = smartBuffer.readInt32LE() / 10
×
NEW
120
                                                instance.position.y = smartBuffer.readInt32LE() / 10
×
121
                                                if (map.version == 0) {
×
NEW
122
                                                        instance.position.z = smartBuffer.readInt32LE() / 10
×
123
                                                } else {
×
NEW
124
                                                        instance.position.z = smartBuffer.readUInt32LE() / 10
×
125
                                                }
×
126
                                        } else {
×
127
                                                instance.position = {
×
NEW
128
                                                        x: smartBuffer.readDoubleLE(),
×
NEW
129
                                                        y: smartBuffer.readDoubleLE(),
×
NEW
130
                                                        z: smartBuffer.readDoubleLE(),
×
131
                                                }
×
132
                                        }
×
133

×
134
                                        instance.rotation = {
×
135
                                                x: 0,
×
136
                                                y: 0,
×
NEW
137
                                                z: smartBuffer.readFloatLE(),
×
138
                                        }
×
139
                                        break
×
140

×
141
                                case 3: // Barrier (wall)
×
NEW
142
                                        smartBuffer.readUInt8() // unused byte
×
143

×
144
                                        if (map.version >= 2) {
×
145
                                                instance.position = {
×
NEW
146
                                                        x: smartBuffer.readInt32LE() / 10,
×
NEW
147
                                                        y: smartBuffer.readInt32LE() / 10,
×
NEW
148
                                                        z: smartBuffer.readUInt32LE() / 10,
×
149
                                                }
×
150

×
151
                                                instance.scale = {
×
NEW
152
                                                        x: smartBuffer.readUInt32LE() / 10,
×
153
                                                        y: 0,
×
NEW
154
                                                        z: smartBuffer.readUInt32LE() / 10,
×
155
                                                }
×
156
                                        } else {
×
157
                                                instance.position = {
×
NEW
158
                                                        x: smartBuffer.readDoubleLE(),
×
NEW
159
                                                        y: smartBuffer.readDoubleLE(),
×
NEW
160
                                                        z: smartBuffer.readDoubleLE(),
×
161
                                                }
×
162

×
163
                                                instance.scale = {
×
NEW
164
                                                        x: smartBuffer.readDoubleLE(),
×
165
                                                        y: 0,
×
NEW
166
                                                        z: smartBuffer.readDoubleLE(),
×
167
                                                }
×
168
                                        }
×
169

×
170
                                        instance.rotation = {
×
171
                                                x: 0,
×
172
                                                y: 0,
×
NEW
173
                                                z: smartBuffer.readFloatLE(),
×
174
                                        }
×
175
                                        break
×
176
                                case 4: // Barrier (floor)
×
NEW
177
                                        smartBuffer.readUInt8() // unused byte
×
178

×
179
                                        if (map.version >= 2) {
×
180
                                                instance.position = {
×
NEW
181
                                                        x: smartBuffer.readInt32LE() / 10,
×
NEW
182
                                                        y: smartBuffer.readInt32LE() / 10,
×
NEW
183
                                                        z: smartBuffer.readUInt32LE() / 10,
×
184
                                                }
×
185

×
186
                                                instance.scale = {
×
NEW
187
                                                        x: smartBuffer.readUInt32LE() / 10,
×
NEW
188
                                                        y: smartBuffer.readUInt32LE() / 10,
×
189
                                                        z: 0,
×
190
                                                }
×
191
                                        } else {
×
192
                                                instance.position = {
×
NEW
193
                                                        x: smartBuffer.readDoubleLE(),
×
NEW
194
                                                        y: smartBuffer.readDoubleLE(),
×
NEW
195
                                                        z: smartBuffer.readDoubleLE(),
×
196
                                                }
×
197

×
198
                                                instance.scale = {
×
NEW
199
                                                        x: smartBuffer.readDoubleLE(),
×
NEW
200
                                                        y: smartBuffer.readDoubleLE(),
×
201
                                                        z: 0,
×
202
                                                }
×
203
                                        }
×
204

×
205
                                        instance.rotation = {
×
206
                                                x: 0,
×
207
                                                y: 0,
×
NEW
208
                                                z: smartBuffer.readFloatLE(),
×
209
                                        }
×
210
                                        break
×
211
                                case 128: // Special
×
NEW
212
                                        const id = smartBuffer.readUInt8()
×
213

×
214
                                        if (map.version <= 1) {
×
NEW
215
                                                const xPos = smartBuffer.readInt32LE()
×
NEW
216
                                                const yPos = smartBuffer.readInt32LE()
×
NEW
217
                                                const zPos = smartBuffer.readUInt32LE()
×
218

×
NEW
219
                                                const xScale = smartBuffer.readUInt32LE()
×
NEW
220
                                                const yScale = smartBuffer.readUInt32LE()
×
NEW
221
                                                const zScale = smartBuffer.readUInt32LE()
×
222
                                        } else {
×
NEW
223
                                                const xPos = smartBuffer.readDoubleLE()
×
NEW
224
                                                const yPos = smartBuffer.readDoubleLE()
×
NEW
225
                                                const zPos = smartBuffer.readDoubleLE()
×
226

×
NEW
227
                                                const xScale = smartBuffer.readDoubleLE()
×
NEW
228
                                                const yScale = smartBuffer.readDoubleLE()
×
NEW
229
                                                const zScale = smartBuffer.readDoubleLE()
×
230
                                        }
×
231

×
NEW
232
                                        const rotation = smartBuffer.readFloatLE()
×
233
                                        break
×
234

×
235
                                default:
×
236
                                        break
×
237
                        }
×
238
                        map.instances.push(instance)
×
239
                }
×
240
                return map
×
241
        } else {
×
242
                throw "Map provided wasn't a .ecmap"
×
243
        }
×
244
}
×
245

2✔
246
// TODO: Many of the map data to write should be optional and have default values for everything to "just work" if the map alone has no checkpoint data, sun or name and just the map blocks.
2✔
247
/**@todo Yet to be documented.
2✔
248
 *
2✔
249
 * @param {CelariaMap} map
2✔
250
 */
2✔
251
export function writeCelariaMap(map, version = 3) {
2✔
252
        // this can modify the original object
×
253
        if (!map.instances)
×
254
                map.instances = [
×
255
                        {
×
256
                                instanceType: 2,
×
257
                                position: { x: 0, y: 0, z: 0 },
×
258
                                rotation: { z: 0 },
×
259
                        },
×
260
                ]
×
261

×
NEW
262
        const output = new SmartBuffer()
×
263
        output.writeString("celaria_map")
×
264
        output.writeUInt8(version) // Version
×
265

×
266
        // Might be a temp file on someone's computer never to be normally seen again
×
267
        const mapName = "DELETE_ME" //map.name ?? "DELETE_ME"
×
268
        output.writeUInt8(mapName.length)
×
269
        output.writeString(mapName)
×
270

×
271
        if (version == 0) output.writeUInt8(0) // unused byte
×
272
        output.writeUInt8(1) // Mode byte: Must be 1 for Celaria server (Java) to work. Otherwise doesn't matter
×
273

×
274
        const numCheckpoints = map.instances.filter((instance) => instance.instanceType === 0 && (instance.blockType === 5 || instance.blockType === 1)).length
×
275
        output.writeUInt8(numCheckpoints)
×
276
        for (let i = 0; i < numCheckpoints; i++) {
×
277
                // Purposefully have impossible to beat times for maps written by cmapLib.js
×
278
                output.writeUInt32LE(1)
×
279
                output.writeUInt32LE(2)
×
280
                output.writeUInt32LE(3)
×
281
                output.writeUInt32LE(4)
×
282
        }
×
283

×
284
        output.writeFloatLE(map.sunRotationHorizontal ?? 40)
×
285
        output.writeFloatLE(map.sunRotationVertical ?? 60)
×
286

×
287
        output.writeDoubleLE(map.previewCamFromX ?? 20)
×
288
        output.writeDoubleLE(map.previewCamFromY ?? 30)
×
289
        output.writeDoubleLE(map.previewCamFromZ ?? 40)
×
290

×
291
        output.writeDoubleLE(map.previewCamToX ?? 200)
×
292
        output.writeDoubleLE(map.previewCamToY ?? 300)
×
293
        output.writeDoubleLE(map.previewCamToZ ?? 200)
×
294

×
295
        output.writeUInt32LE(map.instances.length)
×
296

×
297
        // write data
×
298
        map.instances.forEach((instance) => {
×
299
                if (!instanceTypeIsSupported(instance.instanceType, version)) return
×
300
                output.writeUInt8(instance.instanceType)
×
301
                switch (instance.instanceType) {
×
302
                        case 0: // block
×
303
                                output.writeUInt8(instance.blockType)
×
304
                                if (version == 0) output.writeUInt8(0) // unused byte
×
305

×
306
                                if (version <= 1) {
×
307
                                        output.writeInt32LE(instance.position.x * 10)
×
308
                                        output.writeInt32LE(instance.position.y * 10)
×
309
                                        output.writeUInt32LE(instance.position.z * 10)
×
310

×
311
                                        output.writeUInt32LE(instance.scale.x * 10)
×
312
                                        output.writeUInt32LE(instance.scale.y * 10)
×
313
                                        output.writeUInt32LE(instance.scale.z * 10)
×
314
                                } else {
×
315
                                        output.writeDoubleLE(instance.position.x)
×
316
                                        output.writeDoubleLE(instance.position.y)
×
317
                                        output.writeDoubleLE(instance.position.z)
×
318

×
319
                                        output.writeDoubleLE(instance.scale.x)
×
320
                                        output.writeDoubleLE(instance.scale.y)
×
321
                                        output.writeDoubleLE(instance.scale.z)
×
322
                                }
×
323

×
324
                                output.writeFloatLE(instance.rotation.z)
×
325

×
326
                                if (instance.blockType === 5) output.writeUInt8(instance.checkpointId)
×
327
                                break
×
328

×
329
                        case 1: // Sphere/gem/collectible/schmilblick
×
330
                                if (version <= 1) {
×
331
                                        output.writeInt32LE(instance.position.x * 10)
×
332
                                        output.writeInt32LE(instance.position.y * 10)
×
333
                                        if (version == 0) {
×
334
                                                output.writeInt32LE(instance.position.z * 10)
×
335
                                        } else {
×
336
                                                output.writeUInt32LE(instance.position.z * 10)
×
337
                                        }
×
338
                                } else {
×
339
                                        output.writeDoubleLE(instance.position.x)
×
340
                                        output.writeDoubleLE(instance.position.y)
×
341
                                        output.writeDoubleLE(instance.position.z)
×
342
                                }
×
343
                                break
×
344
                        case 2: // Player spawn
×
345
                                output.writeUInt8(0) // unused byte
×
346

×
347
                                if (version <= 1) {
×
348
                                        output.writeInt32LE(instance.position.x * 10)
×
349
                                        output.writeInt32LE(instance.position.y * 10)
×
350
                                        if (version == 0) {
×
351
                                                output.writeInt32LE(instance.position.z * 10)
×
352
                                        } else {
×
353
                                                output.writeUInt32LE(instance.position.z * 10)
×
354
                                        }
×
355
                                } else {
×
356
                                        output.writeDoubleLE(instance.position.x)
×
357
                                        output.writeDoubleLE(instance.position.y)
×
358
                                        output.writeDoubleLE(instance.position.z)
×
359
                                }
×
360

×
361
                                output.writeFloatLE(instance.rotation.z)
×
362
                                break
×
363

×
364
                        case 3: // Barrier (wall)
×
365
                                output.writeUInt8(0) // unused byte
×
366

×
367
                                if (version === 3) {
×
368
                                        output.writeInt32LE(instance.position * 10)
×
369
                                        output.writeInt32LE(instance.position * 10)
×
370
                                        output.writeUInt32LE(instance.position * 10)
×
371

×
372
                                        output.writeUInt32LE(instance.scale.x * 10)
×
373
                                        output.writeUInt32LE(instance.scale.z * 10)
×
374
                                } else {
×
375
                                        output.writeDoubleLE(instance.position.x)
×
376
                                        output.writeDoubleLE(instance.position.y)
×
377
                                        output.writeDoubleLE(instance.position.z)
×
378

×
379
                                        output.writeDoubleLE(instance.scale.x)
×
380
                                        output.writeDoubleLE(instance.scale.z)
×
381
                                }
×
382

×
383
                                output.writeFloatLE(instance.rotation.z)
×
384
                                break
×
385
                        case 4: // Barrier (floor)
×
386
                                output.writeUInt8(0) // unused byte
×
387

×
388
                                if (version === 3) {
×
389
                                        output.writeInt32LE(instance.position * 10)
×
390
                                        output.writeInt32LE(instance.position * 10)
×
391
                                        output.writeUInt32LE(instance.position * 10)
×
392

×
393
                                        output.writeUInt32LE(instance.scale.x * 10)
×
394
                                        output.writeUInt32LE(instance.scale.y * 10)
×
395
                                } else {
×
396
                                        output.writeDoubleLE(instance.position.x)
×
397
                                        output.writeDoubleLE(instance.position.y)
×
398
                                        output.writeDoubleLE(instance.position.z)
×
399

×
400
                                        output.writeDoubleLE(instance.scale.x)
×
401
                                        output.writeDoubleLE(instance.scale.y)
×
402
                                }
×
403

×
404
                                output.writeFloatLE(instance.rotation.z)
×
405
                                break
×
406
                        default:
×
407
                                break
×
408
                }
×
409
        })
×
410

×
411
        return output.toBuffer()
×
412
}
×
413

2✔
414
// TODO: Again, this uses the .ecmap versions (0 - 4)
2✔
415
/**@todo Yet to be documented.
2✔
416
 *
2✔
417
 * @param {any} instanceType
2✔
418
 * @param {number} version
2✔
419
 */
2✔
420
function instanceTypeIsSupported(instanceType, version) {
×
421
        switch (instanceType) {
×
422
                case 3:
×
423
                        if (version < 3) return false
×
424
                        break
×
425
                case 4:
×
426
                        if (version < 3) return false
×
427
                        break
×
428

×
429
                default:
×
430
                        break
×
431
        }
×
432

×
433
        return true
×
434
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc