Files
rolld/rolld_backend/game/src/index.js
kerboul aa27725c4e feat: nouveau build WebGL last_build + fixes stats et schema Colyseus
- Unity build last_build remplace build_mai
- NetworkSchema.cs: correction types sbyte pour int8 (fix OverflowException Colyseus)
- StatsTracker: envoi periodique toutes les 30s, plus de dependance aux round events
- StatsTracker: cooldown client 6s pour respecter le rate-limit serveur
- StatsPage: correction row.value au lieu de row[activeTab]
- StatsPage: suppression onglet Courses (racesPlayed)
- Backend index.js: logging POST /stats/update
- Scene Tutorial: mise a jour, suppression assets obsoletes (TutorialInfo, physicMaterials)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 00:12:14 +02:00

119 lines
4.1 KiB
JavaScript

const cors = require('cors');
const { Server, matchMaker } = require('@colyseus/core');
const { WebSocketTransport } = require('@colyseus/ws-transport');
const { ArenaRoom } = require('./rooms/ArenaRoom');
const Stats = require('./stats/StatsManager');
const Chat = require('./chat/ChatManager');
const { z } = require('zod');
const PORT = process.env.PORT || 2567;
const statsUpdateSchema = z.object({
name: z.string().min(1).max(32),
stats: z.object({
totalDistance: z.number().optional(),
totalJumps: z.number().optional(),
maxSpeed: z.number().optional(),
bestRaceTime: z.number().optional(),
racesPlayed: z.number().optional(),
qualifications: z.number().optional(),
eliminations: z.number().optional(),
checkpointsTotal: z.number().optional(),
bumpsGiven: z.number().optional(),
totalPlaytime: z.number().optional(),
}),
});
const chatSendSchema = z.object({
name: z.string().min(1).max(32),
text: z.string().min(1).max(200),
});
let _gameServer;
const gameServer = new Server({
transport: new WebSocketTransport(),
express: (app) => {
app.use(cors());
app.use(require('express').json());
app.get('/health', (_req, res) => {
res.json({ service: 'game', status: 'ok', timestamp: new Date().toISOString() });
});
app.get('/', (_req, res) => res.send('🎮 Game server running'));
// ── Stats ────────────────────────────────────────────────────────────
app.get('/stats', (_req, res) => {
res.json(Stats.getAll());
});
app.get('/stats/leaderboard/:key', (req, res) => {
const board = Stats.getLeaderboard(req.params.key);
if (!board) return res.status(400).json({ error: 'invalid key' });
res.json(board);
});
app.post('/stats/update', (req, res) => {
const parsed = statsUpdateSchema.safeParse(req.body);
if (!parsed.success) {
console.warn('[Stats] Bad update request:', JSON.stringify(parsed.error.issues));
return res.status(400).json({ error: parsed.error.issues });
}
const ok = Stats.update(parsed.data.name, parsed.data.stats);
console.log(`[Stats] Update for "${parsed.data.name}": ok=${ok}`, JSON.stringify(parsed.data.stats));
res.json({ ok });
});
// ── Rooms ────────────────────────────────────────────────────────────
app.get('/rooms', async (_req, res) => {
try {
const rooms = await matchMaker.query({ name: 'arena' });
res.json(rooms.map(r => ({
roomId: r.roomId,
clients: r.clients,
maxClients: r.maxClients,
metadata: r.metadata || {},
})));
} catch (_) {
res.json([]);
}
});
// ── Chat ─────────────────────────────────────────────────────────────
app.get('/chat/history', (req, res) => {
res.json(Chat.getHistory(req.query.since));
});
app.post('/chat/send', (req, res) => {
const parsed = chatSendSchema.safeParse(req.body);
if (!parsed.success) return res.status(400).json({ error: parsed.error.issues });
const msg = Chat.push(parsed.data.name, parsed.data.text);
if (!msg) return res.status(429).json({ error: 'empty or invalid message' });
// Broadcast to all active Colyseus rooms
if (_gameServer) {
try {
const rooms = _gameServer.matchMaker?.rooms;
if (rooms) {
for (const room of rooms.values()) {
room.broadcast('chat', msg);
}
}
} catch (_) {}
}
res.json(msg);
});
},
});
_gameServer = gameServer;
gameServer.define('arena', ArenaRoom);
console.log('✅ ArenaRoom registered');
gameServer.listen(PORT).then(() => {
console.log(`🎮 Game server running on ws://localhost:${PORT}`);
});