diff --git a/packages/server/src/messages/start.ts b/packages/server/src/messages/start.ts index 280bce92..12f67edd 100644 --- a/packages/server/src/messages/start.ts +++ b/packages/server/src/messages/start.ts @@ -6,6 +6,7 @@ import { logs } from "../logs.js" import { Player } from "../player/player.js" import type { Room } from "../room/base.js" import type { State } from "../state/state.js" +import { variantParser } from "../utils/variantParser.js" /** * @param this @@ -42,9 +43,19 @@ export function start( return } if (variantData) { - if (variantsConfig.parse) { - variantData = variantsConfig.parse(variantData) + try { + if (variantsConfig.parse) { + variantData = variantsConfig.parse(variantData) + } else { + variantData = variantParser(variantData) + } + } catch (e) { + client?.send("gameError", { + data: "Game room setup config parsing failed. " + e.message, + }) + return } + const validationResults = variantsConfig.validate?.(variantData) if (validationResults !== true) { diff --git a/packages/server/src/room/base.ts b/packages/server/src/room/base.ts index 30f4f2e0..5f0a6e4a 100644 --- a/packages/server/src/room/base.ts +++ b/packages/server/src/room/base.ts @@ -19,7 +19,6 @@ import type { Player, ServerPlayerMessage, Bot } from "../player/index.js" import { State } from "../state/state.js" import { debugRoomMessage } from "../utils/debugRoomMessage.js" -import { VariantsConfig } from "./gameVariants.js" import type { RoomDefinition } from "./roomType.js" type BroadcastOptions = IBroadcastOptions & { diff --git a/packages/server/src/room/index.ts b/packages/server/src/room/index.ts index 4c759f79..fc5e7cea 100644 --- a/packages/server/src/room/index.ts +++ b/packages/server/src/room/index.ts @@ -1,4 +1,3 @@ export { defineRoom, RoomConstructor } from "./defineRoom.js" export { Room } from "./base.js" export { RoomDefinition } from "./roomType.js" -export { VariantsConfig } from "./gameVariants.js" diff --git a/packages/server/src/room/roomType.ts b/packages/server/src/room/roomType.ts index f682375d..36bc688f 100644 --- a/packages/server/src/room/roomType.ts +++ b/packages/server/src/room/roomType.ts @@ -6,7 +6,6 @@ import type { Player } from "../player/index.js" import type { State } from "../state/state.js" import type { Room } from "./base.js" -import { VariantsConfig } from "./gameVariants.js" /** * Extracted from colyseus room, and suggesting. diff --git a/packages/server/src/state/state.ts b/packages/server/src/state/state.ts index b6c36702..6a2bf20f 100644 --- a/packages/server/src/state/state.ts +++ b/packages/server/src/state/state.ts @@ -6,7 +6,6 @@ import { containsChildren } from "../annotations/containsChildren.js" import { type } from "../annotations/type.js" import { Player } from "../player/player.js" import { PlayerViewPosition } from "../playerViewPosition.js" -import { VariantsConfig } from "../room/gameVariants.js" import { applyTraitsMixins, Entity } from "../traits/entity.js" import { IdentityTrait } from "../traits/identity.js" import { LabelTrait } from "../traits/label.js" diff --git a/packages/server/src/utils/__test__/variantParset.test.ts b/packages/server/src/utils/__test__/variantParset.test.ts new file mode 100644 index 00000000..6092efea --- /dev/null +++ b/packages/server/src/utils/__test__/variantParset.test.ts @@ -0,0 +1,17 @@ +import { variantParser } from "../variantParser.js" + +describe("deep object", () => { + it("parses single deep field", () => { + const data = { + "foo.bar": true, + "card.suit": "S", + "card.rank": "A", + first: 1, + } + const result = variantParser(data) + expect(result["foo"]["bar"]).toBe(true) + expect(result["card"]["suit"]).toBe("S") + expect(result["card"]["rank"]).toBe("A") + expect(result["first"]).toBe(1) + }) +}) diff --git a/packages/server/src/utils/variantParser.ts b/packages/server/src/utils/variantParser.ts new file mode 100644 index 00000000..2baf427a --- /dev/null +++ b/packages/server/src/utils/variantParser.ts @@ -0,0 +1,49 @@ +function deepAssign( + object: Record, + path: string[], + value: unknown, +): void { + if (path.length === 1) { + // as deep as it gets, set + object[path[0]] = value + return + } + + const key = path.shift() + + if (object[key] === undefined) { + object[key] = {} + } else if (typeof object[key] !== "object") { + throw new Error( + `Expected to access "${path.join(".")} but it's of type ${typeof object[ + key + ]}"`, + ) + } + + deepAssign(object[key], path, value) +} + +export const variantParser = >( + value: unknown, +): T => { + // Expected flat object of props. Possible sub objects with dot notation + + if (typeof value !== "object") { + throw new Error("Variant data not an object, cannot parse") + } + + const result = {} + + Object.keys(value).forEach((key) => { + const isPath = key.includes("$") + + if (!isPath) { + result[key] = value[key] + } else { + deepAssign(result, key.split("$"), value[key]) + } + }) + + return result as T +} diff --git a/packages/types/src/index.d.ts b/packages/types/src/index.d.ts index 61a0ab6d..8952cec3 100644 --- a/packages/types/src/index.d.ts +++ b/packages/types/src/index.d.ts @@ -1,6 +1,7 @@ /// /// /// +/// interface RoomCreateOptions { [key: string]: unknown diff --git a/packages/server/src/room/gameVariants.ts b/packages/types/src/variants.d.ts similarity index 78% rename from packages/server/src/room/gameVariants.ts rename to packages/types/src/variants.d.ts index fb752458..07ea68e5 100644 --- a/packages/server/src/room/gameVariants.ts +++ b/packages/types/src/variants.d.ts @@ -1,11 +1,15 @@ -type VariantPreset = { +type VariantPreset< + T extends Record = Record, +> = { name: string data: DeepPartial /** Should this preset be merging with the defaults or not? */ includesDefaults?: boolean } -export type VariantsConfig = { +type VariantsConfig< + T extends Record = Record, +> = { defaults: Required /** * Decide how you want to send variant data from the client