Add local relay server and apply formatting fixes

This commit is contained in:
Christian Semmler 2026-02-28 12:15:19 -08:00
parent fb1d596704
commit 21a12d480c
No known key found for this signature in database
GPG Key ID: 086DAA1360BEEE5C
6 changed files with 170 additions and 17 deletions

View File

@ -12,28 +12,21 @@
#include "mxgeometry/mxgeometry3d.h"
#include "realtime/realtime.h"
#include "roi/legoroi.h"
#include <vec.h>
#include <SDL3/SDL_stdinc.h>
#include <SDL3/SDL_timer.h>
#include <cmath>
#include <vec.h>
#include <vector>
using namespace Multiplayer;
// clang-format off
static const char* g_vehicleROINames[VEHICLE_COUNT] = {
"copter", "jsuser", "dunebugy", "bike", "board", "moto", "towtk", "ambul"
};
static const char* g_vehicleROINames[VEHICLE_COUNT] =
{"copter", "jsuser", "dunebugy", "bike", "board", "moto", "towtk", "ambul"};
static const char* g_rideAnimNames[VEHICLE_COUNT] = {
NULL, NULL, NULL, "CNs001Bd", "CNs001sk", "CNs011Ni", NULL, NULL
};
static const char* g_rideAnimNames[VEHICLE_COUNT] = {NULL, NULL, NULL, "CNs001Bd", "CNs001sk", "CNs011Ni", NULL, NULL};
static const char* g_rideVehicleROINames[VEHICLE_COUNT] = {
NULL, NULL, NULL, "bikebd", "board", "motoni", NULL, NULL
};
// clang-format on
static const char* g_rideVehicleROINames[VEHICLE_COUNT] = {NULL, NULL, NULL, "bikebd", "board", "motoni", NULL, NULL};
static bool IsLargeVehicle(int8_t p_vehicleType)
{
@ -43,10 +36,9 @@ static bool IsLargeVehicle(int8_t p_vehicleType)
RemotePlayer::RemotePlayer(uint32_t p_peerId, uint8_t p_actorId)
: m_peerId(p_peerId), m_actorId(p_actorId), m_roi(nullptr), m_spawned(false), m_visible(false), m_targetSpeed(0.0f),
m_targetVehicleType(VEHICLE_NONE), m_targetWorldId(-1), m_lastUpdateTime(SDL_GetTicks()),
m_hasReceivedUpdate(false), m_walkAnim(nullptr), m_walkRoiMap(nullptr), m_walkRoiMapSize(0),
m_animTime(0.0f), m_idleTime(0.0f), m_wasMoving(false), m_idleAnim(nullptr), m_idleRoiMap(nullptr),
m_idleRoiMapSize(0), m_idleAnimTime(0.0f), m_rideAnim(nullptr), m_rideRoiMap(nullptr),
m_rideRoiMapSize(0), m_rideVehicleROI(nullptr),
m_hasReceivedUpdate(false), m_walkAnim(nullptr), m_walkRoiMap(nullptr), m_walkRoiMapSize(0), m_animTime(0.0f),
m_idleTime(0.0f), m_wasMoving(false), m_idleAnim(nullptr), m_idleRoiMap(nullptr), m_idleRoiMapSize(0),
m_idleAnimTime(0.0f), m_rideAnim(nullptr), m_rideRoiMap(nullptr), m_rideRoiMapSize(0), m_rideVehicleROI(nullptr),
m_vehicleROI(nullptr), m_currentVehicleType(VEHICLE_NONE)
{
SDL_snprintf(m_uniqueName, sizeof(m_uniqueName), "%s_mp_%u", LegoActor::GetActorName(p_actorId), p_peerId);
@ -412,7 +404,12 @@ void RemotePlayer::UpdateAnimation(float p_deltaTime)
MxMatrix transform(m_roi->GetLocal2World());
LegoTreeNode* root = m_idleAnim->GetRoot();
for (LegoU32 i = 0; i < root->GetNumChildren(); i++) {
LegoROI::ApplyAnimationTransformation(root->GetChild(i), transform, (LegoTime) timeInCycle, m_idleRoiMap);
LegoROI::ApplyAnimationTransformation(
root->GetChild(i),
transform,
(LegoTime) timeInCycle,
m_idleRoiMap
);
}
}
}

View File

@ -0,0 +1,2 @@
.wrangler/
node_modules/

View File

@ -0,0 +1,33 @@
{
"name": "server",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"ws": "^8.19.0"
}
},
"node_modules/ws": {
"version": "8.19.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
}
}
}

View File

@ -0,0 +1,5 @@
{
"dependencies": {
"ws": "^8.19.0"
}
}

View File

@ -0,0 +1,104 @@
import { createServer } from "http";
import { WebSocketServer } from "ws";
const PORT = process.env.PORT || 8787;
const rooms = new Map();
function getRoom(roomId) {
if (!rooms.has(roomId)) {
rooms.set(roomId, { connections: new Map(), nextPeerId: 1 });
}
return rooms.get(roomId);
}
const server = createServer((req, res) => {
if (req.url === "/" || req.url === "/health") {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ status: "ok" }));
} else {
res.writeHead(404);
res.end("Not Found");
}
});
const wss = new WebSocketServer({ server });
wss.on("connection", (ws, req) => {
const pathParts = (req.url || "").split("/").filter(Boolean);
if (pathParts.length !== 2 || pathParts[0] !== "room") {
console.log(`[REJECT] Invalid path: ${req.url}`);
ws.close(1008, "Invalid path");
return;
}
const roomId = pathParts[1];
const room = getRoom(roomId);
const peerId = room.nextPeerId++;
const peerIdStr = String(peerId);
room.connections.set(peerIdStr, ws);
console.log(`[CONNECT] Peer ${peerId} joined room "${roomId}" (${room.connections.size} peers)`);
// Send the peer its assigned ID as the first message
const idMsg = Buffer.alloc(5);
idMsg.writeUInt8(0xff, 0);
idMsg.writeUInt32LE(peerId, 1);
ws.send(idMsg);
ws.on("message", (data) => {
if (!(data instanceof Buffer) || data.length < 9) {
return;
}
const msgType = data.readUInt8(0);
console.log(`[MSG] Peer ${peerId} sent type=${msgType} len=${data.length}`);
// Stamp the peerId into the message header (bytes 1-4)
const stamped = Buffer.from(data);
stamped.writeUInt32LE(peerId, 1);
// Broadcast to all other peers in this room
let sent = 0;
for (const [id, peer] of room.connections) {
if (id !== peerIdStr) {
try {
peer.send(stamped);
sent++;
} catch {
room.connections.delete(id);
}
}
}
console.log(`[RELAY] Forwarded to ${sent} peers`);
});
const onClose = () => {
console.log(`[DISCONNECT] Peer ${peerId} left room "${roomId}" (${room.connections.size - 1} peers remaining)`);
room.connections.delete(peerIdStr);
// Broadcast LEAVE message to remaining peers
const leaveMsg = Buffer.alloc(9);
leaveMsg.writeUInt8(2, 0); // MSG_LEAVE
leaveMsg.writeUInt32LE(peerId, 1);
leaveMsg.writeUInt32LE(0, 5); // sequence 0
for (const [, peer] of room.connections) {
try {
peer.send(leaveMsg);
} catch {
// Ignore send errors on cleanup
}
}
// Clean up empty rooms
if (room.connections.size === 0) {
rooms.delete(pathParts[1]);
}
};
ws.on("close", onClose);
ws.on("error", onClose);
});
server.listen(PORT, () => {
console.log(`Relay server listening on http://localhost:${PORT}`);
});

View File

@ -0,0 +1,12 @@
name = "isle-relay"
main = "relay.ts"
compatibility_date = "2024-01-01"
[durable_objects]
bindings = [
{ name = "GAME_ROOM", class_name = "GameRoom" }
]
[[migrations]]
tag = "v1"
new_sqlite_classes = ["GameRoom"]