-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/matchmaking #59
base: feature/matchmaking
Are you sure you want to change the base?
Changes from all commits
7636538
b4b35bc
afd66c4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,4 @@ | ||
import { SaveGameResponse, SavedData, SeasonalEvents } from "../types/save-game"; | ||
|
||
import { DBConstants } from "./constants"; | ||
import Datastore from "@seald-io/nedb"; | ||
import { Logger } from "./logger"; | ||
|
@@ -8,21 +7,26 @@ import crypto from 'crypto'; | |
import { readFile } from "fs/promises"; | ||
|
||
const CURRENT_VERSION = 1; | ||
|
||
export enum Collections { | ||
USERS = "users", | ||
SAVE_GAME = "save-games", | ||
SERVER_INFO = "server-info" // Meta info about the server itself | ||
SERVER_INFO = "server-info", // Meta info about the server itself | ||
MATCHMAKING_QUEUE = "matchmaking-queue", // New collection for matchmaking queue | ||
GAME_SESSIONS = "game-sessions", // New collection for active game sessions | ||
MATCHMAKING_CONFIG = "matchmaking-config" // New collection for matchmaking settings | ||
} | ||
|
||
export type WithOptionalId<T> = T & { _id?: string }; | ||
|
||
export class Database { | ||
db!: Record<Collections, Datastore>; | ||
token!: string; | ||
|
||
constructor() { } | ||
|
||
async init() { | ||
Logger.log("Initialiting NeDB database connection"); | ||
Logger.log("Initializing NeDB database connection"); | ||
let db: Partial<typeof this.db> = {}; | ||
try { | ||
const promises: Promise<unknown>[] = []; | ||
|
@@ -35,9 +39,7 @@ export class Database { | |
Logger.log("NeDB loaded"); | ||
} catch (error) { | ||
Logger.log(String(error)); | ||
Logger.log( | ||
"Persistent NeDB has failed. Server will work but progress will be lost at restart" | ||
); | ||
Logger.log("Persistent NeDB has failed. Server will work but progress will be lost at restart"); | ||
db = {}; | ||
for (const collection of this.getAllDataStores()) { | ||
db[collection] = new Datastore(); | ||
|
@@ -58,6 +60,9 @@ export class Database { | |
private async postInitHook() { | ||
await this.initBaseSavegame(); | ||
await this.initSettings(); | ||
await this.initMatchmakingQueue(); // Initialize matchmaking queue | ||
await this.initGameSessions(); // Initialize game sessions | ||
await this.initMatchmakingConfig(); // Initialize matchmaking configuration | ||
} | ||
|
||
private async initBaseSavegame() { | ||
|
@@ -98,8 +103,34 @@ export class Database { | |
await this.checkVersionAndMigrations(settings.version); | ||
} | ||
|
||
private async initMatchmakingQueue() { | ||
const collection = this.collection<MatchmakingQueue>(Collections.MATCHMAKING_QUEUE); | ||
// Initialize the matchmaking queue if necessary | ||
const queue = await collection.findOneAsync({}); | ||
if (!queue) { | ||
await collection.insertAsync({ players: [] }); // Empty initial queue | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure I understand the design here, do we only have a queue? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Initial testing for me, we can do more here like:
This part can be deleted, because I was testing... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, knowing it's a test, it's good to merge. About your suggestion, we're not gonna use regions yet, also, a single region may have several queues (for instance, several monsters queue). I was hoping to have Matchmaking Sessions in the Array instead of players themselves There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thinking more about this, I just don't think I want the matchmaking data on the database. It doesn't make sense because if a server restarts we're losing the websocket connections anyway, so all matchmaking info is irrelevant. This is not blocking for merging the pull request because it can be deleted anyway |
||
} | ||
} | ||
|
||
private async initGameSessions() { | ||
const collection = this.collection<GameSession>(Collections.GAME_SESSIONS); | ||
// Initialize the game sessions collection if necessary | ||
const sessions = await collection.findOneAsync({}); | ||
if (!sessions) { | ||
await collection.insertAsync({ sessions: [] }); // Empty initial game sessions | ||
} | ||
} | ||
|
||
private async initMatchmakingConfig() { | ||
const collection = this.collection<MatchmakingConfig>(Collections.MATCHMAKING_CONFIG); | ||
// Initialize matchmaking configuration if necessary | ||
const config = await collection.findOneAsync({}); | ||
if (!config) { | ||
await collection.insertAsync({ settings: {} }); // Default config | ||
} | ||
} | ||
|
||
private async checkVersionAndMigrations(version: number) { | ||
// Switch without breaks because migrations should be secuencial and cummulative | ||
switch (version) { | ||
default: // If version was pre-0 | ||
await this.DLCCharactersFix(); | ||
|
@@ -145,3 +176,18 @@ export class Database { | |
Logger.log("Migration Done"); | ||
} | ||
} | ||
|
||
// Define the new types for matchmaking | ||
interface MatchmakingQueue { | ||
players: string[]; // Array of player IDs waiting for a match | ||
} | ||
|
||
interface GameSession { | ||
sessionId: string; | ||
players: string[]; // Array of player IDs in the session | ||
state: string; // e.g., 'waiting', 'active', 'completed' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not required for merging, but this could be an ENUM There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, you're correct, I will make it as enum's |
||
} | ||
|
||
interface MatchmakingConfig { | ||
settings: Record<string, any>; // Store configuration settings for matchmaking | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,6 +22,12 @@ import { | |
SlotChangesResponse, | ||
UploadPlayerSettingsRequest, | ||
UploadPlayerSettingsResponse, | ||
MatchmakingQueueRequest, | ||
MatchmakingQueueResponse, | ||
CreateGameSessionRequest, | ||
CreateGameSessionResponse, | ||
MatchmakingConfigRequest, | ||
MatchmakingConfigResponse | ||
} from "../types/vhs-the-game-types"; | ||
import { | ||
Monsters, | ||
|
@@ -44,6 +50,7 @@ import jwt_to_pem from 'jwk-to-pem'; | |
import randomstring from "randomstring"; | ||
import { readFile } from "fs/promises"; | ||
import { LobbyManager } from "./lobby-manager"; | ||
import { MatchmakingManager } from "./matchmaking-manager"; // Import matchmaking manager | ||
|
||
type DiscoverResponse = SaveGameResponse | MathmakingInfoResponse; | ||
|
||
|
@@ -118,12 +125,10 @@ export class Handler { | |
case DiscoverTypes.INITIAL_LOAD: | ||
try { | ||
const [userSaveGame, serverInfo] = await Promise.all([Handler.getUserSaveGame(request.body.accountIdToDiscover ?? id), Handler.getGeneralServerInfo()]); | ||
// TODO when we actually implement the bitsToDiscoverFlag PROPERLY we should remove this | ||
if (request.body.accountIdToDiscover != null) { | ||
delete userSaveGame.data.playerSettingsData; | ||
} | ||
userSaveGame.data.DDT_SpecificLoadoutsBit = userSaveGame.data.DDT_AllLoadoutsBit; | ||
// End of the TODO remove in the future block | ||
return response.send(deepmerge(userSaveGame, serverInfo)); | ||
} catch (e) { | ||
const str = String(e); | ||
|
@@ -133,6 +138,57 @@ export class Handler { | |
} | ||
} | ||
|
||
// New endpoint for matchmaking queue | ||
static async matchmake( | ||
request: Request<any, MatchmakingQueueResponse | string, MatchmakingQueueRequest>, | ||
response: Response<MatchmakingQueueResponse | string> | ||
) { | ||
const id = Handler.checkOwnTokenAndGetId(request); | ||
try { | ||
const result = await MatchmakingManager.addToQueue(id); | ||
response.send({ | ||
log: { logSuccessful: true }, | ||
data: result, | ||
}); | ||
} catch (error) { | ||
response.status(500).send("Error handling matchmaking request"); | ||
} | ||
} | ||
|
||
// New endpoint for creating a game session | ||
static async createGameSession( | ||
request: Request<any, CreateGameSessionResponse | string, CreateGameSessionRequest>, | ||
response: Response<CreateGameSessionResponse | string> | ||
) { | ||
const id = Handler.checkOwnTokenAndGetId(request); | ||
try { | ||
const result = await MatchmakingManager.createGameSession(id); | ||
response.send({ | ||
log: { logSuccessful: true }, | ||
data: result, | ||
}); | ||
} catch (error) { | ||
response.status(500).send("Error creating game session"); | ||
} | ||
} | ||
|
||
// New endpoint for matchmaking configuration | ||
static async updateMatchmakingConfig( | ||
request: Request<any, MatchmakingConfigResponse | string, MatchmakingConfigRequest>, | ||
response: Response<MatchmakingConfigResponse | string> | ||
) { | ||
const id = Handler.checkOwnTokenAndGetId(request); | ||
try { | ||
await MatchmakingManager.updateConfig(request.body); | ||
response.send({ | ||
log: { logSuccessful: true }, | ||
data: { success: true }, | ||
}); | ||
} catch (error) { | ||
response.status(500).send("Error updating matchmaking configuration"); | ||
} | ||
} | ||
|
||
static async setCharacterLoadout( | ||
request: Request< | ||
any, | ||
|
@@ -184,7 +240,6 @@ export class Handler { | |
return response.send({ | ||
log: { logSuccessful: true }, | ||
data: { | ||
// TODO return the truth instead of this | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason to remove this comment? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mean I can't remember removing this .. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok so if you agree I'll add it back |
||
changedSlotNames: Object.keys(request.body.slotChanges).map((item) => { | ||
return { [item]: true }; | ||
}), | ||
|
@@ -201,9 +256,8 @@ export class Handler { | |
>, | ||
response: Response<SetWeaponLoadoutsForCharacterResponse | string> | ||
) { | ||
const id = Handler.checkOwnTokenAndGetId(request,); | ||
const id = Handler.checkOwnTokenAndGetId(request); | ||
const saveData = await Handler.getUserSaveGame(id); | ||
///@ts-ignore incomplete typings | ||
const loadout: | ||
| { | ||
[x: string]: { | ||
|
@@ -438,7 +492,6 @@ export class Handler { | |
return {data: {DDT_SeasonalEventBit: {activeSeasonalEventTypes: [event]}}} | ||
} | ||
|
||
|
||
private static generateToken(id: string) { | ||
return jwt.sign(id, db.token); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any reason for changing this from the async/await syntax into the callback one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The primary reason for switching to callback syntax is due to NeDB's native callback-based design, avoiding the extra overhead of wrapping those methods with promises unless necessary. With callbacks, the execution is immediate, and the function typically returns as soon as the operation is complete. (we are getting closer to VHS standarts)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Giving this is a REST API (i.e: most of the overhead is the HTTP+TCP handshake) I doubt a microtask queuing (that's all the overhead a promise gives) is really relevant but it is an improvement, so thanks for doing it and for your patience explaining it