forked from zombieb/galvanic-corrosion-rewrite
b
This commit is contained in:
@@ -1,5 +1,72 @@
|
||||
import path from "node:path";
|
||||
import { ServerContentBase } from "../ContentBase.ts";
|
||||
import { RootPath } from "../../util/path.ts";
|
||||
|
||||
interface AvatarImport {
|
||||
allPossibleCombinations: {
|
||||
_avatarItemData: {
|
||||
Name: string
|
||||
},
|
||||
_avatarItemVisualData: {
|
||||
prefabGuid: string,
|
||||
maskGuid: string,
|
||||
swatchGuid: string,
|
||||
decalGuid: string
|
||||
}
|
||||
}[]
|
||||
}
|
||||
|
||||
interface AvatarItemExport {
|
||||
AvatarItemType: AvatarItemType,
|
||||
AvatarItemDesc: string,
|
||||
FriendlyName: string,
|
||||
Tooltip: string,
|
||||
Rarity: ItemRarity
|
||||
}
|
||||
export enum AvatarItemType {
|
||||
Outfit,
|
||||
HairDye
|
||||
}
|
||||
export enum ItemRarity {
|
||||
None = -1,
|
||||
Common,
|
||||
Uncommon = 10,
|
||||
Rare = 20,
|
||||
Epic = 30,
|
||||
Legendary = 50
|
||||
}
|
||||
|
||||
export class AvatarContentBase extends ServerContentBase {
|
||||
|
||||
#rawImport: AvatarItemExport[] = [];
|
||||
|
||||
getAllPossibleCombinations() {
|
||||
return this.#rawImport;
|
||||
}
|
||||
|
||||
formatAllPossibleCombinations() {
|
||||
const parsed = JSON.parse(Deno.readTextFileSync(path.join(RootPath, '/res/avatar.json'))) as AvatarImport;
|
||||
function formatVisualData(data: {
|
||||
prefabGuid: string,
|
||||
maskGuid: string,
|
||||
swatchGuid: string,
|
||||
decalGuid: string
|
||||
}) {
|
||||
const p = data.prefabGuid ? data.prefabGuid : '';
|
||||
const m = data.maskGuid ? data.maskGuid : '';
|
||||
const s = data.swatchGuid ? data.swatchGuid : '';
|
||||
const d = data.decalGuid ? data.decalGuid : '';
|
||||
return `${p},${s},${m},${d},`
|
||||
}
|
||||
this.#rawImport = parsed.allPossibleCombinations.map(data => ({
|
||||
AvatarItemType: AvatarItemType.Outfit,
|
||||
AvatarItemDesc: formatVisualData(data._avatarItemVisualData),
|
||||
FriendlyName: data._avatarItemData.Name,
|
||||
Tooltip: "pre-avatar update item",
|
||||
Rarity: ItemRarity.None
|
||||
}));
|
||||
}
|
||||
|
||||
override start() {
|
||||
this.formatAllPossibleCombinations();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import z from "zod";
|
||||
import { CommandExec } from "./cmdtypes.ts";
|
||||
|
||||
export interface CommandOptions {
|
||||
key: string[],
|
||||
subcommands?: Command[],
|
||||
@@ -16,7 +15,6 @@ export default class Command {
|
||||
exec: CommandExec | null;
|
||||
validate: z.ZodTuple | null;
|
||||
help: string | null;
|
||||
#argsLength: number;
|
||||
|
||||
constructor(options: CommandOptions) {
|
||||
this.subCmds = options.subcommands || [];
|
||||
@@ -24,7 +22,6 @@ export default class Command {
|
||||
this.exec = options.exec || null;
|
||||
this.validate = options.zod || null;
|
||||
this.help = options.help || null;
|
||||
this.#argsLength = options.zod ? options.zod.def.items.length : 0;
|
||||
}
|
||||
|
||||
getKey() {
|
||||
@@ -40,12 +37,10 @@ export default class Command {
|
||||
const cmd = this.subCmds.find(cmd => cmd.getKey().includes(root));
|
||||
if (cmd) {
|
||||
const newArgs = args.slice(1);
|
||||
if (cmd.#argsLength && newArgs.length !== cmd.#argsLength && cmd.help) return new Error(cmd.help);
|
||||
if (cmd.validate) {
|
||||
const res = cmd.validate.safeParse(newArgs);
|
||||
if (res.success) return cmd.dispatch(...res.data);
|
||||
else if (cmd.help) return new Error(cmd.help);
|
||||
else if (cmd.#argsLength) return new Error(`'${root}' validation error: expected ${cmd.#argsLength} args, got ${newArgs.length}`);
|
||||
else return new Error(`'${root}' validation error`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,6 @@ export default class GameConfigsBase extends ServerContentBase {
|
||||
|
||||
#kvKey = 'gameconfigs';
|
||||
|
||||
#usageError = new Error("Usage: <key: string> [<value: string>]");
|
||||
|
||||
async getGameConfig(key: string) {
|
||||
return (await this.kv.getKv().get<string>([this.#kvKey, key])).value;
|
||||
}
|
||||
@@ -25,17 +23,16 @@ export default class GameConfigsBase extends ServerContentBase {
|
||||
this.kv.getKv().list<string>({ prefix: [this.#kvKey] })
|
||||
)).map(val =>
|
||||
({ Key: val.key[1] as string, Value: val.value })
|
||||
).filter(gc => gc.Key && gc.Value); // ensure both the key and value exist
|
||||
).filter(gc => gc.Key && typeof gc.Value == 'string');
|
||||
}
|
||||
|
||||
override start(): void {
|
||||
override start() {
|
||||
this.server.Commands.addRootCommand(new Command({
|
||||
key: ['gameconfigs', 'gameconfig', 'gc'],
|
||||
subcommands: [
|
||||
new Command({
|
||||
key: ['get', 'g'],
|
||||
exec: async (key: string) => {
|
||||
if (!key) return this.#usageError;
|
||||
return await this.getGameConfig(key);
|
||||
},
|
||||
zod: z.tuple([
|
||||
@@ -45,20 +42,18 @@ export default class GameConfigsBase extends ServerContentBase {
|
||||
}),
|
||||
new Command({
|
||||
key: ['set', 's'],
|
||||
exec: async (key: string, value: string) => {
|
||||
if (!key || !value) return this.#usageError;
|
||||
exec: async (key: string, ...values: string[]) => {
|
||||
const value = values.join(' ');
|
||||
return await this.setGameConfig(key, value);
|
||||
},
|
||||
zod: z.tuple([
|
||||
z.string(),
|
||||
z.string()
|
||||
]),
|
||||
help: 'Set a new GameConfig: <key: string>, <value: string>'
|
||||
]).rest(z.string().optional()),
|
||||
help: 'Set a new GameConfig: <key: string>, ...<values: string[] (joined by " ")>'
|
||||
}),
|
||||
new Command({
|
||||
key: ['del', 'rem', 'd', 'remove'],
|
||||
exec: async (key: string) => {
|
||||
if (!key) return this.#usageError;
|
||||
return await this.delGameConfig(key);
|
||||
},
|
||||
zod: z.tuple([
|
||||
@@ -72,6 +67,7 @@ export default class GameConfigsBase extends ServerContentBase {
|
||||
const configs = await this.getAllGameConfigs();
|
||||
return configs;
|
||||
},
|
||||
zod: z.tuple([]),
|
||||
help: 'List all saved GameConfigs'
|
||||
}),
|
||||
]
|
||||
|
||||
16
src/server/presence/base.ts
Normal file
16
src/server/presence/base.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { ServerContentBase } from "../ContentBase.ts";
|
||||
import type Profile from "../profiles/profile.ts";
|
||||
|
||||
class Presence {
|
||||
|
||||
}
|
||||
|
||||
export class PresenceBase extends ServerContentBase {
|
||||
|
||||
#presenceMap: Map<Profile, Presence> = new Map();
|
||||
|
||||
getPresence() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
35
src/server/profiles/content/Avatar.ts
Normal file
35
src/server/profiles/content/Avatar.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import z from "zod";
|
||||
import ProfileContentManager from "./base.ts";
|
||||
|
||||
export const profileAvatarSchema = z.object({
|
||||
OutfitSelections: z.string(),
|
||||
HairColor: z.string(),
|
||||
SkinColor: z.string(),
|
||||
FaceFeatures: z.string(),
|
||||
});
|
||||
export type ProfileAvatar = z.infer<typeof profileAvatarSchema>;
|
||||
|
||||
export class ProfileAvatarManager extends ProfileContentManager {
|
||||
|
||||
#key = this.profile.constructProfilePropertyKey('avatar');
|
||||
#noAvatar: ProfileAvatar = {
|
||||
OutfitSelections: "",
|
||||
HairColor: "",
|
||||
SkinColor: "",
|
||||
FaceFeatures: ""
|
||||
}
|
||||
|
||||
async getAvatar(): Promise<ProfileAvatar> {
|
||||
const item = await this.kv.getKv().get(this.#key);
|
||||
if (item.value) {
|
||||
const parsed = profileAvatarSchema.safeParse(item.value);
|
||||
if (parsed.success) return parsed.data;
|
||||
else return this.#noAvatar;
|
||||
} else return this.#noAvatar;
|
||||
}
|
||||
|
||||
async setAvatar(outfit: ProfileAvatar) {
|
||||
await this.kv.getKv().set(this.#key, outfit);
|
||||
}
|
||||
|
||||
}
|
||||
34
src/server/profiles/content/Matchmaking.ts
Normal file
34
src/server/profiles/content/Matchmaking.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import ProfileContentManager from "./base.ts";
|
||||
|
||||
export enum DeviceClass {
|
||||
Unknown,
|
||||
VR,
|
||||
Screen,
|
||||
Mobile,
|
||||
VRLow,
|
||||
Quest2
|
||||
}
|
||||
|
||||
|
||||
export class ProfileMatchmakingManager extends ProfileContentManager {
|
||||
|
||||
#deviceClassKey = this.profile.constructProfilePropertyKey('deviceclass');
|
||||
async setLastDeviceClass(dc: DeviceClass) {
|
||||
await this.kv.getKv().set(this.#deviceClassKey, dc);
|
||||
}
|
||||
async getLastDeviceClass(): Promise<DeviceClass | null> {
|
||||
return (await this.kv.getKv().get<DeviceClass>(this.#deviceClassKey)).value || null;
|
||||
}
|
||||
|
||||
#loginLockKey = this.profile.constructProfilePropertyKey('loginlock');
|
||||
async setLoginLock(lock: string) {
|
||||
await this.kv.getKv().set(this.#deviceClassKey, lock);
|
||||
}
|
||||
async getLoginLock(): Promise<string> {
|
||||
return (await this.kv.getKv().get<string>(this.#loginLockKey)).value || "";
|
||||
}
|
||||
async hasLoginLock() {
|
||||
return (await this.kv.getKv().get<string>(this.#loginLockKey)).value ? true : false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import KV from "../persistence/kv.ts";
|
||||
import { ProfileRole } from "../platforms/base.ts";
|
||||
import { type ServerBase } from "../server.ts";
|
||||
import { type SignalRSocketHandler } from "../socket/signalr/socket.ts";
|
||||
import { ProfileAvatarManager } from "./content/Avatar.ts";
|
||||
import { ProfileSettingsManager } from "./content/Settings.ts";
|
||||
import ProfileManagerBase from "./manager.ts";
|
||||
import { recNetAccountSchema, SelfAccount, type RecNetAccount } from "./types/profile.ts";
|
||||
@@ -17,6 +18,7 @@ class Profile {
|
||||
#selfAcc: SelfAccount;
|
||||
|
||||
Settings: ProfileSettingsManager;
|
||||
Avatar: ProfileAvatarManager;
|
||||
|
||||
constructor(acc: SelfAccount, kv: KV, server: ServerBase) {
|
||||
this.#id = acc.accountId;
|
||||
@@ -25,6 +27,7 @@ class Profile {
|
||||
this.#server = server;
|
||||
|
||||
this.Settings = new ProfileSettingsManager(this, this.#kv);
|
||||
this.Avatar = new ProfileAvatarManager(this, this.#kv);
|
||||
}
|
||||
|
||||
async #saveSelfAcc() {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AvatarContentBase } from "./avatars/base.ts";
|
||||
import { EventManager } from "./baseevent.ts";
|
||||
import { CommandsBase } from "./commands/commands.ts";
|
||||
import GameConfigsBase from "./gameconfigs/base.ts";
|
||||
@@ -16,6 +17,7 @@ class ServerBase extends EventManager<ServerEvents> {
|
||||
GameConfigs = new GameConfigsBase(this, 'gameconfigs', true);
|
||||
Commands = new CommandsBase(this, 'commands');
|
||||
Platforms = new PlatformsManager(this, 'platforms', true);
|
||||
Avatars = new AvatarContentBase(this, 'avatars');
|
||||
}
|
||||
|
||||
const Server = new ServerBase();
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
import { SocketTarget } from "./targets/targetbase.ts";
|
||||
import type Profile from "../../profiles/profile.ts";
|
||||
import { detailedLog } from "../../../main.ts";
|
||||
import { PlayerSocketSubscriptionTarget } from "./targets/SubscribeToPlayers.ts";
|
||||
|
||||
const logmessages = true;
|
||||
|
||||
@@ -39,7 +40,7 @@ export class SignalRSocketHandler {
|
||||
|
||||
player.setSocketHandler(this);
|
||||
|
||||
//this.#Targets.set('SubscribeToPlayers', new PlayerSocketSubscriptionTarget(this));
|
||||
this.#Targets.set('SubscribeToPlayers', new PlayerSocketSubscriptionTarget(this));
|
||||
|
||||
for (const target of this.#Targets.values()) target.onInit();
|
||||
|
||||
@@ -55,7 +56,9 @@ export class SignalRSocketHandler {
|
||||
if (!targetExec) return { type: TargetResultType.NotATarget } as TargetResultNotATarget;
|
||||
else {
|
||||
try {
|
||||
return { type: TargetResultType.Success, data: await targetExec.exec(args) } as TargetResultSuccess<T>;
|
||||
const parsed = targetExec.zod.safeParse(args);
|
||||
if (parsed.success) return { type: TargetResultType.Success, data: await targetExec.exec(args) } as TargetResultSuccess<T>;
|
||||
else return { type: TargetResultType.Failure, err: "Argument parse failure" } as TargetResultFailure;
|
||||
} catch (err) {
|
||||
this.#log.w(`Target '${target}' function error: ${err}`);
|
||||
if (err instanceof Error) return { type: TargetResultType.Failure, err: err } as TargetResultFailure;
|
||||
|
||||
18
src/server/socket/signalr/targets/SubscribeToPlayers.ts
Normal file
18
src/server/socket/signalr/targets/SubscribeToPlayers.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import z from "zod";
|
||||
import { SocketTarget } from "./targetbase.ts";
|
||||
|
||||
export class PlayerSocketSubscriptionTarget extends SocketTarget {
|
||||
|
||||
#ids: number[] = [];
|
||||
|
||||
override zod = z.tuple([]).rest(z.number());
|
||||
|
||||
override exec(...ids: number[]) {
|
||||
this.#ids = ids;
|
||||
}
|
||||
|
||||
getIds() {
|
||||
return this.#ids;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,26 +1,12 @@
|
||||
/* 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 z from "zod";
|
||||
import type { SignalRSocketHandler } from "../socket.ts";
|
||||
|
||||
export class SocketTarget {
|
||||
|
||||
socket: SignalRSocketHandler;
|
||||
|
||||
zod: z.ZodTuple = z.tuple([]);
|
||||
|
||||
constructor(socket: SignalRSocketHandler) {
|
||||
this.socket = socket;
|
||||
}
|
||||
@@ -33,8 +19,7 @@ export class SocketTarget {
|
||||
return;
|
||||
}
|
||||
|
||||
// deno-lint-ignore require-await
|
||||
async exec(_args: unknown) {
|
||||
exec(_args: unknown): Promise<unknown> | unknown {
|
||||
throw new Error("Execution for this target is not set.");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user