FLINT AND STEEL!!!! THE NETHER!!!!!! RELEASE!!!!!!!!!
* Account bio support (fetch only route right now)
* Room cloning fixes
- Dorm Room cloning is still broken
* Instance changing fixes
* Presence: VRMovementMode and StatusVisibility updates automatically
* Routes for the above two properties
* Settings can take numbers, too (enums)
* No microtransations in my game (parental controls)
* A whole lotta routes for various unfinished but planned features
- Equipment
- Consumables
- Objectives
- Checklist (orientation rewards)
- Objectives (three daily tasks)
- Image metadata
- Community Board
- Player Events
- Storefronts
* Matchmaking instance querying
- Empty instances are not yet cleared
* Avatar items, saved avatars, save current avatar routes
* No loading screen tips for now
* Send presence at an interval over the socket
- Error FROSTBITE is reported in the game logs during bootup sometimes. Maybe due to the lack of ping messages?
* Socket push notifications
Note to self: Set up deno compilation in runners on gitea
This commit is contained in:
@@ -27,10 +27,10 @@
|
|||||||
{
|
{
|
||||||
"Name": "DormRoom",
|
"Name": "DormRoom",
|
||||||
"ReplicationId": "68251132-5662-5c34-08b1-4a830a27955b",
|
"ReplicationId": "68251132-5662-5c34-08b1-4a830a27955b",
|
||||||
"Description": "Your private room",
|
"Description": "Your private room.",
|
||||||
"Accessibility": 2,
|
"Accessibility": 2,
|
||||||
"SupportsLevelVoting": false,
|
"SupportsLevelVoting": false,
|
||||||
"CloningAllowed": false,
|
"CloningAllowed": true,
|
||||||
"SupportsScreens": true,
|
"SupportsScreens": true,
|
||||||
"SupportsWalkVR": true,
|
"SupportsWalkVR": true,
|
||||||
"SupportsTeleportVR": true,
|
"SupportsTeleportVR": true,
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ class RoomsBase {
|
|||||||
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.Name, details.Room.Name),
|
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.Name, details.Room.Name),
|
||||||
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.Description, details.Room.Description),
|
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.Description, details.Room.Description),
|
||||||
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.CreatorPlayerId, details.Room.CreatorPlayerId),
|
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.CreatorPlayerId, details.Room.CreatorPlayerId),
|
||||||
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.ImageName, `DefaultProfileImage.png`),
|
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.ImageName, details.Room.ImageName),
|
||||||
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.State, details.Room.State),
|
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.State, details.Room.State),
|
||||||
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.Accessibility, details.Room.Accessibility),
|
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.Accessibility, details.Room.Accessibility),
|
||||||
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.SupportsLevelVoting, `${details.Room.SupportsLevelVoting}`),
|
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.SupportsLevelVoting, `${details.Room.SupportsLevelVoting}`),
|
||||||
@@ -144,7 +144,7 @@ class RoomsBase {
|
|||||||
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.AllowsJuniors, `${details.Room.AllowsJuniors}`),
|
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.AllowsJuniors, `${details.Room.AllowsJuniors}`),
|
||||||
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.RoomWarningMask, details.Room.RoomWarningMask),
|
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.RoomWarningMask, details.Room.RoomWarningMask),
|
||||||
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.CustomRoomWarning, details.Room.CustomRoomWarning),
|
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.CustomRoomWarning, details.Room.CustomRoomWarning),
|
||||||
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.DisableMicAutoMute, `${details.Room.DisableMicAutoMute}`),
|
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.DisableMicAutoMute, `${details.Room.DisableMicAutoMute ? details.Room.DisableMicAutoMute : 'null'}`),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
for (const subroom of details.Scenes) {
|
for (const subroom of details.Scenes) {
|
||||||
@@ -159,7 +159,7 @@ class RoomsBase {
|
|||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
Redis.Database.hset(subRootMetaKey, this.subroomMetaKeys.RoomSceneLocationId, subroom.RoomSceneLocationId),
|
Redis.Database.hset(subRootMetaKey, this.subroomMetaKeys.RoomSceneLocationId, subroom.RoomSceneLocationId),
|
||||||
Redis.Database.hset(subRootMetaKey, this.subroomMetaKeys.Name, subroom.RoomSceneLocationId),
|
Redis.Database.hset(subRootMetaKey, this.subroomMetaKeys.Name, subroom.Name),
|
||||||
Redis.Database.hset(subRootMetaKey, this.subroomMetaKeys.IsSandbox, `${subroom.IsSandbox}`),
|
Redis.Database.hset(subRootMetaKey, this.subroomMetaKeys.IsSandbox, `${subroom.IsSandbox}`),
|
||||||
Redis.Database.hset(subRootMetaKey, this.subroomMetaKeys.DataBlobName, subroom.DataBlobName),
|
Redis.Database.hset(subRootMetaKey, this.subroomMetaKeys.DataBlobName, subroom.DataBlobName),
|
||||||
Redis.Database.hset(subRootMetaKey, this.subroomMetaKeys.MaxPlayers, subroom.MaxPlayers),
|
Redis.Database.hset(subRootMetaKey, this.subroomMetaKeys.MaxPlayers, subroom.MaxPlayers),
|
||||||
@@ -168,10 +168,10 @@ class RoomsBase {
|
|||||||
]);
|
]);
|
||||||
await Redis.Database.sadd(Redis.buildKey(Redis.KeyGroups.Rooms.Root, details.Room.RoomId.toString(), this.roomRootKeys.Subrooms), newSubId);
|
await Redis.Database.sadd(Redis.buildKey(Redis.KeyGroups.Rooms.Root, details.Room.RoomId.toString(), this.roomRootKeys.Subrooms), newSubId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await Redis.Database.set(Redis.buildKey(Redis.KeyGroups.Room_Names, details.Room.Name), details.Room.RoomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async cloneRoom(roomid: number, newname: string, newowner: Profile, dorm?: boolean) {
|
async cloneRoom(roomid: number, newname: string, newowner: Profile, dorm?: boolean) {
|
||||||
const canBeClonedRaw = await Redis.Database.hget(Redis.buildKey(
|
const canBeClonedRaw = await Redis.Database.hget(Redis.buildKey(
|
||||||
Redis.KeyGroups.Rooms.Root,
|
Redis.KeyGroups.Rooms.Root,
|
||||||
@@ -188,7 +188,7 @@ class RoomsBase {
|
|||||||
if (!canBeCloned) return null;
|
if (!canBeCloned) return null;
|
||||||
const beforeRoom = await Rooms.get(roomid); // room must exist
|
const beforeRoom = await Rooms.get(roomid); // room must exist
|
||||||
if (!beforeRoom || !beforeRoom.Room.CloningAllowed) return null; // room must be cloneable
|
if (!beforeRoom || !beforeRoom.Room.CloningAllowed) return null; // room must be cloneable
|
||||||
if (await Rooms.getByName(newname)) return null; // room name cannot be taken
|
if (await Rooms.getByName(newname) && beforeRoom.Room.Name !== 'DormRoom') return null; // room name cannot be taken
|
||||||
|
|
||||||
const newId = await this.#getAvailableRoomId();
|
const newId = await this.#getAvailableRoomId();
|
||||||
beforeRoom.Room.CreatorPlayerId = newowner.getId();
|
beforeRoom.Room.CreatorPlayerId = newowner.getId();
|
||||||
@@ -197,9 +197,9 @@ class RoomsBase {
|
|||||||
if (dorm) {
|
if (dorm) {
|
||||||
beforeRoom.Room.IsAGRoom = true;
|
beforeRoom.Room.IsAGRoom = true;
|
||||||
beforeRoom.Room.IsDormRoom = true;
|
beforeRoom.Room.IsDormRoom = true;
|
||||||
|
beforeRoom.Room.CloningAllowed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
await Redis.Database.set(Redis.buildKey(Redis.KeyGroups.Room_Names, newname), newId);
|
|
||||||
await Rooms.#setRoom(beforeRoom);
|
await Rooms.#setRoom(beforeRoom);
|
||||||
return beforeRoom;
|
return beforeRoom;
|
||||||
|
|
||||||
@@ -217,17 +217,17 @@ class RoomsBase {
|
|||||||
|
|
||||||
const baseDorm = await Rooms.getByName("DormRoom");
|
const baseDorm = await Rooms.getByName("DormRoom");
|
||||||
|
|
||||||
log.d('got base dorm');
|
log.d('Base dorm existed');
|
||||||
if (!baseDorm) return null;
|
if (!baseDorm) return null;
|
||||||
log.d('base dorm is not null');
|
log.d('Base dorm was not null');
|
||||||
const newDorm = await this.cloneRoom(baseDorm.Room.RoomId, "DormRoom", player, true);
|
const newDorm = await this.cloneRoom(baseDorm.Room.RoomId, "DormRoom", player, true);
|
||||||
|
log.d('New dorm existed');
|
||||||
|
if (!newDorm) return null;
|
||||||
|
log.d('New dorm was not null');
|
||||||
await Redis.Database.hset(Redis.buildKey(
|
await Redis.Database.hset(Redis.buildKey(
|
||||||
Redis.KeyGroups.Rooms.Root,
|
Redis.KeyGroups.Rooms.Root,
|
||||||
Redis.KeyGroups.Rooms.PlayerDorms
|
Redis.KeyGroups.Rooms.PlayerDorms
|
||||||
), player.getId().toString(), baseDorm.Room.RoomId);
|
), player.getId().toString(), newDorm.Room.RoomId);
|
||||||
log.d('got new dorm');
|
|
||||||
if (!newDorm) return null;
|
|
||||||
log.d('new dorm is not null');
|
|
||||||
return newDorm;
|
return newDorm;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,7 +249,7 @@ class RoomsBase {
|
|||||||
SupportsLevelVoting: builtinRoom.SupportsLevelVoting,
|
SupportsLevelVoting: builtinRoom.SupportsLevelVoting,
|
||||||
IsAGRoom: true,
|
IsAGRoom: true,
|
||||||
IsDormRoom: builtinRoom.Name == 'DormRoom',
|
IsDormRoom: builtinRoom.Name == 'DormRoom',
|
||||||
CloningAllowed: builtinRoom.CloningAllowed,
|
CloningAllowed: builtinRoom.Name == 'DormRoom' ? true : builtinRoom.CloningAllowed,
|
||||||
SupportsScreens: builtinRoom.SupportsScreens,
|
SupportsScreens: builtinRoom.SupportsScreens,
|
||||||
SupportsWalkVR: builtinRoom.SupportsWalkVR,
|
SupportsWalkVR: builtinRoom.SupportsWalkVR,
|
||||||
SupportsTeleportVR: builtinRoom.SupportsTeleportVR,
|
SupportsTeleportVR: builtinRoom.SupportsTeleportVR,
|
||||||
|
|||||||
31
src/data/content/storefronts.ts
Normal file
31
src/data/content/storefronts.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/* 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 enum StorefrontTypes {
|
||||||
|
None,
|
||||||
|
LaserTag,
|
||||||
|
RecCenter,
|
||||||
|
Watch,
|
||||||
|
Quest_LostSkulls = 100,
|
||||||
|
Quest_Dracula,
|
||||||
|
RecRoyale = 200,
|
||||||
|
Cafe = 300,
|
||||||
|
Paintball = 400,
|
||||||
|
Bowling = 500,
|
||||||
|
StuntRunner = 600,
|
||||||
|
DormMirror = 700
|
||||||
|
}
|
||||||
@@ -53,7 +53,7 @@ class MatchmakingBase {
|
|||||||
loginLocks.delete(prof.getId());
|
loginLocks.delete(prof.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
async matchmake(options: MatchmakingOptions) {
|
async matchmake(options: MatchmakingOptions): Promise<MatchmakingResponse> {
|
||||||
|
|
||||||
if (options.instanceId) {
|
if (options.instanceId) {
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ class MatchmakingBase {
|
|||||||
if (instance.isFull) return { errorCode: MatchmakingErrorCode.InsufficientSpace }
|
if (instance.isFull) return { errorCode: MatchmakingErrorCode.InsufficientSpace }
|
||||||
else if (instance.isPrivate) return { errorCode: MatchmakingErrorCode.RoomInstanceIsPrivate };
|
else if (instance.isPrivate) return { errorCode: MatchmakingErrorCode.RoomInstanceIsPrivate };
|
||||||
|
|
||||||
Instances.setPlayerInstance(options.profile, instance);
|
await Instances.setPlayerInstance(options.profile, instance);
|
||||||
return { errorCode: MatchmakingErrorCode.Success, roomInstance: instance };
|
return { errorCode: MatchmakingErrorCode.Success, roomInstance: instance };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +96,7 @@ class MatchmakingBase {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
Instances.setPlayerInstance(options.profile, foundInstance);
|
await Instances.setPlayerInstance(options.profile, foundInstance);
|
||||||
return { errorCode: MatchmakingErrorCode.Success, roomInstance: foundInstance };
|
return { errorCode: MatchmakingErrorCode.Success, roomInstance: foundInstance };
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import Logging from "@proxnet/undead-logging";
|
|||||||
import { Profile } from "../profiles.ts";
|
import { Profile } from "../profiles.ts";
|
||||||
import { RoomInstance, InstanceOptions } from "./types.ts";
|
import { RoomInstance, InstanceOptions } from "./types.ts";
|
||||||
import { Config } from "../../config.ts";
|
import { Config } from "../../config.ts";
|
||||||
|
import Presence from "./presence.ts";
|
||||||
|
|
||||||
const log = new Logging("Instances");
|
const log = new Logging("Instances");
|
||||||
|
|
||||||
@@ -150,7 +151,7 @@ class InstancesBase {
|
|||||||
*
|
*
|
||||||
* Synchronizes profile instance to `instance` and adds player to instance.
|
* Synchronizes profile instance to `instance` and adds player to instance.
|
||||||
*/
|
*/
|
||||||
setPlayerInstance(player: Profile, instance: RoomInstance) {
|
async setPlayerInstance(player: Profile, instance: RoomInstance) {
|
||||||
const currentInstance = player.getInstance();
|
const currentInstance = player.getInstance();
|
||||||
if (currentInstance === instance) return;
|
if (currentInstance === instance) return;
|
||||||
|
|
||||||
@@ -161,7 +162,11 @@ class InstancesBase {
|
|||||||
|
|
||||||
if (this.instanceCanFitPlayer(instance)) {
|
if (this.instanceCanFitPlayer(instance)) {
|
||||||
this.getInstancePlayers(instance).add(player);
|
this.getInstancePlayers(instance).add(player);
|
||||||
|
|
||||||
player.setInstance(instance);
|
player.setInstance(instance);
|
||||||
|
const pres = await Presence.get(player);
|
||||||
|
pres.update();
|
||||||
|
|
||||||
this.updateSingleInstanceIsFull(instance);
|
this.updateSingleInstanceIsFull(instance);
|
||||||
} else log.w(`Instance ${instance.roomInstanceId} is full. Cannot add player ${player.getId()}`);
|
} else log.w(`Instance ${instance.roomInstanceId} is full. Cannot add player ${player.getId()}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,31 +62,43 @@ class PlayerPresence {
|
|||||||
playerId: number;
|
playerId: number;
|
||||||
statusVisibility: PlayerStatusVisibility;
|
statusVisibility: PlayerStatusVisibility;
|
||||||
deviceClass: DeviceClass;
|
deviceClass: DeviceClass;
|
||||||
vrMovementMode: VRMovementMode;
|
vrMovementMode: VRMovementMode | undefined;
|
||||||
roomInstance: RoomInstance | null;
|
roomInstance: RoomInstance | null;
|
||||||
|
|
||||||
lastSeen: Date;
|
lastSeen: Date;
|
||||||
|
|
||||||
async updateStatusVisibility() {
|
async updateStatusVisibility() {
|
||||||
const PlayerStatusVisibilityEnum = z.nativeEnum(PlayerStatusVisibility);
|
const PlayerStatusVisibilityEnum = z.nativeEnum(PlayerStatusVisibility);
|
||||||
type PlayerStatusVisibilityEnum = z.infer<typeof PlayerStatusVisibilityEnum>;
|
|
||||||
|
|
||||||
const visibilityResult = PlayerStatusVisibilityEnum.safeParse(await this.#profile.Settings.getSetting(SettingKey.PlayerStatusVisibility));
|
const visibilityResult = PlayerStatusVisibilityEnum.safeParse(await this.#profile.Settings.getSetting(SettingKey.PlayerStatusVisibility));
|
||||||
if (visibilityResult.success) this.statusVisibility = visibilityResult.data;
|
if (visibilityResult.success) this.statusVisibility = visibilityResult.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateVRMovementMode() {
|
||||||
|
const VRMovementModeEnum = z.nativeEnum(VRMovementMode);
|
||||||
|
|
||||||
|
const modeResult = VRMovementModeEnum.safeParse(await this.#profile.Settings.getSetting(SettingKey.VRMovementMode));
|
||||||
|
if (modeResult.success) this.vrMovementMode = modeResult.data;
|
||||||
|
}
|
||||||
|
|
||||||
async update() {
|
async update() {
|
||||||
this.updateOffline();
|
this.updateOffline();
|
||||||
if (!this.offline) await this.updateStatusVisibility();
|
if (!this.offline) {
|
||||||
else this.statusVisibility = PlayerStatusVisibility.Offline;
|
await this.updateStatusVisibility();
|
||||||
|
await this.updateVRMovementMode();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.vrMovementMode = undefined;
|
||||||
|
this.statusVisibility = PlayerStatusVisibility.Offline;
|
||||||
|
}
|
||||||
|
|
||||||
this.deviceClass = await this.#profile.getKnownDeviceClass();
|
this.deviceClass = await this.#profile.getKnownDeviceClass();
|
||||||
|
|
||||||
this.vrMovementMode = await this.#profile.getVRMovementMode();
|
this.roomInstance = this.#profile.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export presence object. Please make sure to update values with `Presence.update()` (async) before calling this.
|
* Export presence
|
||||||
*/
|
*/
|
||||||
async export() {
|
async export() {
|
||||||
await this.update();
|
await this.update();
|
||||||
@@ -97,7 +109,7 @@ class PlayerPresence {
|
|||||||
deviceClass: this.deviceClass,
|
deviceClass: this.deviceClass,
|
||||||
vrMovementMode: this.vrMovementMode
|
vrMovementMode: this.vrMovementMode
|
||||||
}
|
}
|
||||||
return Object.assign({}, exp); // hard clone
|
return exp;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateOffline() {
|
updateOffline() {
|
||||||
@@ -118,10 +130,8 @@ class PresenceBase {
|
|||||||
*/
|
*/
|
||||||
async getAllPresences() {
|
async getAllPresences() {
|
||||||
const presSet: Set<PresenceExport> = new Set();
|
const presSet: Set<PresenceExport> = new Set();
|
||||||
for (const pres of presence.values()) {
|
for (const pres of presence.values())
|
||||||
await pres.update();
|
|
||||||
presSet.add(await pres.export());
|
presSet.add(await pres.export());
|
||||||
}
|
|
||||||
return presSet;
|
return presSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ GNU Affero General Public License for more details.
|
|||||||
You should have received a copy of the GNU Affero General Public License
|
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/>. */
|
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
import { IntegratedRoomScene, RoomDetails } from "../content/roomtypes.ts";
|
import { RoomDetails } from "../content/roomtypes.ts";
|
||||||
import { Profile } from "../profiles.ts";
|
import { Profile } from "../profiles.ts";
|
||||||
|
|
||||||
export enum PhotonRegionCodeString {
|
export enum PhotonRegionCodeString {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
|||||||
import { Redis } from "../../db.ts";
|
import { Redis } from "../../db.ts";
|
||||||
import { ProfileContentManager } from "./base/profilemanagerbase.ts";
|
import { ProfileContentManager } from "./base/profilemanagerbase.ts";
|
||||||
|
|
||||||
interface AvatarSettings {
|
export interface AvatarSettings {
|
||||||
OutfitSelections: string,
|
OutfitSelections: string,
|
||||||
HairColor: string,
|
HairColor: string,
|
||||||
SkinColor: string,
|
SkinColor: string,
|
||||||
|
|||||||
@@ -37,11 +37,11 @@ export class ProfileSettingsManager extends ProfileContentManager {
|
|||||||
return await Redis.Database.hget(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId.toString(), Redis.KeyGroups.Profiles.Settings), key);
|
return await Redis.Database.hget(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId.toString(), Redis.KeyGroups.Profiles.Settings), key);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setSetting(key: SettingKey, value: string) {
|
async setSetting(key: SettingKey, value: string | number) {
|
||||||
await Redis.Database.hset(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId.toString(), Redis.KeyGroups.Profiles.Settings), key, value);
|
await Redis.Database.hset(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId.toString(), Redis.KeyGroups.Profiles.Settings), key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setSettingRaw(key: string, value: string) {
|
async setSettingRaw(key: string, value: string | number) {
|
||||||
await Redis.Database.hset(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId.toString(), Redis.KeyGroups.Profiles.Settings), key, value);
|
await Redis.Database.hset(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId.toString(), Redis.KeyGroups.Profiles.Settings), key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -217,6 +217,16 @@ class Profile {
|
|||||||
return (await Redis.Database.sismember(Redis.buildKey(Redis.KeyGroups.Operators), this.#id.toString())) >= 1;
|
return (await Redis.Database.sismember(Redis.buildKey(Redis.KeyGroups.Operators), this.#id.toString())) >= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getBio() {
|
||||||
|
const bio = await Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.#id.toString(), Redis.KeyGroups.Profiles.Bio));
|
||||||
|
if (!bio) return "This player has not yet set their bio. Remind them to set one!";
|
||||||
|
else return bio;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setBio(bio: string) {
|
||||||
|
await Redis.Database.set(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.#id.toString(), Redis.KeyGroups.Profiles.Bio), bio);
|
||||||
|
}
|
||||||
|
|
||||||
async export() {
|
async export() {
|
||||||
return await Profile.getExportAccount(this.#id);
|
return await Profile.getExportAccount(this.#id);
|
||||||
}
|
}
|
||||||
@@ -256,7 +266,7 @@ class Profile {
|
|||||||
|
|
||||||
const data = await this.Settings.getSetting(SettingKey.VRMovementMode);
|
const data = await this.Settings.getSetting(SettingKey.VRMovementMode);
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
log.w(`No known VR movement mode for ${this.#id} (harmless if OOBE not ran)`);
|
log.w(`No known VR movement mode for ${this.#id}`);
|
||||||
return VRMovementMode.Teleport;
|
return VRMovementMode.Teleport;
|
||||||
}
|
}
|
||||||
const parsedData = parseInt(data);
|
const parsedData = parseInt(data);
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ export const KeyGroups = {
|
|||||||
Settings: "settings",
|
Settings: "settings",
|
||||||
DeviceClass: "deviceClass",
|
DeviceClass: "deviceClass",
|
||||||
Xp: "xp",
|
Xp: "xp",
|
||||||
|
Bio: "bio",
|
||||||
Relationships: {
|
Relationships: {
|
||||||
Root: "relationships",
|
Root: "relationships",
|
||||||
IncomingFriendRequests: "incomingFriendRequests",
|
IncomingFriendRequests: "incomingFriendRequests",
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
|||||||
|
|
||||||
import { APIUtils } from "../apiutils.ts";
|
import { APIUtils } from "../apiutils.ts";
|
||||||
import { route as AccountRoute } from "./account/account.ts";
|
import { route as AccountRoute } from "./account/account.ts";
|
||||||
|
import { route as ParentalControlRoute } from "./account/parentalcontrol.ts";
|
||||||
|
|
||||||
export const route = APIUtils.createRouter("/accountservice");
|
export const route = APIUtils.createRouter("/accountservice");
|
||||||
|
|
||||||
route.router.use(AccountRoute.path, AccountRoute.router);
|
route.router.use(AccountRoute.path, AccountRoute.router);
|
||||||
|
route.router.use(ParentalControlRoute.path, ParentalControlRoute.router);
|
||||||
@@ -17,17 +17,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
|||||||
|
|
||||||
import { APIUtils } from "../../apiutils.ts";
|
import { APIUtils } from "../../apiutils.ts";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import { Profile } from "../../data/profiles.ts";
|
import UnifiedProfile, { Profile } from "../../data/profiles.ts";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
export const route = APIUtils.createRouter("/account");
|
export const route = APIUtils.createRouter("/account");
|
||||||
|
|
||||||
interface CreateAccountRequestBody {
|
|
||||||
platform: string;
|
|
||||||
platformId: string;
|
|
||||||
deviceId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const CreateAccountRequestBodySchema = z.object({
|
const CreateAccountRequestBodySchema = z.object({
|
||||||
platform: z.string(),
|
platform: z.string(),
|
||||||
platformId: z.string(),
|
platformId: z.string(),
|
||||||
@@ -107,3 +101,32 @@ route.router.get("/me",
|
|||||||
},
|
},
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
interface BioFetchParams {
|
||||||
|
id?: string
|
||||||
|
}
|
||||||
|
route.router.get('/:id/bio',
|
||||||
|
|
||||||
|
APIUtils.Authentication,
|
||||||
|
|
||||||
|
async (rq: express.Request<BioFetchParams>, rs: express.Response) => {
|
||||||
|
|
||||||
|
const unparsedId = rq.params.id;
|
||||||
|
if (!unparsedId) {
|
||||||
|
rs.sendStatus(500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const parsedId = parseInt(unparsedId);
|
||||||
|
if (isNaN(parsedId)) {
|
||||||
|
rs.sendStatus(400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const player = UnifiedProfile.get(parsedId);
|
||||||
|
|
||||||
|
rs.json({
|
||||||
|
accountId: parsedId,
|
||||||
|
bio: await player.getBio(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
);
|
||||||
35
src/routes/account/parentalcontrol.ts
Normal file
35
src/routes/account/parentalcontrol.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
/* 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 { AuthType } from "../../data/users.ts";
|
||||||
|
|
||||||
|
export const route = APIUtils.createRouter('/parentalcontrol');
|
||||||
|
|
||||||
|
route.router.get('/me',
|
||||||
|
|
||||||
|
APIUtils.Authentication,
|
||||||
|
APIUtils.AuthenticationType(AuthType.Game),
|
||||||
|
|
||||||
|
(_rq, rs) => {
|
||||||
|
rs.json({
|
||||||
|
accountId: rs.locals.profile.getId(),
|
||||||
|
disallowInAppPurchases: true // no transations here buddy
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
);
|
||||||
@@ -30,6 +30,14 @@ import { route as AvatarRoute } from "./api/avatar.ts";
|
|||||||
import { route as QuickPlayRoute } from "./api/quickplay.ts";
|
import { route as QuickPlayRoute } from "./api/quickplay.ts";
|
||||||
import { route as RoomsRoute } from "./api/rooms.ts";
|
import { route as RoomsRoute } from "./api/rooms.ts";
|
||||||
import { route as ChallengeRoute } from "./api/challenge.ts";
|
import { route as ChallengeRoute } from "./api/challenge.ts";
|
||||||
|
import { route as EquipmentRoute } from "./api/equipment.ts";
|
||||||
|
import { route as ConsumablesRoute } from "./api/consumables.ts";
|
||||||
|
import { route as ObjectivesRoute } from "./api/objectives.ts";
|
||||||
|
import { route as ChecklistRoute } from "./api/checklist.ts";
|
||||||
|
import { route as ImagesRoute } from "./api/images.ts";
|
||||||
|
import { route as CommunityBoardRoute } from "./api/communityboard.ts";
|
||||||
|
import { route as PlayerEventsRoute } from "./api/playerevents.ts";
|
||||||
|
import { route as StorefrontsRoute } from "./api/storefronts.ts";
|
||||||
|
|
||||||
export const route = APIUtils.createRouter("/api");
|
export const route = APIUtils.createRouter("/api");
|
||||||
|
|
||||||
@@ -47,3 +55,11 @@ route.router.use(AvatarRoute.path, AvatarRoute.router);
|
|||||||
route.router.use(QuickPlayRoute.path, QuickPlayRoute.router);
|
route.router.use(QuickPlayRoute.path, QuickPlayRoute.router);
|
||||||
route.router.use(RoomsRoute.path, RoomsRoute.router);
|
route.router.use(RoomsRoute.path, RoomsRoute.router);
|
||||||
route.router.use(ChallengeRoute.path, ChallengeRoute.router);
|
route.router.use(ChallengeRoute.path, ChallengeRoute.router);
|
||||||
|
route.router.use(EquipmentRoute.path, EquipmentRoute.router);
|
||||||
|
route.router.use(ConsumablesRoute.path, ConsumablesRoute.router);
|
||||||
|
route.router.use(ObjectivesRoute.path, ObjectivesRoute.router);
|
||||||
|
route.router.use(ChecklistRoute.path, ChecklistRoute.router);
|
||||||
|
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);
|
||||||
@@ -15,8 +15,11 @@ GNU Affero General Public License for more details.
|
|||||||
You should have received a copy of the GNU Affero General Public License
|
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/>. */
|
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 { AuthType } from "../../data/users.ts";
|
import { AuthType } from "../../data/users.ts";
|
||||||
|
import express from "express";
|
||||||
|
import { AvatarSettings } from "../../data/profile/avatar.ts";
|
||||||
|
|
||||||
export const route = APIUtils.createRouter("/avatar");
|
export const route = APIUtils.createRouter("/avatar");
|
||||||
|
|
||||||
@@ -31,13 +34,49 @@ route.router.get('/v2',
|
|||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const AvatarSettingsSchema = z.object({
|
||||||
|
OutfitSelections: z.string(),
|
||||||
|
HairColor: z.string(),
|
||||||
|
SkinColor: z.string(),
|
||||||
|
FaceFeatures: z.string()
|
||||||
|
});
|
||||||
|
route.router.post('/v2/set',
|
||||||
|
|
||||||
|
APIUtils.Authentication,
|
||||||
|
APIUtils.AuthenticationType(AuthType.Game),
|
||||||
|
express.json(),
|
||||||
|
APIUtils.validateRequestBody(AvatarSettingsSchema),
|
||||||
|
|
||||||
|
(rq: express.Request<NoBody, NoBody, AvatarSettings>, rs: express.Response) => {
|
||||||
|
rs.locals.profile.Avatar.setAvatar(rq.body);
|
||||||
|
rs.sendStatus(200);
|
||||||
|
},
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
route.router.get('/v4/items',
|
route.router.get('/v4/items',
|
||||||
|
|
||||||
APIUtils.Authentication,
|
APIUtils.Authentication,
|
||||||
APIUtils.AuthenticationType(AuthType.Game),
|
APIUtils.AuthenticationType(AuthType.Game),
|
||||||
|
|
||||||
(_rq, rs) => {
|
APIUtils.emptyArrayResponse
|
||||||
rs.json([]);
|
|
||||||
}
|
);
|
||||||
|
|
||||||
|
route.router.get('/v3/saved',
|
||||||
|
|
||||||
|
APIUtils.Authentication,
|
||||||
|
APIUtils.AuthenticationType(AuthType.Game),
|
||||||
|
|
||||||
|
APIUtils.emptyArrayResponse
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
route.router.get('/v2/gifts',
|
||||||
|
|
||||||
|
APIUtils.Authentication,
|
||||||
|
APIUtils.AuthenticationType(AuthType.Game),
|
||||||
|
|
||||||
|
APIUtils.emptyArrayResponse
|
||||||
|
|
||||||
);
|
);
|
||||||
30
src/routes/api/checklist.ts
Normal file
30
src/routes/api/checklist.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/* 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 { AuthType } from "../../data/users.ts";
|
||||||
|
|
||||||
|
export const route = APIUtils.createRouter('/checklist');
|
||||||
|
|
||||||
|
route.router.get('/v1/current',
|
||||||
|
|
||||||
|
APIUtils.Authentication,
|
||||||
|
APIUtils.AuthenticationType(AuthType.Game),
|
||||||
|
|
||||||
|
APIUtils.emptyArrayResponse
|
||||||
|
|
||||||
|
);
|
||||||
51
src/routes/api/communityboard.ts
Normal file
51
src/routes/api/communityboard.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
/* 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 { Config } from "../../config.ts";
|
||||||
|
import { AuthType } from "../../data/users.ts";
|
||||||
|
|
||||||
|
const config = Config.getConfig();
|
||||||
|
|
||||||
|
export const route = APIUtils.createRouter('/communityboard');
|
||||||
|
|
||||||
|
route.router.get('/v1/current',
|
||||||
|
|
||||||
|
APIUtils.Authentication,
|
||||||
|
APIUtils.AuthenticationType(AuthType.Game),
|
||||||
|
|
||||||
|
(_rq, rs) => {
|
||||||
|
rs.json({
|
||||||
|
FeaturedPlayer: {
|
||||||
|
Id: 1,
|
||||||
|
TitleOverride: "",
|
||||||
|
UrlOverride: ""
|
||||||
|
},
|
||||||
|
FeaturedRoomGroup: {
|
||||||
|
Name: "",
|
||||||
|
FeaturedRooms: []
|
||||||
|
},
|
||||||
|
CurrentAnnouncement: {
|
||||||
|
Message: config.public.motd,
|
||||||
|
MoreInfoUrl: ""
|
||||||
|
},
|
||||||
|
InstagramImages: [],
|
||||||
|
Videos: []
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
);
|
||||||
30
src/routes/api/consumables.ts
Normal file
30
src/routes/api/consumables.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/* 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 { AuthType } from "../../data/users.ts";
|
||||||
|
|
||||||
|
export const route = APIUtils.createRouter('/consumables');
|
||||||
|
|
||||||
|
route.router.get('/v1/getUnlocked',
|
||||||
|
|
||||||
|
APIUtils.Authentication,
|
||||||
|
APIUtils.AuthenticationType(AuthType.Game),
|
||||||
|
|
||||||
|
APIUtils.emptyArrayResponse
|
||||||
|
|
||||||
|
);
|
||||||
30
src/routes/api/equipment.ts
Normal file
30
src/routes/api/equipment.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/* 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 { AuthType } from "../../data/users.ts";
|
||||||
|
|
||||||
|
export const route = APIUtils.createRouter('/equipment');
|
||||||
|
|
||||||
|
route.router.get('/v2/getUnlocked',
|
||||||
|
|
||||||
|
APIUtils.Authentication,
|
||||||
|
APIUtils.AuthenticationType(AuthType.Game),
|
||||||
|
|
||||||
|
APIUtils.emptyArrayResponse
|
||||||
|
|
||||||
|
)
|
||||||
@@ -19,6 +19,4 @@ import { APIUtils } from "../../apiutils.ts";
|
|||||||
|
|
||||||
export const route = APIUtils.createRouter("/gameconfigs");
|
export const route = APIUtils.createRouter("/gameconfigs");
|
||||||
|
|
||||||
route.router.get("/v1/all", (_rq, rs) => {
|
route.router.get("/v1/all", APIUtils.emptyArrayResponse);
|
||||||
rs.json([]);
|
|
||||||
});
|
|
||||||
30
src/routes/api/images.ts
Normal file
30
src/routes/api/images.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/* 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 { AuthType } from "../../data/users.ts";
|
||||||
|
|
||||||
|
export const route = APIUtils.createRouter('/images');
|
||||||
|
|
||||||
|
route.router.get('/v2/named',
|
||||||
|
|
||||||
|
APIUtils.Authentication,
|
||||||
|
APIUtils.AuthenticationType(AuthType.Game),
|
||||||
|
|
||||||
|
APIUtils.emptyArrayResponse
|
||||||
|
|
||||||
|
);
|
||||||
@@ -23,8 +23,6 @@ route.router.get('/v2/get',
|
|||||||
|
|
||||||
APIUtils.Authentication,
|
APIUtils.Authentication,
|
||||||
|
|
||||||
(_rq, rs) => {
|
APIUtils.emptyArrayResponse
|
||||||
rs.json([]); // temporary
|
|
||||||
}
|
|
||||||
|
|
||||||
)
|
)
|
||||||
35
src/routes/api/objectives.ts
Normal file
35
src/routes/api/objectives.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
/* 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 { AuthType } from "../../data/users.ts";
|
||||||
|
|
||||||
|
export const route = APIUtils.createRouter('/objectives');
|
||||||
|
|
||||||
|
route.router.get('/v1/myprogress',
|
||||||
|
|
||||||
|
APIUtils.Authentication,
|
||||||
|
APIUtils.AuthenticationType(AuthType.Game),
|
||||||
|
|
||||||
|
(_rq, rs) => {
|
||||||
|
rs.json({
|
||||||
|
Objectives: [],
|
||||||
|
ObjectiveGroups: []
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
);
|
||||||
44
src/routes/api/playerevents.ts
Normal file
44
src/routes/api/playerevents.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
/* 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 { AuthType } from "../../data/users.ts";
|
||||||
|
|
||||||
|
export const route = APIUtils.createRouter('/playerevents');
|
||||||
|
|
||||||
|
route.router.get('/v1/all',
|
||||||
|
|
||||||
|
APIUtils.Authentication,
|
||||||
|
APIUtils.AuthenticationType(AuthType.Game),
|
||||||
|
|
||||||
|
(_rq, rs) => {
|
||||||
|
rs.json({
|
||||||
|
Created: [],
|
||||||
|
Responses: []
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
route.router.get('/v1',
|
||||||
|
|
||||||
|
APIUtils.Authentication,
|
||||||
|
APIUtils.AuthenticationType(AuthType.Game),
|
||||||
|
|
||||||
|
APIUtils.emptyArrayResponse
|
||||||
|
|
||||||
|
);
|
||||||
@@ -21,8 +21,6 @@ export const route = APIUtils.createRouter("/playersubscriptions");
|
|||||||
|
|
||||||
route.router.get('/v1/my',
|
route.router.get('/v1/my',
|
||||||
|
|
||||||
(_rq, rs) => {
|
APIUtils.emptyArrayResponse
|
||||||
rs.json([]); // temporary: todo
|
|
||||||
}
|
|
||||||
|
|
||||||
);
|
);
|
||||||
@@ -17,6 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
|||||||
|
|
||||||
import { APIUtils } from "../../apiutils.ts";
|
import { APIUtils } from "../../apiutils.ts";
|
||||||
import Rooms from "../../data/content/rooms.ts";
|
import Rooms from "../../data/content/rooms.ts";
|
||||||
|
import { RoomAccessibility } from "../../data/content/roomtypes.ts";
|
||||||
import { AuthType } from "../../data/users.ts";
|
import { AuthType } from "../../data/users.ts";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
|
|
||||||
@@ -46,4 +47,75 @@ route.router.get('/v4/details/:roomId',
|
|||||||
else rs.json(room);
|
else rs.json(room);
|
||||||
},
|
},
|
||||||
|
|
||||||
)
|
);
|
||||||
|
|
||||||
|
route.router.get('/v2/myrooms',
|
||||||
|
|
||||||
|
APIUtils.Authentication,
|
||||||
|
APIUtils.AuthenticationType(AuthType.Game),
|
||||||
|
|
||||||
|
APIUtils.emptyArrayResponse
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
route.router.get('/v1/hot',
|
||||||
|
|
||||||
|
APIUtils.Authentication,
|
||||||
|
APIUtils.AuthenticationType(AuthType.Game),
|
||||||
|
|
||||||
|
async (_rq, rs) => {
|
||||||
|
// temporary: return all public AG rooms for testing
|
||||||
|
const rooms = await Rooms.getAllBuiltinRoomGenerations();
|
||||||
|
rs.json(rooms.map(room => room.Room).filter(room => room.Accessibility !== RoomAccessibility.Private));
|
||||||
|
},
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
route.router.get('/v2/baserooms',
|
||||||
|
|
||||||
|
APIUtils.Authentication,
|
||||||
|
APIUtils.AuthenticationType(AuthType.Game),
|
||||||
|
|
||||||
|
async (_rq, rs) => {
|
||||||
|
const rooms = await Rooms.getAllBuiltinRoomGenerations();
|
||||||
|
rs.json(rooms.map(room => room.Room).filter(room => room.CloningAllowed));
|
||||||
|
},
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
interface GetRoomByNameParams {
|
||||||
|
name?: string
|
||||||
|
}
|
||||||
|
route.router.get('/v2/name/:name',
|
||||||
|
|
||||||
|
APIUtils.Authentication,
|
||||||
|
APIUtils.AuthenticationType(AuthType.Game),
|
||||||
|
|
||||||
|
async (rq: express.Request<GetRoomByNameParams>, rs: express.Response) => {
|
||||||
|
if (!rq.params.name) {
|
||||||
|
rs.sendStatus(400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const room = await Rooms.getByName(rq.params.name.trim());
|
||||||
|
if (!room || rq.params.name == 'DormRoom') {
|
||||||
|
rs.sendStatus(404);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
rs.json(room.Room);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
route.router.post('/v1/roomRolePermissions',
|
||||||
|
|
||||||
|
APIUtils.Authentication,
|
||||||
|
APIUtils.AuthenticationType(AuthType.Game),
|
||||||
|
|
||||||
|
(rq, rs) => {
|
||||||
|
rs.sendStatus(200);
|
||||||
|
},
|
||||||
|
|
||||||
|
);
|
||||||
42
src/routes/api/storefronts.ts
Normal file
42
src/routes/api/storefronts.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/* 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 express from "express";
|
||||||
|
|
||||||
|
export const route = APIUtils.createRouter('/storefronts');
|
||||||
|
|
||||||
|
interface StorefrontFetchParams {
|
||||||
|
id?: string
|
||||||
|
}
|
||||||
|
route.router.get('/v3/giftdropstore/:id',
|
||||||
|
|
||||||
|
APIUtils.Authentication,
|
||||||
|
|
||||||
|
(rq: express.Request<StorefrontFetchParams>, rs: express.Response) => {
|
||||||
|
if (!rq.params.id) {
|
||||||
|
rs.sendStatus(400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rs.json({
|
||||||
|
StorefrontType: parseInt(rq.params.id),
|
||||||
|
NextUpdate: new Date(Date.now() + 604_800_000).toISOString(),
|
||||||
|
StoreItems: []
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
);
|
||||||
@@ -23,8 +23,6 @@ route.router.get('/LoadingScreenTipData',
|
|||||||
|
|
||||||
APIUtils.Authentication,
|
APIUtils.Authentication,
|
||||||
|
|
||||||
(_rq, rs) => {
|
APIUtils.emptyArrayResponse
|
||||||
rs.json([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
);
|
);
|
||||||
@@ -18,8 +18,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
|||||||
import { APIUtils } from "../apiutils.ts";
|
import { APIUtils } from "../apiutils.ts";
|
||||||
import { route as PlayerRoute } from "./match/player.ts";
|
import { route as PlayerRoute } from "./match/player.ts";
|
||||||
import { route as GotoRoute } from "./match/goto.ts";
|
import { route as GotoRoute } from "./match/goto.ts";
|
||||||
|
import { route as RoomRoute } from "./match/room.ts";
|
||||||
|
|
||||||
export const route = APIUtils.createRouter('/match');
|
export const route = APIUtils.createRouter('/match');
|
||||||
|
|
||||||
route.router.use(PlayerRoute.path, PlayerRoute.router);
|
route.router.use(PlayerRoute.path, PlayerRoute.router);
|
||||||
route.router.use(GotoRoute.path, GotoRoute.router);
|
route.router.use(GotoRoute.path, GotoRoute.router);
|
||||||
|
route.router.use(RoomRoute.path, RoomRoute.router);
|
||||||
@@ -15,12 +15,15 @@ GNU Affero General Public License for more details.
|
|||||||
You should have received a copy of the GNU Affero General Public License
|
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/>. */
|
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 } from "../../apiutils.ts";
|
||||||
import Matchmaking from "../../data/live/base.ts";
|
import Matchmaking from "../../data/live/base.ts";
|
||||||
import { MatchmakingErrorCode } from "../../data/live/types.ts";
|
import { MatchmakingErrorCode } from "../../data/live/types.ts";
|
||||||
import { AuthType } from "../../data/users.ts";
|
import { AuthType } from "../../data/users.ts";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
|
|
||||||
|
const log = new Logging("MatchGotoRoute");
|
||||||
|
|
||||||
export const route = APIUtils.createRouter('/goto');
|
export const route = APIUtils.createRouter('/goto');
|
||||||
|
|
||||||
interface MatchmakingParams {
|
interface MatchmakingParams {
|
||||||
@@ -35,6 +38,7 @@ route.router.post('/room/:roomName',
|
|||||||
|
|
||||||
async (rq: express.Request<MatchmakingParams>, rs: express.Response) => {
|
async (rq: express.Request<MatchmakingParams>, rs: express.Response) => {
|
||||||
if (!rq.params.roomName) {
|
if (!rq.params.roomName) {
|
||||||
|
log.d('Matchmake failed: No room specified');
|
||||||
rs.json({
|
rs.json({
|
||||||
errorCode: MatchmakingErrorCode.NoSuchRoom
|
errorCode: MatchmakingErrorCode.NoSuchRoom
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ import Presence, { PresenceExport } from "../../data/live/presence.ts";
|
|||||||
import { AuthType } from "../../data/users.ts";
|
import { AuthType } from "../../data/users.ts";
|
||||||
import Logging from "@proxnet/undead-logging";
|
import Logging from "@proxnet/undead-logging";
|
||||||
import UnifiedProfile from "../../data/profiles.ts";
|
import UnifiedProfile from "../../data/profiles.ts";
|
||||||
|
import { PlayerStatusVisibility, VRMovementMode } from "../../data/live/types.ts";
|
||||||
|
import { SettingKey } from "../../data/content/settings.ts";
|
||||||
|
|
||||||
const log = new Logging("MatchPlayerRoute");
|
const log = new Logging("MatchPlayerRoute");
|
||||||
|
|
||||||
@@ -51,7 +53,6 @@ route.router.get('/',
|
|||||||
const presExport: PresenceExport[] = [];
|
const presExport: PresenceExport[] = [];
|
||||||
for (const id of ids) {
|
for (const id of ids) {
|
||||||
const pres = await Presence.get(UnifiedProfile.get(id));
|
const pres = await Presence.get(UnifiedProfile.get(id));
|
||||||
await pres.update();
|
|
||||||
presExport.push(await pres.export());
|
presExport.push(await pres.export());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,8 +101,51 @@ route.router.post('/heartbeat',
|
|||||||
|
|
||||||
async (_rq, rs) => {
|
async (_rq, rs) => {
|
||||||
const pres = await Presence.get(rs.locals.profile);
|
const pres = await Presence.get(rs.locals.profile);
|
||||||
await pres.update();
|
|
||||||
rs.json(await pres.export());
|
rs.json(await pres.export());
|
||||||
}
|
}
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
interface StatusVisibilityBody {
|
||||||
|
statusVisibility: PlayerStatusVisibility
|
||||||
|
}
|
||||||
|
const StatusVisibilitySchema = z.object({
|
||||||
|
statusVisibility: z.nativeEnum(PlayerStatusVisibility)
|
||||||
|
});
|
||||||
|
|
||||||
|
route.router.put('/statusvisibility',
|
||||||
|
|
||||||
|
APIUtils.Authentication,
|
||||||
|
APIUtils.AuthenticationType(AuthType.Game),
|
||||||
|
express.urlencoded({ extended: true }),
|
||||||
|
APIUtils.validateRequestBody(StatusVisibilitySchema),
|
||||||
|
|
||||||
|
async (rq: express.Request<NoBody, NoBody, StatusVisibilityBody>, rs: express.Response) => {
|
||||||
|
rs.locals.profile.Settings.setSetting(SettingKey.PlayerStatusVisibility, rq.body.statusVisibility.toString());
|
||||||
|
(await Presence.get(rs.locals.profile)).updateStatusVisibility();
|
||||||
|
rs.sendStatus(200);
|
||||||
|
},
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
interface VRMovementModeBody {
|
||||||
|
vrMovementMode: PlayerStatusVisibility
|
||||||
|
}
|
||||||
|
const VRMovementModeSchema = z.object({
|
||||||
|
vrMovementMode: z.nativeEnum(VRMovementMode)
|
||||||
|
});
|
||||||
|
|
||||||
|
route.router.put('/vrmovementmode',
|
||||||
|
|
||||||
|
APIUtils.Authentication,
|
||||||
|
APIUtils.AuthenticationType(AuthType.Game),
|
||||||
|
express.urlencoded({ extended: true }),
|
||||||
|
APIUtils.validateRequestBody(VRMovementModeSchema),
|
||||||
|
|
||||||
|
async (rq: express.Request<NoBody, NoBody, VRMovementModeBody>, rs: express.Response) => {
|
||||||
|
rs.locals.profile.Settings.setSetting(SettingKey.VRMovementMode, rq.body.vrMovementMode.toString());
|
||||||
|
(await Presence.get(rs.locals.profile)).updateVRMovementMode();
|
||||||
|
rs.sendStatus(200);
|
||||||
|
},
|
||||||
|
|
||||||
|
);
|
||||||
56
src/routes/match/room.ts
Normal file
56
src/routes/match/room.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
/* 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 Instances from "../../data/live/instances.ts";
|
||||||
|
import { AuthType } from "../../data/users.ts";
|
||||||
|
import express from "express";
|
||||||
|
|
||||||
|
export const route = APIUtils.createRouter('/room');
|
||||||
|
|
||||||
|
interface RoomGetInstancesParams {
|
||||||
|
id?: string
|
||||||
|
}
|
||||||
|
route.router.get('/:id/instances',
|
||||||
|
|
||||||
|
APIUtils.Authentication,
|
||||||
|
APIUtils.AuthenticationType(AuthType.Game),
|
||||||
|
|
||||||
|
(rq: express.Request<RoomGetInstancesParams>, rs: express.Response) => {
|
||||||
|
// TODO: send forbidden request when player is requesting instances for a room that they do not have permissions for
|
||||||
|
if (!rq.params.id) {
|
||||||
|
rs.sendStatus(400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const parsedId = parseInt(rq.params.id);
|
||||||
|
if (isNaN(parsedId)) {
|
||||||
|
rs.sendStatus(400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const instances = Instances.getAllRoomInstances(parsedId);
|
||||||
|
rs.json(instances.values().toArray().map(instance => ({
|
||||||
|
roomInstanceId: instance.roomInstanceId,
|
||||||
|
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())
|
||||||
|
})));
|
||||||
|
},
|
||||||
|
|
||||||
|
);
|
||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
CompletionMessage,
|
CompletionMessage,
|
||||||
Message,
|
Message,
|
||||||
MessageKind,
|
MessageKind,
|
||||||
|
PushNotificationId,
|
||||||
SignalMessageType,
|
SignalMessageType,
|
||||||
SignalRMessage,
|
SignalRMessage,
|
||||||
SignalRMessageSchema,
|
SignalRMessageSchema,
|
||||||
@@ -32,6 +33,7 @@ import {
|
|||||||
} from "./types.ts";
|
} from "./types.ts";
|
||||||
import { SocketTarget } from "./targets/targetbase.ts";
|
import { SocketTarget } from "./targets/targetbase.ts";
|
||||||
import { PlayerSocketSubscriptionTarget } from "./targets/SubscribeToPlayers.ts";
|
import { PlayerSocketSubscriptionTarget } from "./targets/SubscribeToPlayers.ts";
|
||||||
|
import Presence from "../data/live/presence.ts";
|
||||||
|
|
||||||
export class SignalRSocketHandler {
|
export class SignalRSocketHandler {
|
||||||
|
|
||||||
@@ -42,6 +44,8 @@ export class SignalRSocketHandler {
|
|||||||
|
|
||||||
#Targets: Map<string, SocketTarget> = new Map();
|
#Targets: Map<string, SocketTarget> = new Map();
|
||||||
|
|
||||||
|
#PresenceUpdateId: number;
|
||||||
|
|
||||||
constructor(socket: WebSocket, player: Profile) {
|
constructor(socket: WebSocket, player: Profile) {
|
||||||
|
|
||||||
this.#socket = socket;
|
this.#socket = socket;
|
||||||
@@ -53,6 +57,11 @@ export class SignalRSocketHandler {
|
|||||||
|
|
||||||
this.#Targets.set('SubscribeToPlayers', new PlayerSocketSubscriptionTarget());
|
this.#Targets.set('SubscribeToPlayers', new PlayerSocketSubscriptionTarget());
|
||||||
|
|
||||||
|
this.#PresenceUpdateId = setInterval(async () => {
|
||||||
|
const pres = await Presence.get(this.#profile);
|
||||||
|
this.sendNotification("PresenceUpdate", await pres.export());
|
||||||
|
}, 8000);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async #dispatchTarget<T = unknown>(target: string, args: unknown): Promise<TargetResult> {
|
async #dispatchTarget<T = unknown>(target: string, args: unknown): Promise<TargetResult> {
|
||||||
@@ -136,6 +145,7 @@ export class SignalRSocketHandler {
|
|||||||
|
|
||||||
destroy(sock: SignalRSocketHandler) {
|
destroy(sock: SignalRSocketHandler) {
|
||||||
return () => {
|
return () => {
|
||||||
|
clearInterval(sock.#PresenceUpdateId);
|
||||||
sock.sendRaw({ type: 7, error: "Socket closed" });
|
sock.sendRaw({ type: 7, error: "Socket closed" });
|
||||||
sock.#socket.close();
|
sock.#socket.close();
|
||||||
sock.#log.i(`Closed hub socket`);
|
sock.#log.i(`Closed hub socket`);
|
||||||
@@ -149,4 +159,16 @@ export class SignalRSocketHandler {
|
|||||||
this.#log.d(`SERVER MESSAGE\n ${type}\n ${JSON.stringify(data as SignalRMessage)}`);
|
this.#log.d(`SERVER MESSAGE\n ${type}\n ${JSON.stringify(data as SignalRMessage)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sendNotification(id: PushNotificationId | string, args: object) {
|
||||||
|
const msg: SignalRMessage = {
|
||||||
|
type: SignalMessageType.Invocation,
|
||||||
|
target: "Notification",
|
||||||
|
arguments: [JSON.stringify({
|
||||||
|
Id: id,
|
||||||
|
Msg: args
|
||||||
|
})]
|
||||||
|
}
|
||||||
|
this.sendRaw(msg);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -145,3 +145,41 @@ export interface TargetResultNotATarget extends TargetResultBase {
|
|||||||
type: TargetResultType.NotATarget
|
type: TargetResultType.NotATarget
|
||||||
}
|
}
|
||||||
export type TargetResult = TargetResultSuccess | TargetResultFailure | TargetResultNotATarget;
|
export type TargetResult = TargetResultSuccess | TargetResultFailure | TargetResultNotATarget;
|
||||||
|
|
||||||
|
export enum PushNotificationId {
|
||||||
|
RelationshipChanged = 1,
|
||||||
|
MessageReceived,
|
||||||
|
MessageDeleted,
|
||||||
|
PresenceHeartbeatResponse,
|
||||||
|
RefreshLogin,
|
||||||
|
Logout,
|
||||||
|
SubscriptionUpdateProfile = 11,
|
||||||
|
SubscriptionUpdatePresence,
|
||||||
|
SubscriptionUpdateGameSession,
|
||||||
|
SubscriptionUpdateRoom = 15,
|
||||||
|
ModerationQuitGame = 20,
|
||||||
|
ModerationUpdateRequired,
|
||||||
|
ModerationKick,
|
||||||
|
ModerationKickAttemptFailed,
|
||||||
|
ModerationRoomBan,
|
||||||
|
ServerMaintenance,
|
||||||
|
GiftPackageReceived = 30,
|
||||||
|
GiftPackageReceivedImmediate,
|
||||||
|
ProfileJuniorStatusUpdate = 40,
|
||||||
|
RelationshipsInvalid = 50,
|
||||||
|
StorefrontBalanceAdd = 60,
|
||||||
|
StorefrontBalanceUpdate,
|
||||||
|
StorefrontBalancePurchase,
|
||||||
|
ConsumableMappingAdded = 70,
|
||||||
|
ConsumableMappingRemoved,
|
||||||
|
PlayerEventCreated = 80,
|
||||||
|
PlayerEventUpdated,
|
||||||
|
PlayerEventDeleted,
|
||||||
|
PlayerEventResponseChanged,
|
||||||
|
PlayerEventResponseDeleted,
|
||||||
|
PlayerEventStateChanged,
|
||||||
|
ChatMessageReceived = 90,
|
||||||
|
CommunityBoardUpdate = 95,
|
||||||
|
CommunityBoardAnnouncementUpdate,
|
||||||
|
InventionModerationStateChanged = 100
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user