hytopia 0.1.71 → 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.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 +49 -0
- package/server.d.ts +5 -0
- package/server.js +1 -1
@@ -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
@@ -25438,6 +25438,55 @@
|
|
25438
25438
|
"isAbstract": false,
|
25439
25439
|
"name": "setCcdEnabled"
|
25440
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
|
+
},
|
25441
25490
|
{
|
25442
25491
|
"kind": "Method",
|
25443
25492
|
"canonicalReference": "server!RigidBody#setCollisionGroupsForSolidColliders:member(1)",
|
package/server.d.ts
CHANGED
@@ -3136,6 +3136,11 @@ export declare class RigidBody {
|
|
3136
3136
|
* @param collisionGroups - The collision groups for solid colliders of the rigid body.
|
3137
3137
|
*/
|
3138
3138
|
setCollisionGroupsForSolidColliders(collisionGroups: CollisionGroups): void;
|
3139
|
+
/**
|
3140
|
+
* Sets the collision groups for sensor colliders of the rigid body.
|
3141
|
+
* @param collisionGroups - The collision groups for sensor colliders of the rigid body.
|
3142
|
+
*/
|
3143
|
+
setCollisionGroupsForSensorColliders(collisionGroups: CollisionGroups): void;
|
3139
3144
|
/**
|
3140
3145
|
* Sets the type of the rigid body.
|
3141
3146
|
* @param type - The type of the rigid body.
|