build config changes
All checks were successful
Galvanic Corrosion Cross-Compile / build (push) Successful in 1m50s

* Commit hash shipped with builds
* Post & pre-build events
* Objective fixes
* Orientation challenge filler
* Custom Rooms base
    - Currently cannot save rooms (CDN not set up)
* Moved root path to path.ts
* Room cloning
* Rewrote instances - the whole thing
* Relationships are still untested
* Charades Words
* AG Room fetch
* Private room matchmaking
* Socket fixes
This commit is contained in:
2025-05-12 09:07:59 -04:00
parent 6a249ef813
commit 83440a9245
96 changed files with 1201 additions and 436 deletions

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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 <https://www.gnu.org/licenses/>. */
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,

View File

@@ -0,0 +1,47 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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 <https://www.gnu.org/licenses/>. */
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)))
}

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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 <https://www.gnu.org/licenses/>. */
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,94 +0,0 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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 <https://www.gnu.org/licenses/>. */
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<ArrayBufferLike>, 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) {
}

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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 <https://www.gnu.org/licenses/>. */
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();
}));

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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";

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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() };
}

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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<RoomInstance, Set<Profile>> = new Map();
/**
* `Map<roomId (number), RoomInstance>`
* `Map<roomId (number), Instance>`
*/
const instanceMap: Map<number, Set<RoomInstance>> = new Map();
const instanceSet: Set<Instance> = new Set();
export class Instance {
#players = new Set<Profile>();
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<Profile> {
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<RoomInstance>, 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);
}
}

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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());
}

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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());
}
}

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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 <https://www.gnu.org/licenses/>. */
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<SelfAccountExport | null> {
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;
}

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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 {

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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",

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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<Deno.NetAddr>) => {
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

20
src/path.ts Normal file
View File

@@ -0,0 +1,20 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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 <https://www.gnu.org/licenses/>. */
import { platform } from "node:process";
export const RootPath = Deno.mainModule.substring(platform == 'win32' ? 8 : 7, Deno.mainModule.length - 11);

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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 <https://www.gnu.org/licenses/>. */
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<NoBody, NoBody, DisplayNameUpdate>, rs: express.Response, nxt: express.NextFunction) => {
rs.locals.profile.setDisplayName(rq.body.displayName);
nxt();
},

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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);
route.router.use(AnnouncementRoute.path, AnnouncementRoute.router);
route.router.use(ActivitiesRoute.path, ActivitiesRoute.router);

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -0,0 +1,34 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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 <https://www.gnu.org/licenses/>. */
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);
},
);

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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 <https://www.gnu.org/licenses/>. */
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
}
]);
},
);

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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
}

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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<NoBody, {}, ReputationBulkBody>, rs: express.Response) => {
async (rq: express.Request<NoBody, NoBody, ReputationBulkBody>, 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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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<NoBody, {}, ProgressionBulkBody>, rs: express.Response) => {
async (rq: express.Request<NoBody, NoBody, ProgressionBulkBody>, 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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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 <https://www.gnu.org/licenses/>. */
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<NoBody, NoBody, CloneRoomBody>, 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);
},
);

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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 <https://www.gnu.org/licenses/>. */
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');

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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 <https://www.gnu.org/licenses/>. */
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<MatchmakingParams>, rs: express.Response, nxt: express.NextFunction) => {
async (rq: express.Request<MatchmakingParams, NoBody, MatchmakingOptions>, 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<MatchmakingParams>, rs: express.Response, nxt: express.NextFunction) => {
async (rq: express.Request<MatchmakingParams, NoBody, MatchmakingOptions>, 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();
},

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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);
}

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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())
})));
},

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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<T = unknown>(target: string, args: unknown): Promise<TargetResult> {
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);

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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

View File

@@ -1,6 +1,6 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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
src/ver.ts Normal file
View File

@@ -0,0 +1,25 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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 <https://www.gnu.org/licenses/>. */
export async function getVersion(): Promise<'development' | 'unknown ver' | string> {
try {
const ver = await import('../ver.ts');
return ver.Version;
} catch {
return "unknown ver"
}
}