import { EnableCameraSceneName } from "./EnableCameraScene"; import { loadAllLayers } from "../Entity/PlayerTexturesLoadingManager"; import { gameManager } from "../Game/GameManager"; import { localUserStore } from "../../Connexion/LocalUserStore"; import { Loader } from "../Components/Loader"; import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures"; import { AbstractCharacterScene } from "./AbstractCharacterScene"; import { areCharacterLayersValid } from "../../Connexion/LocalUser"; import { SelectCharacterSceneName } from "./SelectCharacterScene"; import { waScaleManager } from "../Services/WaScaleManager"; import { analyticsClient } from "../../Administration/AnalyticsClient"; import { isMediaBreakpointUp } from "../../Utils/BreakpointsUtils"; import { PUSHER_URL } from "../../Enum/EnvironmentVariable"; import { CustomWokaBodyPart, CustomWokaBodyPartOrder, CustomWokaPreviewer, CustomWokaPreviewerConfig, } from "../Components/CustomizeWoka/CustomWokaPreviewer"; import { DraggableGrid } from "@home-based-studio/phaser3-utils"; import { WokaBodyPartSlot, WokaBodyPartSlotConfig } from "../Components/CustomizeWoka/WokaBodyPartSlot"; import { DraggableGridEvent } from "@home-based-studio/phaser3-utils/lib/utils/gui/containers/grids/DraggableGrid"; import { Button } from "../Components/Ui/Button"; import { wokaList } from "../../Messages/JsonMessages/PlayerTextures"; import { TexturesHelper } from "../Helpers/TexturesHelper"; import { IconButton, IconButtonConfig, IconButtonEvent } from "../Components/Ui/IconButton"; export const CustomizeSceneName = "CustomizeScene"; export class CustomizeScene extends AbstractCharacterScene { private customWokaPreviewer!: CustomWokaPreviewer; private bodyPartsDraggableGridLeftShadow!: Phaser.GameObjects.Image; private bodyPartsDraggableGridRightShadow!: Phaser.GameObjects.Image; private bodyPartsDraggableGrid!: DraggableGrid; private bodyPartsButtons!: Record; private randomizeButton!: Button; private finishButton!: Button; private selectedLayers: number[] = [0, 0, 0, 0, 0, 0]; private layers: BodyResourceDescriptionInterface[][] = []; private selectedBodyPartType?: CustomWokaBodyPart; protected lazyloadingAttempt = true; //permit to update texture loaded after renderer private loader: Loader; private readonly SLOT_DIMENSION = 100; constructor() { super({ key: CustomizeSceneName, }); this.loader = new Loader(this); } public preload(): void { this.input.dragDistanceThreshold = 10; this.load.image("iconClothes", "/resources/icons/icon_clothes.png"); this.load.image("iconAccessory", "/resources/icons/icon_accessory.png"); this.load.image("iconHat", "/resources/icons/icon_hat.png"); this.load.image("iconHair", "/resources/icons/icon_hair.png"); this.load.image("iconEyes", "/resources/icons/icon_eyes.png"); this.load.image("iconBody", "/resources/icons/icon_body.png"); this.load.image("iconTurn", "/resources/icons/icon_turn.png"); this.load.spritesheet("floorTiles", "/resources/tilesets/floor_tiles.png", { frameWidth: 32, frameHeight: 32 }); TexturesHelper.createRectangleTexture(this, "gridEdgeShadow", this.cameras.main.width * 0.2, 115, 0x000000); const wokaMetadataKey = "woka-list" + gameManager.currentStartedRoom.href; this.cache.json.remove(wokaMetadataKey); this.superLoad .json( wokaMetadataKey, `${PUSHER_URL}/woka/list?roomUrl=` + encodeURIComponent(gameManager.currentStartedRoom.href), undefined, { responseType: "text", headers: { Authorization: localUserStore.getAuthToken() ?? "", }, withCredentials: true, }, (key, type, data) => { this.playerTextures.loadPlayerTexturesMetadata(wokaList.parse(data)); this.layers = loadAllLayers(this.load, this.playerTextures); this.lazyloadingAttempt = false; } ) .catch((e) => console.error(e)); //this function must stay at the end of preload function this.loader.addLoader(); } public create(): void { this.createSlotBackgroundTextures(); this.initializeCustomWokaPreviewer(); this.initializeBodyPartsDraggableGrid(); this.initializeEdgeShadows(); this.initializeBodyPartsButtons(); this.initializeRandomizeButton(); this.initializeFinishButton(); this.selectedBodyPartType = CustomWokaBodyPart.Body; this.bodyPartsButtons.Body.select(); this.bindEventHandlers(); this.refreshPlayerCurrentOutfit(); this.onResize(); } public update(time: number, dt: number): void { this.customWokaPreviewer.update(); } public onResize(): void { this.handleCustomWokaPreviewerOnResize(); this.handleBodyPartButtonsOnResize(); this.handleRandomizeButtonOnResize(); this.handleFinishButtonOnResize(); this.handleBodyPartsDraggableGridOnResize(); } public nextSceneToCamera() { const layers: string[] = []; let i = 0; for (const layerItem of this.selectedLayers) { if (layerItem !== undefined) { layers.push(this.layers[i][layerItem].id); } i++; } if (!areCharacterLayersValid(layers)) { return; } analyticsClient.validationWoka("CustomizeWoka"); gameManager.setCharacterLayers(layers); this.scene.stop(CustomizeSceneName); waScaleManager.restoreZoom(); gameManager.tryResumingGame(EnableCameraSceneName); } public backToPreviousScene() { this.scene.stop(CustomizeSceneName); waScaleManager.restoreZoom(); this.scene.run(SelectCharacterSceneName); } private createSlotBackgroundTextures(): void { for (let i = 0; i < 4; i += 1) { TexturesHelper.createFloorRectangleTexture(this, `floorTexture${i}`, 50, 50, "floorTiles", i); } } private initializeCustomWokaPreviewer(): void { this.customWokaPreviewer = new CustomWokaPreviewer( this, 0, 0, this.getCustomWokaPreviewerConfig() ).setDisplaySize(200, 200); } private initializeBodyPartsDraggableGrid(): void { this.bodyPartsDraggableGrid = new DraggableGrid(this, { position: { x: 0, y: 0 }, maskPosition: { x: 0, y: 0 }, dimension: { x: 485, y: 165 }, horizontal: true, repositionToCenter: true, itemsInRow: 1, margin: { left: (innerWidth / waScaleManager.getActualZoom() - this.SLOT_DIMENSION) * 0.5, right: (innerWidth / waScaleManager.getActualZoom() - this.SLOT_DIMENSION) * 0.5, }, spacing: 5, debug: { showDraggableSpace: false, }, }); } private initializeEdgeShadows(): void { this.bodyPartsDraggableGridLeftShadow = this.add .image(0, this.cameras.main.worldView.y + this.cameras.main.height, "gridEdgeShadow") .setAlpha(1, 0, 1, 0) .setOrigin(0, 0.5); this.bodyPartsDraggableGridRightShadow = this.add .image( this.cameras.main.worldView.x + this.cameras.main.width, this.cameras.main.worldView.y + this.cameras.main.height, "gridEdgeShadow" ) .setAlpha(1, 0, 1, 0) .setFlipX(true) .setOrigin(1, 0.5); } private initializeBodyPartsButtons(): void { this.bodyPartsButtons = { [CustomWokaBodyPart.Accessory]: new IconButton( this, 0, 0, this.getDefaultIconButtonConfig("iconAccessory") ), [CustomWokaBodyPart.Body]: new IconButton(this, 0, 0, this.getDefaultIconButtonConfig("iconBody")), [CustomWokaBodyPart.Clothes]: new IconButton(this, 0, 0, this.getDefaultIconButtonConfig("iconClothes")), [CustomWokaBodyPart.Eyes]: new IconButton(this, 0, 0, this.getDefaultIconButtonConfig("iconEyes")), [CustomWokaBodyPart.Hair]: new IconButton(this, 0, 0, this.getDefaultIconButtonConfig("iconHair")), [CustomWokaBodyPart.Hat]: new IconButton(this, 0, 0, this.getDefaultIconButtonConfig("iconHat")), }; } private getDefaultIconButtonConfig(iconTextureKey: string): IconButtonConfig { return { iconTextureKey, width: 25, height: 25, idle: { color: 0xffffff, borderThickness: 3, borderColor: 0xe7e7e7, }, hover: { color: 0xe7e7e7, borderThickness: 3, borderColor: 0xadafbc, }, pressed: { color: 0xadafbc, borderThickness: 3, borderColor: 0xadafbc, }, selected: { color: 0xadafbc, borderThickness: 3, borderColor: 0x209cee, }, }; } private initializeRandomizeButton(): void { this.randomizeButton = new Button(this, 50, 50, { width: 95, height: 50, idle: { color: 0xffffff, textColor: "#000000", borderThickness: 3, borderColor: 0xe7e7e7, }, hover: { color: 0xe7e7e7, textColor: "#000000", borderThickness: 3, borderColor: 0xadafbc, }, pressed: { color: 0xadafbc, textColor: "#000000", borderThickness: 3, borderColor: 0xadafbc, }, }); this.randomizeButton.setText("Randomize"); } private initializeFinishButton(): void { this.finishButton = new Button(this, 50, 50, { width: 95, height: 50, idle: { color: 0x209cee, textColor: "#ffffff", borderThickness: 3, borderColor: 0x006bb3, }, hover: { color: 0x0987db, textColor: "#ffffff", borderThickness: 3, borderColor: 0x006bb3, }, pressed: { color: 0x006bb3, textColor: "#ffffff", borderThickness: 3, borderColor: 0x006bb3, }, }); this.finishButton.setText("Finish"); } private refreshPlayerCurrentOutfit(): void { let i = 0; for (const layerItem of this.selectedLayers) { const bodyPart = CustomWokaBodyPart[CustomWokaBodyPartOrder[i] as CustomWokaBodyPart]; this.customWokaPreviewer.updateSprite(this.layers[i][layerItem].id, bodyPart); i += 1; } } private getCurrentlySelectedWokaTexturesRecord(): Record { return { [CustomWokaBodyPart.Accessory]: this.layers[CustomWokaBodyPartOrder.Accessory][this.selectedLayers[CustomWokaBodyPartOrder.Accessory]] .id, [CustomWokaBodyPart.Body]: this.layers[CustomWokaBodyPartOrder.Body][this.selectedLayers[CustomWokaBodyPartOrder.Body]].id, [CustomWokaBodyPart.Clothes]: this.layers[CustomWokaBodyPartOrder.Clothes][this.selectedLayers[CustomWokaBodyPartOrder.Clothes]].id, [CustomWokaBodyPart.Eyes]: this.layers[CustomWokaBodyPartOrder.Eyes][this.selectedLayers[CustomWokaBodyPartOrder.Eyes]].id, [CustomWokaBodyPart.Hair]: this.layers[CustomWokaBodyPartOrder.Hair][this.selectedLayers[CustomWokaBodyPartOrder.Hair]].id, [CustomWokaBodyPart.Hat]: this.layers[CustomWokaBodyPartOrder.Hat][this.selectedLayers[CustomWokaBodyPartOrder.Hat]].id, }; } private handleCustomWokaPreviewerOnResize(): void { this.customWokaPreviewer.x = this.cameras.main.worldView.x + this.cameras.main.width / 2; this.customWokaPreviewer.y = this.customWokaPreviewer.displayHeight * 0.5 + 10; } private handleBodyPartButtonsOnResize(): void { const ratio = innerHeight / innerWidth; const slotDimension = 50; for (const part in this.bodyPartsButtons) { this.bodyPartsButtons[part as CustomWokaBodyPart].setDisplaySize(slotDimension, slotDimension); } const slotSize = this.bodyPartsButtons.Accessory.displayHeight; if (ratio > 1.6) { const middle = Math.floor(this.customWokaPreviewer.x); const left = Math.floor(middle - slotSize - 23); const right = Math.floor(middle + slotSize + 23); const top = Math.floor( this.customWokaPreviewer.y + this.customWokaPreviewer.displayHeight * 0.5 + slotSize * 1.5 + 30 ); const bottom = Math.floor(top + slotSize + 23); this.bodyPartsButtons.Body.setPosition(left, top); this.bodyPartsButtons.Eyes.setPosition(middle, top); this.bodyPartsButtons.Hair.setPosition(right, top); this.bodyPartsButtons.Clothes.setPosition(left, bottom); this.bodyPartsButtons.Hat.setPosition(middle, bottom); this.bodyPartsButtons.Accessory.setPosition(right, bottom); return; } const left = Math.floor( this.customWokaPreviewer.x - this.customWokaPreviewer.displayWidth * 0.5 - slotSize * 0.5 - 24 ); const right = Math.floor( this.customWokaPreviewer.x + this.customWokaPreviewer.displayWidth * 0.5 + slotSize * 0.5 + 24 ); const top = Math.floor(0 + slotSize * 0.5 + 11); const middle = Math.floor(top + slotSize + 24); const bottom = Math.floor(middle + slotSize + 24); this.bodyPartsButtons.Body.setPosition(left, top); this.bodyPartsButtons.Eyes.setPosition(left, middle); this.bodyPartsButtons.Hair.setPosition(left, bottom); this.bodyPartsButtons.Clothes.setPosition(right, top); this.bodyPartsButtons.Hat.setPosition(right, middle); this.bodyPartsButtons.Accessory.setPosition(right, bottom); } private handleBodyPartsDraggableGridOnResize(): void { const gridHeight = 110; const gridWidth = innerWidth / waScaleManager.getActualZoom(); const gridTopMargin = Math.max( this.finishButton.y + this.finishButton.displayHeight * 0.5, this.bodyPartsButtons.Hair.y + this.bodyPartsButtons.Hair.displayHeight * 0.5 ); const gridBottomMargin = this.cameras.main.worldView.y + this.cameras.main.height; const yPos = gridTopMargin + (gridBottomMargin - gridTopMargin) * 0.5; const gridPos = { x: this.cameras.main.worldView.x + this.cameras.main.width / 2, y: yPos, }; this.bodyPartsDraggableGridLeftShadow.setPosition(0, yPos); this.bodyPartsDraggableGridRightShadow.setPosition( this.cameras.main.worldView.x + this.cameras.main.width, yPos ); try { this.bodyPartsDraggableGrid.changeDraggableSpacePosAndSize( gridPos, { x: gridWidth, y: gridHeight }, gridPos ); } catch (error) { console.warn(error); } this.populateGrid(); const selectedGridItem = this.selectGridItem(); if (selectedGridItem) { this.centerGridOnItem(selectedGridItem); } } private handleRandomizeButtonOnResize(): void { const x = this.customWokaPreviewer.x + (this.customWokaPreviewer.displayWidth - this.randomizeButton.displayWidth) * 0.5; const y = this.customWokaPreviewer.y + (this.customWokaPreviewer.displayHeight + this.randomizeButton.displayHeight) * 0.5 + 10; this.randomizeButton.setPosition(x, y); } private handleFinishButtonOnResize(): void { const x = this.customWokaPreviewer.x - (this.customWokaPreviewer.displayWidth - this.randomizeButton.displayWidth) * 0.5; const y = this.customWokaPreviewer.y + (this.customWokaPreviewer.displayHeight + this.randomizeButton.displayHeight) * 0.5 + 10; this.finishButton.setPosition(x, y); } private getCustomWokaPreviewerConfig(): CustomWokaPreviewerConfig { return { color: 0xffffff, borderThickness: 1, borderColor: 0xadafbc, bodyPartsOffsetX: -1, }; } private getWokaBodyPartSlotConfig(bodyPart?: CustomWokaBodyPart, newTextureKey?: string): WokaBodyPartSlotConfig { const textures = this.getCurrentlySelectedWokaTexturesRecord(); if (bodyPart && newTextureKey) { textures[bodyPart] = newTextureKey; } return { color: 0xffffff, borderThickness: 1, borderColor: 0xadafbc, borderSelectedColor: 0x209cee, textureKeys: textures, offsetX: -4, offsetY: 2, }; } private bindEventHandlers(): void { this.bindKeyboardEventHandlers(); this.events.addListener("wake", () => { waScaleManager.saveZoom(); waScaleManager.zoomModifier = isMediaBreakpointUp("md") ? 3 : 1; }); this.randomizeButton.on(Phaser.Input.Events.POINTER_UP, () => { this.randomizeOutfit(); this.clearGrid(); this.deselectAllButtons(); this.refreshPlayerCurrentOutfit(); }); this.finishButton.on(Phaser.Input.Events.POINTER_UP, () => { this.nextSceneToCamera(); }); for (const bodyPart in CustomWokaBodyPart) { const button = this.bodyPartsButtons[bodyPart as CustomWokaBodyPart]; button.on(IconButtonEvent.Clicked, (selected: boolean) => { if (!selected) { this.selectBodyPartType(bodyPart as CustomWokaBodyPart); } }); } this.bodyPartsDraggableGrid.on(DraggableGridEvent.ItemClicked, (item: WokaBodyPartSlot) => { void this.bodyPartsDraggableGrid.centerOnItem(this.bodyPartsDraggableGrid.getAllItems().indexOf(item), 500); this.deselectAllSlots(); item.select(true); this.setNewBodyPart(Number(item.getId())); }); } private bindKeyboardEventHandlers(): void { this.input.keyboard.on("keyup-ENTER", () => { this.nextSceneToCamera(); }); this.input.keyboard.on("keyup-BACKSPACE", () => { this.backToPreviousScene(); }); this.input.keyboard.on("keydown-LEFT", () => { this.selectNextGridItem(true); }); this.input.keyboard.on("keydown-RIGHT", () => { this.selectNextGridItem(); }); this.input.keyboard.on("keydown-UP", () => { this.selectNextCategory(true); }); this.input.keyboard.on("keydown-DOWN", () => { this.selectNextCategory(); }); this.input.keyboard.on("keydown-W", () => { this.selectNextCategory(true); }); this.input.keyboard.on("keydown-S", () => { this.selectNextCategory(); }); this.input.keyboard.on("keydown-A", () => { this.selectNextGridItem(true); }); this.input.keyboard.on("keydown-D", () => { this.selectNextGridItem(); }); } private selectBodyPartType(bodyPart: CustomWokaBodyPart): void { this.selectedBodyPartType = bodyPart; this.deselectAllButtons(); const button = this.bodyPartsButtons[bodyPart]; button.select(true); this.populateGrid(); const selectedGridItem = this.selectGridItem(); if (!selectedGridItem) { return; } this.bodyPartsDraggableGrid.moveContentToBeginning(); this.centerGridOnItem(selectedGridItem); } private setNewBodyPart(bodyPartIndex: number) { this.changeOutfitPart(bodyPartIndex); this.refreshPlayerCurrentOutfit(); } private selectGridItem(): WokaBodyPartSlot | undefined { const bodyPartType = this.selectedBodyPartType; if (!bodyPartType) { return; } const items = this.bodyPartsDraggableGrid.getAllItems() as WokaBodyPartSlot[]; const item = items.find( (item) => item.getContentData()[bodyPartType] === this.getBodyPartSelectedItemId(bodyPartType) ); item?.select(); return item; } private getBodyPartSelectedItemId(bodyPartType: CustomWokaBodyPart): string { const categoryIndex = CustomWokaBodyPartOrder[bodyPartType]; return this.layers[categoryIndex][this.selectedLayers[categoryIndex]].id; } private selectNextGridItem(previous: boolean = false): void { if (!this.selectedBodyPartType) { return; } const currentIndex = this.getCurrentlySelectedItemIndex(); if (previous ? currentIndex > 0 : currentIndex < this.bodyPartsDraggableGrid.getAllItems().length - 1) { this.deselectAllSlots(); const item = this.bodyPartsDraggableGrid.getAllItems()[ currentIndex + (previous ? -1 : 1) ] as WokaBodyPartSlot; if (item) { item.select(); this.setNewBodyPart(Number(item.getId())); this.centerGridOnItem(item); } } } private selectNextCategory(previous: boolean = false): void { if (!this.selectedBodyPartType) { this.selectBodyPartType(CustomWokaBodyPart.Body); return; } if (previous && this.selectedBodyPartType === CustomWokaBodyPart.Body) { return; } if (!previous && this.selectedBodyPartType === CustomWokaBodyPart.Accessory) { return; } const index = CustomWokaBodyPartOrder[this.selectedBodyPartType] + (previous ? -1 : 1); this.selectBodyPartType(CustomWokaBodyPart[CustomWokaBodyPartOrder[index] as CustomWokaBodyPart]); } private getCurrentlySelectedItemIndex(): number { const bodyPartType = this.selectedBodyPartType; if (!bodyPartType) { return -1; } const items = this.bodyPartsDraggableGrid.getAllItems() as WokaBodyPartSlot[]; return items.findIndex( (item) => item.getContentData()[bodyPartType] === this.getBodyPartSelectedItemId(bodyPartType) ); } private centerGridOnItem(item: WokaBodyPartSlot, duration: number = 500): void { void this.bodyPartsDraggableGrid.centerOnItem( this.bodyPartsDraggableGrid.getAllItems().indexOf(item), duration ); } private randomizeOutfit(): void { for (let i = 0; i < this.selectedLayers.length; i += 1) { this.selectedLayers[i] = Math.floor(Math.random() * this.layers[i].length); } } private changeOutfitPart(index: number): void { if (this.selectedBodyPartType === undefined) { return; } this.selectedLayers[CustomWokaBodyPartOrder[this.selectedBodyPartType]] = index; } private populateGrid(): void { if (this.selectedBodyPartType === undefined) { return; } const bodyPartsLayer = this.layers[CustomWokaBodyPartOrder[this.selectedBodyPartType]]; this.clearGrid(); for (let i = 0; i < bodyPartsLayer.length; i += 1) { const slot = new WokaBodyPartSlot( this, 0, 0, { ...this.getWokaBodyPartSlotConfig(this.selectedBodyPartType, bodyPartsLayer[i].id), offsetX: 0, offsetY: 0, }, i ).setDisplaySize(this.SLOT_DIMENSION, this.SLOT_DIMENSION); this.bodyPartsDraggableGrid.addItem(slot); } } private clearGrid(): void { this.bodyPartsDraggableGrid.clearAllItems(); } private deselectAllButtons(): void { for (const bodyPart in CustomWokaBodyPart) { this.bodyPartsButtons[bodyPart as CustomWokaBodyPart].select(false); } } private deselectAllSlots(): void { this.bodyPartsDraggableGrid.getAllItems().forEach((slot) => (slot as WokaBodyPartSlot).select(false)); } }