This commit is contained in:
React User
2026-05-04 08:50:50 +00:00
parent 146510ddeb
commit b104d35a9d
140 changed files with 685 additions and 6289 deletions
+88 -12
View File
@@ -6,8 +6,13 @@ import { authenticateMatchmakingRequest, registerAuthRoutes } from './auth_bridg
import {
getPlayerLast10MatchRecord,
insertMatchForNewRoom,
insertRematchMatch,
loadUsernamesForUserPair,
MATCH_ENTRY_FEE_RC,
registerMatchRoutes,
updateMatchUserBlue,
type CreateRematchRoomFn,
type MatchRoomSuccessEnvelope,
} from './matches';
import {
initServerLog,
@@ -66,7 +71,6 @@ const gameName = configuredGame.name || "game";
logVerbose(logLevel, typedSettings);
registerAuthRoutes(app, typedSettings);
registerMatchRoutes(app, typedSettings);
let Rooms: Room[] = [];
let Queue: QueueEntry[] = [];
@@ -89,6 +93,83 @@ function removeUserFromQueue(userId: number): void {
}
}
/** GET / JSON only: same order as match DB — first joined = red, second = blue. Always includes `entry_fee`. */
function roomResponseForViewer(room: Room, viewerUserId: number): Room {
const idx = room.Players.findIndex((p) => p.UserId === viewerUserId);
const your_team: 'red' | 'blue' | null = idx === 0 ? 'red' : idx === 1 ? 'blue' : null;
const entry_fee = room.entry_fee ?? MATCH_ENTRY_FEE_RC;
return { ...room, entry_fee, your_team };
}
/** Same envelope as `POST /internal/rematch` success: both sides when seated, else `null` for open slot. */
function roomMatchSuccessEnvelope(room: Room): MatchRoomSuccessEnvelope {
const entry_fee = room.entry_fee ?? MATCH_ENTRY_FEE_RC;
const redId = room.Players[0]?.UserId;
const blueId = room.Players[1]?.UserId;
return {
ok: true,
entry_fee,
for_red: redId != null ? roomResponseForViewer(room, redId) : null,
for_blue: blueId != null ? roomResponseForViewer(room, blueId) : null,
};
}
const createRematchRoom: CreateRematchRoomFn = async ({ userRedId, userBlueId, entryFee }) => {
return runMatchmakerExclusive(async () => {
const names = await loadUsernamesForUserPair(typedSettings, userRedId, userBlueId);
if (!names) {
return { ok: false, status: 404, error: 'One or both players not found' };
}
for (const room of Rooms) {
room.Players = room.Players.filter((p) => p.UserId !== userRedId && p.UserId !== userBlueId);
}
for (let i = Rooms.length - 1; i >= 0; i--) {
if (Rooms[i]!.Players.length === 0) {
Rooms.splice(i, 1);
}
}
removeUserFromQueue(userRedId);
removeUserFromQueue(userBlueId);
const matchId = await insertRematchMatch(typedSettings, userRedId, userBlueId, entryFee);
if (matchId == null) {
return { ok: false, status: 500, error: 'Failed to create match row' };
}
const newPort = GetRandomPort(typedSettings.port_range_min, typedSettings.port_range_max);
const [l10Red, l10Blue] = await Promise.all([
getPlayerLast10MatchRecord(typedSettings, userRedId),
getPlayerLast10MatchRecord(typedSettings, userBlueId),
]);
const redName = names.nameById.get(userRedId)!;
const blueName = names.nameById.get(userBlueId)!;
const newRoom: Room = {
Players: [
{ Name: redName, LastSeen: Date.now(), UserId: userRedId, ...l10Red },
{ Name: blueName, LastSeen: Date.now(), UserId: userBlueId, ...l10Blue },
],
GameName: gameName,
Port: newPort,
InitTime: Date.now(),
entry_fee: entryFee,
match_id: matchId,
instance_started: true,
};
Rooms.push(newRoom);
OpenGameInstance(configuredGame.exe, newRoom.Port, newRoom.match_id);
recordHistory(
`Rematch room port ${newPort} match ${matchId}${redName} vs ${blueName} (entry_fee ${entryFee})`
);
return roomMatchSuccessEnvelope(newRoom);
});
};
registerMatchRoutes(app, typedSettings, { createRematchRoom });
app.get('/settings', (req: Request, res: Response) => {
if (req.query.password !== typedSettings.password) {
res.send("403 Unauthorized");
@@ -221,7 +302,7 @@ app.get('/', async (req: Request, res: Response) => {
if (joinedRoom !== null) { // Already in a room. Stopping here
removeUserFromQueue(userId);
res.send(roomResponseForViewer(joinedRoom, userId));
res.json(roomMatchSuccessEnvelope(joinedRoom));
return;
}
@@ -234,7 +315,8 @@ app.get('/', async (req: Request, res: Response) => {
Players: [{ Name: username, LastSeen: Date.now(), UserId: userId, ...l10 }],
GameName: gameName,
Port: newPort,
InitTime: Date.now()
InitTime: Date.now(),
entry_fee: MATCH_ENTRY_FEE_RC,
};
const matchId = await insertMatchForNewRoom(typedSettings, userId);
if (matchId != null) {
@@ -245,7 +327,7 @@ app.get('/', async (req: Request, res: Response) => {
recordHistory(
`${username} created a room on port ${newPort} (match id ${newRoom.match_id ?? '—'}) — waiting for opponent`
);
res.send(roomResponseForViewer(newRoom, userId));
res.json(roomMatchSuccessEnvelope(newRoom));
return;
}
} else {
@@ -267,7 +349,7 @@ app.get('/', async (req: Request, res: Response) => {
`new room created with port ${room.Port} and matchId ${room.match_id ?? '—'} for ${p0.Name} and ${p1.Name}`
);
}
res.send(roomResponseForViewer(room, userId));
res.json(roomMatchSuccessEnvelope(room));
return;
}
@@ -362,6 +444,7 @@ app.get('/reopen', async (req: Request, res: Response) => {
GameName: gameName,
Port: port,
InitTime: Date.now(),
entry_fee: MATCH_ENTRY_FEE_RC,
instance_started: true,
};
const matchId = await insertMatchForNewRoom(typedSettings);
@@ -384,13 +467,6 @@ app.get('/reopen', async (req: Request, res: Response) => {
}
});
/** GET / JSON only: same order as match DB — first joined = red, second = blue. */
function roomResponseForViewer(room: Room, viewerUserId: number): Room {
const idx = room.Players.findIndex((p) => p.UserId === viewerUserId);
const your_team: 'red' | 'blue' | null = idx === 0 ? 'red' : idx === 1 ? 'blue' : null;
return { ...room, your_team };
}
app.use((err: unknown, _req: Request, res: Response, next: express.NextFunction) => {
const e = err as { status?: number; type?: string };
if (e.status === 400 && e.type === 'entity.parse.failed') {