diff --git a/LICENSE b/LICENSE index 5ac38e6..495293c 100644 --- a/LICENSE +++ b/LICENSE @@ -623,7 +623,7 @@ copy of the Program in return for a fee. Galvanic Corrosion - Rec Room custom server for communities. - Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/README.md b/README.md index da9af1a..20a6125 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,18 @@ # Galvanic Corrosion -delectable yum yum +delectable yum yum
Rec Room custom server for communities. Fast runtime and easy setup.
Built for Rec Room build 1063 (Timestamp: 637191339113673856, Version: 20200306) -drawing +drawing
+Photo taken by Nick Gromicko, CMI® # Disclaimer Galvanic Corrosion and its contributors are **not** associated in **any** form with: -* RecNet ("the RecNet platform") * Rec Room, Inc. -* Exit Games Inc. * Against Gravity -* Photon Network, Photon Engine, or services associated +* Exit Games Inc. * Any person(s) in contact with or employed for or with Rec Room Inc. ## Configuration diff --git a/deno.json b/deno.json index d6b8fe6..3aa81da 100644 --- a/deno.json +++ b/deno.json @@ -1,9 +1,13 @@ { "tasks": { - "compile-win": "deno compile --include res --include src --target x86_64-pc-windows-msvc -o build/GalvanicCorrosion.exe -A src/main.ts", - "compile-linux": "deno compile --include res --include src --target x86_64-unknown-linux-gnu -o build/GalvanicCorrosion -A src/main.ts", - "cross-compile": "deno run compile-win && deno run compile-linux", - "dev": "deno run -A src/main.ts --dev" + "compile-win-a": "deno compile --include res --include src --target x86_64-pc-windows-msvc -o build/GalvanicCorrosion.exe -A src/main.ts", + "compile-linux-a": "deno compile --include res --include src --target x86_64-unknown-linux-gnu -o build/GalvanicCorrosion -A src/main.ts", + "compile-win": "deno run prebuild && deno run compile-win-a && deno run postbuild", + "compile-linux": "deno run prebuild && deno run compile-linux-a && deno run postbuild", + "cross-compile": "deno run prebuild && deno run compile-win-a && deno run compile-linux-a && deno run postbuild", + "dev": "deno run -A src/main.ts --dev", + "prebuild": "deno run -A ./prebuild.ts", + "postbuild": "deno run -A ./postbuild.ts" }, "imports": { "@gz/jwt": "jsr:@gz/jwt@^0.1.0", @@ -28,5 +32,6 @@ "./src/types/express.ts", "./src/types/http.ts" ] - } + }, + "version": "0.1.0" } diff --git a/postbuild.ts b/postbuild.ts new file mode 100644 index 0000000..1851e03 --- /dev/null +++ b/postbuild.ts @@ -0,0 +1,24 @@ +/* Galvanic Corrosion - Rec Room custom server for communities. + +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . */ + +try { + Deno.removeSync('./ver.ts'); + Deno.renameSync('./ver.ts.bak', 'ver.ts'); +} catch (err) { + console.error(`Cannot post-build version information: ${err}`); + Deno.exit(1); +} \ No newline at end of file diff --git a/prebuild.ts b/prebuild.ts new file mode 100644 index 0000000..47b8dfb --- /dev/null +++ b/prebuild.ts @@ -0,0 +1,38 @@ +/* Galvanic Corrosion - Rec Room custom server for communities. + +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . */ + +interface DenoProj { + version: string +} + +try { + const file = JSON.parse(Deno.readTextFileSync('./deno.json').toString()) as DenoProj; + + const devVer = Deno.readTextFileSync('./ver.ts'); + const commitHash = new Deno.Command("git", { args: ["rev-parse", "--short=12", "HEAD"] }).outputSync(); + + const newVerString = `${file.version}-${new TextDecoder().decode(commitHash.stdout).trim()}`; + + if (file.version) { + Deno.writeTextFileSync('./ver.ts.bak', devVer); + Deno.writeTextFileSync('./ver.ts', devVer.replace('development', newVerString)); + console.info('Built version information'); + } +} catch (err) { + console.error(`Cannot build version information: ${err}`); + Deno.exit(1); +} \ No newline at end of file diff --git a/res/words.json b/res/words.json new file mode 100644 index 0000000..7099c96 --- /dev/null +++ b/res/words.json @@ -0,0 +1,262 @@ +{ + "easy": [ + "Bike", + "Light", + "Jacket", + "Butterfly", + "Orange", + "Triangle", + "Lemon", + "Crab", + "Alligator", + "Shoe", + "Purse", + "Leg", + "Neck", + "Starfish", + "Box", + "Backpack", + "House", + "Duck", + "Skateboard", + "Cupcake", + "Hamburger", + "Owl", + "Cup", + "Hand", + "Smile", + "Curl", + "Hippo", + "Turtle", + "Truck", + "Beach", + "Ocean", + "Pants", + "Lips", + "Drum", + "Dragon", + "Egg", + "Fish", + "Desk", + "Mouth", + "Feather", + "Grapes", + "Zoo", + "Bathroom", + "Bus", + "Shirt", + "Grass", + "Snail", + "Ice Cream Cone", + "Snake", + "Arm", + "Cookie", + "Table", + "Dinosaur", + "Dream", + "Frog", + "Ant", + "Sheep", + "Boat", + "Baby", + "Bowl", + "Banana", + "Pig", + "Dog", + "Ants", + "Corn", + "Coat", + "Slide", + "Comb", + "Bug", + "Pizza", + "Plant", + "Pencil", + "Key", + "Cloud", + "Lamp", + "Balloon", + "Robot", + "Chimney", + "Motorcycle", + "Bounce", + "Square", + "Pie", + "Swimming Pool", + "Bumblebee", + "Flower", + "Lollipop", + "Bird", + "King", + "Jellyfish", + "Bone", + "Island", + "Moon", + "Seashell", + "Nail", + "Bunk Bed", + "Jail", + "Ring", + "Family", + "Airplane", + "Earth", + "Hair", + "Snowman", + "Car", + "Hook", + "Sea", + "Pen", + "Diamond", + "Spoon", + "Kite", + "Woman", + "Socks", + "Mouse", + "Cube", + "Finger", + "Lizard", + "Angel", + "Mickey Mouse", + "Spider", + "Mountain", + "Branch", + "Spider Web", + "Ship", + "Bunny", + "Face", + "Music", + "Bell", + "Rainbow", + "Star" + ], + "hard": [ + "Parent", + "Concession Stand", + "Tablespoon", + "Puppet", + "Gold", + "Mirror", + "Nanny", + "Irrigation", + "Humidity", + "Postcard", + "Musician", + "Chairman", + "Human", + "Barber", + "Judge", + "Jedi", + "Customer", + "Fur", + "Punk", + "Elf", + "Sled", + "Vitamin", + "Print", + "Internet", + "Swarm", + "Zoo", + "Shelter", + "Blizzard", + "Dust Bunny", + "Printer Ink", + "Oxcart", + "Carat", + "Black Belt", + "Fireman Pole", + "Darkness", + "Carpenter", + "Coach", + "Back Flip", + "Extension Cord", + "Flu", + "Telephone Booth", + "Country", + "Sticky Note", + "Traffic Jam", + "Science", + "Wooly Mammoth", + "Captain", + "Driveway", + "Mysterious", + "Dew", + "Injury", + "Sunburn", + "Grandpa", + "Ceiling Fan", + "Cruise", + "Chime", + "Accounting", + "Hour", + "End Zone", + "Miner", + "Government", + "Lunar Rover", + "Pro", + "Date", + "Police", + "Fireside", + "Hand Soap", + "Ditch", + "Expert", + "Fade", + "Nap", + "Last", + "Hot Tub", + "Loveseat", + "Braid", + "Rodeo", + "Coastline", + "Molar", + "Drugstore", + "Tow Truck", + "Pickup Truck", + "Shrink Ray", + "Crane", + "Bedbug", + "Beluga Whale", + "Fiance", + "Fizz", + "Junk", + "Yolk", + "Photosynthesis", + "Wobble", + "Hairspray", + "Crop Duster", + "Recycle", + "Script", + "Vein", + "Jaw", + "Thaw", + "Goalkeeper", + "Optometrist", + "Shower Curtain", + "Cowboy", + "Tide", + "Quadrant", + "Bride", + "Pile", + "Midnight", + "Amusement Park", + "Orbit", + "Goblin", + "Bonnet", + "Snore", + "Cape", + "Truck Stop", + "Ginger", + "Hermit Crab", + "Safe", + "Jungle", + "Student", + "Headache", + "Scuba Diving", + "Neighborhood", + "Bookend", + "Crow's Nest", + "Plantation", + "Owner", + "Stuffed Animal", + "Gown" + ] +} \ No newline at end of file diff --git a/src/apiutils.ts b/src/apiutils.ts index acf6831..7d35d4e 100644 --- a/src/apiutils.ts +++ b/src/apiutils.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/config.ts b/src/config.ts index 23976b8..c192a22 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/data/baseevent.ts b/src/data/baseevent.ts index 3dd0c64..531ca3d 100644 --- a/src/data/baseevent.ts +++ b/src/data/baseevent.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/data/config.ts b/src/data/config.ts index aeefb64..4d80935 100644 --- a/src/data/config.ts +++ b/src/data/config.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -17,7 +17,7 @@ along with this program. If not, see . */ import { Config } from "../config.ts"; import { Redis } from "../db.ts"; -import { Objectives } from "./objectives.ts"; +import { Objectives, ObjectiveType } from "./objectives.ts"; export type LevelProgressionItem = { Level: number; @@ -67,7 +67,43 @@ export function getConfig() { StartsInMinutes: 0, }, LevelProgressionMaps: generateLevelProgressionMap(), - DailyObjectives: [[{type: -1,score:0},{type: -1,score:0},{type: -1,score:0}],[{type: -1,score:0},{type: -1,score:0},{type: -1,score:0}],[{type: -1,score:0},{type: -1,score:0},{type: -1,score:0}]], + DailyObjectives: [ + [ + {type: ObjectiveType.Default, score: 0}, + {type: ObjectiveType.Default, score: 0}, + {type: ObjectiveType.Default, score: 0} + ], + [ + {type: ObjectiveType.Default, score: 0}, + {type: ObjectiveType.Default, score: 0}, + {type: ObjectiveType.Default, score: 0} + ], + [ + {type: ObjectiveType.Default, score: 0}, + {type: ObjectiveType.Default, score: 0}, + {type: ObjectiveType.Default, score: 0} + ], + [ + {type: ObjectiveType.Default, score: 0}, + {type: ObjectiveType.Default, score: 0}, + {type: ObjectiveType.Default, score: 0} + ], + [ + {type: ObjectiveType.Default, score: 0}, + {type: ObjectiveType.Default, score: 0}, + {type: ObjectiveType.Default, score: 0} + ], + [ + {type: ObjectiveType.Default, score: 0}, + {type: ObjectiveType.Default, score: 0}, + {type: ObjectiveType.Default, score: 0} + ], + [ + {type: ObjectiveType.Default, score: 0}, + {type: ObjectiveType.Default, score: 0}, + {type: ObjectiveType.Default, score: 0} + ] + ], AutoMicMutingConfig: { MicSpamVolumeThreshold: 1.125, MicVolumeSampleInterval: 0.25, diff --git a/src/data/content/activities.ts b/src/data/content/activities.ts new file mode 100644 index 0000000..f00f1a7 --- /dev/null +++ b/src/data/content/activities.ts @@ -0,0 +1,47 @@ +/* Galvanic Corrosion - Rec Room custom server for communities. + +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . */ + +import Logging from "@proxnet/undead-logging"; +import { RootPath } from "../../path.ts"; + +const log = new Logging("Activities"); + +enum CharadesWordsDifficulty { + Easy, + Hard +} +interface CharadesWord { + EN_US: string, + Difficulty: CharadesWordsDifficulty +} +interface WordsConfig { + easy: string[], + hard: string[] +} +let charades: WordsConfig | undefined; +try { + const data = Deno.readTextFileSync(`${RootPath}/res/words.json`); + charades = JSON.parse(data); +} catch (err) { + log.e(`Could not read charades words config from disk!`); +} + +export function getWords() { + if (!charades) return [{EN_US: "Galvanic Corrosion", Difficulty: CharadesWordsDifficulty.Easy}] as CharadesWord[] + else return charades.easy.map(val => ({EN_US: val, Difficulty: CharadesWordsDifficulty.Easy} as CharadesWord)) + .concat(charades.hard.map(val => ({EN_US: val, Difficulty: CharadesWordsDifficulty.Hard} as CharadesWord))) +} \ No newline at end of file diff --git a/src/data/content/avatar.ts b/src/data/content/avatar.ts index 79a3aa6..d98ee96 100644 --- a/src/data/content/avatar.ts +++ b/src/data/content/avatar.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/data/content/baseimages.ts b/src/data/content/baseimages.ts index 5d3b4a0..8cd882d 100644 --- a/src/data/content/baseimages.ts +++ b/src/data/content/baseimages.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -15,9 +15,7 @@ GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -import { platform } from "node:process"; - -export const RootPath = Deno.mainModule.substring(platform == 'win32' ? 8 : 7, Deno.mainModule.length - 11); +import { RootPath } from "../../path.ts"; export function getBaseImage(name: string) { try { @@ -31,4 +29,4 @@ export function getAllBaseImages() { Deno.readDirSync(`${RootPath}/res/img/`).map((val) => val.isFile ? val.name : undefined), ).filter((val) => typeof val == "string"); } -// todo: make this async +// todo: make this async \ No newline at end of file diff --git a/src/data/content/consumable.ts b/src/data/content/consumable.ts index 50ee12c..88a9977 100644 --- a/src/data/content/consumable.ts +++ b/src/data/content/consumable.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/data/content/data.ts b/src/data/content/data.ts deleted file mode 100644 index fecb6ec..0000000 --- a/src/data/content/data.ts +++ /dev/null @@ -1,94 +0,0 @@ -/* Galvanic Corrosion - Rec Room custom server for communities. - -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as published -by the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . */ - -import { Buffer } from "node:buffer"; -import { Redis } from "../../db.ts"; -import { generateRandomString } from "../../apiutils.ts"; - -export enum FileType { - Unknown, - RoomSave, - Holotar, - Image, - Video, - Invention -} - -export function getFileName(prefix: string, type: FileType) { - switch (type) { - case FileType.RoomSave: - return `${prefix}.room`; - case FileType.Holotar: - return `${prefix}.holotar`; - case FileType.Image: - return `${prefix}.image`; - case FileType.Video: - return `${prefix}.video`; - case FileType.Invention: - return `${prefix}.invention`; - default: - return `${prefix}.unknown` - } -} - -export async function getFile(name: string) { - const data = await Redis.Database.getBuffer(Redis.buildKey( - Redis.KeyGroups.Content.Root, - name - )); - - if (!data) return null; - else return data; -} - -/** - * @returns Name of the new file - */ -export async function setFile(data: Buffer, type: FileType, name?: string) { - let filename = generateRandomString(24); - if (name) filename = name; - const finalName = getFileName(filename, type); - - await Redis.Database.set(Redis.buildKey( - Redis.KeyGroups.Content.Root, - finalName - ), data); - await initFileMeta(filename, type); - return filename; -} - -interface FileMeta { - created: Date | string; - fetchCount: number; - type: FileType -} - -async function initFileMeta(filename: string, type: FileType) { - const meta: FileMeta = { - created: new Date(), - fetchCount: 0, - type: type - } - await Redis.Database.hset(Redis.buildKey( - Redis.KeyGroups.Content.Files, - getFileName(filename, type) - ), meta); -} - -export async function incrementFileFetches(name: string, type: FileType) { - -} \ No newline at end of file diff --git a/src/data/content/gamerooms.ts b/src/data/content/gamerooms.ts index 5f0cb78..df24cfb 100644 --- a/src/data/content/gamerooms.ts +++ b/src/data/content/gamerooms.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/data/content/rooms.ts b/src/data/content/rooms.ts index 6644849..d6a11ee 100644 --- a/src/data/content/rooms.ts +++ b/src/data/content/rooms.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -16,13 +16,12 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ import { Redis } from "../../db.ts"; -import { RootPath } from "./baseimages.ts"; import { Profile } from "../profiles.ts"; import Logging from "@proxnet/undead-logging"; -import { BuiltinRoom, FactoryMode, IntegratedRoomScene, RoomAccessibility, RoomDetails, RoomState, WriteMode } from "./rooms/DataTypes.ts"; +import { BuiltinRoom, FactoryMode, IntegratedRoomScene, RoomAccessibility, RoomDataTypes, RoomDetails, RoomState, WriteMode } from "./rooms/DataTypes.ts"; import { RoomFactory } from "./rooms/RoomFactory.ts"; import { SubroomFactory } from "./rooms/SubroomFactory.ts"; -import { Image } from "https://deno.land/x/imagescript@1.3.0/ImageScript.js"; +import { RootPath } from "../../path.ts"; const log = new Logging("Rooms"); @@ -87,34 +86,27 @@ class RoomsBase { } async cloneRoom(roomid: number, newname: string, newowner: Profile) { - enum CloneResult { - Success, - DoesNotAllowCloning, - CannotCloneDormRoom, - NameIsTaken, - Unknown - } interface RoomClone { factory?: RoomFactory; - result: CloneResult; + result: RoomDataTypes.CreateModifyRoomStatus; } const factory = await new RoomFactory({ id: roomid, factoryMode: FactoryMode.Fetch }).init(); - if (!factory || !factory.CloningAllowed) return { result: CloneResult.DoesNotAllowCloning } as RoomClone; - if (factory.Name == 'DormRoom') return { result: CloneResult.CannotCloneDormRoom } as RoomClone; - if (factory.Name == newname) return { result: CloneResult.NameIsTaken } as RoomClone; - + if (!factory || !factory.CloningAllowed) return { result: RoomDataTypes.CreateModifyRoomStatus.PermissionDenied } as RoomClone; + if (factory.Name == 'DormRoom') return { result: RoomDataTypes.CreateModifyRoomStatus.ReservedName } as RoomClone; + if (factory.Name == newname) return { result: RoomDataTypes.CreateModifyRoomStatus.DuplicateName } as RoomClone; + const newFactory = await new RoomFactory({ id: await Rooms.#getAvailableRoomId(), factoryMode: FactoryMode.Write }).init(); - if (!newFactory) return { result: CloneResult.Unknown } as RoomClone; + if (!newFactory) return { result: RoomDataTypes.CreateModifyRoomStatus.Unknown } as RoomClone; newFactory.CreatorPlayerId = newowner.getId(); newFactory.Description = factory.Description; - newFactory.Name = factory.Name; - newFactory.ImageName = factory.Description; - newFactory.State = factory.State; - newFactory.RoomAccessibility = factory.RoomAccessibility; + newFactory.Name = newname; + newFactory.ImageName = factory.ImageName; + newFactory.State = RoomState.Active; + newFactory.RoomAccessibility = RoomAccessibility.Private; newFactory.SupportsLevelVoting = factory.SupportsLevelVoting; - newFactory.IsAGRoom = factory.IsAGRoom; + newFactory.IsAGRoom = false; newFactory.IsDormRoom = factory.IsDormRoom; newFactory.CloningAllowed = false; // new rooms cannot be cloned newFactory.AllowsJuniors = factory.AllowsJuniors; @@ -128,8 +120,8 @@ class RoomsBase { const oldSubroomIds = await factory.getAllSubroomIds(); const promises = oldSubroomIds.map(async (id) => { - const newSubroomFactory = newFactory.getSubroom(id, FactoryMode.Write, WriteMode.Overwrite); - const oldSubroomFactory = factory.getSubroom(id, FactoryMode.Fetch); + const newSubroomFactory = await newFactory.getSubroom(id, FactoryMode.Write, WriteMode.Overwrite).init(); + const oldSubroomFactory = await factory.getSubroom(id, FactoryMode.Fetch).init(); newSubroomFactory.RoomSceneLocationId = oldSubroomFactory.RoomSceneLocationId; newSubroomFactory.Name = oldSubroomFactory.Name; @@ -143,10 +135,11 @@ class RoomsBase { await Promise.all(promises); await newFactory.write(); + newFactory.factoryMode = FactoryMode.Fetch; return { factory: newFactory, - result: CloneResult.Success + result: RoomDataTypes.CreateModifyRoomStatus.Success } as RoomClone } @@ -199,8 +192,8 @@ class RoomsBase { factory.CustomRoomWarning = ""; factory.addHardwareSupport('*'); - - const subroomFactory = factory.getSubroom(await this.#getAvailableSubRoomId(id), FactoryMode.Write, WriteMode.WriteIfFree); + + const subroomFactory = await factory.getSubroom(await this.#getAvailableSubRoomId(id), FactoryMode.Write, WriteMode.WriteIfFree).init(); if (!subroomFactory) return null; subroomFactory.RoomSceneLocationId = IntegratedRoomScene.DormRoom; @@ -226,7 +219,7 @@ class RoomsBase { const factory = await new RoomFactory({ id: newId, factoryMode: FactoryMode.Write, writeMode: WriteMode.Overwrite }).init(); if (!factory) return; - + factory.Name = builtinRoom.Name; factory.Description = builtinRoom.Description; factory.CreatorPlayerId = 1; @@ -268,7 +261,7 @@ class RoomsBase { subroomFactory.DataBlobName = ""; subroomFactory.MaxPlayers = subroom.MaxPlayers; subroomFactory.CanMatchmakeInto = subroom.CanMatchmakeInto; - + await subroomFactory.write(); })); diff --git a/src/data/content/rooms/DataTypes.ts b/src/data/content/rooms/DataTypes.ts index def392f..13c6332 100644 --- a/src/data/content/rooms/DataTypes.ts +++ b/src/data/content/rooms/DataTypes.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -37,9 +37,9 @@ export enum RoomState { } export enum RoomAccessibility { - Private, - Public, - Unlisted + Private, + Public, + Unlisted } export interface BuiltinScene { @@ -143,6 +143,7 @@ export enum IntegratedRoomScene { DiscGolfLake = "f6f7256c-e438-4299-b99e-d20bef8cf7e0", DiscGolfPropulsion = "d9378c9f-80bc-46fb-ad1e-1bed8a674f55", Dodgeball = "3d474b26-26f7-45e9-9a36-9b02847d5e6f", + DodgeballVR = "3d474b26-26f7-45e9-9a36-9b02847d5e6f", Paddleball = "d89f74fa-d51e-477a-a425-025a891dd499", Paintball_River = "e122fe98-e7db-49e8-a1b1-105424b6e1f0", Paintball_Homestead = "a785267d-c579-42ea-be43-fec1992d1ca7", @@ -179,7 +180,6 @@ export enum IntegratedRoomScene { Stadium = "6d5eea4b-f069-4ed0-9916-0e2f07df0d03", Hangar = "239e676c-f12f-489f-bf3a-d4c383d692c3", CyberJunkCity = "9d6456ce-6264-48b4-808d-2d96b3d91038", - DodgeballVR = "3d474b26-26f7-45e9-9a36-9b02847d5e6f", Crescendo = "49cb8993-a956-43e2-86f4-1318f279b22a", Bowling = "ae929543-9a07-41d5-8ee9-dbbee8c36800", BowlingAlley = "ae929543-9a07-41d5-8ee9-dbbee8c36800", @@ -188,4 +188,31 @@ export enum IntegratedRoomScene { StuntRunnerBaseRoom = "882e9b96-7115-4b03-86f6-c0c9d8e22e00", } +export enum CreateModifyRoomStatus { + Success, + Unknown, + PermissionDenied, + RoomNotActive, + RoomDoesNotExist, + RoomHasNoDataBlob, + DuplicateName = 10, + ReservedName, + InappropriateName, + InappropriateDescription, + TooManyRooms = 20, + InvalidMaxPlayers = 30, + DataHistoryDoesNotExist = 40, + DataHistoryAlreadyActive, + InvalidTags = 50, + NoStartingRoomScene = 55, + RoomUnderModerationReview = 100, + PlayerHasRoomUnderModerationReview, + AccessibilityUnderModerationLock, + JuniorStatusFail = 200, + InappropriateCustomWarning = 300, + PartialBanSuccessBanPower = 400, + TargetHasBanPower, + PlayerIsRoomBanned = 410 +} + export * as RoomDataTypes from "./DataTypes.ts"; \ No newline at end of file diff --git a/src/data/content/rooms/RoomFactory.ts b/src/data/content/rooms/RoomFactory.ts index cf9723a..99c0444 100644 --- a/src/data/content/rooms/RoomFactory.ts +++ b/src/data/content/rooms/RoomFactory.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/data/content/rooms/SubroomFactory.ts b/src/data/content/rooms/SubroomFactory.ts index d739911..874f905 100644 --- a/src/data/content/rooms/SubroomFactory.ts +++ b/src/data/content/rooms/SubroomFactory.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/data/content/settings.ts b/src/data/content/settings.ts index 84728bc..a876481 100644 --- a/src/data/content/settings.ts +++ b/src/data/content/settings.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/data/content/storefronts.ts b/src/data/content/storefronts.ts index dd09df5..19ed271 100644 --- a/src/data/content/storefronts.ts +++ b/src/data/content/storefronts.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/data/live/base.ts b/src/data/live/base.ts index 20e162d..21e5c09 100644 --- a/src/data/live/base.ts +++ b/src/data/live/base.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -60,14 +60,14 @@ class MatchmakingBase { const instance = Instances.getInstance(options.instanceId); if (instance) { - if (Instances.playerIsInInstance(options.profile, instance)) return { errorCode: MatchmakingErrorCode.AlreadyInTargetInstance }; + if (instance.hasPlayer(options.profile)) return { errorCode: MatchmakingErrorCode.AlreadyInTargetInstance }; else { if (instance.isFull) return { errorCode: MatchmakingErrorCode.InsufficientSpace } else if (instance.isPrivate) return { errorCode: MatchmakingErrorCode.RoomInstanceIsPrivate }; - await Instances.setPlayerInstance(options.profile, instance); - Instances.clearAllRoomEmptyInstances(instance.roomId); - return { errorCode: MatchmakingErrorCode.Success, roomInstance: instance }; + await instance.addPlayer(options.profile); + Instances.clearEmptyInstances(instance.roomId); + return { errorCode: MatchmakingErrorCode.Success, roomInstance: instance.snapshot() }; } } else return { errorCode: MatchmakingErrorCode.NoSuchGame } @@ -82,7 +82,7 @@ class MatchmakingBase { if (targetRoom.Room.State !== RoomDataTypes.RoomState.Active) return { errorCode: MatchmakingErrorCode.RoomIsNotActive }; const roomId = targetRoom.Room.RoomId; - Instances.clearAllRoomEmptyInstances(roomId); + Instances.clearEmptyInstances(roomId); // get all available instance let allInstances = Instances.getAllRoomInstances(roomId).values().toArray().filter(instance => !instance.isPrivate && !instance.isFull); @@ -108,9 +108,9 @@ class MatchmakingBase { }); allInstances = allInstances.sort((a, b) => { - const pidsA = Instances.getInstancePlayers(a); - const pidsB = Instances.getInstancePlayers(b); - return pidsA.size - pidsB.size; + const pidsA = a.getAllPlayers(); + const pidsB = b.getAllPlayers(); + return pidsA.length - pidsB.length; }).reverse(); // Largest instances first const foundInstance = allInstances[0]; @@ -125,8 +125,8 @@ class MatchmakingBase { IsDorm: options.roomName == 'DormRoom' }); - Instances.clearAllRoomEmptyInstances(roomId); - return { errorCode: MatchmakingErrorCode.Success, roomInstance: newInstance }; + Instances.clearEmptyInstances(roomId); + return { errorCode: MatchmakingErrorCode.Success, roomInstance: newInstance.snapshot() }; } else { @@ -134,11 +134,11 @@ class MatchmakingBase { if (currentInstance?.roomInstanceId == foundInstance.roomInstanceId) return { errorCode: MatchmakingErrorCode.AlreadyInBestInstance }; - await Instances.setPlayerInstance(options.profile, foundInstance); + await foundInstance.addPlayer(options.profile); - Instances.clearAllRoomEmptyInstances(roomId); + Instances.clearEmptyInstances(roomId); - return { errorCode: MatchmakingErrorCode.Success, roomInstance: foundInstance }; + return { errorCode: MatchmakingErrorCode.Success, roomInstance: foundInstance.snapshot() }; } diff --git a/src/data/live/instances.ts b/src/data/live/instances.ts index eaa60b0..58d793b 100644 --- a/src/data/live/instances.ts +++ b/src/data/live/instances.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -21,84 +21,191 @@ import { RoomInstance, InstanceOptions } from "./types.ts"; import { Config } from "../../config.ts"; import Presence from "./presence.ts"; import { RoomFactory } from "../content/rooms/RoomFactory.ts"; +import { RoomDataTypes } from "../content/rooms/DataTypes.ts"; +import { PushNotificationId } from "../../socket/types.ts"; const log = new Logging("Instances"); const config = Config.getConfig(); -const instancePlayers: Map> = new Map(); /** - * `Map` + * `Map` */ -const instanceMap: Map> = new Map(); +const instanceSet: Set = new Set(); + +export class Instance { + + #players = new Set(); + timeCreated = new Date().toISOString(); + + #id: number; + #room: RoomDataTypes.RoomDetails | undefined; + #subroom: RoomDataTypes.RoomScene | undefined; + + #eventId?: number; // not yet implemented + #name?: string; + #priv?: boolean; + #inProgress?: boolean; + #blob?: string; + + constructor(id: number) { + this.#id = id; + } + + async init(options: InstanceOptions) { + + const scene = options.Room.Scenes[options.SceneIndex]; + if (!scene) throw new Error("The specified scene did not exist."); + + let instanceName; + if (scene.Name == 'Home' || scene.Name === options.Room.Room.Name) instanceName = `^${options.Room.Room.Name}`; + else instanceName = `^${options.Room.Room.Name}.${scene.Name}`; + if (options.IsDorm) { + const dormCreatorPlayer = UnifiedProfile.get(options.Room.Room.CreatorPlayerId); + const player = await dormCreatorPlayer.export(); + if (player) instanceName = `@${player.displayName}'s Dorm`; + } + + this.#room = options.Room; + this.#subroom = scene; + this.#name = instanceName; + this.#blob = scene.DataBlobName; + this.#inProgress = false; + this.#priv = options.Private ? options.Private : false; + + return this; + + } + + equalInstance(instance: RoomInstance) { + return instance.roomInstanceId == this.#id; + } + + getAllPlayers() { + return this.#players.values().toArray(); + } + + hasPlayer(player: Profile) { + return this.getAllPlayers().includes(player); + } + + removePlayer(player: Profile) { + if (!this.hasPlayer(player)) throw new Error(`Cannot remove player ${player.getId()} from instance ${this.#id} they are not in`); + this.#players.delete(player); + player.setInstance(null); + } + + async addPlayer(player: Profile) { + const currentInstance = player.getInstance(); + if (currentInstance && currentInstance.equalInstance(this)) return; + + if (currentInstance) currentInstance.removePlayer(player); + + if (!this.isFull) { + const instancePlayers = this.getAllPlayers(); + const profileExport = await player.export(); + log.i(`Player ${player.getId()} "${profileExport?.displayName}" went to '${this.name}' with ${instancePlayers.length} other players`); + + this.#players.add(player); + player.setInstance(this); + + const pres = await Presence.get(player); + pres.update(); + + const room = await new RoomFactory({ id: this.roomId }).init(); + await room?.addVisit(); + + // move some of this to a dedicated "onPlayerMove" function + } else log.w(`Instance ${this.roomInstanceId} is full. Cannot add player ${player.getId()}`); + + log.d(`Players in instance ${this.#id}: ${this.#players.values().toArray().map(prof => prof.getId()).join(',')}`); + } + + get roomInstanceId() { return this.#id } + + get roomId() { return this.#room ? this.#room?.Room.RoomId : 0 } + + get subRoomId() { return this.#subroom ? this.#subroom?.RoomSceneId : 0 } + + get location() { return this.#subroom ? this.#subroom?.RoomSceneLocationId : "" } + + get dataBlob() { return this.#blob ? this.#blob : undefined } + set dataBlob(data) { this.#blob = data } + + get eventId() { return this.#eventId } + + get photonRegionId() { return config.public.photonRegionId } + + get photonRoomId() { return `GC20200306-${this.#id}` } + + get name() { return this.#name ? this.#name : "InstanceNameError" } + + get maxCapacity() { return this.#subroom ? this.#subroom.MaxPlayers : 8 } + + get isFull() { return this.#players.size >= this.maxCapacity } + + get isPrivate() { return this.#priv ? this.#priv : false } + set isPrivate(data) { this.#priv = data } + + get isInProgress() { return this.#inProgress ? this.#inProgress : false } + set isInProgress(data) { this.#inProgress = data } + + snapshot() { + const inst: RoomInstance = { + roomInstanceId: this.roomInstanceId, + roomId: this.roomId, + subRoomId: this.subRoomId, + location: this.location, + dataBlob: this.dataBlob, + eventId: this.eventId, + photonRegionId: this.photonRegionId, + photonRoomId: this.photonRoomId, + name: this.name, + maxCapacity: this.maxCapacity, + isFull: this.isFull, + isPrivate: this.isPrivate, + isInProgress: this.isInProgress + } + return inst; + } + + destroy() { + instanceSet.delete(this); + if (this.#players.size !== 0) for (const player of this.#players) player.getSocketHandler()?.sendNotification(PushNotificationId.Logout); + } + +} class InstancesBase { getInstance(id: number) { - const instances = instancePlayers.keys(); + const instances = instanceSet.values().toArray(); const instance = instances.find(val => val.roomInstanceId == id); if (instance) return instance; else return null; } getAllInstances() { - return new Set([...instanceMap.values()].flatMap(set => [...set])); + return new Set([...instanceSet.values().toArray()]); } getAllRoomInstances(roomId: number) { - let instances = instanceMap.get(roomId); - if (!instances) { - instances = new Set(); - instanceMap.set(roomId, instances); - } - return instances; - } - - getInstancePlayers(instance: RoomInstance): Set { - let players = instancePlayers.get(instance); - if (!players) { - players = new Set(); - instancePlayers.set(instance, players); - } - return players; + return new Set([...this.getAllInstances().values().toArray().filter(val => val.roomId == roomId)]); } - clearEmptyInstances(instances: Set, roomId?: number) { - const beforeCount = instances.size; - for (const instance of instances) { - const profiles = this.getInstancePlayers(instance); - if (profiles.size === 0) { + clearEmptyInstances(roomId?: number) { + const beforeCount = instanceSet.size; + for (const instance of instanceSet) { + if (roomId && instance.roomId == roomId) continue; + const profiles = instance.getAllPlayers(); + if (profiles.length === 0) { log.i(`Instance ${instance.roomInstanceId} empty, deleting`); - instancePlayers.delete(instance); - this.getAllRoomInstances(instance.roomId).delete(instance); + instance.destroy(); } } - const afterCount = instances.size; - log.d(`Cleared ${roomId !== undefined ? `room ${roomId}` : 'all'} empty instances.\n Instances before: ${beforeCount}\n Instances after: ${afterCount}`); - } - - clearAllEmptyInstances() { - this.clearEmptyInstances(this.getAllInstances()); - } - - clearAllRoomEmptyInstances(roomId: number) { - this.clearEmptyInstances(this.getAllRoomInstances(roomId), roomId); - } - - updateGlobalInstancesIsFull() { - for (const instance of this.getAllInstances()) - instance.isFull = this.getInstancePlayers(instance).size >= instance.maxCapacity; - } - - updateSingleInstanceIsFull(instance: RoomInstance) { - const profiles = this.getInstancePlayers(instance); - if (profiles.size >= instance.maxCapacity) instance.isFull = true; - else instance.isFull = false; - } - - instanceCanFitPlayer(instance: RoomInstance) { - return this.getInstancePlayers(instance).size < instance.maxCapacity; + const afterCount = instanceSet.size; + log.d(`Cleared empty instances for roomId ${roomId ? roomId : "*"}.\n Instances before: ${beforeCount}\n Instances after: ${afterCount}`); } #generateUniqueInstanceId() { @@ -112,96 +219,19 @@ class InstancesBase { * Create an instance with options. * * If `options.FirstPlayer` is not specified, the created instance will not contain any players and may be removed. - * - * If one is, the player will be automatically added to the instance and their `profile.getInstance()` will be synchronized. */ async createInstance(options: InstanceOptions) { - const scene = options.Room.Scenes[options.SceneIndex]; const newId = this.#generateUniqueInstanceId(); - if (!scene) throw new Error("The specified scene did not exist."); + const newInstance = await new Instance(newId).init(options); - let instanceName = scene.Name === "Home" || scene.Name === options.Room.Room.Name ? `^${options.Room.Room.Name}` : `^${options.Room.Room.Name}.${scene.Name}`; - if (options.IsDorm) { - const dormCreatorPlayer = UnifiedProfile.get(options.Room.Room.CreatorPlayerId); - const player = await dormCreatorPlayer.export(); - if (player) instanceName = `@${player.displayName}'s Dorm`; - } - const newInstance: RoomInstance = { - roomInstanceId: newId, - roomId: options.Room.Room.RoomId, - subRoomId: scene.RoomSceneId, - location: scene.RoomSceneLocationId, - dataBlob: scene.DataBlobName, - eventId: options.EventId, - photonRegionId: config.public.photonRegionId, - photonRoomId: `20200306-GC${newId}`, - name: instanceName, - maxCapacity: scene.MaxPlayers, - isFull: false, - isPrivate: typeof options.Private !== 'boolean' ? false : options.Private, - isInProgress: false - }; - - this.getAllRoomInstances(options.Room.Room.RoomId).add(newInstance); - if (options.FirstPlayer) { - this.setPlayerInstance(options.FirstPlayer, newInstance); - this.getInstancePlayers(newInstance).add(options.FirstPlayer); - } + instanceSet.add(newInstance); + if (options.FirstPlayer) + newInstance.addPlayer(options.FirstPlayer); return newInstance; } - - /** - * Call only when the player is ready to be moved to an instance - * - * Synchronizes profile instance to `instance` and adds player to instance. - */ - async setPlayerInstance(player: Profile, instance: RoomInstance) { - const currentInstance = player.getInstance(); - if (currentInstance === instance) return; - - if (currentInstance) { - this.getInstancePlayers(currentInstance).delete(player); - this.updateSingleInstanceIsFull(currentInstance); - } - - if (this.instanceCanFitPlayer(instance)) { - const instancePlayers = this.getInstancePlayers(instance); - log.i(`Player ${player.getId()} went to '${instance.name}' with ${instancePlayers.size} other players`); - instancePlayers.add(player); - - player.setInstance(instance); - const pres = await Presence.get(player); - pres.update(); - - this.updateSingleInstanceIsFull(instance); - } else log.w(`Instance ${instance.roomInstanceId} is full. Cannot add player ${player.getId()}`); - - const room = await new RoomFactory({ id: instance.roomId }).init(); - await room?.addVisit(); - } - - playerIsInInstance(player: Profile, instance: RoomInstance) { - - const profiles = this.getInstancePlayers(instance); - return profiles.has(player); - - } - - /** - * Call only when the player is ready to be removed (or when not responding) - * - * Synchronizes profile instance to `null` and removes player from instance. - */ - removePlayerFromCurrentInstance(player: Profile) { - const instance = player.getInstance(); - if (!instance) return; - this.getInstancePlayers(instance).delete(player); - player.setInstance(null); - this.updateSingleInstanceIsFull(instance); - } } diff --git a/src/data/live/presence.ts b/src/data/live/presence.ts index 8df3b85..f68607a 100644 --- a/src/data/live/presence.ts +++ b/src/data/live/presence.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -20,6 +20,7 @@ import { SettingKey } from "../content/settings.ts"; import { Profile } from "../profiles.ts"; import { DeviceClass, PlayerStatusVisibility, RoomInstance, VRMovementMode } from "./types.ts"; import Logging from "@proxnet/undead-logging"; +import { Instance } from "./instances.ts"; const log = new Logging("Presence"); @@ -31,6 +32,7 @@ export interface PresenceExport { vrMovementMode?: VRMovementMode; } +// This whole class needs rewritten .. probably class PlayerPresence { intervalId: number; @@ -63,7 +65,7 @@ class PlayerPresence { statusVisibility: PlayerStatusVisibility; deviceClass: DeviceClass; vrMovementMode: VRMovementMode | undefined; - roomInstance: RoomInstance | null; + roomInstance: Instance | null; lastSeen: Date; @@ -102,9 +104,10 @@ class PlayerPresence { */ async export() { await this.update(); + const inst = this.roomInstance?.snapshot(); const exp: PresenceExport = { playerId: this.playerId, - roomInstance: this.roomInstance, + roomInstance: inst ? inst : null, statusVisibility: this.statusVisibility, deviceClass: this.deviceClass, vrMovementMode: this.vrMovementMode diff --git a/src/data/live/types.ts b/src/data/live/types.ts index 60bb408..3ab657a 100644 --- a/src/data/live/types.ts +++ b/src/data/live/types.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/data/objectives.ts b/src/data/objectives.ts index 2bc9588..0aea0e2 100644 --- a/src/data/objectives.ts +++ b/src/data/objectives.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/data/platformtypes.ts b/src/data/platformtypes.ts index 5d88162..064dcd7 100644 --- a/src/data/platformtypes.ts +++ b/src/data/platformtypes.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/data/profile/avatar.ts b/src/data/profile/avatar.ts index f39d325..199ddf1 100644 --- a/src/data/profile/avatar.ts +++ b/src/data/profile/avatar.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/data/profile/base/events.ts b/src/data/profile/base/events.ts index 088feaa..45cdee2 100644 --- a/src/data/profile/base/events.ts +++ b/src/data/profile/base/events.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/data/profile/base/profilemanagerbase.ts b/src/data/profile/base/profilemanagerbase.ts index 24c69ae..7e27d79 100644 --- a/src/data/profile/base/profilemanagerbase.ts +++ b/src/data/profile/base/profilemanagerbase.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/data/profile/progression.ts b/src/data/profile/progression.ts index 87062b5..33ebe26 100644 --- a/src/data/profile/progression.ts +++ b/src/data/profile/progression.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/data/profile/relationships.ts b/src/data/profile/relationships.ts index 52f9892..1b20e37 100644 --- a/src/data/profile/relationships.ts +++ b/src/data/profile/relationships.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -41,6 +41,7 @@ interface Relationship { Favorited?: ReciprocalStatus } +// Literally all of this is untested export class ProfileRelationshipManager extends ProfileContentManager { #baseRelationships: Relationship[] = [ @@ -123,15 +124,15 @@ export class ProfileRelationshipManager extends ProfileContentManager { return (await Redis.Database.smembers(this.#friendsKey)).map(val => parseInt(val)).filter(val => !isNaN(val)); } - async #getIncomingRequests() { + async getIncomingRequests() { return (await Redis.Database.smembers(this.#incomingFriends)).map(val => parseInt(val)).filter(val => !isNaN(val)); } - async #getOutgoingRequests() { + async getOutgoingRequests() { return (await Redis.Database.smembers(this.#outgoingFriends)).map(val => parseInt(val)).filter(val => !isNaN(val)); } - createRemoteRootKey(remoteProfileId: number) { + #createRemoteRootKey(remoteProfileId: number) { return Redis.buildKey( Redis.KeyGroups.Profiles.Root, remoteProfileId.toString(), @@ -140,7 +141,7 @@ export class ProfileRelationshipManager extends ProfileContentManager { } async #clearAssociationWithRemote(remoteProfileId: number) { - const remoteRootKey = this.createRemoteRootKey(remoteProfileId); + const remoteRootKey = this.#createRemoteRootKey(remoteProfileId); await Redis.Database.srem(this.#incomingFriends, remoteProfileId); await Redis.Database.srem(Redis.buildKey(remoteRootKey, Redis.KeyGroups.Profiles.Relationships.OutgoingFriendRequests), this.profileId); await Redis.Database.srem(Redis.buildKey(remoteRootKey, Redis.KeyGroups.Profiles.Relationships.IncomingFriendRequests), this.profileId); @@ -148,27 +149,27 @@ export class ProfileRelationshipManager extends ProfileContentManager { } async acceptRequest(remoteProfileId: number) { - const requests = await this.#getIncomingRequests(); + const requests = await this.getIncomingRequests(); if (!requests.includes(remoteProfileId)) return; await this.#clearAssociationWithRemote(remoteProfileId); - const remoteRootKey = this.createRemoteRootKey(remoteProfileId); + const remoteRootKey = this.#createRemoteRootKey(remoteProfileId); await Redis.Database.sadd(this.#friendsKey, remoteProfileId); await Redis.Database.sadd(Redis.buildKey(remoteRootKey, Redis.KeyGroups.Profiles.Relationships.Friends), this.profileId); } async denyRequest(remoteProfileId: number) { - const requests = await this.#getIncomingRequests(); + const requests = await this.getIncomingRequests(); if (!requests.includes(remoteProfileId)) return; await this.#clearAssociationWithRemote(remoteProfileId); } async getRelationshipType(remoteProfileId: number) { const isFriendsWithRemote = (await Redis.Database.sismember(this.#friendsKey, remoteProfileId)) >= 1; - const remoteSentRequest = (await this.#getIncomingRequests()).includes(remoteProfileId); - const localSentRequest = (await this.#getOutgoingRequests()).includes(remoteProfileId); + const remoteSentRequest = (await this.getIncomingRequests()).includes(remoteProfileId); + const localSentRequest = (await this.getOutgoingRequests()).includes(remoteProfileId); if (isFriendsWithRemote || (remoteSentRequest && localSentRequest)) return RelationshipType.Friend; else if (remoteSentRequest) return RelationshipType.FriendRequestReceived; @@ -177,7 +178,7 @@ export class ProfileRelationshipManager extends ProfileContentManager { } async getMutedReciprocal(remoteProfileId: number) { - const remoteKey = this.createRemoteRootKey(remoteProfileId); + const remoteKey = this.#createRemoteRootKey(remoteProfileId); const localMuted = (await this.getAllMuted()).includes(remoteProfileId); const remoteMuted = await Redis.Database.sismember(Redis.buildKey(remoteKey, Redis.KeyGroups.Profiles.Relationships.Muted), this.profileId); @@ -189,7 +190,7 @@ export class ProfileRelationshipManager extends ProfileContentManager { } async getIgnoredReciprocal(remoteProfileId: number) { - const remoteKey = this.createRemoteRootKey(remoteProfileId); + const remoteKey = this.#createRemoteRootKey(remoteProfileId); const localIgnored = (await this.getAllMuted()).includes(remoteProfileId); const remoteIgnored = await Redis.Database.sismember(Redis.buildKey(remoteKey, Redis.KeyGroups.Profiles.Relationships.Ignoring), this.profileId); @@ -211,12 +212,12 @@ export class ProfileRelationshipManager extends ProfileContentManager { } async sendPlayerFriendRequest(player: Profile) { - await Redis.Database.sadd(Redis.buildKey(this.createRemoteRootKey(player.getId()), Redis.KeyGroups.Profiles.Relationships.IncomingFriendRequests), this.profileId); + await Redis.Database.sadd(Redis.buildKey(this.#createRemoteRootKey(player.getId()), Redis.KeyGroups.Profiles.Relationships.IncomingFriendRequests), this.profileId); await Redis.Database.sadd(this.#outgoingFriends, player.getId()); } async removePlayerFriendRequest(player: Profile) { - await Redis.Database.srem(Redis.buildKey(this.createRemoteRootKey(player.getId()), Redis.KeyGroups.Profiles.Relationships.IncomingFriendRequests), this.profileId); + await Redis.Database.srem(Redis.buildKey(this.#createRemoteRootKey(player.getId()), Redis.KeyGroups.Profiles.Relationships.IncomingFriendRequests), this.profileId); await Redis.Database.srem(this.#outgoingFriends, player.getId()); } diff --git a/src/data/profile/reputation.ts b/src/data/profile/reputation.ts index 450eb21..a04d74e 100644 --- a/src/data/profile/reputation.ts +++ b/src/data/profile/reputation.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/data/profile/rooms.ts b/src/data/profile/rooms.ts index c84cc50..e1dfbe1 100644 --- a/src/data/profile/rooms.ts +++ b/src/data/profile/rooms.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -23,11 +23,15 @@ export class ProfileRoomsManager extends ProfileContentManager { #rootKey = Redis.buildKey( Redis.KeyGroups.Profiles.Root, this.profileId.toString(), - Redis.KeyGroups.Profiles.Avatar.Root + Redis.KeyGroups.Profiles.Rooms ); - async getRooms() { - // get player rooms + async getOwnedRoomIds() { + return (await Redis.Database.smembers(this.#rootKey)).map(val => parseInt(val)).filter(val => !isNaN(val)); + } + + async addOwnedRoomId(id: number) { + await Redis.Database.sadd(this.#rootKey, id.toString()); } } \ No newline at end of file diff --git a/src/data/profile/settings.ts b/src/data/profile/settings.ts index 5830979..767f2b6 100644 --- a/src/data/profile/settings.ts +++ b/src/data/profile/settings.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/data/profileevents.ts b/src/data/profileevents.ts index 7f8e693..26fec25 100644 --- a/src/data/profileevents.ts +++ b/src/data/profileevents.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/data/profiles.ts b/src/data/profiles.ts index 5e19046..bd09dee 100644 --- a/src/data/profiles.ts +++ b/src/data/profiles.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -16,23 +16,25 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ import { Redis } from "../db.ts"; +import Logging from "@proxnet/undead-logging"; import Dictionary from "./usernames.ts"; import { Config } from "../config.ts"; import { AuthType } from "./users.ts"; import * as JsonWebToken from "@gz/jwt"; import { TokenBaseFormat } from "../apiutils.ts"; -import { DeviceClass, RoomInstance, VRMovementMode } from "./live/types.ts"; +import { DeviceClass, VRMovementMode } from "./live/types.ts"; import { SettingKey } from "./content/settings.ts"; import { z } from "zod"; import { SignalRSocketHandler } from "../socket/socket.ts"; import { ProfileSettingsManager } from "./profile/settings.ts"; import { ProfileProgressionManager } from "./profile/progression.ts"; import { ProfileReputationManager } from "./profile/reputation.ts"; -import Logging from "@proxnet/undead-logging"; import { ProfileRelationshipManager } from "./profile/relationships.ts"; import { ProfileAvatarManager } from "./profile/avatar.ts"; import { EventManager } from "./baseevent.ts"; import { ProfileEvents, ProfileUpdatedEvent } from "./profileevents.ts"; +import { Instance } from "./live/instances.ts"; +import { ProfileRoomsManager } from "./profile/rooms.ts"; const config = Config.getConfig(); @@ -149,7 +151,6 @@ class Profile extends EventManager { return profile; } - // surely this can be written better static getExportAccount(id: number): Promise { return new Promise((resolve, _reject) => { Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Profiles.Root, id.toString(), Redis.KeyGroups.Profiles.Username)).then((val) => { @@ -177,6 +178,11 @@ class Profile extends EventManager { }); } + /** + * Get player account exports in bulk. Returns only profiles that exist. + * @param ids Player IDs + * @returns Promise - Array of Profiles + */ static async getExportAccountsBulk(ids: number[]) { const accs = await Promise.all( ids.map((val) => this.getExportAccount(val)), @@ -186,7 +192,7 @@ class Profile extends EventManager { #id: number; - #instance: RoomInstance | null = null; + #instance: Instance | null = null; #socket: SignalRSocketHandler | null = null; @@ -195,6 +201,7 @@ class Profile extends EventManager { Reputation: ProfileReputationManager; Relationships: ProfileRelationshipManager; Avatar: ProfileAvatarManager; + Rooms: ProfileRoomsManager; constructor(id: number) { super(); @@ -206,6 +213,7 @@ class Profile extends EventManager { this.Reputation = new ProfileReputationManager(this.#id); this.Relationships = new ProfileRelationshipManager(this.#id); this.Avatar = new ProfileAvatarManager(this.#id); + this.Rooms = new ProfileRoomsManager(this.#id); } #emitProfileUpdated() { @@ -216,7 +224,7 @@ class Profile extends EventManager { this.emit(ProfileEvents.BaseUpdated, ev); } - setInstance(instance: RoomInstance | null) { + setInstance(instance: Instance | null) { this.#instance = instance; } diff --git a/src/data/steam.ts b/src/data/steam.ts index e2f1564..504882f 100644 --- a/src/data/steam.ts +++ b/src/data/steam.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/data/usernames.ts b/src/data/usernames.ts index 1284a42..2ba96d1 100644 --- a/src/data/usernames.ts +++ b/src/data/usernames.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/data/users.ts b/src/data/users.ts index 14d72df..0d5b8c0 100644 --- a/src/data/users.ts +++ b/src/data/users.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -24,7 +24,6 @@ import { TokenBaseFormat } from "../apiutils.ts"; type UserInitOptions = { client_id: string; pubkey: string; - captcha?: string; }; export enum AuthType { diff --git a/src/db.ts b/src/db.ts index 4395d93..40273af 100644 --- a/src/db.ts +++ b/src/db.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -71,7 +71,7 @@ export const KeyGroups = { }, Content: { Root: "content", - Files: "file-meta", + Words: "charades" }, Profile_Usernames: "profile-usernames", PlatformAssociations: "platforms", diff --git a/src/discord.ts b/src/discord.ts index e89f8ce..940224d 100644 --- a/src/discord.ts +++ b/src/discord.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/main.ts b/src/main.ts index 88f9b15..d4ef688 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -27,15 +27,16 @@ import { decode } from "@gz/jwt"; import UnifiedProfile, { ProfileTokenFormat } from "./data/profiles.ts"; import { SocketHandoff } from "./socket/handoff.ts"; import { SignalRSocketHandler } from "./socket/socket.ts"; -import Rooms from "./data/content/rooms.ts"; import { GameConfigs } from "./data/config.ts"; -import { RootPath } from "./data/content/baseimages.ts"; +import { getVersion } from "./ver.ts"; +import Rooms from "./data/content/rooms.ts"; +import { PushNotificationId } from "./socket/types.ts"; const instanceId = generateRandomString(64); const log = new Log.default("Main"); -log.i(`Starting Galvanic Corrosion..`); +log.i(`Galvanic Corrosion '${await getVersion()}'`); const config = Config.getConfig(); @@ -158,7 +159,7 @@ try { }}, async (req: Request, info: Deno.ServeHandlerInfo) => { const path = new URL(req.url).pathname; const upgrade = req.headers.get('Upgrade') === 'websocket'; - log.n(`U:${upgrade}; ${info.remoteAddr.hostname}:${info.remoteAddr.port} ${req.method} ${path}`); + log.n(`SOCK U:${upgrade}; ${info.remoteAddr.hostname}:${info.remoteAddr.port} ${req.method} ${path}`); if (path === '/negotiate' && req.method == 'POST') return new Response(JSON.stringify({})); @@ -199,15 +200,17 @@ try { Deno.addSignalListener("SIGINT", () => { if (shuttingDown) return; shuttingDown = true; + for (const handoff of SocketHandoff.all()) handoff.complete(); + for (const sock of UnifiedProfile.getAllSockets()) sock.sendNotification(PushNotificationId.ModerationQuitGame); + }); + Deno.addSignalListener("SIGINT", () => { + if (shuttingDown) return; log.i(`Shutting down`); abort.abort(); // websockets http.close(); http.closeAllConnections(); }); - Deno.addSignalListener("SIGINT", () => { - for (const handoff of SocketHandoff.all()) handoff.complete(); - }); if (!(await UnifiedProfile.existsByName("Coach"))) UnifiedProfile.create({ username: "Coach", id: 1 }); // create Coach id 1 if they do not exist if (!(await UnifiedProfile.existsByName("Server"))) UnifiedProfile.create({ username: "Server", id: 2 }); // create Server id 2 if they do not exist diff --git a/src/path.ts b/src/path.ts new file mode 100644 index 0000000..eb03948 --- /dev/null +++ b/src/path.ts @@ -0,0 +1,20 @@ +/* Galvanic Corrosion - Rec Room custom server for communities. + +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . */ + +import { platform } from "node:process"; + +export const RootPath = Deno.mainModule.substring(platform == 'win32' ? 8 : 7, Deno.mainModule.length - 11); \ No newline at end of file diff --git a/src/routes/account.ts b/src/routes/account.ts index dd6d893..c509858 100644 --- a/src/routes/account.ts +++ b/src/routes/account.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/routes/account/account.ts b/src/routes/account/account.ts index 7e293c9..de1be8a 100644 --- a/src/routes/account/account.ts +++ b/src/routes/account/account.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -15,14 +15,17 @@ GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -import { APIUtils } from "../../apiutils.ts"; +import { APIUtils, NoBody } from "../../apiutils.ts"; import express from "express"; import UnifiedProfile, { Profile } from "../../data/profiles.ts"; import { z } from "zod"; import { AuthType } from "../../data/users.ts"; +import Logging from "@proxnet/undead-logging"; export const route = APIUtils.createRouter("/account"); +const log = new Logging("AccountRoute"); + const CreateAccountRequestBodySchema = z.object({ platform: z.string(), platformId: z.string(), @@ -70,11 +73,9 @@ route.router.get("/bulk", } else if (typeof rq.query.id == "string") { - const id = parseInt(rq.query.id, 10); + const id = parseInt(rq.query.id); if (isNaN(id)) { - rs.json( - APIUtils.genericResponseFormat(true, "Query data error"), - ); + rs.json(APIUtils.genericResponseFormat(true, "Query data error")); return; } else { rs.json([await Profile.getExportAccount(id)].filter((val) => val !== null)); @@ -116,7 +117,7 @@ route.router.put("/me/displayname", express.urlencoded({ extended: true }), APIUtils.validateRequestBody(DisplayNameUpdateSchema), - (rq: express.Request<{}, {}, DisplayNameUpdate>, rs: express.Response, nxt: express.NextFunction) => { + (rq: express.Request, rs: express.Response, nxt: express.NextFunction) => { rs.locals.profile.setDisplayName(rq.body.displayName); nxt(); }, diff --git a/src/routes/account/parentalcontrol.ts b/src/routes/account/parentalcontrol.ts index e551d3f..590df79 100644 --- a/src/routes/account/parentalcontrol.ts +++ b/src/routes/account/parentalcontrol.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/routes/api.ts b/src/routes/api.ts index afb4d09..0935838 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -39,6 +39,7 @@ import { route as CommunityBoardRoute } from "./api/communityboard.ts"; import { route as PlayerEventsRoute } from "./api/playerevents.ts"; import { route as StorefrontsRoute } from "./api/storefronts.ts"; import { route as AnnouncementRoute } from "./api/announcement.ts"; +import { route as ActivitiesRoute } from "./api/activities.ts"; export const route = APIUtils.createRouter("/api"); @@ -64,4 +65,5 @@ route.router.use(ImagesRoute.path, ImagesRoute.router); route.router.use(CommunityBoardRoute.path, CommunityBoardRoute.router); route.router.use(PlayerEventsRoute.path, PlayerEventsRoute.router); route.router.use(StorefrontsRoute.path, StorefrontsRoute.router); -route.router.use(AnnouncementRoute.path, AnnouncementRoute.router); \ No newline at end of file +route.router.use(AnnouncementRoute.path, AnnouncementRoute.router); +route.router.use(ActivitiesRoute.path, ActivitiesRoute.router); \ No newline at end of file diff --git a/src/routes/api/PlayerReporting.ts b/src/routes/api/PlayerReporting.ts index 0b52f32..57e329e 100644 --- a/src/routes/api/PlayerReporting.ts +++ b/src/routes/api/PlayerReporting.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/routes/api/activities.ts b/src/routes/api/activities.ts new file mode 100644 index 0000000..7d9ef75 --- /dev/null +++ b/src/routes/api/activities.ts @@ -0,0 +1,34 @@ +/* Galvanic Corrosion - Rec Room custom server for communities. + +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . */ + +import { APIUtils } from "../../apiutils.ts"; +import { getWords } from "../../data/content/activities.ts"; + +export const route = APIUtils.createRouter("/activities"); + +const rateLimit = new APIUtils.RateLimiter(); + +const words = getWords(); +route.router.get('/charades/v1/words', + + rateLimit.middle(), + + (_rq, rs) => { + rs.json(words); + }, + +); \ No newline at end of file diff --git a/src/routes/api/announcement.ts b/src/routes/api/announcement.ts index 30d49ad..45ae93b 100644 --- a/src/routes/api/announcement.ts +++ b/src/routes/api/announcement.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/routes/api/avatar.ts b/src/routes/api/avatar.ts index 1065a35..cc4cfc0 100644 --- a/src/routes/api/avatar.ts +++ b/src/routes/api/avatar.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/routes/api/challenge.ts b/src/routes/api/challenge.ts index 522e97e..67140a1 100644 --- a/src/routes/api/challenge.ts +++ b/src/routes/api/challenge.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/routes/api/checklist.ts b/src/routes/api/checklist.ts index e5b4690..dc09ec4 100644 --- a/src/routes/api/checklist.ts +++ b/src/routes/api/checklist.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -16,6 +16,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ import { APIUtils } from "../../apiutils.ts"; +import { ObjectiveType } from "../../data/objectives.ts"; import { AuthType } from "../../data/users.ts"; export const route = APIUtils.createRouter('/checklist'); @@ -25,6 +26,171 @@ route.router.get('/v1/current', APIUtils.Authentication, APIUtils.AuthenticationType(AuthType.Game), - APIUtils.emptyArrayResponse + (_rq, rs) => { + rs.json([ + { + Order: 1, + Objective: ObjectiveType.CompleteAnyDaily, + Count: 1, + CreditAmount: -1 + }, + { + Order: 1, + Objective: ObjectiveType.CompleteAnyDaily, + Count: 1, + CreditAmount: -1 + }, + { + Order: 1, + Objective: ObjectiveType.CompleteAnyDaily, + Count: 1, + CreditAmount: -1 + }, + { + Order: 1, + Objective: ObjectiveType.CompleteAnyDaily, + Count: 1, + CreditAmount: -1 + }, + { + Order: 1, + Objective: ObjectiveType.CompleteAnyDaily, + Count: 1, + CreditAmount: -1 + }, + { + Order: 1, + Objective: ObjectiveType.CompleteAnyDaily, + Count: 1, + CreditAmount: -1 + }, + { + Order: 1, + Objective: ObjectiveType.CompleteAnyDaily, + Count: 1, + CreditAmount: -1 + }, + { + Order: 1, + Objective: ObjectiveType.CompleteAnyDaily, + Count: 1, + CreditAmount: -1 + }, + { + Order: 1, + Objective: ObjectiveType.CompleteAnyDaily, + Count: 1, + CreditAmount: -1 + }, + { + Order: 1, + Objective: ObjectiveType.CompleteAnyDaily, + Count: 1, + CreditAmount: -1 + }, + { + Order: 1, + Objective: ObjectiveType.CompleteAnyDaily, + Count: 1, + CreditAmount: -1 + }, + { + Order: 1, + Objective: ObjectiveType.CompleteAnyDaily, + Count: 1, + CreditAmount: -1 + }, + { + Order: 1, + Objective: ObjectiveType.CompleteAnyDaily, + Count: 1, + CreditAmount: -1 + }, + { + Order: 1, + Objective: ObjectiveType.CompleteAnyDaily, + Count: 1, + CreditAmount: -1 + }, + { + Order: 1, + Objective: ObjectiveType.CompleteAnyDaily, + Count: 1, + CreditAmount: -1 + }, + { + Order: 1, + Objective: ObjectiveType.CompleteAnyDaily, + Count: 1, + CreditAmount: -1 + }, + { + Order: 1, + Objective: ObjectiveType.CompleteAnyDaily, + Count: 1, + CreditAmount: -1 + }, + { + Order: 1, + Objective: ObjectiveType.CompleteAnyDaily, + Count: 1, + CreditAmount: -1 + }, + { + Order: 1, + Objective: ObjectiveType.CompleteAnyDaily, + Count: 1, + CreditAmount: -1 + }, + { + Order: 1, + Objective: ObjectiveType.CompleteAnyDaily, + Count: 1, + CreditAmount: -1 + }, + { + Order: 1, + Objective: ObjectiveType.CompleteAnyDaily, + Count: 1, + CreditAmount: -1 + }, + { + Order: 1, + Objective: ObjectiveType.CompleteAnyDaily, + Count: 1, + CreditAmount: -1 + }, + { + Order: 1, + Objective: ObjectiveType.CompleteAnyDaily, + Count: 1, + CreditAmount: -1 + }, + { + Order: 1, + Objective: ObjectiveType.CompleteAnyDaily, + Count: 1, + CreditAmount: -1 + }, + { + Order: 1, + Objective: ObjectiveType.CompleteAnyDaily, + Count: 1, + CreditAmount: -1 + }, + { + Order: 1, + Objective: ObjectiveType.CompleteAnyDaily, + Count: 1, + CreditAmount: -1 + }, + { + Order: 1, + Objective: ObjectiveType.CompleteAnyDaily, + Count: 1, + CreditAmount: -1 + } + ]); + }, ); \ No newline at end of file diff --git a/src/routes/api/communityboard.ts b/src/routes/api/communityboard.ts index d6d733d..6e80c3f 100644 --- a/src/routes/api/communityboard.ts +++ b/src/routes/api/communityboard.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/routes/api/config.ts b/src/routes/api/config.ts index edc1245..a939084 100644 --- a/src/routes/api/config.ts +++ b/src/routes/api/config.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/routes/api/consumables.ts b/src/routes/api/consumables.ts index 96d1cc3..339358c 100644 --- a/src/routes/api/consumables.ts +++ b/src/routes/api/consumables.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/routes/api/equipment.ts b/src/routes/api/equipment.ts index 427e6e6..601c726 100644 --- a/src/routes/api/equipment.ts +++ b/src/routes/api/equipment.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/routes/api/gameconfigs.ts b/src/routes/api/gameconfigs.ts index d49b3b4..a442247 100644 --- a/src/routes/api/gameconfigs.ts +++ b/src/routes/api/gameconfigs.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/routes/api/images.ts b/src/routes/api/images.ts index 46cf4ac..fb9f50d 100644 --- a/src/routes/api/images.ts +++ b/src/routes/api/images.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/routes/api/messages.ts b/src/routes/api/messages.ts index 04a249e..5776b57 100644 --- a/src/routes/api/messages.ts +++ b/src/routes/api/messages.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/routes/api/objectives.ts b/src/routes/api/objectives.ts index 939a8c1..a74e60e 100644 --- a/src/routes/api/objectives.ts +++ b/src/routes/api/objectives.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -36,6 +36,17 @@ route.router.get('/v1/myprogress', ); +route.router.post('/v1/updateobjective', + + APIUtils.Authentication, + APIUtils.AuthenticationType(AuthType.Game), + + (_rq, rs) => { + rs.sendStatus(200); + }, + +) + interface ClearGroupRequestBody { Group: number } diff --git a/src/routes/api/playerReputation.ts b/src/routes/api/playerReputation.ts index fcedc1b..a5769e5 100644 --- a/src/routes/api/playerReputation.ts +++ b/src/routes/api/playerReputation.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -57,7 +57,7 @@ route.router.post('/v1/bulk', express.urlencoded({ extended: true }), APIUtils.validateRequestBody(reputationBulkSchema), - async (rq: express.Request, rs: express.Response) => { + async (rq: express.Request, rs: express.Response) => { if (typeof rq.body.Ids == 'object') { const reputations = rq.body.Ids .map(id => parseInt(id)).filter(id => !isNaN(id)) // parse as int[] and filter out non-numbers diff --git a/src/routes/api/playerevents.ts b/src/routes/api/playerevents.ts index 5be5c7d..116fa39 100644 --- a/src/routes/api/playerevents.ts +++ b/src/routes/api/playerevents.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/routes/api/players.ts b/src/routes/api/players.ts index ecb7328..c535248 100644 --- a/src/routes/api/players.ts +++ b/src/routes/api/players.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -68,7 +68,7 @@ route.router.post('/v1/progression/bulk', express.urlencoded({ extended: true }), APIUtils.validateRequestBody(progressionBulkSchema), - async (rq: express.Request, rs: express.Response) => { + async (rq: express.Request, rs: express.Response) => { if (typeof rq.body.Ids == 'object') { const progressions = rq.body.Ids .map(id => parseInt(id)).filter(id => !isNaN(id)) // filter out non-numbers diff --git a/src/routes/api/playersubscriptions.ts b/src/routes/api/playersubscriptions.ts index 791a009..18c9680 100644 --- a/src/routes/api/playersubscriptions.ts +++ b/src/routes/api/playersubscriptions.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/routes/api/quickplay.ts b/src/routes/api/quickplay.ts index fd7073e..a897ae0 100644 --- a/src/routes/api/quickplay.ts +++ b/src/routes/api/quickplay.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/routes/api/relationships.ts b/src/routes/api/relationships.ts index 897563c..fde0d68 100644 --- a/src/routes/api/relationships.ts +++ b/src/routes/api/relationships.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/routes/api/rooms.ts b/src/routes/api/rooms.ts index d90f474..741dfe9 100644 --- a/src/routes/api/rooms.ts +++ b/src/routes/api/rooms.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -15,11 +15,13 @@ GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -import { APIUtils } from "../../apiutils.ts"; +import { z } from "zod"; +import { APIUtils, NoBody } from "../../apiutils.ts"; import Rooms from "../../data/content/rooms.ts"; import { RoomDataTypes } from "../../data/content/rooms/DataTypes.ts"; import { AuthType } from "../../data/users.ts"; import express from "express"; +import { RoomFactory } from "../../data/content/rooms/RoomFactory.ts"; export const route = APIUtils.createRouter("/rooms"); @@ -54,7 +56,19 @@ route.router.get('/v2/myrooms', APIUtils.Authentication, APIUtils.AuthenticationType(AuthType.Game), - APIUtils.emptyArrayResponse + async (_rq, rs) => { + const ids = await rs.locals.profile.Rooms.getOwnedRoomIds(); + if (ids.length == 0) { + rs.json([]); + return; + } + + const roomFactoriesPreInit = ids.map(id => new RoomFactory({ id: id })); + const roomFactories = (await Promise.all(roomFactoriesPreInit.map(factory => factory.init()))).filter(val => val !== null); + const detailsPromises = (await Promise.all(roomFactories.map(factory => factory.export()))); + + rs.json(detailsPromises.map(roomDetails => roomDetails.Room)); + }, ); @@ -119,8 +133,58 @@ route.router.post('/v1/roomRolePermissions', APIUtils.Authentication, APIUtils.AuthenticationType(AuthType.Game), - (rq, rs) => { + (_rq, rs) => { rs.sendStatus(200); }, ); + +route.router.get('/v1/agRoomIds', + + APIUtils.Authentication, + APIUtils.AuthenticationType(AuthType.Game), + + async (_rq, rs) => { + + const rooms = await Rooms.getAllBuiltinRoomGenerations(); + rs.json(rooms.map(det => det.Room.RoomId)); + + }, + +); + +const CloneRoomSchema = z.object({ + Name: z.string(), + RoomId: z.number() +}); +interface CloneRoomBody { + Name: string, + RoomId: number +} + +route.router.post('/v1/clone', + + APIUtils.Authentication, + APIUtils.AuthenticationType(AuthType.Game), + express.json(), + APIUtils.validateRequestBody(CloneRoomSchema), + + async (rq: express.Request, rs: express.Response) => { + + const room = await Rooms.cloneRoom(rq.body.RoomId, rq.body.Name, rs.locals.profile); + + const masterRoomFactory = await new RoomFactory({ id: rq.body.RoomId }).init(); + + rs.json({ + Result: room.result, + RoomDetails: room.result == RoomDataTypes.CreateModifyRoomStatus.Success ? await room.factory?.export() : await masterRoomFactory?.export() + }); + + if ( + room.result == RoomDataTypes.CreateModifyRoomStatus.Success + && room.factory + ) rs.locals.profile.Rooms.addOwnedRoomId(room.factory.RoomId); + + }, + +); \ No newline at end of file diff --git a/src/routes/api/settings.ts b/src/routes/api/settings.ts index 5afa691..2b167ff 100644 --- a/src/routes/api/settings.ts +++ b/src/routes/api/settings.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/routes/api/storefronts.ts b/src/routes/api/storefronts.ts index e7a4b73..4353b6d 100644 --- a/src/routes/api/storefronts.ts +++ b/src/routes/api/storefronts.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -18,7 +18,7 @@ along with this program. If not, see . */ import { APIUtils } from "../../apiutils.ts"; import express from "express"; import { AuthType } from "../../data/users.ts"; -import { CurrencyType, StorefrontBalanceType } from "../../data/content/storefronts.ts"; +import { StorefrontBalanceType } from "../../data/content/storefronts.ts"; export const route = APIUtils.createRouter('/storefronts'); diff --git a/src/routes/api/versioncheck.ts b/src/routes/api/versioncheck.ts index 0f3590f..2fa04b2 100644 --- a/src/routes/api/versioncheck.ts +++ b/src/routes/api/versioncheck.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/routes/auth.ts b/src/routes/auth.ts index c097f5f..5568d2f 100644 --- a/src/routes/auth.ts +++ b/src/routes/auth.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/routes/auth/account.ts b/src/routes/auth/account.ts index 5225e4e..b92e768 100644 --- a/src/routes/auth/account.ts +++ b/src/routes/auth/account.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/routes/auth/cachedlogin.ts b/src/routes/auth/cachedlogin.ts index 610800a..728eca8 100644 --- a/src/routes/auth/cachedlogin.ts +++ b/src/routes/auth/cachedlogin.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/routes/auth/connect.ts b/src/routes/auth/connect.ts index 3055bba..fb89a09 100644 --- a/src/routes/auth/connect.ts +++ b/src/routes/auth/connect.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/routes/cdn.ts b/src/routes/cdn.ts index 1aa472c..d3e41b5 100644 --- a/src/routes/cdn.ts +++ b/src/routes/cdn.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/routes/cdn/config.ts b/src/routes/cdn/config.ts index 4fa970f..4b95641 100644 --- a/src/routes/cdn/config.ts +++ b/src/routes/cdn/config.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/routes/img.ts b/src/routes/img.ts index e9ff74c..3f5e808 100644 --- a/src/routes/img.ts +++ b/src/routes/img.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/routes/match.ts b/src/routes/match.ts index b633eb2..a103f9a 100644 --- a/src/routes/match.ts +++ b/src/routes/match.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/routes/match/goto.ts b/src/routes/match/goto.ts index 4c075bd..f4d3397 100644 --- a/src/routes/match/goto.ts +++ b/src/routes/match/goto.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -16,11 +16,12 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ import Logging from "@proxnet/undead-logging"; -import { APIUtils } from "../../apiutils.ts"; +import { APIUtils, NoBody } from "../../apiutils.ts"; import Matchmaking from "../../data/live/base.ts"; import { MatchmakingErrorCode } from "../../data/live/types.ts"; import { AuthType } from "../../data/users.ts"; import express from "express"; +import { z } from "zod"; const log = new Logging("MatchGotoRoute"); @@ -31,13 +32,31 @@ interface MatchmakingParams { subRoomName?: string } +const ProperCaseBooleanSchema = z.preprocess((val) => { + if (val === "True") return true; + if (val === "False") return false; + if (typeof val === "boolean") return val; // allow raw booleans too + return val; // will fail validation +}, z.boolean()); + +interface MatchmakingOptions { + CreatePrivateInstance?: string, + BypassMovementModeRestriction?: string +} + route.router.post('/room/:roomName', APIUtils.Authentication, APIUtils.AuthenticationType(AuthType.Game), APIUtils.startTimer, + express.urlencoded({ extended: true }), + APIUtils.validateRequestBody(z.object({ + CreatePrivateInstance: ProperCaseBooleanSchema.optional(), + BypassMovementModeRestriction: ProperCaseBooleanSchema.optional() + })), - async (rq: express.Request, rs: express.Response, nxt: express.NextFunction) => { + async (rq: express.Request, rs: express.Response, nxt: express.NextFunction) => { + log.d(`Player ${rs.locals.profile.getId()} is requesting to matchmake\n Room: '${rq.params.roomName}'\n Body: ${JSON.stringify(rq.body)}`); if (!rq.params.roomName) { log.d('Matchmake failed: No room specified'); rs.json({ @@ -45,7 +64,11 @@ route.router.post('/room/:roomName', }); return; } - rs.json(await Matchmaking.matchmake({ profile: rs.locals.profile, roomName: rq.params.roomName })); + rs.json(await Matchmaking.matchmake({ + profile: rs.locals.profile, + roomName: rq.params.roomName, + private: rq.body.CreatePrivateInstance ? rq.body.CreatePrivateInstance == 'True' : undefined + })); nxt(); }, @@ -57,8 +80,10 @@ route.router.post('/room/:roomName/:subRoomName', APIUtils.Authentication, APIUtils.AuthenticationType(AuthType.Game), APIUtils.startTimer, + express.urlencoded({ extended: true }), - async (rq: express.Request, rs: express.Response, nxt: express.NextFunction) => { + async (rq: express.Request, rs: express.Response, nxt: express.NextFunction) => { + log.d(`Player ${rs.locals.profile.getId()} is requesting to matchmake\n Room: '${rq.params.roomName}'\n Subroom: '${rq.params.subRoomName}'\n Body: ${JSON.stringify(rq.body)}`); if (!rq.params.roomName) { log.d('Matchmake failed: No room specified'); rs.json({ @@ -66,7 +91,12 @@ route.router.post('/room/:roomName/:subRoomName', }); return; } - rs.json(await Matchmaking.matchmake({ profile: rs.locals.profile, roomName: rq.params.roomName, subRoomName: rq.params.subRoomName })); + rs.json(await Matchmaking.matchmake({ + profile: rs.locals.profile, + roomName: rq.params.roomName, + subRoomName: rq.params.subRoomName, + private: rq.body.CreatePrivateInstance ? rq.body.CreatePrivateInstance == 'True' : undefined + })); nxt(); }, diff --git a/src/routes/match/player.ts b/src/routes/match/player.ts index 06dc66c..e65f568 100644 --- a/src/routes/match/player.ts +++ b/src/routes/match/player.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -25,7 +25,6 @@ import Logging from "@proxnet/undead-logging"; import UnifiedProfile from "../../data/profiles.ts"; import { PlayerStatusVisibility, VRMovementMode } from "../../data/live/types.ts"; import { SettingKey } from "../../data/content/settings.ts"; -import Instances from "../../data/live/instances.ts"; const log = new Logging("MatchPlayerRoute"); @@ -86,7 +85,7 @@ route.router.post('/logout', APIUtils.validateRequestBody(LoginSchema), (_rq, rs) => { - Instances.removePlayerFromCurrentInstance(rs.locals.profile); + rs.locals.profile.getInstance()?.removePlayer(rs.locals.profile); rs.sendStatus(200); } @@ -129,10 +128,10 @@ route.router.put('/statusvisibility', ); interface VRMovementModeBody { - vrMovementMode: PlayerStatusVisibility + vrMovementMode: VRMovementMode } const VRMovementModeSchema = z.object({ - vrMovementMode: z.nativeEnum(VRMovementMode) + vrMovementMode: z.enum(Object.values(VRMovementMode).map(String) as [string, ...string[]]) }); route.router.put('/vrmovementmode', @@ -155,7 +154,7 @@ route.router.put('/photonregionpings', APIUtils.Authentication, APIUtils.AuthenticationType(AuthType.Game), - (rq, rs) => { + (_rq, rs) => { rs.sendStatus(200); } diff --git a/src/routes/match/room.ts b/src/routes/match/room.ts index 58a58d8..974f3f0 100644 --- a/src/routes/match/room.ts +++ b/src/routes/match/room.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -48,8 +48,8 @@ route.router.get('/:id/instances', roomId: parsedId, subRoomId: instance.subRoomId, isFull: instance.isFull, - createdAt: new Date().toISOString(), // TODO: rewrite instance - create instance class rather than using sets - include datetime when instance was created - playerIds: Instances.getInstancePlayers(instance).values().toArray().map(profile => profile.getId()) + createdAt: instance.timeCreated, + playerIds: instance.getAllPlayers().map(profile => profile.getId()) }))); }, diff --git a/src/routes/nameserver.ts b/src/routes/nameserver.ts index e5a6dcf..9b0744a 100644 --- a/src/routes/nameserver.ts +++ b/src/routes/nameserver.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/routes/user.ts b/src/routes/user.ts index 090d562..04da4b4 100644 --- a/src/routes/user.ts +++ b/src/routes/user.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/socket/handoff.ts b/src/socket/handoff.ts index a78aa7e..67f9c35 100644 --- a/src/socket/handoff.ts +++ b/src/socket/handoff.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/socket/route.ts b/src/socket/route.ts index 1b6f708..5c7f5a6 100644 --- a/src/socket/route.ts +++ b/src/socket/route.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/socket/socket.ts b/src/socket/socket.ts index ed5a9a4..239772c 100644 --- a/src/socket/socket.ts +++ b/src/socket/socket.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -34,10 +34,9 @@ import { import { SocketTarget } from "./targets/targetbase.ts"; import { PlayerSocketSubscriptionTarget } from "./targets/SubscribeToPlayers.ts"; import Presence from "../data/live/presence.ts"; -import Instances from "../data/live/instances.ts"; import Matchmaking from "../data/live/base.ts"; -const logmessages = true; +const logmessages = false; export class SignalRSocketHandler { @@ -50,6 +49,8 @@ export class SignalRSocketHandler { #PeriodicalId: number; + #killed = false; + constructor(socket: WebSocket, player: Profile) { this.#socket = socket; @@ -62,6 +63,7 @@ export class SignalRSocketHandler { this.#Targets.set('SubscribeToPlayers', new PlayerSocketSubscriptionTarget(this)); this.#PeriodicalId = setInterval(async () => { + if (this.#killed) return; if (this.#socket.readyState !== this.#socket.CLOSED) { const pres = await Presence.get(this.#profile); this.sendNotification("PresenceUpdate", await pres.export()); @@ -69,9 +71,18 @@ export class SignalRSocketHandler { } }, 8000); + this.#socket.onclose = (ev) => { + this.#log.d(`Close reason: ${ev.reason}`); + } + } async #dispatchTarget(target: string, args: unknown): Promise { + if (this.#killed) { + const error = "Tried to dispatch socket target on dead socket"; + this.#log.w(error); + return { type: TargetResultType.Failure, err: error }; + } const targetExec = this.#Targets.get(target); if (!targetExec) return { type: TargetResultType.NotATarget } as TargetResultNotATarget; else { @@ -152,6 +163,7 @@ export class SignalRSocketHandler { destroy(sock: SignalRSocketHandler, internal: boolean | undefined = false) { return () => { + sock.#killed = true; clearInterval(sock.#PeriodicalId); sock.sendRaw({ type: 7, error: "Socket closed" }); if (!internal) sock.#socket.close(); @@ -160,7 +172,7 @@ export class SignalRSocketHandler { for (const target of sock.#Targets.values()) target.destroy(); - Instances.removePlayerFromCurrentInstance(this.#profile); + this.#profile.getInstance()?.removePlayer(this.#profile); Matchmaking.deleteLoginLock(this.#profile); } } @@ -174,13 +186,13 @@ export class SignalRSocketHandler { } } - sendNotification(id: PushNotificationId | string, args: object) { + sendNotification(id: PushNotificationId | string, args?: object) { const msg: SignalRMessage = { type: SignalMessageType.Invocation, target: "Notification", arguments: [JSON.stringify({ Id: id, - Msg: args + Msg: args ? args : {} })] } this.sendRaw(msg); diff --git a/src/socket/targets/SubscribeToPlayers.ts b/src/socket/targets/SubscribeToPlayers.ts index 28777ae..6693869 100644 --- a/src/socket/targets/SubscribeToPlayers.ts +++ b/src/socket/targets/SubscribeToPlayers.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/socket/targets/targetbase.ts b/src/socket/targets/targetbase.ts index be38d8d..9dedfdb 100644 --- a/src/socket/targets/targetbase.ts +++ b/src/socket/targets/targetbase.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/socket/types.ts b/src/socket/types.ts index 1e52cd8..95e9f88 100644 --- a/src/socket/types.ts +++ b/src/socket/types.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/types/express.ts b/src/types/express.ts index 0ae161b..9ab4dec 100644 --- a/src/types/express.ts +++ b/src/types/express.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/types/http.ts b/src/types/http.ts index 5388628..618eba5 100644 --- a/src/types/http.ts +++ b/src/types/http.ts @@ -1,6 +1,6 @@ /* Galvanic Corrosion - Rec Room custom server for communities. -Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/ver.ts b/src/ver.ts new file mode 100644 index 0000000..8a8bb79 --- /dev/null +++ b/src/ver.ts @@ -0,0 +1,25 @@ +/* Galvanic Corrosion - Rec Room custom server for communities. + +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . */ + +export async function getVersion(): Promise<'development' | 'unknown ver' | string> { + try { + const ver = await import('../ver.ts'); + return ver.Version; + } catch { + return "unknown ver" + } +} \ No newline at end of file diff --git a/ver.ts b/ver.ts new file mode 100644 index 0000000..3f851eb --- /dev/null +++ b/ver.ts @@ -0,0 +1,18 @@ +/* Galvanic Corrosion - Rec Room custom server for communities. + +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . */ + +export const Version = 'development'; \ No newline at end of file