Switching from "name" to "id" in texture object + using zod for woka/list validation

This commit is contained in:
David Négrier 2022-02-25 17:05:34 +01:00
parent da469b64d2
commit 08fffab410
11 changed files with 57 additions and 69 deletions

View File

@ -16,11 +16,6 @@ export function isUserNameValid(value: unknown): boolean {
export function areCharacterLayersValid(value: string[] | null): boolean { export function areCharacterLayersValid(value: string[] | null): boolean {
if (!value || !value.length) return false; if (!value || !value.length) return false;
for (let i = 0; i < value.length; i++) {
if (/^\w+$/.exec(value[i]) === null) {
return false;
}
}
return true; return true;
} }

View File

@ -622,7 +622,7 @@ export class RoomConnection implements RoomConnection {
characterLayer: CharacterLayerMessage characterLayer: CharacterLayerMessage
): BodyResourceDescriptionInterface { ): BodyResourceDescriptionInterface {
return { return {
name: characterLayer.name, id: characterLayer.name,
img: characterLayer.url, img: characterLayer.url,
}; };
} }

View File

@ -85,11 +85,11 @@ export abstract class Character extends Container implements OutlineableInterfac
.catch(() => { .catch(() => {
return lazyLoadPlayerCharacterTextures(scene.load, [ return lazyLoadPlayerCharacterTextures(scene.load, [
{ {
name: "color_22", id: "color_22",
img: "resources/customisation/character_color/character_color21.png", img: "resources/customisation/character_color/character_color21.png",
}, },
{ {
name: "eyes_23", id: "eyes_23",
img: "resources/customisation/character_eyes/character_eyes23.png", img: "resources/customisation/character_eyes/character_eyes23.png",
}, },
]).then((textures) => { ]).then((textures) => {

View File

@ -5,7 +5,8 @@ export interface BodyResourceDescriptionListInterface {
} }
export interface BodyResourceDescriptionInterface { export interface BodyResourceDescriptionInterface {
name: string; id: string;
label: string;
img: string; img: string;
level?: number; level?: number;
} }
@ -89,7 +90,7 @@ export class PlayerTextures {
const resources: BodyResourceDescriptionListInterface = {}; const resources: BodyResourceDescriptionListInterface = {};
for (const collection of category.collections) { for (const collection of category.collections) {
for (const texture of collection.textures) { for (const texture of collection.textures) {
resources[texture.id] = { name: texture.name, img: texture.url }; resources[texture.id] = { id: texture.id, label: texture.name, img: texture.url };
} }
} }
return resources; return resources;
@ -97,5 +98,5 @@ export class PlayerTextures {
} }
export const OBJECTS: BodyResourceDescriptionInterface[] = [ export const OBJECTS: BodyResourceDescriptionInterface[] = [
{ name: "teleportation", img: "resources/objects/teleportation.png" }, { id: "teleportation", label: "Teleport", img: "resources/objects/teleportation.png" },
]; ];

View File

@ -14,7 +14,7 @@ export const loadAllLayers = (load: LoaderPlugin): BodyResourceDescriptionInterf
const layerArray: BodyResourceDescriptionInterface[] = []; const layerArray: BodyResourceDescriptionInterface[] = [];
Object.values(layer).forEach((textureDescriptor) => { Object.values(layer).forEach((textureDescriptor) => {
layerArray.push(textureDescriptor); layerArray.push(textureDescriptor);
load.spritesheet(textureDescriptor.name, textureDescriptor.img, { frameWidth: 32, frameHeight: 32 }); load.spritesheet(textureDescriptor.id, textureDescriptor.img, { frameWidth: 32, frameHeight: 32 });
}); });
returnArray.push(layerArray); returnArray.push(layerArray);
}); });
@ -23,7 +23,7 @@ export const loadAllLayers = (load: LoaderPlugin): BodyResourceDescriptionInterf
export const loadAllDefaultModels = (load: LoaderPlugin): BodyResourceDescriptionInterface[] => { export const loadAllDefaultModels = (load: LoaderPlugin): BodyResourceDescriptionInterface[] => {
const returnArray = Object.values(PlayerTextures.PLAYER_RESOURCES); const returnArray = Object.values(PlayerTextures.PLAYER_RESOURCES);
returnArray.forEach((playerResource: BodyResourceDescriptionInterface) => { returnArray.forEach((playerResource: BodyResourceDescriptionInterface) => {
load.spritesheet(playerResource.name, playerResource.img, { frameWidth: 32, frameHeight: 32 }); load.spritesheet(playerResource.id, playerResource.img, { frameWidth: 32, frameHeight: 32 });
}); });
return returnArray; return returnArray;
}; };
@ -46,7 +46,7 @@ export const lazyLoadPlayerCharacterTextures = (
textures.forEach((texture) => { textures.forEach((texture) => {
try { try {
//TODO refactor //TODO refactor
if (!loadPlugin.textureManager.exists(texture.name)) { if (!loadPlugin.textureManager.exists(texture.id)) {
promisesList.push( promisesList.push(
createLoadingPromise(loadPlugin, texture, { createLoadingPromise(loadPlugin, texture, {
frameWidth: 32, frameWidth: 32,
@ -69,7 +69,7 @@ export const lazyLoadPlayerCharacterTextures = (
//If the loading fail, we render the default model instead. //If the loading fail, we render the default model instead.
return returnPromise.then((keys) => return returnPromise.then((keys) =>
keys.map((key) => { keys.map((key) => {
return typeof key !== "string" ? key.name : key; return typeof key !== "string" ? key.id : key;
}) })
); );
}; };
@ -80,22 +80,22 @@ export const createLoadingPromise = (
frameConfig: FrameConfig frameConfig: FrameConfig
) => { ) => {
return new CancelablePromise<BodyResourceDescriptionInterface>((res, rej, cancel) => { return new CancelablePromise<BodyResourceDescriptionInterface>((res, rej, cancel) => {
if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) { if (loadPlugin.textureManager.exists(playerResourceDescriptor.id)) {
return res(playerResourceDescriptor); return res(playerResourceDescriptor);
} }
cancel(() => { cancel(() => {
loadPlugin.off("loaderror"); loadPlugin.off("loaderror");
loadPlugin.off("filecomplete-spritesheet-" + playerResourceDescriptor.name); loadPlugin.off("filecomplete-spritesheet-" + playerResourceDescriptor.id);
return; return;
}); });
loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, frameConfig); loadPlugin.spritesheet(playerResourceDescriptor.id, playerResourceDescriptor.img, frameConfig);
const errorCallback = (file: { src: string }) => { const errorCallback = (file: { src: string }) => {
if (file.src !== playerResourceDescriptor.img) return; if (file.src !== playerResourceDescriptor.img) return;
console.error("failed loading player resource: ", playerResourceDescriptor); console.error("failed loading player resource: ", playerResourceDescriptor);
rej(playerResourceDescriptor); rej(playerResourceDescriptor);
loadPlugin.off("filecomplete-spritesheet-" + playerResourceDescriptor.name, successCallback); loadPlugin.off("filecomplete-spritesheet-" + playerResourceDescriptor.id, successCallback);
loadPlugin.off("loaderror", errorCallback); loadPlugin.off("loaderror", errorCallback);
}; };
const successCallback = () => { const successCallback = () => {
@ -103,7 +103,7 @@ export const createLoadingPromise = (
res(playerResourceDescriptor); res(playerResourceDescriptor);
}; };
loadPlugin.once("filecomplete-spritesheet-" + playerResourceDescriptor.name, successCallback); loadPlugin.once("filecomplete-spritesheet-" + playerResourceDescriptor.id, successCallback);
loadPlugin.on("loaderror", errorCallback); loadPlugin.on("loaderror", errorCallback);
}); });
}; };

View File

@ -199,13 +199,13 @@ export class CustomizeScene extends AbstractCharacterScene {
const children: Array<string> = new Array<string>(); const children: Array<string> = new Array<string>();
for (let j = 0; j <= layerNumber; j++) { for (let j = 0; j <= layerNumber; j++) {
if (j === layerNumber) { if (j === layerNumber) {
children.push(this.layers[j][selectedItem].name); children.push(this.layers[j][selectedItem].id);
} else { } else {
const layer = this.selectedLayers[j]; const layer = this.selectedLayers[j];
if (layer === undefined) { if (layer === undefined) {
continue; continue;
} }
children.push(this.layers[j][layer].name); children.push(this.layers[j][layer].id);
} }
} }
return children; return children;
@ -283,7 +283,7 @@ export class CustomizeScene extends AbstractCharacterScene {
let i = 0; let i = 0;
for (const layerItem of this.selectedLayers) { for (const layerItem of this.selectedLayers) {
if (layerItem !== undefined) { if (layerItem !== undefined) {
layers.push(this.layers[i][layerItem].name); layers.push(this.layers[i][layerItem].id);
} }
i++; i++;
} }

View File

@ -151,16 +151,16 @@ export class SelectCharacterScene extends AbstractCharacterScene {
const playerResource = this.playerModels[i]; const playerResource = this.playerModels[i];
//check already exist texture //check already exist texture
if (this.players.find((c) => c.texture.key === playerResource.name)) { if (this.players.find((c) => c.texture.key === playerResource.id)) {
continue; continue;
} }
const [middleX, middleY] = this.getCharacterPosition(); const [middleX, middleY] = this.getCharacterPosition();
const player = this.physics.add.sprite(middleX, middleY, playerResource.name, 0); const player = this.physics.add.sprite(middleX, middleY, playerResource.id, 0);
this.setUpPlayer(player, i); this.setUpPlayer(player, i);
this.anims.create({ this.anims.create({
key: playerResource.name, key: playerResource.id,
frames: this.anims.generateFrameNumbers(playerResource.name, { start: 0, end: 11 }), frames: this.anims.generateFrameNumbers(playerResource.id, { start: 0, end: 11 }),
frameRate: 8, frameRate: 8,
repeat: -1, repeat: -1,
}); });
@ -185,7 +185,7 @@ export class SelectCharacterScene extends AbstractCharacterScene {
this.currentSelectUser = 0; this.currentSelectUser = 0;
} }
this.selectedPlayer = this.players[this.currentSelectUser]; this.selectedPlayer = this.players[this.currentSelectUser];
this.selectedPlayer.play(this.playerModels[this.currentSelectUser].name); this.selectedPlayer.play(this.playerModels[this.currentSelectUser].id);
} }
protected moveUser() { protected moveUser() {
@ -270,7 +270,7 @@ export class SelectCharacterScene extends AbstractCharacterScene {
protected updateSelectedPlayer(): void { protected updateSelectedPlayer(): void {
this.selectedPlayer?.anims?.pause(this.selectedPlayer?.anims.currentAnim.frames[0]); this.selectedPlayer?.anims?.pause(this.selectedPlayer?.anims.currentAnim.frames[0]);
const player = this.players[this.currentSelectUser]; const player = this.players[this.currentSelectUser];
player?.play(this.playerModels[this.currentSelectUser].name); player?.play(this.playerModels[this.currentSelectUser].id);
this.selectedPlayer = player; this.selectedPlayer = player;
localUserStore.setPlayerCharacterIndex(this.currentSelectUser); localUserStore.setPlayerCharacterIndex(this.currentSelectUser);
} }

View File

@ -53,7 +53,8 @@
"prom-client": "^12.0.0", "prom-client": "^12.0.0",
"qs": "^6.10.3", "qs": "^6.10.3",
"query-string": "^6.13.3", "query-string": "^6.13.3",
"uuidv4": "^6.0.7" "uuidv4": "^6.0.7",
"zod": "^3.12.0"
}, },
"devDependencies": { "devDependencies": {
"@types/circular-json": "^0.4.0", "@types/circular-json": "^0.4.0",

View File

@ -1,46 +1,35 @@
import * as tg from "generic-type-guard"; import * as tg from "generic-type-guard";
import { z } from "zod";
//The list of all the player textures, both the default models and the partial textures used for customization //The list of all the player textures, both the default models and the partial textures used for customization
export const isWokaTexture = new tg.IsInterface() const wokaTexture = z.object({
.withProperties({ id: z.string(),
id: tg.isString, name: z.string(),
name: tg.isString, url: z.string(),
url: tg.isString, tags: z.array(z.string()).optional(),
position: tg.isNumber, tintable: z.boolean().optional(),
}) });
.withOptionalProperties({
tags: tg.isArray(tg.isString),
tintable: tg.isBoolean,
})
.get();
export type WokaTexture = tg.GuardedType<typeof isWokaTexture>; export type WokaTexture = z.infer<typeof wokaTexture>;
export const isWokaTextureCollection = new tg.IsInterface() const wokaTextureCollection = z.object({
.withProperties({ name: z.string(),
name: tg.isString, textures: z.array(wokaTexture),
position: tg.isNumber, });
textures: tg.isArray(isWokaTexture),
})
.get();
export type WokaTextureCollection = tg.GuardedType<typeof isWokaTextureCollection>; export type WokaTextureCollection = z.infer<typeof wokaTextureCollection>;
export const isWokaPartType = new tg.IsInterface() const wokaPartType = z.object({
.withProperties({ collections: z.array(wokaTextureCollection),
collections: tg.isArray(isWokaTextureCollection), required: z.boolean().optional(),
}) });
.withOptionalProperties({
required: tg.isBoolean,
})
.get();
export type WokaPartType = tg.GuardedType<typeof isWokaPartType>; export type WokaPartType = z.infer<typeof wokaPartType>;
export const isWokaList = new tg.IsInterface().withStringIndexSignature(isWokaPartType).get(); export const wokaList = z.record(wokaPartType);
export type WokaList = tg.GuardedType<typeof isWokaList>; export type WokaList = z.infer<typeof wokaList>;
export const wokaPartNames = ["woka", "body", "eyes", "hair", "clothes", "hat", "accessory"]; export const wokaPartNames = ["woka", "body", "eyes", "hair", "clothes", "hat", "accessory"];

View File

@ -1,6 +1,6 @@
import axios from "axios"; import axios, { AxiosResponse } from "axios";
import { ADMIN_API_TOKEN, ADMIN_API_URL } from "../Enum/EnvironmentVariable"; import { ADMIN_API_TOKEN, ADMIN_API_URL } from "../Enum/EnvironmentVariable";
import { isWokaList, WokaList } from "../Enum/PlayerTextures"; import { wokaList, WokaList } from "../Enum/PlayerTextures";
import { WokaServiceInterface } from "./WokaServiceInterface"; import { WokaServiceInterface } from "./WokaServiceInterface";
class AdminWokaService implements WokaServiceInterface { class AdminWokaService implements WokaServiceInterface {
@ -9,7 +9,7 @@ class AdminWokaService implements WokaServiceInterface {
*/ */
getWokaList(roomUrl: string, token: string): Promise<WokaList | undefined> { getWokaList(roomUrl: string, token: string): Promise<WokaList | undefined> {
return axios return axios
.get(`${ADMIN_API_URL}/api/woka/list`, { .get<unknown, AxiosResponse<unknown>>(`${ADMIN_API_URL}/api/woka/list`, {
headers: { Authorization: `${ADMIN_API_TOKEN}` }, headers: { Authorization: `${ADMIN_API_TOKEN}` },
params: { params: {
roomUrl, roomUrl,
@ -17,10 +17,7 @@ class AdminWokaService implements WokaServiceInterface {
}, },
}) })
.then((res) => { .then((res) => {
if (isWokaList(res.data)) { return wokaList.parse(res.data);
throw new Error("Bad response format provided by woka list endpoint");
}
return res.data;
}) })
.catch((err) => { .catch((err) => {
console.error(`Cannot get woka list from admin API with token: ${token}`, err); console.error(`Cannot get woka list from admin API with token: ${token}`, err);

View File

@ -2834,3 +2834,8 @@ z-schema@^4.2.3:
validator "^13.6.0" validator "^13.6.0"
optionalDependencies: optionalDependencies:
commander "^2.7.1" commander "^2.7.1"
zod@^3.12.0:
version "3.12.0"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.12.0.tgz#84ba9f6bdb7835e2483982d5f52cfffcb6a00346"
integrity sha512-w+mmntgEL4hDDL5NLFdN6Fq2DSzxfmlSoJqiYE1/CApO8EkOCxvJvRYEVf8Vr/lRs3i6gqoiyFM6KRcWqqdBzQ==