hytopia 0.1.70 → 0.1.72

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.
@@ -0,0 +1,506 @@
1
+ import {
2
+ Audio,
3
+ BlockType,
4
+ Collider,
5
+ ColliderShape,
6
+ Entity,
7
+ GameServer,
8
+ RigidBodyType,
9
+ startServer,
10
+ Player,
11
+ PlayerEntity,
12
+ SceneUI,
13
+ World,
14
+ } from 'hytopia';
15
+
16
+ import worldMap from './assets/map.json';
17
+
18
+ enum GameWallDirection {
19
+ NORTH = 'NORTH',
20
+ SOUTH = 'SOUTH',
21
+ WEST = 'WEST',
22
+ EAST = 'EAST',
23
+ }
24
+
25
+ const GAME_LEVEL_DURATION_SECONDS = 10;
26
+ const GAME_START_POSITION = { x: 1, y: 25, z: 1 };
27
+ const GAME_START_DELAY_SECONDS = 3;
28
+ const GAME_WALL_DESPAWN_DISTANCE = 18;
29
+ const GAME_WALL_SPAWN_POSITIONS = {
30
+ [GameWallDirection.NORTH]: { x: 1, y: 20, z: -10 }, // North (x width)
31
+ [GameWallDirection.SOUTH]: { x: 1, y: 20, z: 12 }, // South (x width)
32
+ [GameWallDirection.WEST]: { x: -10, y: 20, z: 1 }, // West (z width)
33
+ [GameWallDirection.EAST]: { x: 12, y: 20, z: 1 }, // East (z width)
34
+ }
35
+ const GAME_WALL_VELOCITY_DIRECTIONS = {
36
+ [GameWallDirection.NORTH]: { x: 0, y: 0, z: 1 },
37
+ [GameWallDirection.SOUTH]: { x: 0, y: 0, z: -1 },
38
+ [GameWallDirection.WEST]: { x: 1, y: 0, z: 0 },
39
+ [GameWallDirection.EAST]: { x: -1, y: 0, z: 0 },
40
+ }
41
+ const GAME_WALL_SHAPES = [
42
+ // lol shape
43
+ [
44
+ [0, 1, 0, 1, 1, 1, 0, 1],
45
+ [0, 1, 0, 1, 0, 1, 0, 1],
46
+ [0, 1, 0, 1, 1, 1, 0, 1],
47
+ ],
48
+ // stairs
49
+ [
50
+ [0, 0, 0, 0, 0, 0, 0, 1],
51
+ [0, 0, 0, 0, 0, 1, 1, 1],
52
+ [0, 0, 0, 1, 1, 1, 1, 1],
53
+ [0, 1, 1, 1, 1, 1, 1, 1],
54
+ [1, 1, 1, 1, 1, 1, 1, 1],
55
+ ],
56
+ // horizontal bar
57
+ [
58
+ [1, 1, 1, 1, 1, 1, 1, 1],
59
+ [1, 1, 1, 1, 1, 1, 1, 1],
60
+ ],
61
+ // vertical bars
62
+ [
63
+ [1, 0, 1, 0, 1, 0, 1, 0],
64
+ [1, 0, 1, 0, 1, 0, 1, 0],
65
+ [1, 0, 1, 0, 1, 0, 1, 0],
66
+ [1, 0, 1, 0, 1, 0, 1, 0],
67
+ ],
68
+ // lattice
69
+ [
70
+ [1, 0, 1, 0, 1, 0, 1, 0],
71
+ [0, 1, 0, 1, 0, 1, 0, 1],
72
+ [1, 0, 1, 0, 1, 0, 1, 0],
73
+ [0, 1, 0, 1, 0, 1, 0, 1],
74
+ [1, 0, 1, 0, 1, 0, 1, 0],
75
+ [0, 0, 0, 1, 0, 1, 0, 0],
76
+ [1, 0, 1, 0, 1, 0, 1, 0],
77
+ ],
78
+ // platforming hole
79
+ [
80
+ [1, 1, 1, 0, 0, 1, 1, 1],
81
+ [1, 1, 1, 0, 0, 1, 1, 1],
82
+ [1, 1, 1, 2, 2, 1, 1, 1],
83
+ [1, 1, 2, 1, 1, 2, 1, 1],
84
+ [1, 2, 1, 1, 1, 1, 2, 1],
85
+ [2, 1, 1, 1, 1, 1, 1, 2],
86
+ ],
87
+ // 3d stairs
88
+ [
89
+ [1, 1, 1, 1, 1, 1, 1, 1],
90
+ [2, 1, 2, 1, 1, 2, 1, 2],
91
+ [2, 3, 2, 3, 3, 2, 3, 2],
92
+ [3, 3, 3, 3, 3, 3, 3, 3],
93
+ ],
94
+ // 3d lattice
95
+ [
96
+ [0, 2, 0, 2, 0, 2, 0, 2],
97
+ [1, 0, 1, 0, 1, 0, 1, 0],
98
+ [0, 2, 0, 2, 0, 2, 0, 2],
99
+ [1, 0, 2, 0, 2, 0, 1, 0],
100
+ [1, 0, 1, 0, 1, 0, 1, 0],
101
+ [0, 2, 0, 2, 0, 2, 0, 2],
102
+ [1, 0, 3, 0, 3, 0, 1, 0],
103
+ ],
104
+ // full wall
105
+ [
106
+ [1, 1, 1, 1, 1, 1, 1, 1],
107
+ [1, 1, 1, 1, 1, 1, 1, 1],
108
+ [1, 1, 1, 1, 1, 1, 1, 1],
109
+ [1, 1, 1, 1, 1, 1, 1, 1],
110
+ ],
111
+ // zig zag challenge
112
+ [
113
+ [1, 1, 1, 0, 0, 0, 0, 0],
114
+ [0, 0, 1, 1, 1, 0, 0, 0],
115
+ [0, 0, 0, 0, 1, 1, 1, 0],
116
+ [0, 0, 0, 0, 0, 0, 1, 1],
117
+ ],
118
+ // triple jump
119
+ [
120
+ [0, 0, 1, 0, 0, 1, 0, 0],
121
+ [0, 0, 1, 0, 0, 1, 0, 0],
122
+ [1, 1, 1, 0, 0, 1, 1, 1],
123
+ ],
124
+ // layered tunnel
125
+ [
126
+ [1, 1, 1, 1, 1, 1, 1, 1],
127
+ [1, 0, 0, 2, 2, 0, 0, 1],
128
+ [1, 0, 0, 3, 3, 0, 0, 1],
129
+ [1, 1, 1, 1, 1, 1, 1, 1],
130
+ ],
131
+ // diagonal dash
132
+ [
133
+ [1, 1, 0, 0, 0, 0, 0, 0],
134
+ [0, 1, 1, 0, 0, 0, 0, 0],
135
+ [0, 0, 1, 1, 0, 0, 0, 0],
136
+ [0, 0, 0, 1, 1, 0, 0, 0],
137
+ [0, 0, 0, 0, 1, 1, 0, 0],
138
+ ],
139
+ // wave rider
140
+ [
141
+ [1, 0, 0, 0, 0, 0, 0, 1],
142
+ [1, 1, 0, 0, 0, 0, 1, 1],
143
+ [0, 1, 1, 0, 0, 1, 1, 0],
144
+ [0, 0, 1, 1, 1, 1, 0, 0],
145
+ ],
146
+ // forward momentum
147
+ [
148
+ [1, 1, 2, 2, 3, 3, 3, 3],
149
+ [1, 1, 2, 2, 3, 3, 3, 3],
150
+ [1, 1, 2, 2, 3, 3, 3, 3],
151
+ ],
152
+ // spiral staircase
153
+ [
154
+ [1, 1, 1, 1, 2, 2, 3, 3],
155
+ [3, 3, 1, 1, 1, 2, 2, 2],
156
+ [2, 3, 3, 1, 1, 1, 1, 2],
157
+ [2, 2, 2, 3, 3, 1, 1, 1],
158
+ ],
159
+ // hopscotch
160
+ [
161
+ [0, 1, 0, 1, 0, 1, 0, 1],
162
+ [0, 2, 0, 2, 0, 2, 0, 2],
163
+ [1, 0, 1, 0, 1, 0, 1, 0],
164
+ [2, 0, 2, 0, 2, 0, 2, 0],
165
+ ],
166
+ // snake path
167
+ [
168
+ [1, 1, 1, 1, 0, 0, 0, 0],
169
+ [0, 0, 0, 1, 0, 0, 0, 0],
170
+ [0, 0, 0, 2, 0, 0, 0, 0],
171
+ [0, 0, 0, 3, 3, 3, 3, 3],
172
+ ],
173
+ // diamond gate
174
+ [
175
+ [1, 1, 1, 0, 0, 1, 1, 1],
176
+ [1, 1, 0, 0, 0, 0, 1, 1],
177
+ [1, 0, 2, 0, 0, 2, 0, 1],
178
+ [0, 0, 0, 3, 3, 0, 0, 0],
179
+ [1, 0, 2, 0, 0, 2, 0, 1],
180
+ [1, 1, 0, 0, 0, 0, 1, 1],
181
+ [1, 1, 1, 0, 0, 1, 1, 1],
182
+ ],
183
+ ];
184
+
185
+ const JOIN_NPC_SPAWN_POSITION = { x: 1, y: 35, z: -7 };
186
+ const PLAYER_SPAWN_POSITION = { x: 1, y: 35, z: 2 };
187
+ const QUEUED_PLAYER_ENTITIES = new Set<PlayerEntity>();
188
+ const GAME_PLAYER_ENTITIES = new Set<PlayerEntity>();
189
+
190
+ let gameLevel = 1;
191
+ let gameState: 'awaitingPlayers' | 'starting' | 'inProgress' = 'awaitingPlayers';
192
+ let gameCountdownStartTime: number | null = null;
193
+ let gameInterval: NodeJS.Timer;
194
+ let gameStartTime: number | null = null;
195
+ let gameUiState: object = {};
196
+
197
+ let gameActiveAudio = new Audio({
198
+ uri: 'audio/music.mp3',
199
+ loop: true,
200
+ volume: 0.2,
201
+ });
202
+
203
+ let gameInactiveAudio = new Audio({
204
+ uri: 'audio/music/overworld.mp3',
205
+ loop: true,
206
+ volume: 0.2,
207
+ });
208
+
209
+ startServer(world => {
210
+ world.loadMap(worldMap);
211
+ world.onPlayerJoin = player => onPlayerJoin(world, player);
212
+ world.onPlayerLeave = player => onPlayerLeave(world, player);
213
+
214
+ spawnJoinNpc(world);
215
+
216
+ gameInactiveAudio.play(world);
217
+ });
218
+
219
+ function onPlayerJoin(world: World, player: Player) {
220
+ player.ui.load('ui/index.html');
221
+ player.ui.sendData(gameUiState);
222
+
223
+ const playerEntity = new PlayerEntity({
224
+ player,
225
+ name: 'Player',
226
+ modelUri: 'models/player.gltf',
227
+ modelLoopedAnimations: [ 'idle' ],
228
+ modelScale: 0.5,
229
+ });
230
+
231
+ playerEntity.spawn(world, PLAYER_SPAWN_POSITION);
232
+
233
+ playerEntity.onTick = () => {
234
+ if (playerEntity.position.y < 5) {
235
+ killPlayer(playerEntity);
236
+ }
237
+ }
238
+
239
+ }
240
+
241
+ function onPlayerLeave(world: World, player: Player) {
242
+ world.entityManager.getAllPlayerEntities(player).forEach(entity => {
243
+ // remove from game state
244
+ removePlayerFromQueue(entity);
245
+ killPlayer(entity);
246
+
247
+ // despawn player entity
248
+ entity.despawn();
249
+ });
250
+ }
251
+
252
+ function spawnJoinNpc(world: World) {
253
+ const joinNpc = new Entity({
254
+ name: 'Join NPC',
255
+ modelUri: 'models/mindflayer.gltf',
256
+ modelLoopedAnimations: [ 'idle' ],
257
+ modelScale: 0.4,
258
+ rigidBodyOptions: {
259
+ rotation: { x: 0, y: 1, z: 0, w: 0 },
260
+ enabledPositions: { x: false, y: true, z: false },
261
+ enabledRotations: { x: false, y: true, z: false },
262
+ colliders: [
263
+ Collider.optionsFromModelUri('models/mindflayer.gltf', 0.4),
264
+ {
265
+ shape: ColliderShape.CYLINDER,
266
+ radius: 2,
267
+ halfHeight: 2,
268
+ isSensor: true,
269
+ onCollision: (other: BlockType | Entity, started: boolean) => {
270
+ if (other instanceof PlayerEntity && started) {
271
+ addPlayerEntityToQueue(world, other);
272
+ }
273
+ }
274
+ }
275
+ ],
276
+ },
277
+ });
278
+
279
+ joinNpc.spawn(world, JOIN_NPC_SPAWN_POSITION);
280
+
281
+ const sceneUi = new SceneUI({
282
+ templateId: 'join-npc-ui',
283
+ attachedToEntity: joinNpc,
284
+ offset: { x: 0, y: 2.5, z: 0 },
285
+ });
286
+
287
+ sceneUi.load(world);
288
+ }
289
+
290
+ function addPlayerEntityToQueue(world: World, playerEntity: PlayerEntity) {
291
+ if (QUEUED_PLAYER_ENTITIES.has(playerEntity)) return;
292
+ QUEUED_PLAYER_ENTITIES.add(playerEntity);
293
+ world.chatManager.sendPlayerMessage(playerEntity.player, 'You have joined the next game queue!', '00FF00');
294
+ uiUpdate({ queueCount: QUEUED_PLAYER_ENTITIES.size });
295
+
296
+ if (gameState === 'awaitingPlayers' && QUEUED_PLAYER_ENTITIES.size > 1) {
297
+ queueGame(world);
298
+ }
299
+
300
+ const queuedSceneUi = new SceneUI({
301
+ templateId: 'player-queued',
302
+ attachedToEntity: playerEntity,
303
+ offset: { x: 0, y: 1, z: 0 },
304
+ });
305
+
306
+ queuedSceneUi.load(world);
307
+ }
308
+
309
+ function queueGame(world: World) {
310
+ gameState = 'starting';
311
+ gameCountdownStartTime = Date.now();
312
+
313
+ uiUpdate({
314
+ gameState,
315
+ gameCountdownStartTime,
316
+ countdown: GAME_START_DELAY_SECONDS,
317
+ });
318
+
319
+ setTimeout(() => {
320
+ QUEUED_PLAYER_ENTITIES.forEach(playerEntity => {
321
+ playerEntity.setPosition(GAME_START_POSITION);
322
+ GAME_PLAYER_ENTITIES.add(playerEntity);
323
+
324
+ world.sceneUIManager.getAllEntityAttachedSceneUIs(playerEntity).forEach(sceneUi => {
325
+ sceneUi.unload(); // unload the queued ui
326
+ });
327
+ });
328
+
329
+ QUEUED_PLAYER_ENTITIES.clear();
330
+ uiUpdate({ queueCount: 0 });
331
+ startGame(world);
332
+ }, GAME_START_DELAY_SECONDS * 1000);
333
+ }
334
+
335
+ function startGame(world: World) {
336
+ gameState = 'inProgress';
337
+ gameStartTime = Date.now();
338
+
339
+ gameInactiveAudio.pause();
340
+ gameActiveAudio.play(world, true);
341
+
342
+ const gameLoop = () => {
343
+ if (!gameStartTime) return clearTimeout(gameInterval);
344
+
345
+ const elapsedTime = Date.now() - gameStartTime;
346
+ const level = Math.floor(elapsedTime / (GAME_LEVEL_DURATION_SECONDS * 1000)) + 1;
347
+
348
+ // Calculate difficulty parameters based on level
349
+ const speedModifier = 1 + (level * 0.15); // 15% faster per level
350
+ const spawnInterval = Math.max(2000, 10000 - (level * 1000)); // Decrease from 10s by 1s per level, min 2s
351
+
352
+ // Generate wall from random direction
353
+ const directions = Object.values(GameWallDirection);
354
+ const randomDirection = directions[Math.floor(Math.random() * directions.length)];
355
+
356
+ generateWall(world, randomDirection, speedModifier);
357
+
358
+ // Dynamically adjust interval
359
+ clearTimeout(gameInterval);
360
+ gameInterval = setTimeout(gameLoop, spawnInterval);
361
+
362
+ // Update level
363
+ if (level > gameLevel) {
364
+ gameLevel = level;
365
+ uiUpdate({ gameLevel });
366
+ }
367
+ }
368
+
369
+ gameLoop(); // invoke to start
370
+
371
+ // Update UI
372
+ uiUpdate({
373
+ gameState: 'inProgress',
374
+ gameStartTime,
375
+ playersRemaining: Array.from(GAME_PLAYER_ENTITIES).map(playerEntity => playerEntity.player.username),
376
+ });
377
+ }
378
+
379
+ function endGame() {
380
+ clearTimeout(gameInterval);
381
+ gameState = 'awaitingPlayers';
382
+ gameLevel = 1;
383
+
384
+ const winner = Array.from(GAME_PLAYER_ENTITIES)[0];
385
+ if (winner) {
386
+ winner.setPosition(PLAYER_SPAWN_POSITION);
387
+ winner.world!.chatManager.sendBroadcastMessage(`${winner.player.username} wins!`, '00FF00');
388
+ }
389
+
390
+ GAME_PLAYER_ENTITIES.clear();
391
+
392
+ gameActiveAudio.pause();
393
+ gameInactiveAudio.play(gameInactiveAudio.world!, true);
394
+
395
+ uiUpdate({ gameState: 'awaitingPlayers', gameLevel: 1 });
396
+ }
397
+
398
+ function killPlayer(playerEntity: PlayerEntity) {
399
+ if (!GAME_PLAYER_ENTITIES.has(playerEntity)) return;
400
+
401
+ // return to spawn position
402
+ playerEntity.setPosition(PLAYER_SPAWN_POSITION);
403
+
404
+ // remove from game player list
405
+ GAME_PLAYER_ENTITIES.delete(playerEntity);
406
+
407
+ if (GAME_PLAYER_ENTITIES.size < 1) {
408
+ endGame();
409
+ }
410
+
411
+ // update remaining players for the ui
412
+ uiUpdate({
413
+ playersRemaining: Array.from(GAME_PLAYER_ENTITIES).map(playerEntity => playerEntity.player.username),
414
+ });
415
+ }
416
+
417
+ function removePlayerFromQueue(playerEntity: PlayerEntity) {
418
+ if (!QUEUED_PLAYER_ENTITIES.has(playerEntity)) return;
419
+
420
+ // remove from queue
421
+ QUEUED_PLAYER_ENTITIES.delete(playerEntity);
422
+
423
+ // update ui
424
+ uiUpdate({ queueCount: QUEUED_PLAYER_ENTITIES.size });
425
+ }
426
+
427
+ function generateWall(world: World, direction: GameWallDirection, speedModifier: number = 1) {
428
+ const selectedShape = GAME_WALL_SHAPES[Math.floor(Math.random() * GAME_WALL_SHAPES.length)];
429
+ const isNorthSouth = direction === GameWallDirection.NORTH || direction === GameWallDirection.SOUTH;
430
+
431
+ // Iterate through the shape array to create wall segments
432
+ for (let y = 0; y < selectedShape.length; y++) {
433
+ for (let x = 0; x < selectedShape[y].length; x++) {
434
+ if (selectedShape[y][x] === 1 || selectedShape[y][x] === 2 || selectedShape[y][x] === 3) {
435
+ // Calculate position offset from center
436
+ const xOffset = (x - selectedShape[y].length / 2) * 1 + 0.5; // 1 block width
437
+ const yOffset = (selectedShape.length - y - 1) * 1 + 0.5; // 1 block height
438
+
439
+ const wallSegment = new Entity({
440
+ blockTextureUri: selectedShape[y][x] === 2 ? 'textures/dirt.png' :
441
+ selectedShape[y][x] === 3 ? 'textures/sand.png' :
442
+ 'textures/oak_leaves.png',
443
+ blockHalfExtents: {
444
+ x: isNorthSouth ? 0.5 : 0.5,
445
+ y: 0.5,
446
+ z: isNorthSouth ? 0.5 : 0.5
447
+ },
448
+ rigidBodyOptions: {
449
+ type: RigidBodyType.KINEMATIC_VELOCITY,
450
+ }
451
+ });
452
+
453
+ const spawnPosition = { ...GAME_WALL_SPAWN_POSITIONS[direction] };
454
+
455
+ // Adjust position based on direction
456
+ if (isNorthSouth) {
457
+ spawnPosition.x += xOffset;
458
+ spawnPosition.y += yOffset;
459
+ if (selectedShape[y][x] === 2) {
460
+ spawnPosition.z += direction === GameWallDirection.NORTH ? 1 : -1;
461
+ } else if (selectedShape[y][x] === 3) {
462
+ spawnPosition.z += direction === GameWallDirection.NORTH ? 2 : -2;
463
+ }
464
+ } else {
465
+ spawnPosition.z += xOffset;
466
+ spawnPosition.y += yOffset;
467
+ if (selectedShape[y][x] === 2) {
468
+ spawnPosition.x += direction === GameWallDirection.WEST ? 1 : -1;
469
+ } else if (selectedShape[y][x] === 3) {
470
+ spawnPosition.x += direction === GameWallDirection.WEST ? 2 : -2;
471
+ }
472
+ }
473
+
474
+ wallSegment.spawn(world, spawnPosition);
475
+
476
+ // Set velocity based on direction
477
+ wallSegment.setLinearVelocity({
478
+ x: GAME_WALL_VELOCITY_DIRECTIONS[direction].x * speedModifier,
479
+ y: GAME_WALL_VELOCITY_DIRECTIONS[direction].y * speedModifier,
480
+ z: GAME_WALL_VELOCITY_DIRECTIONS[direction].z * speedModifier,
481
+ });
482
+
483
+ wallSegment.onTick = () => {
484
+ if (!wallSegment.isSpawned) return;
485
+ const position = wallSegment.position;
486
+
487
+ if (Math.abs(position.x - spawnPosition.x) > GAME_WALL_DESPAWN_DISTANCE || Math.abs(position.z - spawnPosition.z) > GAME_WALL_DESPAWN_DISTANCE) {
488
+ wallSegment.setLinearVelocity({ x: 0, y: -32, z: 0 }); // drop it out of the world to trigger decollision
489
+
490
+ setTimeout(() => {
491
+ wallSegment.despawn();
492
+ }, 1000);
493
+ }
494
+ }
495
+ }
496
+ }
497
+ }
498
+ }
499
+
500
+ function uiUpdate(data: object) {
501
+ gameUiState = { ...gameUiState, ...data };
502
+
503
+ GameServer.instance.playerManager.getConnectedPlayers().forEach(player => {
504
+ player.ui.sendData(data);
505
+ });
506
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "hole-in-wall-game",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "keywords": [],
10
+ "author": "",
11
+ "license": "ISC",
12
+ "dependencies": {
13
+ "@hytopia.com/assets": "^0.1.6",
14
+ "hytopia": "latest"
15
+ }
16
+ }
@@ -0,0 +1,6 @@
1
+ # wall-dodge-game
2
+
3
+ A game where players compete to survive the longest while dodging walls.
4
+
5
+ Includes a number of features such as leaderboards, countdown systems,
6
+ and more.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hytopia",
3
- "version": "0.1.70",
3
+ "version": "0.1.72",
4
4
  "description": "The HYTOPIA SDK makes it easy for developers to create massively multiplayer games using JavaScript or TypeScript.",
5
5
  "main": "server.js",
6
6
  "bin": {
package/server.api.json CHANGED
@@ -4665,6 +4665,45 @@
4665
4665
  "name": "ChatManager",
4666
4666
  "preserveMemberOrder": false,
4667
4667
  "members": [
4668
+ {
4669
+ "kind": "Property",
4670
+ "canonicalReference": "server!ChatManager#onBroadcastMessage:member",
4671
+ "docComment": "/**\n * A function that is called when a broadcast (public) message is sent by a player or the server.\n *\n * @param player - The player that sent the message, or undefined if the message is a system message from the server.\n *\n * @param message - The message to send.\n *\n * @param color - The color of the message as a hex color code, excluding #.\n */\n",
4672
+ "excerptTokens": [
4673
+ {
4674
+ "kind": "Content",
4675
+ "text": "onBroadcastMessage?: "
4676
+ },
4677
+ {
4678
+ "kind": "Content",
4679
+ "text": "(player: "
4680
+ },
4681
+ {
4682
+ "kind": "Reference",
4683
+ "text": "Player",
4684
+ "canonicalReference": "server!Player:class"
4685
+ },
4686
+ {
4687
+ "kind": "Content",
4688
+ "text": " | undefined, message: string, color?: string) => void"
4689
+ },
4690
+ {
4691
+ "kind": "Content",
4692
+ "text": ";"
4693
+ }
4694
+ ],
4695
+ "isReadonly": false,
4696
+ "isOptional": true,
4697
+ "releaseTag": "Public",
4698
+ "name": "onBroadcastMessage",
4699
+ "propertyTypeTokenRange": {
4700
+ "startIndex": 1,
4701
+ "endIndex": 4
4702
+ },
4703
+ "isStatic": false,
4704
+ "isProtected": false,
4705
+ "isAbstract": false
4706
+ },
4668
4707
  {
4669
4708
  "kind": "Method",
4670
4709
  "canonicalReference": "server!ChatManager#registerCommand:member(1)",
@@ -4733,7 +4772,7 @@
4733
4772
  {
4734
4773
  "kind": "Method",
4735
4774
  "canonicalReference": "server!ChatManager#sendBroadcastMessage:member(1)",
4736
- "docComment": "/**\n * Send a broadcast message to all players in the world.\n *\n * @param message - The message to send.\n *\n * @param color - The color of the message as a hex color code, excluding #.\n *\n * @example\n * ```typescript\n * chatManager.sendBroadcastMessage('Hello, world!', 'FF00AA');\n * ```\n *\n */\n",
4775
+ "docComment": "/**\n * Send a system broadcast message to all players in the world.\n *\n * @param message - The message to send.\n *\n * @param color - The color of the message as a hex color code, excluding #.\n *\n * @example\n * ```typescript\n * chatManager.sendBroadcastMessage('Hello, world!', 'FF00AA');\n * ```\n *\n */\n",
4737
4776
  "excerptTokens": [
4738
4777
  {
4739
4778
  "kind": "Content",
@@ -4797,7 +4836,7 @@
4797
4836
  {
4798
4837
  "kind": "Method",
4799
4838
  "canonicalReference": "server!ChatManager#sendPlayerMessage:member(1)",
4800
- "docComment": "/**\n * Send a message to a specific player, only visible to them.\n *\n * @param player - The player to send the message to.\n *\n * @param message - The message to send.\n *\n * @param color - The color of the message as a hex color code, excluding #.\n *\n * @example\n * ```typescript\n * chatManager.sendPlayerMessage(player, 'Hello, player!', 'FF00AA');\n * ```\n *\n */\n",
4839
+ "docComment": "/**\n * Send a system message to a specific player, only visible to them.\n *\n * @param player - The player to send the message to.\n *\n * @param message - The message to send.\n *\n * @param color - The color of the message as a hex color code, excluding #.\n *\n * @example\n * ```typescript\n * chatManager.sendPlayerMessage(player, 'Hello, player!', 'FF00AA');\n * ```\n *\n */\n",
4801
4840
  "excerptTokens": [
4802
4841
  {
4803
4842
  "kind": "Content",
@@ -25399,6 +25438,55 @@
25399
25438
  "isAbstract": false,
25400
25439
  "name": "setCcdEnabled"
25401
25440
  },
25441
+ {
25442
+ "kind": "Method",
25443
+ "canonicalReference": "server!RigidBody#setCollisionGroupsForSensorColliders:member(1)",
25444
+ "docComment": "/**\n * Sets the collision groups for sensor colliders of the rigid body.\n *\n * @param collisionGroups - The collision groups for sensor colliders of the rigid body.\n */\n",
25445
+ "excerptTokens": [
25446
+ {
25447
+ "kind": "Content",
25448
+ "text": "setCollisionGroupsForSensorColliders(collisionGroups: "
25449
+ },
25450
+ {
25451
+ "kind": "Reference",
25452
+ "text": "CollisionGroups",
25453
+ "canonicalReference": "server!CollisionGroups:type"
25454
+ },
25455
+ {
25456
+ "kind": "Content",
25457
+ "text": "): "
25458
+ },
25459
+ {
25460
+ "kind": "Content",
25461
+ "text": "void"
25462
+ },
25463
+ {
25464
+ "kind": "Content",
25465
+ "text": ";"
25466
+ }
25467
+ ],
25468
+ "isStatic": false,
25469
+ "returnTypeTokenRange": {
25470
+ "startIndex": 3,
25471
+ "endIndex": 4
25472
+ },
25473
+ "releaseTag": "Public",
25474
+ "isProtected": false,
25475
+ "overloadIndex": 1,
25476
+ "parameters": [
25477
+ {
25478
+ "parameterName": "collisionGroups",
25479
+ "parameterTypeTokenRange": {
25480
+ "startIndex": 1,
25481
+ "endIndex": 2
25482
+ },
25483
+ "isOptional": false
25484
+ }
25485
+ ],
25486
+ "isOptional": false,
25487
+ "isAbstract": false,
25488
+ "name": "setCollisionGroupsForSensorColliders"
25489
+ },
25402
25490
  {
25403
25491
  "kind": "Method",
25404
25492
  "canonicalReference": "server!RigidBody#setCollisionGroupsForSolidColliders:member(1)",