action-engine-js 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/LICENSE +45 -0
  2. package/README.md +348 -0
  3. package/actionengine/3rdparty/goblin/goblin.js +9609 -0
  4. package/actionengine/3rdparty/goblin/goblin.min.js +5 -0
  5. package/actionengine/camera/actioncamera.js +90 -0
  6. package/actionengine/camera/cameracollisionhandler.js +69 -0
  7. package/actionengine/character/actioncharacter.js +360 -0
  8. package/actionengine/character/actioncharacter3D.js +61 -0
  9. package/actionengine/core/app.js +430 -0
  10. package/actionengine/debug/basedebugpanel.js +858 -0
  11. package/actionengine/display/canvasmanager.js +75 -0
  12. package/actionengine/display/gl/programmanager.js +570 -0
  13. package/actionengine/display/gl/shaders/lineshader.js +118 -0
  14. package/actionengine/display/gl/shaders/objectshader.js +1756 -0
  15. package/actionengine/display/gl/shaders/particleshader.js +43 -0
  16. package/actionengine/display/gl/shaders/shadowshader.js +319 -0
  17. package/actionengine/display/gl/shaders/spriteshader.js +100 -0
  18. package/actionengine/display/gl/shaders/watershader.js +67 -0
  19. package/actionengine/display/graphics/actionmodel3D.js +191 -0
  20. package/actionengine/display/graphics/actionsprite3D.js +230 -0
  21. package/actionengine/display/graphics/lighting/actiondirectionalshadowlight.js +864 -0
  22. package/actionengine/display/graphics/lighting/actionlight.js +211 -0
  23. package/actionengine/display/graphics/lighting/actionomnidirectionalshadowlight.js +862 -0
  24. package/actionengine/display/graphics/lighting/lightingconstants.js +263 -0
  25. package/actionengine/display/graphics/lighting/lightmanager.js +789 -0
  26. package/actionengine/display/graphics/renderableobject.js +44 -0
  27. package/actionengine/display/graphics/renderers/actionrenderer2D.js +341 -0
  28. package/actionengine/display/graphics/renderers/actionrenderer3D/actionrenderer3D.js +655 -0
  29. package/actionengine/display/graphics/renderers/actionrenderer3D/canvasmanager3D.js +82 -0
  30. package/actionengine/display/graphics/renderers/actionrenderer3D/debugrenderer3D.js +493 -0
  31. package/actionengine/display/graphics/renderers/actionrenderer3D/objectrenderer3D.js +790 -0
  32. package/actionengine/display/graphics/renderers/actionrenderer3D/spriteRenderer3D.js +266 -0
  33. package/actionengine/display/graphics/renderers/actionrenderer3D/sunrenderer3D.js +140 -0
  34. package/actionengine/display/graphics/renderers/actionrenderer3D/waterrenderer3D.js +173 -0
  35. package/actionengine/display/graphics/renderers/actionrenderer3D/weatherrenderer3D.js +87 -0
  36. package/actionengine/display/graphics/texture/proceduraltexture.js +192 -0
  37. package/actionengine/display/graphics/texture/texturemanager.js +242 -0
  38. package/actionengine/display/graphics/texture/textureregistry.js +177 -0
  39. package/actionengine/input/actionscrollablearea.js +1405 -0
  40. package/actionengine/input/inputhandler.js +1647 -0
  41. package/actionengine/math/geometry/geometrybuilder.js +161 -0
  42. package/actionengine/math/geometry/glbexporter.js +364 -0
  43. package/actionengine/math/geometry/glbloader.js +722 -0
  44. package/actionengine/math/geometry/modelcodegenerator.js +97 -0
  45. package/actionengine/math/geometry/triangle.js +33 -0
  46. package/actionengine/math/geometry/triangleutils.js +34 -0
  47. package/actionengine/math/mathutils.js +25 -0
  48. package/actionengine/math/matrix4.js +785 -0
  49. package/actionengine/math/physics/actionphysics.js +108 -0
  50. package/actionengine/math/physics/actionphysicsobject3D.js +164 -0
  51. package/actionengine/math/physics/actionphysicsworld3D.js +238 -0
  52. package/actionengine/math/physics/actionraycast.js +129 -0
  53. package/actionengine/math/physics/shapes/actionphysicsbox3D.js +158 -0
  54. package/actionengine/math/physics/shapes/actionphysicscapsule3D.js +200 -0
  55. package/actionengine/math/physics/shapes/actionphysicscompoundshape3D.js +147 -0
  56. package/actionengine/math/physics/shapes/actionphysicscone3D.js +126 -0
  57. package/actionengine/math/physics/shapes/actionphysicsconvexshape3D.js +72 -0
  58. package/actionengine/math/physics/shapes/actionphysicscylinder3D.js +117 -0
  59. package/actionengine/math/physics/shapes/actionphysicsmesh3D.js +74 -0
  60. package/actionengine/math/physics/shapes/actionphysicsplane3D.js +100 -0
  61. package/actionengine/math/physics/shapes/actionphysicssphere3D.js +95 -0
  62. package/actionengine/math/quaternion.js +61 -0
  63. package/actionengine/math/vector2.js +277 -0
  64. package/actionengine/math/vector3.js +318 -0
  65. package/actionengine/math/viewfrustum.js +136 -0
  66. package/actionengine/network/ACTIONNETREADME.md +810 -0
  67. package/actionengine/network/client/ActionNetManager.js +802 -0
  68. package/actionengine/network/client/ActionNetManagerGUI.js +1709 -0
  69. package/actionengine/network/client/ActionNetManagerP2P.js +1537 -0
  70. package/actionengine/network/client/SyncSystem.js +422 -0
  71. package/actionengine/network/p2p/ActionNetPeer.js +142 -0
  72. package/actionengine/network/p2p/ActionNetTrackerClient.js +623 -0
  73. package/actionengine/network/p2p/DataConnection.js +282 -0
  74. package/actionengine/network/p2p/README.md +510 -0
  75. package/actionengine/network/p2p/example.html +502 -0
  76. package/actionengine/network/server/ActionNetServer.js +577 -0
  77. package/actionengine/network/server/ActionNetServerSSL.js +579 -0
  78. package/actionengine/network/server/ActionNetServerUtils.js +458 -0
  79. package/actionengine/network/server/SERVERREADME.md +314 -0
  80. package/actionengine/network/server/package-lock.json +35 -0
  81. package/actionengine/network/server/package.json +13 -0
  82. package/actionengine/network/server/start.bat +27 -0
  83. package/actionengine/network/server/start.sh +25 -0
  84. package/actionengine/network/server/startwss.bat +27 -0
  85. package/actionengine/sound/audiomanager.js +1589 -0
  86. package/actionengine/sound/soundfont/ACTIONSOUNDFONT_README.md +205 -0
  87. package/actionengine/sound/soundfont/actionparser.js +718 -0
  88. package/actionengine/sound/soundfont/actionreverb.js +252 -0
  89. package/actionengine/sound/soundfont/actionsoundfont.js +543 -0
  90. package/actionengine/sound/soundfont/sf2playerlicence.txt +29 -0
  91. package/actionengine/sound/soundfont/soundfont.js +2 -0
  92. package/dist/action-engine.min.js +328 -0
  93. package/package.json +35 -0
@@ -0,0 +1,579 @@
1
+ /**
2
+ * Multiplayer Game Server (SSL/WSS Version)
3
+ *
4
+ * A dedicated secure WebSocket server for multiplayer games.
5
+ * Usage: node ActionNetServerSSL.js [port] [maxPlayersPerRoom]
6
+ * Example: node ActionNetServerSSL.js 8443 4
7
+ */
8
+
9
+ const https = require("https");
10
+ const fs = require("fs");
11
+ const WebSocket = require("ws");
12
+ const ActionNetServerUtils = require("./ActionNetServerUtils");
13
+
14
+ // ========================================
15
+ // CONFIGURATION
16
+ // ========================================
17
+ const CONFIG = {
18
+ port: 8443, // Default WSS port
19
+ debug: true,
20
+ maxPlayersPerRoom: -1,
21
+ // Windows certificate paths (absolute paths)
22
+ certPath: "C:\\path\\to\\your\\cert.pem",
23
+ keyPath: "C:\\path\\to\\your\\privkey.pem"
24
+ };
25
+ // ========================================
26
+
27
+ // Allow command line override if needed
28
+ const port = process.argv[2] || CONFIG.port;
29
+ const maxPlayersPerRoom = process.argv[3] || CONFIG.maxPlayersPerRoom;
30
+
31
+ // Load SSL certificates
32
+ let server;
33
+ try {
34
+ const options = {
35
+ cert: fs.readFileSync(CONFIG.certPath),
36
+ key: fs.readFileSync(CONFIG.keyPath)
37
+ };
38
+ server = https.createServer(options);
39
+ console.log("SSL certificates loaded successfully");
40
+ } catch (error) {
41
+ console.error("ERROR loading SSL certificates:", error.message);
42
+ console.error(`Expected files at:`);
43
+ console.error(` Cert: ${CONFIG.certPath}`);
44
+ console.error(` Key: ${CONFIG.keyPath}`);
45
+ process.exit(1);
46
+ }
47
+
48
+ // Create WebSocket server
49
+ const wss = new WebSocket.Server({ server });
50
+
51
+ // Create ActionNet server utilities
52
+ const utils = new ActionNetServerUtils(wss);
53
+
54
+ // Game state management
55
+ const gameRooms = new Map(); // roomName -> game state
56
+
57
+ console.log(`Starting Secure Multiplayer Game server on port ${port}...`);
58
+ console.log(`Max players per room: ${maxPlayersPerRoom === -1 ? 'No limit' : maxPlayersPerRoom}`);
59
+
60
+ wss.on("connection", (ws, req) => {
61
+ console.log("New player connected from:", req.socket.remoteAddress);
62
+
63
+ // Send current room list immediately when client connects
64
+ const roomList = utils.getRoomList();
65
+ if (roomList.length > 0) {
66
+ utils.sendToClient(ws, {
67
+ type: "roomList",
68
+ rooms: roomList
69
+ });
70
+ console.log(`Sent current room list to new player: ${roomList.join(", ")}`);
71
+ }
72
+
73
+ ws.on("message", (data) => {
74
+ try {
75
+ const message = JSON.parse(data.toString());
76
+ if (message.type === "connect") {
77
+ const displayName = utils.generateUniqueDisplayName(message.username);
78
+ console.log("Received connect message:", { ...message, displayName });
79
+ } else if (CONFIG.debug && message.type !== "pieceUpdate") {
80
+ // Don't spam logs with pieceUpdate messages
81
+ console.log("Received message:", message.type, message.playerNumber || "");
82
+ }
83
+
84
+ switch (message.type) {
85
+ case "connect":
86
+ handleConnect(ws, message);
87
+ break;
88
+
89
+ case "joinRoom":
90
+ handleJoinRoom(ws, message);
91
+ break;
92
+
93
+ case "leaveRoom":
94
+ handleLeaveRoom(ws, message);
95
+ break;
96
+
97
+ case "changeUsername":
98
+ handleChangeUsername(ws, message);
99
+ break;
100
+
101
+ case "ping":
102
+ // Auto-respond to pings
103
+ utils.sendToClient(ws, {
104
+ type: "pong",
105
+ sequence: message.sequence,
106
+ timestamp: message.timestamp
107
+ });
108
+ break;
109
+
110
+ default:
111
+ // All other messages are game messages - relay to room
112
+ relayGameMessage(ws, message);
113
+ break;
114
+ }
115
+ } catch (error) {
116
+ console.error("Error parsing message:", error);
117
+ }
118
+ });
119
+
120
+ ws.on("close", () => {
121
+ console.log("Player disconnected");
122
+ handleDisconnect(ws);
123
+ });
124
+
125
+ ws.on("error", (error) => {
126
+ console.error("WebSocket error:", error);
127
+ });
128
+ });
129
+
130
+ /**
131
+ * Handle player connection
132
+ */
133
+ function handleConnect(ws, message) {
134
+ const success = utils.registerClient(ws, message);
135
+
136
+ if (!success) {
137
+ // Client was rejected (duplicate username)
138
+ return;
139
+ }
140
+
141
+ const client = utils.getClient(ws);
142
+ console.log(`Player ${client.displayName} connected to lobby`);
143
+
144
+ // Send dedicated success message for reliable detection
145
+ utils.sendToClient(ws, {
146
+ type: "connectSuccess"
147
+ });
148
+
149
+ // Send room list
150
+ utils.sendToClient(ws, {
151
+ type: "roomList",
152
+ rooms: utils.getRoomList()
153
+ });
154
+
155
+ // Broadcast updated room list to all clients
156
+ utils.broadcastToAll({
157
+ type: "roomList",
158
+ rooms: utils.getRoomList()
159
+ });
160
+ }
161
+
162
+ /**
163
+ * Handle room join request
164
+ */
165
+ function handleJoinRoom(ws, message) {
166
+ const client = utils.getClient(ws);
167
+ if (!client) {
168
+ console.warn("Join request from unregistered client");
169
+ return;
170
+ }
171
+
172
+ const roomName = message.roomName;
173
+
174
+ // Validate room name
175
+ if (!roomName || roomName.trim() === "") {
176
+ utils.sendToClient(ws, {
177
+ type: "error",
178
+ text: "Invalid room name. Room name cannot be empty."
179
+ });
180
+ return;
181
+ }
182
+
183
+ // Check if room is full (if limit is set)
184
+ if (maxPlayersPerRoom !== -1 && utils.roomExists(roomName)) {
185
+ const roomClients = utils.getClientsInRoom(roomName);
186
+ if (roomClients.length >= maxPlayersPerRoom) {
187
+ utils.sendToClient(ws, {
188
+ type: "error",
189
+ text: "Room is full."
190
+ });
191
+ return;
192
+ }
193
+ }
194
+
195
+ // Add client to room
196
+ const wasNewRoom = !utils.roomExists(roomName);
197
+ utils.addToRoom(ws, roomName);
198
+
199
+ // Initialize game state for the room if it's new
200
+ if (wasNewRoom) {
201
+ initializeGameRoom(roomName);
202
+ console.log(`Created new game room: ${roomName}`);
203
+
204
+ // Broadcast updated room list to all clients
205
+ utils.broadcastToAll({
206
+ type: "roomList",
207
+ rooms: utils.getRoomList()
208
+ });
209
+ }
210
+
211
+ console.log(`Player ${client.displayName} joined room: ${roomName}`);
212
+
213
+ // Broadcast join message to the room
214
+ const joinMessage = wasNewRoom
215
+ ? `${client.displayName} created and joined room "${roomName}"`
216
+ : `${client.displayName} joined room "${roomName}"`;
217
+ utils.broadcastToRoom(roomName, {
218
+ type: "system",
219
+ text: joinMessage,
220
+ roomName: roomName,
221
+ timestamp: Date.now()
222
+ });
223
+
224
+ // Send dedicated success message for reliable detection
225
+ utils.sendToClient(ws, {
226
+ type: "joinSuccess",
227
+ roomName: roomName
228
+ });
229
+
230
+ // Send user list for this room
231
+ utils.sendToClient(ws, {
232
+ type: "userList",
233
+ users: utils.getClientsInRoom(roomName),
234
+ roomName: roomName
235
+ });
236
+
237
+ // Broadcast updated user list to room
238
+ utils.broadcastToRoom(roomName, {
239
+ type: "userList",
240
+ users: utils.getClientsInRoom(roomName),
241
+ roomName: roomName
242
+ });
243
+ }
244
+
245
+ /**
246
+ * Handle leave room request
247
+ */
248
+ function handleLeaveRoom(ws, message) {
249
+ const client = utils.getClient(ws);
250
+ if (!client) return;
251
+
252
+ // Check if this player is the host BEFORE removing them
253
+ const wasHost = utils.isHost(ws);
254
+
255
+ const oldRoom = utils.removeFromRoom(ws, true); // true = return to lobby
256
+
257
+ if (oldRoom) {
258
+ console.log(`Player ${client.displayName} left room: ${oldRoom}`);
259
+
260
+ // Notify user
261
+ utils.sendToClient(ws, {
262
+ type: "system",
263
+ text: `Left Tetris battle "${oldRoom}". You are now in the lobby.`
264
+ });
265
+
266
+ // Send updated room list
267
+ utils.sendToClient(ws, {
268
+ type: "roomList",
269
+ rooms: utils.getRoomList()
270
+ });
271
+
272
+ // If host left, close the room entirely
273
+ if (wasHost && utils.roomExists(oldRoom)) {
274
+ console.log(`Host left room ${oldRoom} - closing room and removing all players`);
275
+
276
+ // Notify remaining players that host left
277
+ utils.broadcastToRoom(oldRoom, {
278
+ type: "hostLeft",
279
+ id: client.id,
280
+ displayName: client.displayName,
281
+ roomName: oldRoom,
282
+ timestamp: Date.now()
283
+ });
284
+
285
+ // Get all clients still in the room
286
+ const remainingClients = Array.from(utils.rooms.get(oldRoom) || []);
287
+
288
+ // Remove all clients from the room and put them back in lobby
289
+ remainingClients.forEach(clientWs => {
290
+ utils.removeFromRoom(clientWs, true); // true = return to lobby
291
+
292
+ // Notify each client they were removed
293
+ utils.sendToClient(clientWs, {
294
+ type: "system",
295
+ text: `Room "${oldRoom}" closed because host left.`
296
+ });
297
+
298
+ // Send updated room list
299
+ utils.sendToClient(clientWs, {
300
+ type: "roomList",
301
+ rooms: utils.getRoomList()
302
+ });
303
+ });
304
+
305
+ // Delete the room
306
+ gameRooms.delete(oldRoom);
307
+
308
+ // Broadcast updated room list to everyone
309
+ utils.broadcastToAll({
310
+ type: "roomList",
311
+ rooms: utils.getRoomList()
312
+ });
313
+ } else if (!utils.roomExists(oldRoom)) {
314
+ // Room became empty naturally
315
+ console.log(`Deleted empty room: ${oldRoom}`);
316
+ gameRooms.delete(oldRoom);
317
+
318
+ // Broadcast updated room list
319
+ utils.broadcastToAll({
320
+ type: "roomList",
321
+ rooms: utils.getRoomList()
322
+ });
323
+ } else {
324
+ // Non-host left, room continues
325
+ // Broadcast user left to remaining clients in room
326
+ utils.broadcastToRoom(oldRoom, {
327
+ type: "userLeft",
328
+ id: client.id,
329
+ displayName: client.displayName,
330
+ roomName: oldRoom,
331
+ timestamp: Date.now()
332
+ });
333
+
334
+ // Send updated user list to room
335
+ utils.broadcastToRoom(oldRoom, {
336
+ type: "userList",
337
+ users: utils.getClientsInRoom(oldRoom),
338
+ roomName: oldRoom
339
+ });
340
+ }
341
+ }
342
+ }
343
+
344
+ /**
345
+ * Handle player disconnect
346
+ */
347
+ function handleDisconnect(ws) {
348
+ // Check if this player was the host BEFORE disconnecting
349
+ const wasHost = utils.isHost(ws);
350
+ const client = utils.getClient(ws);
351
+ const roomName = client ? client.roomName : null;
352
+
353
+ const clientInfo = utils.handleDisconnect(ws);
354
+
355
+ if (clientInfo) {
356
+ const { id, displayName } = clientInfo;
357
+ const disconnectRoomName = clientInfo.roomName;
358
+
359
+ if (disconnectRoomName) {
360
+ console.log(`Player ${displayName} disconnected from room: ${disconnectRoomName}`);
361
+
362
+ // If host disconnected, close the room entirely
363
+ if (wasHost && utils.roomExists(disconnectRoomName)) {
364
+ console.log(`Host disconnected from room ${disconnectRoomName} - closing room and removing all players`);
365
+
366
+ // Notify remaining players that host left
367
+ utils.broadcastToRoom(disconnectRoomName, {
368
+ type: "hostLeft",
369
+ id: id,
370
+ displayName: displayName,
371
+ roomName: disconnectRoomName,
372
+ timestamp: Date.now()
373
+ });
374
+
375
+ // Get all clients still in the room
376
+ const remainingClients = Array.from(utils.rooms.get(disconnectRoomName) || []);
377
+
378
+ // Remove all clients from the room and put them back in lobby
379
+ remainingClients.forEach(clientWs => {
380
+ utils.removeFromRoom(clientWs, true); // true = return to lobby
381
+
382
+ // Notify each client they were removed
383
+ utils.sendToClient(clientWs, {
384
+ type: "system",
385
+ text: `Room "${disconnectRoomName}" closed because host disconnected.`
386
+ });
387
+
388
+ // Send updated room list
389
+ utils.sendToClient(clientWs, {
390
+ type: "roomList",
391
+ rooms: utils.getRoomList()
392
+ });
393
+ });
394
+
395
+ // Delete the room
396
+ gameRooms.delete(disconnectRoomName);
397
+
398
+ // Broadcast updated room list to everyone
399
+ utils.broadcastToAll({
400
+ type: "roomList",
401
+ rooms: utils.getRoomList()
402
+ });
403
+ } else if (!utils.roomExists(disconnectRoomName)) {
404
+ // Room became empty naturally
405
+ console.log(`Deleted empty room: ${disconnectRoomName}`);
406
+ gameRooms.delete(disconnectRoomName);
407
+
408
+ // Broadcast updated room list
409
+ utils.broadcastToAll({
410
+ type: "roomList",
411
+ rooms: utils.getRoomList()
412
+ });
413
+ } else {
414
+ // Non-host disconnected, room continues
415
+ // Broadcast user left to room
416
+ utils.broadcastToRoom(disconnectRoomName, {
417
+ type: "userLeft",
418
+ id: id,
419
+ displayName: displayName,
420
+ roomName: disconnectRoomName,
421
+ timestamp: Date.now()
422
+ });
423
+
424
+ // Send updated user list
425
+ utils.broadcastToRoom(disconnectRoomName, {
426
+ type: "userList",
427
+ users: utils.getClientsInRoom(disconnectRoomName),
428
+ roomName: disconnectRoomName
429
+ });
430
+ }
431
+ } else {
432
+ console.log(`Player ${displayName} disconnected from lobby`);
433
+ }
434
+ }
435
+ }
436
+
437
+ /**
438
+ * Handle username change request
439
+ */
440
+ function handleChangeUsername(ws, message) {
441
+ const client = utils.getClient(ws);
442
+ if (!client) {
443
+ console.log("Username change from unknown client");
444
+ return;
445
+ }
446
+
447
+ const newUsername = message.username;
448
+
449
+ // Validate new username
450
+ if (!newUsername || newUsername.trim() === "" || newUsername.length < 2) {
451
+ utils.sendToClient(ws, {
452
+ type: "error",
453
+ text: "Username must be at least 2 characters long"
454
+ });
455
+ return;
456
+ }
457
+
458
+ if (!/^[a-zA-Z0-9_-]+$/.test(newUsername)) {
459
+ utils.sendToClient(ws, {
460
+ type: "error",
461
+ text: "Username can only contain letters, numbers, underscores, and hyphens"
462
+ });
463
+ return;
464
+ }
465
+
466
+ // Check if new username is already taken by another client
467
+ const existingClient = Array.from(utils.clients.values()).find(
468
+ (c) => c.username === newUsername && c.id !== client.id
469
+ );
470
+ if (existingClient) {
471
+ utils.sendToClient(ws, {
472
+ type: "error",
473
+ text: "Username is already taken"
474
+ });
475
+ return;
476
+ }
477
+
478
+ console.log(`Player ${client.displayName} changing username from ${client.username} to ${newUsername}`);
479
+
480
+ // Update client info
481
+ const oldUsername = client.username;
482
+ const oldDisplayName = client.displayName;
483
+
484
+ // Generate display name for the new username (exclude self from check)
485
+ const newDisplayName = utils.generateUniqueDisplayName(newUsername, client.id);
486
+ client.username = newUsername;
487
+ client.displayName = newDisplayName;
488
+
489
+ console.log(`Player ${oldDisplayName} is now ${newDisplayName}`);
490
+
491
+ // Send success confirmation
492
+ utils.sendToClient(ws, {
493
+ type: "usernameChangeSuccess",
494
+ oldUsername: oldUsername,
495
+ newUsername: newUsername,
496
+ displayName: client.displayName
497
+ });
498
+
499
+ // Broadcast updated user list to current room
500
+ if (client.roomName) {
501
+ utils.broadcastToRoom(client.roomName, {
502
+ type: "userList",
503
+ users: utils.getClientsInRoom(client.roomName),
504
+ roomName: client.roomName
505
+ });
506
+
507
+ // Broadcast username change notification
508
+ utils.broadcastToRoom(client.roomName, {
509
+ type: "system",
510
+ text: `${oldDisplayName} changed their name to ${client.displayName}`,
511
+ roomName: client.roomName,
512
+ timestamp: Date.now()
513
+ });
514
+ }
515
+ }
516
+
517
+ /**
518
+ * Relay game message to other players in the room
519
+ * Server doesn't care about message content - just routes it
520
+ */
521
+ function relayGameMessage(ws, message) {
522
+ const client = utils.getClient(ws);
523
+ if (!client || !client.roomName) {
524
+ return;
525
+ }
526
+
527
+ // Simply broadcast the entire message to room, excluding sender
528
+ utils.broadcastToRoom(client.roomName, message, ws);
529
+ }
530
+
531
+ /**
532
+ * Initialize game state for a new room
533
+ */
534
+ function initializeGameRoom(roomName) {
535
+ gameRooms.set(roomName, {
536
+ players: new Map(),
537
+ startTime: Date.now(),
538
+ gameStarted: false
539
+ });
540
+
541
+ console.log(`Initialized game state for room: ${roomName}`);
542
+ }
543
+
544
+ /**
545
+ * Clean up game state for a room
546
+ */
547
+ function cleanupGameRoom(roomName) {
548
+ gameRooms.delete(roomName);
549
+ console.log(`Cleaned up game state for room: ${roomName}`);
550
+ }
551
+
552
+ // Start the server
553
+ server.listen(port, () => {
554
+ console.log(`Secure Multiplayer Game server running on wss://localhost:${port}`);
555
+ console.log("Press Ctrl+C to stop the server");
556
+ });
557
+
558
+ // Graceful shutdown
559
+ process.on("SIGINT", () => {
560
+ console.log("\nShutting down Game server...");
561
+ wss.clients.forEach((client) => {
562
+ client.close();
563
+ });
564
+ server.close(() => {
565
+ console.log("Game server stopped");
566
+ process.exit(0);
567
+ });
568
+ });
569
+
570
+ // Handle uncaught exceptions
571
+ process.on("uncaughtException", (error) => {
572
+ console.error("Uncaught Exception:", error);
573
+ process.exit(1);
574
+ });
575
+
576
+ process.on("unhandledRejection", (reason, promise) => {
577
+ console.error("Unhandled Rejection at:", promise, "reason:", reason);
578
+ process.exit(1);
579
+ });