This repository has been archived by the owner on Sep 17, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(lobbies): create lobbies module
- Loading branch information
1 parent
6fef75d
commit ee6bab4
Showing
18 changed files
with
1,507 additions
and
49 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
export interface Config { | ||
lobbies: LobbyConfig, | ||
lobbyRules: LobbyRule[], | ||
players: { | ||
maxPerIp?: number; | ||
maxUnconnected?: number; | ||
unconnectedExpireAfter: number; | ||
autoDestroyAfter?: number; | ||
} | ||
} | ||
|
||
export interface LobbyRule { | ||
tags: Record<string, string>, | ||
config: Partial<LobbyConfig>, | ||
} | ||
|
||
export interface LobbyConfig extends Record<PropertyKey, unknown> { | ||
destroyOnEmptyAfter?: number | null; | ||
unreadyExpireAfter: number; | ||
maxPlayers: number; | ||
maxPlayersDirect: number; | ||
enableDynamicMaxPlayers?: PlayerRange, | ||
enableDynamicMaxPlayersDirect?: PlayerRange, | ||
enableCreate: boolean, | ||
enableDestroy: boolean, | ||
enableFind: boolean, | ||
enableFindOrCreate: boolean, | ||
enableJoin: boolean, | ||
enableList: boolean, | ||
} | ||
|
||
export interface PlayerRange { | ||
min: number, | ||
max: number, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# Unconnected Players | ||
|
||
## Why it exists? | ||
|
||
- high load & low player caps | ||
- preventing botting | ||
|
||
## What happens when players fail to connect? | ||
|
||
- Unconnected players stack up | ||
- How lobbies API handles it | ||
- Max players per IP: if creating another player and goes over ip limit, will | ||
delete the old unconnected player for the same IP | ||
- Maximum unconnected players: if too many unconnected players, we'll start | ||
discarding the oldest unconnected player |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
{ | ||
"name": "Lobbies", | ||
"description": "Lobby & player management.", | ||
"icon": "game-board", | ||
"tags": [ | ||
"core", "multiplayer" | ||
], | ||
"authors": [ | ||
"NathanFlurry" | ||
], | ||
"status": "stable", | ||
"scripts": { | ||
"create": { | ||
"name": "Create Lobby", | ||
"description": "Creates a new lobby on-demand.", | ||
"public": true | ||
}, | ||
"destroy": { | ||
"name": "Destroy Lobby", | ||
"description": "Destroys an existing lobby.", | ||
"public": true | ||
}, | ||
"find_or_create": { | ||
"name": "Find Or Create Lobby", | ||
"description": "Finds a lobby or creates one if there are no available spots for players.", | ||
"public": true | ||
}, | ||
"join": { | ||
"name": "Join Lobby", | ||
"description": "Add a player to an existing lobby.", | ||
"public": true | ||
}, | ||
"list": { | ||
"name": "List Lobbies", | ||
"description": "List & query all lobbies.", | ||
"public": true | ||
}, | ||
"set_lobby_ready": { | ||
"name": "Set Lobby Ready", | ||
"description": "Called on lobby startup after initiation to notify it can start accepting player. This should be called after operations like loading maps are complete.", | ||
"public": true | ||
}, | ||
"set_player_connected": { | ||
"name": "Set Player Connected", | ||
"description": "Called when a player connects to the lobby.", | ||
"public": true | ||
}, | ||
"set_player_disconnected": { | ||
"name": "Set Player Disconnected", | ||
"description": "Called when a player disconnects from the lobby.", | ||
"public": true | ||
}, | ||
"find": { | ||
"name": "Find Lobby", | ||
"description": "Finds an existing lobby with a given query. This will not create a new lobby, see `find_or_create` instead.", | ||
"public": true | ||
}, | ||
"force_gc": { | ||
"name": "Force Garbage Collection", | ||
"description": "Rarely used. Forces the matchmaker to purge lobbies & players." | ||
} | ||
}, | ||
"actors": { | ||
"lobby_manager": {} | ||
}, | ||
"errors": { | ||
"lobby_not_found": { | ||
"name": "Lobby Not Found", | ||
"description": "Lobby not found." | ||
}, | ||
"lobby_create_missing_players": { | ||
"name": "Lobby Create Missing Players", | ||
"description": "When creating a lobby with `config.lobbies.autoDestroyWhenEmpty`, a lobby must be created with players in order to avoid creating an empty lobby." | ||
}, | ||
"lobby_full": { | ||
"name": "Lobby Full", | ||
"description": "No more players can join this lobby." | ||
}, | ||
"more_players_than_max": { | ||
"name": "More Players Than Max", | ||
"description": "More players were passed to the create lobby than the number of max players in a lobby." | ||
}, | ||
"lobby_already_ready": { | ||
"name": "Lobby Already Ready", | ||
"description": "Lobby already set as ready." | ||
}, | ||
"player_already_connected": { | ||
"name": "Player Already Connected", | ||
"description": "The player has already connected to this server. This error helps mitigate botting attacks by only allowing one scoket to connect to a game server for every player." | ||
}, | ||
"player_disconnected": { | ||
"name": "Player Disconnected", | ||
"description": "The player has already disconnected from the server. Create a new player for the specified lobby using the `join` script." | ||
}, | ||
"no_matching_lobbies": { | ||
"name": "No Matching Lobbies", | ||
"description": "No lobbies matched the given query." | ||
}, | ||
"too_many_players_for_ip": { | ||
"name": "Too Many Players For IP", | ||
"description": "The player has too many existing players for the given IP." | ||
} | ||
}, | ||
"dependencies": { | ||
"tokens": {} | ||
}, | ||
"defaultConfig": { | ||
"lobbies": { | ||
"destroyOnEmptyAfter": 60000, | ||
"unreadyExpireAfter": 300000, | ||
"maxPlayers": 16, | ||
"maxPlayersDirect": 16, | ||
"enableCreate": false, | ||
"enableDestroy": false, | ||
"enableFind": true, | ||
"enableFindOrCreate": true, | ||
"enableJoin": true, | ||
"enableList": true | ||
}, | ||
"lobbyRules": [], | ||
"players": { | ||
"maxPerIp": 8, | ||
"maxUnconnected": 128, | ||
"unconnectedExpireAfter": 60000, | ||
"autoDestroyAfter": 4147200000 | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import { | ||
CreateLobbyRequest, | ||
CreateLobbyResponse, | ||
} from "../actors/lobby_manager.ts"; | ||
import { ScriptContext } from "../module.gen.ts"; | ||
import { | ||
Lobby, | ||
PlayerRequest, | ||
PlayerWithToken, | ||
} from "../utils/types.ts"; | ||
|
||
export interface Request { | ||
version: string; | ||
tags: Record<string, string>; | ||
maxPlayers: number; | ||
maxPlayersDirect: number; | ||
|
||
players: PlayerRequest[]; | ||
} | ||
|
||
export interface Response { | ||
lobby: Lobby; | ||
players: PlayerWithToken[]; | ||
} | ||
|
||
// TODO: Doc why we create tokens on the script and not the DO | ||
|
||
export async function run( | ||
ctx: ScriptContext, | ||
req: Request, | ||
): Promise<Response> { | ||
// Setup lobby | ||
// | ||
// This token will be disposed if the lobby is not created | ||
const lobbyId = crypto.randomUUID(); | ||
const { token: lobbyToken } = await ctx.modules.tokens.create({ | ||
type: "lobby", | ||
meta: { lobbyId: lobbyId }, | ||
}); | ||
|
||
// Setup players | ||
const playerOpts: PlayerRequest[] = []; | ||
const playerTokens: Record<string, string> = {}; | ||
for (const _player of req.players) { | ||
const playerId = crypto.randomUUID(); | ||
const { token: playerToken } = await ctx.modules.tokens.create({ | ||
type: "player", | ||
meta: { lobbyId: lobbyId, playerId: playerId }, | ||
}); | ||
playerOpts.push({ playerId }); | ||
playerTokens[playerId] = playerToken.token; | ||
} | ||
|
||
const { lobby, players }: CreateLobbyResponse = await ctx.actors.lobbyManager.getOrCreateAndCall( | ||
"default", | ||
undefined, | ||
"createLobby", | ||
{ | ||
lobby: { | ||
lobbyId, | ||
version: req.version, | ||
tags: req.tags, | ||
lobbyToken: lobbyToken.token, | ||
maxPlayers: req.maxPlayers, | ||
maxPlayersDirect: req.maxPlayersDirect, | ||
}, | ||
players: playerOpts, | ||
} as CreateLobbyRequest, | ||
); | ||
|
||
return { | ||
lobby, | ||
players: players.map((x) => ({ token: playerTokens[x.id], ...x })), | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { DestroyLobbyRequest } from "../actors/lobby_manager.ts"; | ||
import { ScriptContext } from "../module.gen.ts"; | ||
|
||
export interface Request { | ||
lobbyId: string; | ||
} | ||
|
||
export interface Response { | ||
} | ||
|
||
export async function run( | ||
ctx: ScriptContext, | ||
req: Request, | ||
): Promise<Response> { | ||
await ctx.actors.lobbyManager.getOrCreateAndCall( | ||
"default", | ||
undefined, | ||
"destroyLobby", | ||
{ lobbyId: req.lobbyId } as DestroyLobbyRequest, | ||
); | ||
|
||
return {}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { | ||
FindLobbyRequest, | ||
FindLobbyResponse, | ||
} from "../actors/lobby_manager.ts"; | ||
import { ScriptContext } from "../module.gen.ts"; | ||
import { Lobby, PlayerRequest, PlayerWithToken } from "../utils/types.ts"; | ||
|
||
export interface Request { | ||
version: string; | ||
tags: Record<string, string>; | ||
players: PlayerRequest[]; | ||
} | ||
|
||
export interface Response { | ||
lobby: Lobby; | ||
players: PlayerWithToken[]; | ||
} | ||
|
||
export async function run( | ||
ctx: ScriptContext, | ||
req: Request, | ||
): Promise<Response> { | ||
// Setup players | ||
const playerOpts: PlayerRequest[] = []; | ||
const playerTokens: Record<string, string> = {}; | ||
for (const _player of req.players) { | ||
const playerId = crypto.randomUUID(); | ||
const { token: playerToken } = await ctx.modules.tokens.create({ | ||
type: "player", | ||
meta: { playerId: playerId }, | ||
}); | ||
playerOpts.push({ playerId }); | ||
playerTokens[playerId] = playerToken.token; | ||
} | ||
|
||
const { lobby, players }: FindLobbyResponse = await ctx.actors.lobbyManager.getOrCreateAndCall( | ||
"default", | ||
undefined, | ||
"findLobby", | ||
{ | ||
query: { | ||
version: req.version, | ||
tags: req.tags, | ||
}, | ||
players: playerOpts, | ||
} as FindLobbyRequest, | ||
); | ||
|
||
return { | ||
lobby, | ||
players: players.map((x) => ({ token: playerTokens[x.id], ...x })), | ||
}; | ||
} |
Oops, something went wrong.