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.
- package/docs/server.chatmanager.md +46 -2
- package/docs/server.chatmanager.onbroadcastmessage.md +13 -0
- package/docs/server.chatmanager.sendbroadcastmessage.md +1 -1
- package/docs/server.chatmanager.sendplayermessage.md +1 -1
- package/docs/server.rigidbody.md +14 -0
- package/docs/server.rigidbody.setcollisiongroupsforsensorcolliders.md +53 -0
- package/examples/hole-in-wall-game/README.md +5 -0
- package/examples/hole-in-wall-game/assets/audio/music.mp3 +0 -0
- package/examples/hole-in-wall-game/assets/map.json +8109 -0
- package/examples/hole-in-wall-game/assets/textures/water.png +0 -0
- package/examples/hole-in-wall-game/assets/ui/index.html +270 -0
- package/examples/hole-in-wall-game/hole-in-the-wall-4.zip +0 -0
- package/examples/hole-in-wall-game/index.ts +506 -0
- package/examples/hole-in-wall-game/package.json +16 -0
- package/examples/wall-dodge-game/README.md +6 -0
- package/package.json +1 -1
- package/server.api.json +90 -2
- package/server.d.ts +17 -2
- package/server.js +37 -37
@@ -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
|
+
}
|
package/package.json
CHANGED
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)",
|