diff --git a/src/main.ts b/src/main.ts index c686aae..7c1ba2e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -148,8 +148,7 @@ const server = Deno.serve({ `${typeof res.status == 'number' ? res.status : "SENT STACK TRACE"}: ${req.method} ${getFullPathFromUrl(new URL(req.url))}`, formatHeader(req.headers, 'Content-Type'), formatHeader(req.headers, 'Connection'), - formatHeader(req.headers, 'User-Agent'), - formatHeader(req.headers, 'Accept-Encoding') + formatHeader(req.headers, 'User-Agent') ]); if (!logBlacklist.includes(url.pathname)) { if (res.status === 404) log.e(netlog); diff --git a/src/routes/api/routes/config.ts b/src/routes/api/routes/config.ts index ddaf0cc..36a8f76 100644 --- a/src/routes/api/routes/config.ts +++ b/src/routes/api/routes/config.ts @@ -1,7 +1,12 @@ +import Server from "../../../server/server.ts"; import { createHonoRoute } from "../../../util/import.ts"; export const route = createHonoRoute("/config"); route.app.get('/v1/amplitude', c => { return c.json({AmplitudeKey: ""}); +}); + +route.app.get('/v2', c => { + return c.json(Server.getPublicConfig()); }); \ No newline at end of file diff --git a/src/routes/api/routes/messages.ts b/src/routes/api/routes/messages.ts index 681d7ee..b62ff13 100644 --- a/src/routes/api/routes/messages.ts +++ b/src/routes/api/routes/messages.ts @@ -1,7 +1,8 @@ +import { authenticate } from "../../../util/api.ts"; import { createHonoRoute } from "../../../util/import.ts"; export const route = createHonoRoute("/messages"); -route.app.get('/v2/get', async c => { +route.app.get('/v2/get', authenticate, async c => { return c.json(await c.get('profile').Messages.getMessages()); }); \ No newline at end of file diff --git a/src/routes/api/routes/playerReputation.ts b/src/routes/api/routes/playerReputation.ts index bade4e5..704f490 100644 --- a/src/routes/api/routes/playerReputation.ts +++ b/src/routes/api/routes/playerReputation.ts @@ -9,21 +9,26 @@ export const route = createHonoRoute("/playerReputation"); const getRepIdParamSchema = z.object({ id: z.coerce.number().min(0).max(2_147_483_647) }) -route.app.get('/v1/:id', typedZValidator('param', getRepIdParamSchema), authenticate, async c => { +route.app.get('/v1/:id', authenticate, typedZValidator('param', getRepIdParamSchema), authenticate, async c => { const { id } = c.req.valid('param'); const prof = await Server.Profiles.get(id); if (!prof) return c.status(404); - else return c.json({ - AccountId: id, - Noteriety: 0.0, - CheerGeneral: 0, - CheerHelpful: 0, - CheerGreatHost: 0, - CheerSportsman: 0, - CheerCreative: 0, - CheerCredit: 0, - SubscriberCount: 0, - SubscribedCount: 0, - }); + else return c.json(await prof.Reputation.export()); +}); + +const getRepBulkBodySchema = z.object({ + Ids: z.array(z.coerce.number()) +}); +route.app.post('/v1/bulk', authenticate, typedZValidator('form', getRepBulkBodySchema), async c => { + const ids = c.req.valid('form').Ids; + + if (typeof ids == 'object') { + const profs = await Server.Profiles.getMany(...ids); + return c.json(await Promise.all(profs.map(prof => prof.Reputation.export()))); + } else { + const prof = await Server.Profiles.get(ids); + if (!prof) return c.json([]); + return c.json([await prof.Reputation.export()]); + } }); \ No newline at end of file diff --git a/src/routes/api/routes/players.ts b/src/routes/api/routes/players.ts index 71d7db8..9de241c 100644 --- a/src/routes/api/routes/players.ts +++ b/src/routes/api/routes/players.ts @@ -2,6 +2,7 @@ import z from "zod"; import { createHonoRoute } from "../../../util/import.ts"; import { authenticate } from "../../../util/api.ts"; import { typedZValidator } from "../../../util/validators.ts"; +import Server from "../../../server/server.ts"; export const route = createHonoRoute("/players"); @@ -14,4 +15,20 @@ const getProgParamSchema = z.object({ }); route.app.get('/v1/progression/:id', authenticate, typedZValidator('param', getProgParamSchema), async c => { return c.json(await c.get('profile').Reputation.export()); +}); + +const getProgBulkBodySchema = z.object({ + Ids: z.union([z.array(z.coerce.number()), z.coerce.number()]) +}); +route.app.post('/v1/progression/bulk', authenticate, typedZValidator('form', getProgBulkBodySchema), async c => { + const ids = c.req.valid('form').Ids; + + if (typeof ids == 'object') { + const profs = await Server.Profiles.getMany(...ids); + return c.json(await Promise.all(profs.map(prof => prof.Progression.get()))); + } else { + const prof = await Server.Profiles.get(ids); + if (!prof) return c.json([]); + return c.json([await prof.Progression.get()]); + } }); \ No newline at end of file diff --git a/src/routes/api/routes/quickPlay.ts b/src/routes/api/routes/quickPlay.ts new file mode 100644 index 0000000..f453afc --- /dev/null +++ b/src/routes/api/routes/quickPlay.ts @@ -0,0 +1,7 @@ +import { createHonoRoute } from "../../../util/import.ts"; + +export const route = createHonoRoute("/quickPlay"); + +route.app.get('/v1/getandclear', c => { + return c.json({}); +}); \ No newline at end of file diff --git a/src/routes/api/routes/relationships.ts b/src/routes/api/routes/relationships.ts index cd0bcfd..2905ae4 100644 --- a/src/routes/api/routes/relationships.ts +++ b/src/routes/api/routes/relationships.ts @@ -1,7 +1,14 @@ +import { HTTPStatus } from "@oneday/http-status"; +import { statusResponse } from "../../../util/api.ts"; import { createHonoRoute } from "../../../util/import.ts"; export const route = createHonoRoute('/relationships'); route.app.get('/v2/get', c => { return c.json([]); +}); + +// deno-lint-ignore require-await +route.app.post('/v1/bulkignoreplatformusers', async c => { + return statusResponse(c, HTTPStatus.OK); }); \ No newline at end of file diff --git a/src/routes/api/routes/settings.ts b/src/routes/api/routes/settings.ts index 9dd31b9..e1ce39d 100644 --- a/src/routes/api/routes/settings.ts +++ b/src/routes/api/routes/settings.ts @@ -1,9 +1,10 @@ import z from "zod"; -import { authenticate } from "../../../util/api.ts"; +import { authenticate, statusResponse } from "../../../util/api.ts"; import { createHonoRoute } from "../../../util/import.ts"; import { typedZValidator } from "../../../util/validators.ts"; import { HonoEnv } from "../../../util/types.ts"; import { Context } from "@hono/hono"; +import { HTTPStatus } from "@oneday/http-status"; export const route = createHonoRoute('/settings'); @@ -25,5 +26,5 @@ route.app.post('/v2/set', typedZValidator('json', settingsSetSchema), async c => const { Key, Value } = c.req.valid('json'); await c.get('profile').Settings.setSetting(Key, Value); - return c.status(200); + return statusResponse(c, HTTPStatus.OK); }); \ No newline at end of file diff --git a/src/routes/match/root.ts b/src/routes/match/root.ts index 5cf5964..8c4fef2 100644 --- a/src/routes/match/root.ts +++ b/src/routes/match/root.ts @@ -1,7 +1,50 @@ import { createHonoRoute, routeImporter } from "../../util/import.ts"; +import { Context, Next } from "@hono/hono"; +import z from "zod"; +import { HonoEnv } from "../../util/types.ts"; +import { statusResponse } from "../../util/api.ts"; +import { HTTPStatus } from "@oneday/http-status"; +import Logging from "@proxnet/undead-logging"; + +const log = new Logging("MatchRoute"); export const route = createHonoRoute('/match'); +const loginLockBodySchema = z.object({ + LoginLock: z.uuidv4() +}); +export const loginLockMiddleware = async (c: Context, nxt: Next) => { + function unauthorized() { + return statusResponse(c, HTTPStatus.Unauthorized); + } + + if (c.req.header("Content-Type") !== "application/x-www-form-urlencoded") return unauthorized(); + try { + const form = await c.req.formData(); + + const body = await loginLockBodySchema.safeParseAsync(Object.fromEntries(form.entries())); + if (body.success) { + if (typeof c.get('profile') == 'undefined') { + log.w(`Profile was not set, cannot validate LoginLock. Was the request authorized?`); + return statusResponse(c, HTTPStatus.InternalServerError); + } + + const profile = c.get('profile'); + + const loginLock = await profile.Matchmaking.getLoginLock(); + if (!loginLock) await profile.Matchmaking.setLoginLock(body.data.LoginLock); + else if (body.data.LoginLock !== loginLock) { + log.w(`LoginLock did not match. The token for this profile could be compromised or the client is an unknown state.`); + return unauthorized(); + } + + return await nxt(); + } else return unauthorized(); + } catch { + return unauthorized(); + } +} + await routeImporter(route.app, 'src/routes/match/', [ 'routes' ]); \ No newline at end of file diff --git a/src/routes/match/routes/goto.ts b/src/routes/match/routes/goto.ts new file mode 100644 index 0000000..525f961 --- /dev/null +++ b/src/routes/match/routes/goto.ts @@ -0,0 +1,13 @@ +import { HTTPStatus } from "@oneday/http-status"; +import { statusResponse } from "../../../util/api.ts"; +import { createHonoRoute } from "../../../util/import.ts"; + +export const route = createHonoRoute("/goto"); + +route.app.post('/room/:roomName', c => { + return statusResponse(c, HTTPStatus.NotImplemented); +}); + +route.app.post('/room/:roomName/:subRoomName', c => { + return statusResponse(c, HTTPStatus.NotImplemented); +}); \ No newline at end of file diff --git a/src/routes/match/routes/player.ts b/src/routes/match/routes/player.ts index f961ac3..b025bfe 100644 --- a/src/routes/match/routes/player.ts +++ b/src/routes/match/routes/player.ts @@ -1,10 +1,20 @@ -import { authenticate } from "../../../util/api.ts"; +import { authenticate, statusResponse } from "../../../util/api.ts"; import { createHonoRoute } from "../../../util/import.ts"; +import { loginLockMiddleware } from "../root.ts"; +import { HTTPStatus } from "@oneday/http-status"; export const route = createHonoRoute("/player"); route.app.use(authenticate); -route.app.post('/login', _c => { - return new Response("OK", { status: 200 }); +route.app.post('/login', authenticate, loginLockMiddleware, async c => { + +}); + +route.app.post('/player/vrmovementmode', authenticate, loginLockMiddleware, async c => { + return statusResponse(c, HTTPStatus.OK); // stub +}); + +route.app.post('/player/statusvisibility', authenticate, loginLockMiddleware, async c => { + return statusResponse(c, HTTPStatus.OK); // stub }); \ No newline at end of file diff --git a/src/server/baseevent.ts b/src/server/baseevent.ts index 553d40b..4c18497 100644 --- a/src/server/baseevent.ts +++ b/src/server/baseevent.ts @@ -5,19 +5,19 @@ export class EventManager { [K in keyof Events]?: Set> } = {}; - on(eventName: K, callback: Callback): void { + on(eventName: K, cb: Callback): void { if (!this.#listeners[eventName]) this.#listeners[eventName] = new Set(); - this.#listeners[eventName]!.add(callback); + this.#listeners[eventName]!.add(cb); } - off(eventName: K, callback: Callback): void { - this.#listeners[eventName]?.delete(callback); + off(eventName: K, cb: Callback): void { + this.#listeners[eventName]?.delete(cb); if (this.#listeners[eventName]?.size === 0) delete this.#listeners[eventName]; } emit(eventName: K, payload: Events[K]): void { - this.#listeners[eventName]?.forEach((callback) => callback(payload)); + this.#listeners[eventName]?.forEach(cb => cb(payload)); } } \ No newline at end of file diff --git a/src/server/instances/Instance.ts b/src/server/instances/Instance.ts index 11a940e..509449e 100644 --- a/src/server/instances/Instance.ts +++ b/src/server/instances/Instance.ts @@ -1,6 +1,6 @@ import { CloudRegionCode } from "../../util/photon.ts"; import type Profile from "../profiles/profile.ts"; -import { RoomInstance, RoomLocation } from "./base.ts"; +import { RoomInstance, RoomLocation } from "./types.ts"; export interface InstanceCreationOptions { roomId: number, diff --git a/src/server/instances/base.ts b/src/server/instances/base.ts index 0f71a95..7d1334d 100644 --- a/src/server/instances/base.ts +++ b/src/server/instances/base.ts @@ -1,63 +1,47 @@ +import Logging from "@proxnet/undead-logging"; import { ServerContentBase } from "../ContentBase.ts"; +import { type Instance } from "./Instance.ts"; -export enum RoomLocation { - Calibration = "f5fbd9c9-e853-4036-9d48-5f68e861af04", - DormRoom = "76d98498-60a1-430c-ab76-b54a29b7a163", - RecCenter = "cbad71af-0831-44d8-b8ef-69edafa841f6", - Charades = "4078dfed-24bb-4db7-863f-578ba48d726b", - TheInkSpace = "1fa06e3c-c307-4c11-a91b-1fabcddb8a96", - Paddleball = "d89f74fa-d51e-477a-a425-025a891dd499", - GoldenTrophy = "91e16e35-f48f-4700-ab8a-a1b79e50e51b", - Orientation = "c79709d8-a31b-48aa-9eb8-cc31ba9505e8", - TheRiseofJumbotron = "acc06e66-c2d0-4361-b0cd-46246a4c455c", - CrimsonCauldron = "949fa41f-4347-45c0-b7ac-489129174045", - IsleOfLostSkulls = "7e01cfe0-820a-406f-b1b3-0a5bf575235c", - RecRoyaleSquads = "253fa009-6e65-4c90-91a1-7137a56a267f", - RecRoyaleSolos = "b010171f-4875-4e89-baba-61e878cd41e1", - Lounge = "a067557f-ca32-43e6-b6e5-daaec60b4f5a", - PerformanceHall = "9932f88f-3929-43a0-a012-a40b5128e346", - MakerRoom = "a75f7547-79eb-47c6-8986-6767abcb4f92", - Park = "0a864c86-5a71-4e18-8041-8124e4dc9d98", - Lake = "f6f7256c-e438-4299-b99e-d20bef8cf7e0", - PropulsionTestRange = "d9378c9f-80bc-46fb-ad1e-1bed8a674f55", - Gym = "3d474b26-26f7-45e9-9a36-9b02847d5e6f", - Stadium = "6d5eea4b-f069-4ed0-9916-0e2f07df0d03", - Hangar = "239e676c-f12f-489f-bf3a-d4c383d692c3", - CyberJunkCity = "9d6456ce-6264-48b4-808d-2d96b3d91038", - Crescendo = "49cb8993-a956-43e2-86f4-1318f279b22a", - BowlingAlley = "ae929543-9a07-41d5-8ee9-dbbee8c36800", - AnimationRecordingStudio = "a95c349c-0f96-4c2d-a4c8-4969ffa8ea44", - StuntRunner = "b7281665-a715-4051-826b-8e08e69c6172", - TheMainEvent = "3a636bd2-f896-424c-9225-c184522c0d87", - StuntRunnerBaseRoom = "882e9b96-7115-4b03-86f6-c0c9d8e22e00", - Registration = "cf61556d-68fd-4288-9ae5-7a512621e569", - ARRoom = "bf268f5f-b55b-41af-8628-32fa4b5d70b6", - PaintballRiver = "e122fe98-e7db-49e8-a1b1-105424b6e1f0", - PaintballHomestead = "a785267d-c579-42ea-be43-fec1992d1ca7", - PaintballQuarry = "ff4c6427-7079-4f59-b22a-69b089420827", - PaintballClearcut = "380d18b5-de9c-49f3-80f7-f4a95c1de161", - PaintballSpillway = "58763055-2dfb-4814-80b8-16fac5c85709", - PaintballDriveIn = "65ddbb48-5a01-4e3e-972d-e5c7419e2bc3", -} - -export interface RoomInstance { - roomInstanceId: number, - roomId: number, - subRoomId: number, - location: RoomLocation, - name: string, - maxCapacity: number, - isFull: boolean, - isPrivate: boolean, - isInProgress: boolean, - photonRegionId: string, - photonRoomId: string, - dataBlob?: string, - eventId?: number -} +const log = new Logging("Instances"); export class InstanceManager extends ServerContentBase { + #instances: Set = new Set(); + clearEmptyInstances() { + log.i(`Starting instance purge\n Before: ${ + this.#instances.size + } instances, ${ + this.#instances.values().reduce((prev, current) => prev + current.getPlayers().length, 0) + } players`); + + return new Promise(() => { + for (const inst of this.#instances) { + if (inst.getPlayers().length === 0) this.deleteInstance(inst); + } + + log.i(`Instance purge complete\n After: ${ + this.#instances.size + } instances, ${ + this.#instances.values().reduce((prev, current) => prev + current.getPlayers().length, 0) + } players`); + }); + } + + getAllInstances() { + + } + + registerInstance(inst: Instance) { + + } + + getInstance(id: number) { + + } + + deleteInstance(inst: Instance) { + + } } \ No newline at end of file diff --git a/src/server/instances/types.ts b/src/server/instances/types.ts new file mode 100644 index 0000000..b5a1d19 --- /dev/null +++ b/src/server/instances/types.ts @@ -0,0 +1,55 @@ +export enum RoomLocation { + Calibration = "f5fbd9c9-e853-4036-9d48-5f68e861af04", + DormRoom = "76d98498-60a1-430c-ab76-b54a29b7a163", + RecCenter = "cbad71af-0831-44d8-b8ef-69edafa841f6", + Charades = "4078dfed-24bb-4db7-863f-578ba48d726b", + TheInkSpace = "1fa06e3c-c307-4c11-a91b-1fabcddb8a96", + Paddleball = "d89f74fa-d51e-477a-a425-025a891dd499", + GoldenTrophy = "91e16e35-f48f-4700-ab8a-a1b79e50e51b", + Orientation = "c79709d8-a31b-48aa-9eb8-cc31ba9505e8", + TheRiseofJumbotron = "acc06e66-c2d0-4361-b0cd-46246a4c455c", + CrimsonCauldron = "949fa41f-4347-45c0-b7ac-489129174045", + IsleOfLostSkulls = "7e01cfe0-820a-406f-b1b3-0a5bf575235c", + RecRoyaleSquads = "253fa009-6e65-4c90-91a1-7137a56a267f", + RecRoyaleSolos = "b010171f-4875-4e89-baba-61e878cd41e1", + Lounge = "a067557f-ca32-43e6-b6e5-daaec60b4f5a", + PerformanceHall = "9932f88f-3929-43a0-a012-a40b5128e346", + MakerRoom = "a75f7547-79eb-47c6-8986-6767abcb4f92", + Park = "0a864c86-5a71-4e18-8041-8124e4dc9d98", + Lake = "f6f7256c-e438-4299-b99e-d20bef8cf7e0", + PropulsionTestRange = "d9378c9f-80bc-46fb-ad1e-1bed8a674f55", + Gym = "3d474b26-26f7-45e9-9a36-9b02847d5e6f", + Stadium = "6d5eea4b-f069-4ed0-9916-0e2f07df0d03", + Hangar = "239e676c-f12f-489f-bf3a-d4c383d692c3", + CyberJunkCity = "9d6456ce-6264-48b4-808d-2d96b3d91038", + Crescendo = "49cb8993-a956-43e2-86f4-1318f279b22a", + BowlingAlley = "ae929543-9a07-41d5-8ee9-dbbee8c36800", + AnimationRecordingStudio = "a95c349c-0f96-4c2d-a4c8-4969ffa8ea44", + StuntRunner = "b7281665-a715-4051-826b-8e08e69c6172", + TheMainEvent = "3a636bd2-f896-424c-9225-c184522c0d87", + StuntRunnerBaseRoom = "882e9b96-7115-4b03-86f6-c0c9d8e22e00", + Registration = "cf61556d-68fd-4288-9ae5-7a512621e569", + ARRoom = "bf268f5f-b55b-41af-8628-32fa4b5d70b6", + PaintballRiver = "e122fe98-e7db-49e8-a1b1-105424b6e1f0", + PaintballHomestead = "a785267d-c579-42ea-be43-fec1992d1ca7", + PaintballQuarry = "ff4c6427-7079-4f59-b22a-69b089420827", + PaintballClearcut = "380d18b5-de9c-49f3-80f7-f4a95c1de161", + PaintballSpillway = "58763055-2dfb-4814-80b8-16fac5c85709", + PaintballDriveIn = "65ddbb48-5a01-4e3e-972d-e5c7419e2bc3" +} + +export interface RoomInstance { + roomInstanceId: number; + roomId: number; + subRoomId: number; + location: RoomLocation; + name: string; + maxCapacity: number; + isFull: boolean; + isPrivate: boolean; + isInProgress: boolean; + photonRegionId: string; + photonRoomId: string; + dataBlob?: string; + eventId?: number; +} \ No newline at end of file diff --git a/src/server/matchmaking/base.ts b/src/server/matchmaking/base.ts new file mode 100644 index 0000000..6552a8c --- /dev/null +++ b/src/server/matchmaking/base.ts @@ -0,0 +1,7 @@ +import { ServerContentBase } from "../ContentBase.ts"; + +export class ServerMatchmakingBase extends ServerContentBase { + + + +} \ No newline at end of file diff --git a/src/server/matchmaking/types.ts b/src/server/matchmaking/types.ts new file mode 100644 index 0000000..6ee0dab --- /dev/null +++ b/src/server/matchmaking/types.ts @@ -0,0 +1,36 @@ +enum MatchmakingErrorCode { + Success, + NoSuchGame, + PlayerNotOnline, + InsufficientSpace, + EventNotStarted, + EventAlreadyFinished, + EventCreatorNotReady, + BlockedFromRoom, + ProfileLocked, + NoBirthday, + MarkedForDelete, + JuniorNotAllowed, + Banned, + AlreadyInBestInstance, + InsufficientRelationship, + UpdateRequired = 16, + AlreadyInTargetInstance, + RegistrationRequired, + UGCNotAllowed, + NoSuchRoom, + RoomCreatorNotReady, + RoomIsNotActive, + RoomBlockedByCreator, + RoomBlockingCreator, + RoomIsPrivate, + RoomInstanceIsPrivate, + DeviceClassNotSupported = 30, + DeviceClassNotSupportedByRoomOwner, + VRMovementModeNotSupportedByRoomOwner, + EventIsPrivate = 35, + RoomInviteExpired = 40, + NoAvailableRegion = 45, + NotorietyTooPoor = 50, + BannedFromRoom = 55 +} \ No newline at end of file diff --git a/src/server/objectives/base.ts b/src/server/objectives/base.ts new file mode 100644 index 0000000..dd01a74 --- /dev/null +++ b/src/server/objectives/base.ts @@ -0,0 +1,101 @@ +import { ServerContentBase } from "../ContentBase.ts"; + +export enum ObjectiveType { + Default = -1, + FirstSessionOfDay = 1, + AddAFriend, + PartyUp, + AllOtherChallenges, + LevelUp, + CheerAPlayer, + PointedAtPlayer, + CheerARoom, + SubscribeToPlayer, + DailyObjective1, + DailyObjective2, + DailyObjective3, + AllDailyObjectives, + CompleteAnyDaily, + CompleteAnyWeekly, + OOBE_GoToLockerRoom = 20, + OOBE_GoToActivity, + OOBE_FinishActivity, + NUX_PunchcardObjective = 25, + NUX_AllPunchcardObjectives, + GoToRecCenter = 30, + FinishActivity, + VisitACustomRoom, + CreateACustomRoom, + ScoreBasketInRecCenter = 35, + UploadPhotoToRecNet, + UpdatePlayerBio, + SaveOutfitSlot, + PurchaseClothingItem, + PurchaseNonClothingItem, + CharadesGames = 100, + CharadesWinsPerformer, + CharadesWinsGuesser, + DiscGolfWins = 200, + DiscGolfGames, + DiscGolfHolesUnderPar, + DodgeballWins = 300, + DodgeballGames, + DodgeballHits, + PaddleballGames = 400, + PaddleballWins, + PaddleballScores, + PaintballAnyModeGames = 500, + PaintballAnyModeWins, + PaintballAnyModeHits, + PaintballCTFWins = 600, + PaintballCTFGames, + PaintballCTFHits, + PaintballFlagCaptures, + PaintballTeamBattleWins = 700, + PaintballTeamBattleGames, + PaintballTeamBattleHits, + PaintballFreeForAllWins = 710, + PaintballFreeForAllGames, + PaintballFreeForAllHits, + SoccerWins = 800, + SoccerGames, + SoccerGoals, + QuestGames = 1000, + QuestWins, + QuestPlayerRevives, + QuestEnemyKills, + QuestGames_Goblin1 = 1010, + QuestWins_Goblin1, + QuestPlayerRevives_Goblin1, + QuestEnemyKills_Goblin1, + QuestGames_Goblin2 = 1020, + QuestWins_Goblin2, + QuestPlayerRevives_Goblin2, + QuestEnemyKills_Goblin2, + QuestGames_Scifi1 = 1030, + QuestWins_Scifi1, + QuestPlayerRevives_Scifi1, + QuestEnemyKills_Scifi1, + QuestGames_Pirate1 = 1040, + QuestWins_Pirate1, + QuestPlayerRevives_Pirate1, + QuestEnemyKills_Pirate1, + ArenaGames = 2000, + ArenaWins, + ArenaPlayerRevives, + ArenaHeroTags, + ArenaBotTags, + RecRoyaleGames = 3000, + RecRoyaleWins, + RecRoyaleTags, +} + +export type Objective = { + type: ObjectiveType; + score: number; +} +export class ServerObjectivesManager extends ServerContentBase { + + // asdf + +} \ No newline at end of file diff --git a/src/server/platforms/base.ts b/src/server/platforms/base.ts index 662756e..dd570a4 100644 --- a/src/server/platforms/base.ts +++ b/src/server/platforms/base.ts @@ -1,7 +1,7 @@ import z from "zod"; import Command from "../commands/command.ts"; import { ServerContentBase } from "../ContentBase.ts"; -import { transformStringToEnum } from "../../util/validators.ts"; +import { transformCheckEnum, transformStringToEnum } from "../../util/validators.ts"; import { sign } from "@hono/hono/jwt"; import { CachedLogin, DbCachedLogin, PlatformMask, PlatformType, TokenFormat, TokenType } from "./types.ts"; @@ -127,7 +127,7 @@ export class PlatformsManager extends ServerContentBase { return await this.getCachedLogins(type, platformId, false); }, zod: z.tuple([ - z.string().transform(transformStringToEnum(PlatformType)), + z.coerce.number().transform(transformCheckEnum(PlatformType)), z.string().min(4) ]), help: 'List all cachedlogins for platformId: ' diff --git a/src/server/platforms/types.ts b/src/server/platforms/types.ts index e187af0..81bc958 100644 --- a/src/server/platforms/types.ts +++ b/src/server/platforms/types.ts @@ -13,7 +13,6 @@ export interface TokenFormat extends TokenFormatBase { export enum ProfileRole { Developer = 'developer', - Moderator = 'moderator', Web = 'webClient', Game = 'gameClient' } diff --git a/src/server/presence/base.ts b/src/server/presence/base.ts index 9b80cc3..06dcda2 100644 --- a/src/server/presence/base.ts +++ b/src/server/presence/base.ts @@ -3,7 +3,7 @@ import { ServerContentBase } from "../ContentBase.ts"; import { DeviceClass } from "../platforms/types.ts"; import Profile from "../profiles/profile.ts"; import { type ServerBase } from "../server.ts"; -import { RoomInstance } from "../instances/base.ts"; +import { RoomInstance } from "../instances/types.ts"; export enum VRMovementMode { TELEPORT, diff --git a/src/server/profiles/content/Matchmaking.ts b/src/server/profiles/content/Matchmaking.ts index 9a4de70..6093f48 100644 --- a/src/server/profiles/content/Matchmaking.ts +++ b/src/server/profiles/content/Matchmaking.ts @@ -15,11 +15,8 @@ export class ProfileMatchmakingManager extends ProfileContentManager { async setLoginLock(lock: string) { await this.kv.getKv().set(this.#deviceClassKey, lock); } - async getLoginLock(): Promise { - return (await this.kv.getKv().get(this.#loginLockKey)).value || ""; - } - async hasLoginLock() { - return (await this.kv.getKv().get(this.#loginLockKey)).value ? true : false; + async getLoginLock(): Promise { + return (await this.kv.getKv().get(this.#loginLockKey)).value; } #lastSeen = this.profile.constructProfilePropertyKey('lastseen'); diff --git a/src/server/profiles/content/Messages.ts b/src/server/profiles/content/Messages.ts index 17bb7b5..c7ad1d5 100644 --- a/src/server/profiles/content/Messages.ts +++ b/src/server/profiles/content/Messages.ts @@ -37,6 +37,7 @@ interface MessageBase { export class ProfileMessageManager extends ProfileContentManager { + // temp // deno-lint-ignore require-await async getMessages() { return []; diff --git a/src/server/profiles/content/Progression.ts b/src/server/profiles/content/Progression.ts index f38f1b0..1a3ca37 100644 --- a/src/server/profiles/content/Progression.ts +++ b/src/server/profiles/content/Progression.ts @@ -2,4 +2,13 @@ import ProfileContentManager from "./base.ts"; export class ProfileProgressionManager extends ProfileContentManager { + // deno-lint-ignore require-await + async get() { + return { + PlayerId: this.profile.getId(), + Level: 1, + XP: 0 + } + } + } // do this soon:tm: \ No newline at end of file diff --git a/src/server/profiles/content/Rooms.ts b/src/server/profiles/content/Rooms.ts new file mode 100644 index 0000000..9bb06ea --- /dev/null +++ b/src/server/profiles/content/Rooms.ts @@ -0,0 +1,23 @@ +import ProfileContentManager from "./base.ts"; + +export class ProfileRoomsManager extends ProfileContentManager { + + #roomsKey = this.profile.constructProfilePropertyKey('rooms'); + #rooms: Set = new Set(); + + async #write() { + await this.kv.getKv().set(this.#roomsKey, this.#rooms); + } + async addRoom(id: number) { + this.#rooms.add(id); + await this.#write(); + } + async delRoom(id: number) { + this.#rooms.delete(id); + await this.#write(); + } + getRooms() { + return this.#rooms; + } + +} \ No newline at end of file diff --git a/src/server/profiles/content/Settings.ts b/src/server/profiles/content/Settings.ts index 4292eb1..90c734e 100644 --- a/src/server/profiles/content/Settings.ts +++ b/src/server/profiles/content/Settings.ts @@ -40,6 +40,7 @@ export class ProfileSettingsManager extends ProfileContentManager { if (!s) settings.push({ Key, Value }); else s.Value = Value; await this.#updateSettings(settings); + this.server.emit('profile.setting.update', { profile: this.profile, key: Key, value: Value }); } } \ No newline at end of file diff --git a/src/server/profiles/content/base.ts b/src/server/profiles/content/base.ts index 831ce9f..115af7f 100644 --- a/src/server/profiles/content/base.ts +++ b/src/server/profiles/content/base.ts @@ -1,13 +1,16 @@ import type KV from "../../persistence/kv.ts"; +import { type ServerBase } from "../../server.ts"; import type Profile from "../profile.ts"; class ProfileContentManager { protected profile: Profile; protected kv: KV; - constructor(profile: Profile, kv: KV) { + protected server: ServerBase + constructor(server: ServerBase, profile: Profile, kv: KV) { this.profile = profile; this.kv = kv; + this.server = server; profile.managers.push(this); } diff --git a/src/server/profiles/events/ProfileUpdatedSetting.ts b/src/server/profiles/events/ProfileUpdatedSetting.ts new file mode 100644 index 0000000..74b1537 --- /dev/null +++ b/src/server/profiles/events/ProfileUpdatedSetting.ts @@ -0,0 +1,7 @@ +import type Profile from "../profile.ts"; + +export interface ProfileUpdatedSettingEvent { + profile: Profile, + key: string, + value: string +} \ No newline at end of file diff --git a/src/server/profiles/manager.ts b/src/server/profiles/manager.ts index eec5e22..dcc45f1 100644 --- a/src/server/profiles/manager.ts +++ b/src/server/profiles/manager.ts @@ -16,6 +16,8 @@ class ProfileManagerBase extends ServerContentBase { #log = new Logging("ProfileManager"); + #logSettingChanges = false; + async #getUnusedId() { let id = Math.round(Math.random() * 2_147_483_647); if (await this.get(id)) id = await this.#getUnusedId(); @@ -78,7 +80,7 @@ class ProfileManagerBase extends ServerContentBase { async getAll() { const keys = this.kv.getKv().list({ prefix: [ ProfileManagerBase.profilesKey ] }); const awaitedKeys = await Array.fromAsync(keys); - return awaitedKeys.map(entry => entry.key).map(val => val[1]).filter(val => typeof val == 'number'); + return awaitedKeys.map(entry => entry.key).filter(key => key.length == 2).map(val => val[1]).filter(val => typeof val == 'number'); } async getByUsername(username: string) { @@ -88,9 +90,24 @@ class ProfileManagerBase extends ServerContentBase { } override start() { + this.server.on('profile.setting.update', ev => { + if (this.#logSettingChanges) this.#log.i(`Profile ${ev.profile} "${ev.profile.getUsername()}" settings update\n Key: "${ev.key}"\n Value: "${ev.value}"`); + }); + this.server.Commands.addRootCommand(new Command({ - key: ['account', 'profile', 'acc', 'prof'], + key: ['account', 'profile', 'acc', 'prof', "profiles"], subcommands: [ + new Command({ + key: ['settingevent', 'se', 'logsettings'], + exec: (val: boolean) => { + this.#logSettingChanges = val; + return val; + }, + zod: z.tuple([ + z.stringbool() + ]), + help: 'Log changes to profile settings', + }), new Command({ key: ['get', 'g', 'fetch', 'f'], exec: async (id: number) => { @@ -104,7 +121,7 @@ class ProfileManagerBase extends ServerContentBase { help: 'Fetch a profile: ' }), new Command({ - key: ['getall', 'listall', 'fetchall', 'all', 'a'], + key: ['getall', 'listall', 'fetchall', 'all', 'a', "list"], exec: async () => { const ids = await this.getAll(); return ids; @@ -132,10 +149,20 @@ class ProfileManagerBase extends ServerContentBase { else return await profile.setRole(role); }, zod: z.tuple([ - z.string().transform(Number), + z.coerce.number(), z.string() ]), - help: 'Set the profile role: ' + help: 'Set the profile role: ' + }), + new Command({ + key: ['settings', 'setting'], + exec: async (id: number) => { + const profile = await this.get(id); + if (profile) return await profile.Settings.getAllSettings(); + else return null; + }, + zod: z.tuple([z.coerce.number()]), + help: "Get player settings" }) ] })); diff --git a/src/server/profiles/profile.ts b/src/server/profiles/profile.ts index b7cc170..4496a0b 100644 --- a/src/server/profiles/profile.ts +++ b/src/server/profiles/profile.ts @@ -7,7 +7,9 @@ import { ProfileAvatarManager } from "./content/Avatar.ts"; import ProfileContentManager from "./content/base.ts"; import { ProfileMatchmakingManager } from "./content/Matchmaking.ts"; import { ProfileMessageManager } from "./content/Messages.ts"; +import { ProfileProgressionManager } from "./content/Progression.ts"; import { ProfileReputationManager } from "./content/Reputation.ts"; +import { ProfileRoomsManager } from "./content/Rooms.ts"; import { ProfileSettingsManager } from "./content/Settings.ts"; import { ProfileSubscriptionsManager } from "./content/Subscriptions.ts"; import ProfileManagerBase from "./manager.ts"; @@ -32,6 +34,8 @@ class Profile { Reputation: ProfileReputationManager; Subscriptions: ProfileSubscriptionsManager; Messages: ProfileMessageManager; + Rooms: ProfileRoomsManager; + Progression: ProfileProgressionManager; constructor(acc: SelfAccount, kv: KV, server: ServerBase) { this.#id = acc.accountId; @@ -39,12 +43,14 @@ class Profile { this.#kv = kv; this.#server = server; - this.Settings = new ProfileSettingsManager(this, this.#kv); - this.Avatar = new ProfileAvatarManager(this, this.#kv); - this.Matchmaking = new ProfileMatchmakingManager(this, this.#kv); - this.Reputation = new ProfileReputationManager(this, this.#kv); - this.Subscriptions = new ProfileSubscriptionsManager(this, this.#kv); - this.Messages = new ProfileMessageManager(this, this.#kv); + this.Settings = new ProfileSettingsManager(server, this, this.#kv); + this.Avatar = new ProfileAvatarManager(server, this, this.#kv); + this.Matchmaking = new ProfileMatchmakingManager(server, this, this.#kv); + this.Reputation = new ProfileReputationManager(server, this, this.#kv); + this.Subscriptions = new ProfileSubscriptionsManager(server, this, this.#kv); + this.Messages = new ProfileMessageManager(server, this, this.#kv); + this.Rooms = new ProfileRoomsManager(server, this, this.#kv); + this.Progression = new ProfileProgressionManager(server, this, this.#kv); } async #saveSelfAcc() { diff --git a/src/server/rooms/base.ts b/src/server/rooms/base.ts index 7e88731..ba25b7d 100644 --- a/src/server/rooms/base.ts +++ b/src/server/rooms/base.ts @@ -7,8 +7,9 @@ import { FactoryMode, HardwareSupports, RoomDataTypes, WriteMode } from "./inter import { AGRoom, AGRoomLocation, AGRoomRuntimeConfig } from "./internal/ClientRoomTypes.ts"; import Command from "../commands/command.ts"; import z from "zod"; -import { RoomLocation } from "../instances/base.ts"; +import { RoomLocation } from "../instances/types.ts"; +const roomIdSchema = z.coerce.number().min(1).max(Math.pow(2, 31)); export class ServerRoomsBase extends ServerContentBase { #subroomKv = new KV('subrooms', true); @@ -54,12 +55,29 @@ export class ServerRoomsBase extends ServerContentBase { }), new Command({ key: ["getplayerdorm", "playerdorm", "pd", "dorm"], - zod: z.tuple([z.coerce.number().min(1).max(Math.pow(2, 31))]), + zod: z.tuple([roomIdSchema]), exec: async (playerId: number) => { - const factory = await this.getPlayerDorm(this.server.Profiles.get(playerId)) + const profile = await this.server.Profiles.get(playerId); + if (!profile) return new Error("Profile does not exist"); + const factory = await this.getPlayerDorm(profile); + if (factory) return await factory.export(); + else return null; }, help: "Get the domroom information for a certain profile/player" - }) + }), + new Command({ + key: ['get', 'g'], + zod: z.tuple([roomIdSchema, z.boolean().optional()]), + exec: async (roomId: number, details?: boolean) => { + const factory = await this.get(roomId); + if (factory) { + if (details) return await factory.export(); + else return (await factory.export()).Room; + } + else return null; + }, + help: "Get room [details]" + }), ], })) } @@ -89,9 +107,9 @@ export class ServerRoomsBase extends ServerContentBase { } const supportsVRLow = room.Scenes.map(scene => locations.find(loc => loc.ReplicationId == scene.RoomSceneLocationId)) - .filter(val => typeof val !== 'undefined').some(loc => loc.SupportsVRLow) ?? false; + .filter(val => typeof val !== 'undefined').some(loc => loc.SupportsVRLow) ?? false; const supportsMobile = room.Scenes.map(scene => locations.find(loc => loc.ReplicationId == scene.RoomSceneLocationId)) - .filter(val => typeof val !== 'undefined').some(loc => loc.SupportsMobile) ?? false; + .filter(val => typeof val !== 'undefined').some(loc => loc.SupportsMobile) ?? false; roomFactory.Name = room.Name; roomFactory.Description = room.Description; @@ -150,10 +168,10 @@ export class ServerRoomsBase extends ServerContentBase { } async getIdFromName(name: string) { - const id = await this.kv.getKv().get([ServerRoomsBase.roomNamesKey, name]); - if (id.value == null) return null; - return id.value; - } + const id = await this.kv.getKv().get([ServerRoomsBase.roomNamesKey, name]); + if (id.value == null) return null; + return id.value; + } async getPlayerDorm(profile: Profile) { const id = await this.kv.getKv().get([ServerRoomsBase.playerDormsKey, profile.getId()]); @@ -178,21 +196,24 @@ export class ServerRoomsBase extends ServerContentBase { }); await roomFactory.addHardwareSupport(...HardwareSupports); - roomFactory.setTags(new Set(["dormroom"])); const subroomFactory = await roomFactory.newSubroom(); - subroomFactory.CanMatchmakeInto = true; - subroomFactory.IsSandbox = true; - subroomFactory.MaxPlayers = 4; - subroomFactory.Name = "Home"; - subroomFactory.RoomId = roomFactory.getRoomId(); - subroomFactory.RoomSceneLocationId = RoomLocation.DormRoom; + subroomFactory.setSubroomProperties({ + CanMatchmakeInto: true, + IsSandbox: true, + MaxPlayers: 4, + Name: "Home", + RoomId: roomFactory.getRoomId(), + RoomSceneLocationId: RoomLocation.DormRoom + }); subroomFactory.addSave(""); roomFactory.addSubroom(subroomFactory.RoomSceneId); await subroomFactory.write(); await roomFactory.write(); + await profile.Rooms.addRoom(roomFactory.getRoomId()); + return roomFactory; } else return await new RoomFactory(this.server, this.kv).init({ mode: FactoryMode.Fetch, id: id.value }); diff --git a/src/server/rooms/internal/RoomDataTypes.ts b/src/server/rooms/internal/RoomDataTypes.ts index 1c9987c..76257b5 100644 --- a/src/server/rooms/internal/RoomDataTypes.ts +++ b/src/server/rooms/internal/RoomDataTypes.ts @@ -149,14 +149,16 @@ export interface DatabaseRoom { Room: DatabaseRoomContent } -export interface DatabaseSubroom { +export interface SubroomProps { RoomId: number, RoomSceneLocationId: string, Name: string, IsSandbox: boolean, MaxPlayers: number, CanMatchmakeInto: boolean, - LatestSaveId: number | null, +} +export interface DatabaseSubroom extends SubroomProps { + LatestSaveId: number | null } export type RoomSaveMap = Map diff --git a/src/server/rooms/internal/RoomFactory.ts b/src/server/rooms/internal/RoomFactory.ts index 2ab1f38..cbb03f9 100644 --- a/src/server/rooms/internal/RoomFactory.ts +++ b/src/server/rooms/internal/RoomFactory.ts @@ -133,6 +133,7 @@ export class RoomFactory { this.#kv.getKv().set([RoomFactory.roomsKey, this.#roomId, 'meta'], this.#obj), this.#kv.getKv().set([RoomFactory.roomsKey, this.#roomId, 'hardware'], this.#hardwareSupport), this.#kv.getKv().set([RoomFactory.roomsKey, this.#roomId, 'tags'], this.#tags), + this.#kv.getKv().set([RoomFactory.roomsKey, this.#roomId, "subrooms"], this.#subrooms) ]); if (!this.IsDormRoom) this.#kv.getKv().set([ServerRoomsBase.roomNamesKey, this.Name], this.#roomId); diff --git a/src/server/rooms/internal/SubroomFactory.ts b/src/server/rooms/internal/SubroomFactory.ts index 8cec311..8c076cb 100644 --- a/src/server/rooms/internal/SubroomFactory.ts +++ b/src/server/rooms/internal/SubroomFactory.ts @@ -83,6 +83,10 @@ export class SubroomFactory { } } + setSubroomProperties(props: RoomDataTypes.SubroomProps) { + Object.assign(this, props); + } + get RoomId() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.RoomId; } set RoomId(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else this.#obj.RoomId = data } diff --git a/src/server/server.ts b/src/server/server.ts index 4ffc3cc..7eb2e7a 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -1,3 +1,4 @@ +import { getNetConfig } from "../net.ts"; import { ServerUpdateEvent } from "../serverevents.ts"; import { AvatarContentBase } from "./avatars/base.ts"; import { EventManager } from "./baseevent.ts"; @@ -5,9 +6,11 @@ import { CommandsBase } from "./commands/commands.ts"; import { ServerContentManager } from "./content/base.ts"; import GameConfigsBase from "./gameconfigs/base.ts"; import { InstanceManager } from "./instances/base.ts"; +import { Objective, ObjectiveType } from "./objectives/base.ts"; import { PlatformsManager } from "./platforms/base.ts"; import { type PresenceUpdateEvent } from "./presence/events/PresenceUpdateEvent.ts"; import { type ProfileUpdateEvent } from "./profiles/events/ProfileUpdate.ts"; +import { ProfileUpdatedSettingEvent } from "./profiles/events/ProfileUpdatedSetting.ts"; import { type RoleUpdateEvent } from "./profiles/events/RoleUpdate.ts"; import ProfileManagerBase from "./profiles/manager.ts"; import { ServerRoomsBase } from "./rooms/base.ts"; @@ -16,6 +19,7 @@ import { RoomUpdatedEvent, SubroomUpdatedEvent } from "./rooms/internal/RoomEven interface ServerEvents { 'profile.roleupdate': RoleUpdateEvent, 'profile.update': ProfileUpdateEvent, + 'profile.setting.update': ProfileUpdatedSettingEvent, 'presence.update': PresenceUpdateEvent, 'server.start': ServerUpdateEvent, 'server.destroy': ServerUpdateEvent, @@ -23,6 +27,31 @@ interface ServerEvents { 'room.subroom.updated': SubroomUpdatedEvent } +export interface LevelProgressionItem { + Level: number; + RequiredXp: number; +}; +interface AutoMicMutingConfig { + MicSpamVolumeThreshold: number; + MicVolumeSampleInterval: number; + MicVolumeSampleRollingWindowLength: number; + MicSpamSamplePercentageForWarning: number; + MicSpamSamplePercentageForWarningToEnd: number; + MicSpamSamplePercentageForForceMute: number; + MicSpamSamplePercentageForForceMuteToEnd: number; + MicSpamWarningStateVolumeMultiplier: number; +}; + +export type PublicConfig = { + ShareBaseUrl: string; + ServerMaintenance: { + StartsInMinutes: number; + }; + LevelProgressionMaps: LevelProgressionItem[]; + DailyObjectives: Objective[][]; + AutoMicMutingConfig: AutoMicMutingConfig; +}; + class ServerBase extends EventManager { Profiles = new ProfileManagerBase(this, 'profiles', true); GameConfigs = new GameConfigsBase(this, 'gameconfigs', true); @@ -36,6 +65,78 @@ class ServerBase extends EventManager { generateMask(...num: number[]) { return num.reduce((sum, val) => sum + val, 0); } + + getPublicConfig() { + const netConfig = getNetConfig(); + + function generateLevelProgressionMap() { + const m: LevelProgressionItem[] = []; + for (let i = 0; i < 31; i++) { + m.push({ + Level: i, + RequiredXp: Math.round(i * 1 * 20), + }); + } + return m; + } + + const conf: PublicConfig = { + ServerMaintenance: { + StartsInMinutes: 0, + }, + LevelProgressionMaps: generateLevelProgressionMap(), + DailyObjectives: [ + [ + { type: ObjectiveType.Default, score: 0 }, + { type: ObjectiveType.GoToRecCenter, score: 0 }, + { type: ObjectiveType.Default, score: 0 } + ], + [ + { type: ObjectiveType.Default, score: 0 }, + { type: ObjectiveType.LevelUp, score: 0 }, + { type: ObjectiveType.Default, score: 0 } + ], + [ + { type: ObjectiveType.Default, score: 0 }, + { type: ObjectiveType.SaveOutfitSlot, score: 0 }, + { type: ObjectiveType.Default, score: 0 } + ], + [ + { type: ObjectiveType.Default, score: 0 }, + { type: ObjectiveType.ScoreBasketInRecCenter, score: 0 }, + { type: ObjectiveType.Default, score: 0 } + ], + [ + { type: ObjectiveType.Default, score: 0 }, + { type: ObjectiveType.VisitACustomRoom, score: 0 }, + { type: ObjectiveType.Default, score: 0 } + ], + [ + { type: ObjectiveType.Default, score: 0 }, + { type: ObjectiveType.SubscribeToPlayer, score: 0 }, + { type: ObjectiveType.Default, score: 0 } + ], + [ + { type: ObjectiveType.Default, score: 0 }, + { type: ObjectiveType.AddAFriend, score: 0 }, + { type: ObjectiveType.Default, score: 0 } + ] + ], + AutoMicMutingConfig: { + MicSpamVolumeThreshold: 1.125, + MicVolumeSampleInterval: 0.25, + MicVolumeSampleRollingWindowLength: 7.0, + MicSpamSamplePercentageForWarning: 0.8, + MicSpamSamplePercentageForWarningToEnd: 0.2, + MicSpamSamplePercentageForForceMute: 0.8, + MicSpamSamplePercentageForForceMuteToEnd: 0.2, + MicSpamWarningStateVolumeMultiplier: 0.25 + }, + ShareBaseUrl: `${netConfig.securePublicHost ? 'https' : 'http'}://${netConfig.publicHost}/{0}` // {0} is replaced by the game + }; + + return conf; + } } const Server = new ServerBase(); diff --git a/src/util/validators.ts b/src/util/validators.ts index 7d7183b..e7a08dc 100644 --- a/src/util/validators.ts +++ b/src/util/validators.ts @@ -23,6 +23,9 @@ export const typedZValidator = < >; }; +/** + * @deprecated Use transformCheckEnum after #.coerce.number() (optionally) + */ export const transformStringToEnum = (anEnum: { [s: string]: string | number }, str?: boolean) => { return (arg: string, ctx: z.RefinementCtx) => { if (!str) { @@ -36,4 +39,13 @@ export const transformStringToEnum = (anEnum: { [s: string]: string | number else ctx.addIssue("string was not a valid enum member"); } } +} + +export const transformCheckEnum = (anEnum: { [s: string]: string | number }) => { + return (arg: number | string, ctx: z.RefinementCtx) => { + if (typeof anEnum[arg] == 'undefined') { + ctx.addIssue("Not an enum member"); + return null; + } else return anEnum[arg] as T; + } } \ No newline at end of file