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

@@ -5,19 +5,19 @@ export class EventManager<Events extends { [K in keyof Events]: unknown }> {
[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])
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 {
this.#listeners[eventName]?.delete(callback);
off<K extends keyof Events>(eventName: K, cb: Callback<Events[K]>): void {
this.#listeners[eventName]?.delete(cb);
if (this.#listeners[eventName]?.size === 0)
delete this.#listeners[eventName];
}
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 type Profile from "../profiles/profile.ts";
import { RoomInstance, RoomLocation } from "./base.ts";
import { RoomInstance, RoomLocation } from "./types.ts";
export interface InstanceCreationOptions {
roomId: number,

View File

@@ -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<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 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>(PlatformType)),
z.coerce.number().transform(transformCheckEnum<PlatformType>(PlatformType)),
z.string().min(4)
]),
help: 'List all cachedlogins for platformId: <type: PlatformType, platformId: string>'

View File

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

View File

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

View File

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

View File

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

View File

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

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 });
else s.Value = Value;
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 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);
}

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");
#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: <id: number>'
}),
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: <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 { 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() {

View File

@@ -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<number>([ServerRoomsBase.roomNamesKey, name]);
if (id.value == null) return null;
return id.value;
}
const id = await this.kv.getKv().get<number>([ServerRoomsBase.roomNamesKey, name]);
if (id.value == null) return null;
return id.value;
}
async getPlayerDorm(profile: Profile) {
const id = await this.kv.getKv().get<number>([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 });

View File

@@ -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<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, '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);

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; }
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 { 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<ServerEvents> {
Profiles = new ProfileManagerBase(this, 'profiles', true);
GameConfigs = new GameConfigsBase(this, 'gameconfigs', true);
@@ -36,6 +65,78 @@ class ServerBase extends EventManager<ServerEvents> {
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();