rooooms are mostly dooone, misc routes, instance management stubs, and matchmaking base

This commit is contained in:
2025-09-07 17:28:13 -04:00
parent 2aa5352350
commit eef3667618
36 changed files with 627 additions and 122 deletions

View File

@@ -148,8 +148,7 @@ const server = Deno.serve({
`${typeof res.status == 'number' ? res.status : "SENT STACK TRACE"}: ${req.method} ${getFullPathFromUrl(new URL(req.url))}`, `${typeof res.status == 'number' ? res.status : "SENT STACK TRACE"}: ${req.method} ${getFullPathFromUrl(new URL(req.url))}`,
formatHeader(req.headers, 'Content-Type'), formatHeader(req.headers, 'Content-Type'),
formatHeader(req.headers, 'Connection'), formatHeader(req.headers, 'Connection'),
formatHeader(req.headers, 'User-Agent'), formatHeader(req.headers, 'User-Agent')
formatHeader(req.headers, 'Accept-Encoding')
]); ]);
if (!logBlacklist.includes(url.pathname)) { if (!logBlacklist.includes(url.pathname)) {
if (res.status === 404) log.e(netlog); if (res.status === 404) log.e(netlog);

View File

@@ -1,3 +1,4 @@
import Server from "../../../server/server.ts";
import { createHonoRoute } from "../../../util/import.ts"; import { createHonoRoute } from "../../../util/import.ts";
export const route = createHonoRoute("/config"); export const route = createHonoRoute("/config");
@@ -5,3 +6,7 @@ export const route = createHonoRoute("/config");
route.app.get('/v1/amplitude', c => { route.app.get('/v1/amplitude', c => {
return c.json({AmplitudeKey: ""}); return c.json({AmplitudeKey: ""});
}); });
route.app.get('/v2', c => {
return c.json(Server.getPublicConfig());
});

View File

@@ -1,7 +1,8 @@
import { authenticate } from "../../../util/api.ts";
import { createHonoRoute } from "../../../util/import.ts"; import { createHonoRoute } from "../../../util/import.ts";
export const route = createHonoRoute("/messages"); 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()); return c.json(await c.get('profile').Messages.getMessages());
}); });

View File

@@ -9,21 +9,26 @@ export const route = createHonoRoute("/playerReputation");
const getRepIdParamSchema = z.object({ const getRepIdParamSchema = z.object({
id: z.coerce.number().min(0).max(2_147_483_647) 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 { id } = c.req.valid('param');
const prof = await Server.Profiles.get(id); const prof = await Server.Profiles.get(id);
if (!prof) return c.status(404); if (!prof) return c.status(404);
else return c.json({ else return c.json(await prof.Reputation.export());
AccountId: id, });
Noteriety: 0.0,
CheerGeneral: 0, const getRepBulkBodySchema = z.object({
CheerHelpful: 0, Ids: z.array(z.coerce.number())
CheerGreatHost: 0, });
CheerSportsman: 0, route.app.post('/v1/bulk', authenticate, typedZValidator('form', getRepBulkBodySchema), async c => {
CheerCreative: 0, const ids = c.req.valid('form').Ids;
CheerCredit: 0,
SubscriberCount: 0, if (typeof ids == 'object') {
SubscribedCount: 0, 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()]);
}
}); });

View File

@@ -2,6 +2,7 @@ import z from "zod";
import { createHonoRoute } from "../../../util/import.ts"; import { createHonoRoute } from "../../../util/import.ts";
import { authenticate } from "../../../util/api.ts"; import { authenticate } from "../../../util/api.ts";
import { typedZValidator } from "../../../util/validators.ts"; import { typedZValidator } from "../../../util/validators.ts";
import Server from "../../../server/server.ts";
export const route = createHonoRoute("/players"); export const route = createHonoRoute("/players");
@@ -15,3 +16,19 @@ const getProgParamSchema = z.object({
route.app.get('/v1/progression/:id', authenticate, typedZValidator('param', getProgParamSchema), async c => { route.app.get('/v1/progression/:id', authenticate, typedZValidator('param', getProgParamSchema), async c => {
return c.json(await c.get('profile').Reputation.export()); 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()]);
}
});

View File

@@ -0,0 +1,7 @@
import { createHonoRoute } from "../../../util/import.ts";
export const route = createHonoRoute("/quickPlay");
route.app.get('/v1/getandclear', c => {
return c.json({});
});

View File

@@ -1,3 +1,5 @@
import { HTTPStatus } from "@oneday/http-status";
import { statusResponse } from "../../../util/api.ts";
import { createHonoRoute } from "../../../util/import.ts"; import { createHonoRoute } from "../../../util/import.ts";
export const route = createHonoRoute('/relationships'); export const route = createHonoRoute('/relationships');
@@ -5,3 +7,8 @@ export const route = createHonoRoute('/relationships');
route.app.get('/v2/get', c => { route.app.get('/v2/get', c => {
return c.json([]); return c.json([]);
}); });
// deno-lint-ignore require-await
route.app.post('/v1/bulkignoreplatformusers', async c => {
return statusResponse(c, HTTPStatus.OK);
});

View File

@@ -1,9 +1,10 @@
import z from "zod"; import z from "zod";
import { authenticate } from "../../../util/api.ts"; import { authenticate, statusResponse } from "../../../util/api.ts";
import { createHonoRoute } from "../../../util/import.ts"; import { createHonoRoute } from "../../../util/import.ts";
import { typedZValidator } from "../../../util/validators.ts"; import { typedZValidator } from "../../../util/validators.ts";
import { HonoEnv } from "../../../util/types.ts"; import { HonoEnv } from "../../../util/types.ts";
import { Context } from "@hono/hono"; import { Context } from "@hono/hono";
import { HTTPStatus } from "@oneday/http-status";
export const route = createHonoRoute('/settings'); 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'); const { Key, Value } = c.req.valid('json');
await c.get('profile').Settings.setSetting(Key, Value); await c.get('profile').Settings.setSetting(Key, Value);
return c.status(200); return statusResponse(c, HTTPStatus.OK);
}); });

View File

@@ -1,7 +1,50 @@
import { createHonoRoute, routeImporter } from "../../util/import.ts"; 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'); export const route = createHonoRoute('/match');
const loginLockBodySchema = z.object({
LoginLock: z.uuidv4()
});
export const loginLockMiddleware = async (c: Context<HonoEnv>, 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/', [ await routeImporter(route.app, 'src/routes/match/', [
'routes' 'routes'
]); ]);

View File

@@ -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);
});

View File

@@ -1,10 +1,20 @@
import { authenticate } from "../../../util/api.ts"; import { authenticate, statusResponse } from "../../../util/api.ts";
import { createHonoRoute } from "../../../util/import.ts"; import { createHonoRoute } from "../../../util/import.ts";
import { loginLockMiddleware } from "../root.ts";
import { HTTPStatus } from "@oneday/http-status";
export const route = createHonoRoute("/player"); export const route = createHonoRoute("/player");
route.app.use(authenticate); route.app.use(authenticate);
route.app.post('/login', _c => { route.app.post('/login', authenticate, loginLockMiddleware, async c => {
return new Response("OK", { status: 200 });
});
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
}); });

View File

@@ -5,19 +5,19 @@ export class EventManager<Events extends { [K in keyof Events]: unknown }> {
[K in keyof Events]?: Set<Callback<Events[K]>> [K in keyof Events]?: Set<Callback<Events[K]>>
} = {}; } = {};
on<K extends keyof Events>(eventName: K, callback: Callback<Events[K]>): void { on<K extends keyof Events>(eventName: K, cb: Callback<Events[K]>): void {
if (!this.#listeners[eventName]) if (!this.#listeners[eventName])
this.#listeners[eventName] = new Set(); this.#listeners[eventName] = new Set();
this.#listeners[eventName]!.add(callback); this.#listeners[eventName]!.add(cb);
} }
off<K extends keyof Events>(eventName: K, callback: Callback<Events[K]>): void { off<K extends keyof Events>(eventName: K, cb: Callback<Events[K]>): void {
this.#listeners[eventName]?.delete(callback); this.#listeners[eventName]?.delete(cb);
if (this.#listeners[eventName]?.size === 0) if (this.#listeners[eventName]?.size === 0)
delete this.#listeners[eventName]; delete this.#listeners[eventName];
} }
emit<K extends keyof Events>(eventName: K, payload: Events[K]): void { emit<K extends keyof Events>(eventName: K, payload: Events[K]): void {
this.#listeners[eventName]?.forEach((callback) => callback(payload)); this.#listeners[eventName]?.forEach(cb => cb(payload));
} }
} }

View File

@@ -1,6 +1,6 @@
import { CloudRegionCode } from "../../util/photon.ts"; import { CloudRegionCode } from "../../util/photon.ts";
import type Profile from "../profiles/profile.ts"; import type Profile from "../profiles/profile.ts";
import { RoomInstance, RoomLocation } from "./base.ts"; import { RoomInstance, RoomLocation } from "./types.ts";
export interface InstanceCreationOptions { export interface InstanceCreationOptions {
roomId: number, roomId: number,

View File

@@ -1,63 +1,47 @@
import Logging from "@proxnet/undead-logging";
import { ServerContentBase } from "../ContentBase.ts"; import { ServerContentBase } from "../ContentBase.ts";
import { type Instance } from "./Instance.ts";
export enum RoomLocation { const log = new Logging("Instances");
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
}
export class InstanceManager extends ServerContentBase { export class InstanceManager extends ServerContentBase {
#instances: Set<Instance> = 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) {
}
} }

View File

@@ -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;
}

View File

@@ -0,0 +1,7 @@
import { ServerContentBase } from "../ContentBase.ts";
export class ServerMatchmakingBase extends ServerContentBase {
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -1,7 +1,7 @@
import z from "zod"; import z from "zod";
import Command from "../commands/command.ts"; import Command from "../commands/command.ts";
import { ServerContentBase } from "../ContentBase.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 { sign } from "@hono/hono/jwt";
import { CachedLogin, DbCachedLogin, PlatformMask, PlatformType, TokenFormat, TokenType } from "./types.ts"; 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); return await this.getCachedLogins(type, platformId, false);
}, },
zod: z.tuple([ zod: z.tuple([
z.string().transform(transformStringToEnum<PlatformType>(PlatformType)), z.coerce.number().transform(transformCheckEnum<PlatformType>(PlatformType)),
z.string().min(4) z.string().min(4)
]), ]),
help: 'List all cachedlogins for platformId: <type: PlatformType, platformId: string>' help: 'List all cachedlogins for platformId: <type: PlatformType, platformId: string>'

View File

@@ -13,7 +13,6 @@ export interface TokenFormat extends TokenFormatBase {
export enum ProfileRole { export enum ProfileRole {
Developer = 'developer', Developer = 'developer',
Moderator = 'moderator',
Web = 'webClient', Web = 'webClient',
Game = 'gameClient' Game = 'gameClient'
} }

View File

@@ -3,7 +3,7 @@ import { ServerContentBase } from "../ContentBase.ts";
import { DeviceClass } from "../platforms/types.ts"; import { DeviceClass } from "../platforms/types.ts";
import Profile from "../profiles/profile.ts"; import Profile from "../profiles/profile.ts";
import { type ServerBase } from "../server.ts"; import { type ServerBase } from "../server.ts";
import { RoomInstance } from "../instances/base.ts"; import { RoomInstance } from "../instances/types.ts";
export enum VRMovementMode { export enum VRMovementMode {
TELEPORT, TELEPORT,

View File

@@ -15,11 +15,8 @@ export class ProfileMatchmakingManager extends ProfileContentManager {
async setLoginLock(lock: string) { async setLoginLock(lock: string) {
await this.kv.getKv().set(this.#deviceClassKey, lock); await this.kv.getKv().set(this.#deviceClassKey, lock);
} }
async getLoginLock(): Promise<string> { async getLoginLock(): Promise<string | null> {
return (await this.kv.getKv().get<string>(this.#loginLockKey)).value || ""; return (await this.kv.getKv().get<string>(this.#loginLockKey)).value;
}
async hasLoginLock() {
return (await this.kv.getKv().get<string>(this.#loginLockKey)).value ? true : false;
} }
#lastSeen = this.profile.constructProfilePropertyKey('lastseen'); #lastSeen = this.profile.constructProfilePropertyKey('lastseen');

View File

@@ -37,6 +37,7 @@ interface MessageBase {
export class ProfileMessageManager extends ProfileContentManager { export class ProfileMessageManager extends ProfileContentManager {
// temp
// deno-lint-ignore require-await // deno-lint-ignore require-await
async getMessages() { async getMessages() {
return []; return [];

View File

@@ -2,4 +2,13 @@ import ProfileContentManager from "./base.ts";
export class ProfileProgressionManager extends ProfileContentManager { 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: } // do this soon:tm:

View File

@@ -0,0 +1,23 @@
import ProfileContentManager from "./base.ts";
export class ProfileRoomsManager extends ProfileContentManager {
#roomsKey = this.profile.constructProfilePropertyKey('rooms');
#rooms: Set<number> = 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;
}
}

View File

@@ -40,6 +40,7 @@ export class ProfileSettingsManager extends ProfileContentManager {
if (!s) settings.push({ Key, Value }); if (!s) settings.push({ Key, Value });
else s.Value = Value; else s.Value = Value;
await this.#updateSettings(settings); await this.#updateSettings(settings);
this.server.emit('profile.setting.update', { profile: this.profile, key: Key, value: Value });
} }
} }

View File

@@ -1,13 +1,16 @@
import type KV from "../../persistence/kv.ts"; import type KV from "../../persistence/kv.ts";
import { type ServerBase } from "../../server.ts";
import type Profile from "../profile.ts"; import type Profile from "../profile.ts";
class ProfileContentManager { class ProfileContentManager {
protected profile: Profile; protected profile: Profile;
protected kv: KV; protected kv: KV;
constructor(profile: Profile, kv: KV) { protected server: ServerBase
constructor(server: ServerBase, profile: Profile, kv: KV) {
this.profile = profile; this.profile = profile;
this.kv = kv; this.kv = kv;
this.server = server;
profile.managers.push(this); profile.managers.push(this);
} }

View File

@@ -0,0 +1,7 @@
import type Profile from "../profile.ts";
export interface ProfileUpdatedSettingEvent {
profile: Profile,
key: string,
value: string
}

View File

@@ -16,6 +16,8 @@ class ProfileManagerBase extends ServerContentBase {
#log = new Logging("ProfileManager"); #log = new Logging("ProfileManager");
#logSettingChanges = false;
async #getUnusedId() { async #getUnusedId() {
let id = Math.round(Math.random() * 2_147_483_647); let id = Math.round(Math.random() * 2_147_483_647);
if (await this.get(id)) id = await this.#getUnusedId(); if (await this.get(id)) id = await this.#getUnusedId();
@@ -78,7 +80,7 @@ class ProfileManagerBase extends ServerContentBase {
async getAll() { async getAll() {
const keys = this.kv.getKv().list({ prefix: [ ProfileManagerBase.profilesKey ] }); const keys = this.kv.getKv().list({ prefix: [ ProfileManagerBase.profilesKey ] });
const awaitedKeys = await Array.fromAsync(keys); 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) { async getByUsername(username: string) {
@@ -88,9 +90,24 @@ class ProfileManagerBase extends ServerContentBase {
} }
override start() { 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({ this.server.Commands.addRootCommand(new Command({
key: ['account', 'profile', 'acc', 'prof'], key: ['account', 'profile', 'acc', 'prof', "profiles"],
subcommands: [ 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({ new Command({
key: ['get', 'g', 'fetch', 'f'], key: ['get', 'g', 'fetch', 'f'],
exec: async (id: number) => { exec: async (id: number) => {
@@ -104,7 +121,7 @@ class ProfileManagerBase extends ServerContentBase {
help: 'Fetch a profile: <id: number>' help: 'Fetch a profile: <id: number>'
}), }),
new Command({ new Command({
key: ['getall', 'listall', 'fetchall', 'all', 'a'], key: ['getall', 'listall', 'fetchall', 'all', 'a', "list"],
exec: async () => { exec: async () => {
const ids = await this.getAll(); const ids = await this.getAll();
return ids; return ids;
@@ -132,10 +149,20 @@ class ProfileManagerBase extends ServerContentBase {
else return await profile.setRole(role); else return await profile.setRole(role);
}, },
zod: z.tuple([ zod: z.tuple([
z.string().transform(Number), z.coerce.number(),
z.string() z.string()
]), ]),
help: 'Set the profile role: <id: number, role: "developer" | "moderator" | "screenshare" | "user">' help: 'Set the profile role: <id: number, role: "gameClient" | "webClient" | "developer">'
}),
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"
}) })
] ]
})); }));

View File

@@ -7,7 +7,9 @@ import { ProfileAvatarManager } from "./content/Avatar.ts";
import ProfileContentManager from "./content/base.ts"; import ProfileContentManager from "./content/base.ts";
import { ProfileMatchmakingManager } from "./content/Matchmaking.ts"; import { ProfileMatchmakingManager } from "./content/Matchmaking.ts";
import { ProfileMessageManager } from "./content/Messages.ts"; import { ProfileMessageManager } from "./content/Messages.ts";
import { ProfileProgressionManager } from "./content/Progression.ts";
import { ProfileReputationManager } from "./content/Reputation.ts"; import { ProfileReputationManager } from "./content/Reputation.ts";
import { ProfileRoomsManager } from "./content/Rooms.ts";
import { ProfileSettingsManager } from "./content/Settings.ts"; import { ProfileSettingsManager } from "./content/Settings.ts";
import { ProfileSubscriptionsManager } from "./content/Subscriptions.ts"; import { ProfileSubscriptionsManager } from "./content/Subscriptions.ts";
import ProfileManagerBase from "./manager.ts"; import ProfileManagerBase from "./manager.ts";
@@ -32,6 +34,8 @@ class Profile {
Reputation: ProfileReputationManager; Reputation: ProfileReputationManager;
Subscriptions: ProfileSubscriptionsManager; Subscriptions: ProfileSubscriptionsManager;
Messages: ProfileMessageManager; Messages: ProfileMessageManager;
Rooms: ProfileRoomsManager;
Progression: ProfileProgressionManager;
constructor(acc: SelfAccount, kv: KV, server: ServerBase) { constructor(acc: SelfAccount, kv: KV, server: ServerBase) {
this.#id = acc.accountId; this.#id = acc.accountId;
@@ -39,12 +43,14 @@ class Profile {
this.#kv = kv; this.#kv = kv;
this.#server = server; this.#server = server;
this.Settings = new ProfileSettingsManager(this, this.#kv); this.Settings = new ProfileSettingsManager(server, this, this.#kv);
this.Avatar = new ProfileAvatarManager(this, this.#kv); this.Avatar = new ProfileAvatarManager(server, this, this.#kv);
this.Matchmaking = new ProfileMatchmakingManager(this, this.#kv); this.Matchmaking = new ProfileMatchmakingManager(server, this, this.#kv);
this.Reputation = new ProfileReputationManager(this, this.#kv); this.Reputation = new ProfileReputationManager(server, this, this.#kv);
this.Subscriptions = new ProfileSubscriptionsManager(this, this.#kv); this.Subscriptions = new ProfileSubscriptionsManager(server, this, this.#kv);
this.Messages = new ProfileMessageManager(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() { async #saveSelfAcc() {

View File

@@ -7,8 +7,9 @@ import { FactoryMode, HardwareSupports, RoomDataTypes, WriteMode } from "./inter
import { AGRoom, AGRoomLocation, AGRoomRuntimeConfig } from "./internal/ClientRoomTypes.ts"; import { AGRoom, AGRoomLocation, AGRoomRuntimeConfig } from "./internal/ClientRoomTypes.ts";
import Command from "../commands/command.ts"; import Command from "../commands/command.ts";
import z from "zod"; 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 { export class ServerRoomsBase extends ServerContentBase {
#subroomKv = new KV('subrooms', true); #subroomKv = new KV('subrooms', true);
@@ -54,12 +55,29 @@ export class ServerRoomsBase extends ServerContentBase {
}), }),
new Command({ new Command({
key: ["getplayerdorm", "playerdorm", "pd", "dorm"], 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) => { 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" 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]"
}),
], ],
})) }))
} }
@@ -178,21 +196,24 @@ export class ServerRoomsBase extends ServerContentBase {
}); });
await roomFactory.addHardwareSupport(...HardwareSupports); await roomFactory.addHardwareSupport(...HardwareSupports);
roomFactory.setTags(new Set(["dormroom"]));
const subroomFactory = await roomFactory.newSubroom(); const subroomFactory = await roomFactory.newSubroom();
subroomFactory.CanMatchmakeInto = true; subroomFactory.setSubroomProperties({
subroomFactory.IsSandbox = true; CanMatchmakeInto: true,
subroomFactory.MaxPlayers = 4; IsSandbox: true,
subroomFactory.Name = "Home"; MaxPlayers: 4,
subroomFactory.RoomId = roomFactory.getRoomId(); Name: "Home",
subroomFactory.RoomSceneLocationId = RoomLocation.DormRoom; RoomId: roomFactory.getRoomId(),
RoomSceneLocationId: RoomLocation.DormRoom
});
subroomFactory.addSave(""); subroomFactory.addSave("");
roomFactory.addSubroom(subroomFactory.RoomSceneId); roomFactory.addSubroom(subroomFactory.RoomSceneId);
await subroomFactory.write(); await subroomFactory.write();
await roomFactory.write(); await roomFactory.write();
await profile.Rooms.addRoom(roomFactory.getRoomId());
return roomFactory; return roomFactory;
} }
else return await new RoomFactory(this.server, this.kv).init({ mode: FactoryMode.Fetch, id: id.value }); else return await new RoomFactory(this.server, this.kv).init({ mode: FactoryMode.Fetch, id: id.value });

View File

@@ -149,14 +149,16 @@ export interface DatabaseRoom {
Room: DatabaseRoomContent Room: DatabaseRoomContent
} }
export interface DatabaseSubroom { export interface SubroomProps {
RoomId: number, RoomId: number,
RoomSceneLocationId: string, RoomSceneLocationId: string,
Name: string, Name: string,
IsSandbox: boolean, IsSandbox: boolean,
MaxPlayers: number, MaxPlayers: number,
CanMatchmakeInto: boolean, CanMatchmakeInto: boolean,
LatestSaveId: number | null, }
export interface DatabaseSubroom extends SubroomProps {
LatestSaveId: number | null
} }
export type RoomSaveMap = Map<number, RoomSave> export type RoomSaveMap = Map<number, RoomSave>

View File

@@ -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, 'meta'], this.#obj),
this.#kv.getKv().set([RoomFactory.roomsKey, this.#roomId, 'hardware'], this.#hardwareSupport), 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, '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); if (!this.IsDormRoom) this.#kv.getKv().set([ServerRoomsBase.roomNamesKey, this.Name], this.#roomId);

View File

@@ -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; } 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 } set RoomId(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else this.#obj.RoomId = data }

View File

@@ -1,3 +1,4 @@
import { getNetConfig } from "../net.ts";
import { ServerUpdateEvent } from "../serverevents.ts"; import { ServerUpdateEvent } from "../serverevents.ts";
import { AvatarContentBase } from "./avatars/base.ts"; import { AvatarContentBase } from "./avatars/base.ts";
import { EventManager } from "./baseevent.ts"; import { EventManager } from "./baseevent.ts";
@@ -5,9 +6,11 @@ import { CommandsBase } from "./commands/commands.ts";
import { ServerContentManager } from "./content/base.ts"; import { ServerContentManager } from "./content/base.ts";
import GameConfigsBase from "./gameconfigs/base.ts"; import GameConfigsBase from "./gameconfigs/base.ts";
import { InstanceManager } from "./instances/base.ts"; import { InstanceManager } from "./instances/base.ts";
import { Objective, ObjectiveType } from "./objectives/base.ts";
import { PlatformsManager } from "./platforms/base.ts"; import { PlatformsManager } from "./platforms/base.ts";
import { type PresenceUpdateEvent } from "./presence/events/PresenceUpdateEvent.ts"; import { type PresenceUpdateEvent } from "./presence/events/PresenceUpdateEvent.ts";
import { type ProfileUpdateEvent } from "./profiles/events/ProfileUpdate.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 { type RoleUpdateEvent } from "./profiles/events/RoleUpdate.ts";
import ProfileManagerBase from "./profiles/manager.ts"; import ProfileManagerBase from "./profiles/manager.ts";
import { ServerRoomsBase } from "./rooms/base.ts"; import { ServerRoomsBase } from "./rooms/base.ts";
@@ -16,6 +19,7 @@ import { RoomUpdatedEvent, SubroomUpdatedEvent } from "./rooms/internal/RoomEven
interface ServerEvents { interface ServerEvents {
'profile.roleupdate': RoleUpdateEvent, 'profile.roleupdate': RoleUpdateEvent,
'profile.update': ProfileUpdateEvent, 'profile.update': ProfileUpdateEvent,
'profile.setting.update': ProfileUpdatedSettingEvent,
'presence.update': PresenceUpdateEvent, 'presence.update': PresenceUpdateEvent,
'server.start': ServerUpdateEvent, 'server.start': ServerUpdateEvent,
'server.destroy': ServerUpdateEvent, 'server.destroy': ServerUpdateEvent,
@@ -23,6 +27,31 @@ interface ServerEvents {
'room.subroom.updated': SubroomUpdatedEvent '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<ServerEvents> { class ServerBase extends EventManager<ServerEvents> {
Profiles = new ProfileManagerBase(this, 'profiles', true); Profiles = new ProfileManagerBase(this, 'profiles', true);
GameConfigs = new GameConfigsBase(this, 'gameconfigs', true); GameConfigs = new GameConfigsBase(this, 'gameconfigs', true);
@@ -36,6 +65,78 @@ class ServerBase extends EventManager<ServerEvents> {
generateMask(...num: number[]) { generateMask(...num: number[]) {
return num.reduce((sum, val) => sum + val, 0); 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(); const Server = new ServerBase();

View File

@@ -23,6 +23,9 @@ export const typedZValidator = <
>; >;
}; };
/**
* @deprecated Use transformCheckEnum after #.coerce.number() (optionally)
*/
export const transformStringToEnum = <T>(anEnum: { [s: string]: string | number }, str?: boolean) => { export const transformStringToEnum = <T>(anEnum: { [s: string]: string | number }, str?: boolean) => {
return (arg: string, ctx: z.RefinementCtx<string | number>) => { return (arg: string, ctx: z.RefinementCtx<string | number>) => {
if (!str) { if (!str) {
@@ -37,3 +40,12 @@ export const transformStringToEnum = <T>(anEnum: { [s: string]: string | number
} }
} }
} }
export const transformCheckEnum = <T>(anEnum: { [s: string]: string | number }) => {
return (arg: number | string, ctx: z.RefinementCtx<number | string>) => {
if (typeof anEnum[arg] == 'undefined') {
ctx.addIssue("Not an enum member");
return null;
} else return anEnum[arg] as T;
}
}