Switching from "name" to "id" in texture object + using zod for woka/list validation
This commit is contained in:
parent
da469b64d2
commit
08fffab410
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -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) => {
|
||||||
|
@ -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" },
|
||||||
];
|
];
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -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++;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
|
@ -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"];
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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==
|
||||||
|
Loading…
Reference in New Issue
Block a user