better woka preview, wip
This commit is contained in:
parent
9f823506b9
commit
138e8aece4
@ -0,0 +1,28 @@
|
||||
import { CustomizedCharacter } from "../../Entity/CustomizedCharacter";
|
||||
import { PlayerAnimationDirections } from "../../Player/Animation";
|
||||
|
||||
export class CustomWokaPreviewer extends Phaser.GameObjects.Container {
|
||||
private background: Phaser.GameObjects.Rectangle;
|
||||
private character: CustomizedCharacter;
|
||||
|
||||
constructor(scene: Phaser.Scene, x: number, y: number) {
|
||||
super(scene, x, y);
|
||||
|
||||
this.background = this.createBackground();
|
||||
this.character = new CustomizedCharacter(scene, 0, 0, ["body19", "clothes4"]);
|
||||
this.character.setScale(4);
|
||||
this.setSize(this.background.displayWidth, this.background.displayHeight);
|
||||
|
||||
this.add([this.background, this.character]);
|
||||
|
||||
this.scene.add.existing(this);
|
||||
}
|
||||
|
||||
public update(): void {
|
||||
this.character.playAnimation(PlayerAnimationDirections.Down, true);
|
||||
}
|
||||
|
||||
private createBackground(): Phaser.GameObjects.Rectangle {
|
||||
return this.scene.add.rectangle(0, 0, 150, 300, 0xbfbfbf, 0.5);
|
||||
}
|
||||
}
|
@ -1,4 +1,9 @@
|
||||
import { PlayerAnimationDirections, PlayerAnimationTypes } from "../Player/Animation";
|
||||
import {
|
||||
AnimationData,
|
||||
getPlayerAnimations,
|
||||
PlayerAnimationDirections,
|
||||
PlayerAnimationTypes,
|
||||
} from "../Player/Animation";
|
||||
import { SpeechBubble } from "./SpeechBubble";
|
||||
import Text = Phaser.GameObjects.Text;
|
||||
import Container = Phaser.GameObjects.Container;
|
||||
@ -20,15 +25,6 @@ import type CancelablePromise from "cancelable-promise";
|
||||
import { TalkIcon } from "../Components/TalkIcon";
|
||||
|
||||
const playerNameY = -25;
|
||||
|
||||
interface AnimationData {
|
||||
key: string;
|
||||
frameRate: number;
|
||||
repeat: number;
|
||||
frameModel: string; //todo use an enum
|
||||
frames: number[];
|
||||
}
|
||||
|
||||
const interactiveRadius = 35;
|
||||
|
||||
export abstract class Character extends Container implements OutlineableInterface {
|
||||
@ -232,7 +228,7 @@ export abstract class Character extends Container implements OutlineableInterfac
|
||||
}
|
||||
}
|
||||
|
||||
public addTextures(textures: string[], frame?: string | number): void {
|
||||
private addTextures(textures: string[], frame?: string | number): void {
|
||||
if (textures.length < 1) {
|
||||
throw new TextureError("no texture given");
|
||||
}
|
||||
@ -243,7 +239,8 @@ export abstract class Character extends Container implements OutlineableInterfac
|
||||
}
|
||||
const sprite = new Sprite(this.scene, 0, 0, texture, frame);
|
||||
this.add(sprite);
|
||||
this.getPlayerAnimations(texture).forEach((d) => {
|
||||
console.log(texture);
|
||||
getPlayerAnimations(texture).forEach((d) => {
|
||||
this.scene.anims.create({
|
||||
key: d.key,
|
||||
frames: this.scene.anims.generateFrameNumbers(d.frameModel, { frames: d.frames }),
|
||||
@ -263,67 +260,6 @@ export abstract class Character extends Container implements OutlineableInterfac
|
||||
return this.scene.plugins.get("rexOutlinePipeline") as unknown as OutlinePipelinePlugin | undefined;
|
||||
}
|
||||
|
||||
private getPlayerAnimations(name: string): AnimationData[] {
|
||||
return [
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Walk}`,
|
||||
frameModel: name,
|
||||
frames: [0, 1, 2, 1],
|
||||
frameRate: 10,
|
||||
repeat: -1,
|
||||
},
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Walk}`,
|
||||
frameModel: name,
|
||||
frames: [3, 4, 5, 4],
|
||||
frameRate: 10,
|
||||
repeat: -1,
|
||||
},
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Walk}`,
|
||||
frameModel: name,
|
||||
frames: [6, 7, 8, 7],
|
||||
frameRate: 10,
|
||||
repeat: -1,
|
||||
},
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Walk}`,
|
||||
frameModel: name,
|
||||
frames: [9, 10, 11, 10],
|
||||
frameRate: 10,
|
||||
repeat: -1,
|
||||
},
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Idle}`,
|
||||
frameModel: name,
|
||||
frames: [1],
|
||||
frameRate: 10,
|
||||
repeat: 1,
|
||||
},
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Idle}`,
|
||||
frameModel: name,
|
||||
frames: [4],
|
||||
frameRate: 10,
|
||||
repeat: 1,
|
||||
},
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Idle}`,
|
||||
frameModel: name,
|
||||
frames: [7],
|
||||
frameRate: 10,
|
||||
repeat: 1,
|
||||
},
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Idle}`,
|
||||
frameModel: name,
|
||||
frames: [10],
|
||||
frameRate: 10,
|
||||
repeat: 1,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
protected playAnimation(direction: PlayerAnimationDirections, moving: boolean): void {
|
||||
if (this.invisible) return;
|
||||
for (const [texture, sprite] of this.sprites.entries()) {
|
||||
|
@ -1,20 +1,54 @@
|
||||
import Container = Phaser.GameObjects.Container;
|
||||
import type { Scene } from "phaser";
|
||||
import Sprite = Phaser.GameObjects.Sprite;
|
||||
import { getPlayerAnimations, PlayerAnimationDirections, PlayerAnimationTypes } from "../Player/Animation";
|
||||
|
||||
/**
|
||||
* A sprite of a customized character (used in the Customize Scene only)
|
||||
*/
|
||||
export class CustomizedCharacter extends Container {
|
||||
private sprites: Phaser.GameObjects.Sprite[];
|
||||
|
||||
public constructor(scene: Scene, x: number, y: number, layers: string[]) {
|
||||
super(scene, x, y);
|
||||
this.sprites = [];
|
||||
this.updateSprites(layers);
|
||||
}
|
||||
|
||||
public updateSprites(layers: string[]): void {
|
||||
this.sprites = [];
|
||||
this.removeAll(true);
|
||||
for (const layer of layers) {
|
||||
this.add(new Sprite(this.scene, 0, 0, layer));
|
||||
for (const texture of layers) {
|
||||
const newSprite = new Sprite(this.scene, 0, 0, texture);
|
||||
this.sprites.push(newSprite);
|
||||
getPlayerAnimations(texture).forEach((d) => {
|
||||
this.scene.anims.create({
|
||||
key: d.key,
|
||||
frames: this.scene.anims.generateFrameNumbers(d.frameModel, { frames: d.frames }),
|
||||
frameRate: d.frameRate,
|
||||
repeat: d.repeat,
|
||||
});
|
||||
});
|
||||
// Needed, otherwise, animations are not handled correctly.
|
||||
if (this.scene) {
|
||||
this.scene.sys.updateList.add(newSprite);
|
||||
}
|
||||
}
|
||||
this.add(this.sprites);
|
||||
}
|
||||
|
||||
public playAnimation(direction: PlayerAnimationDirections, moving: boolean): void {
|
||||
for (const sprite of this.sprites) {
|
||||
if (!sprite.anims) {
|
||||
console.error("ANIMS IS NOT DEFINED!!!");
|
||||
return;
|
||||
}
|
||||
const textureKey = sprite.texture.key;
|
||||
if (moving && (!sprite.anims.currentAnim || sprite.anims.currentAnim.key !== direction)) {
|
||||
sprite.play(textureKey + "-" + direction + "-" + PlayerAnimationTypes.Walk, true);
|
||||
} else if (!moving) {
|
||||
sprite.anims.play(textureKey + "-" + direction + "-" + PlayerAnimationTypes.Idle, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,19 +9,22 @@ import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures"
|
||||
import { AbstractCharacterScene } from "./AbstractCharacterScene";
|
||||
import { areCharacterLayersValid } from "../../Connexion/LocalUser";
|
||||
import { SelectCharacterSceneName } from "./SelectCharacterScene";
|
||||
import { activeRowStore, customCharacterSceneVisibleStore } from "../../Stores/CustomCharacterStore";
|
||||
import { activeRowStore } from "../../Stores/CustomCharacterStore";
|
||||
import { waScaleManager } from "../Services/WaScaleManager";
|
||||
import { CustomizedCharacter } from "../Entity/CustomizedCharacter";
|
||||
import { get } from "svelte/store";
|
||||
import { analyticsClient } from "../../Administration/AnalyticsClient";
|
||||
import { isMediaBreakpointUp } from "../../Utils/BreakpointsUtils";
|
||||
import { PUSHER_URL } from "../../Enum/EnvironmentVariable";
|
||||
import { CustomWokaPreviewer } from "../Components/CustomizeWoka/CustomWokaPreviewer";
|
||||
|
||||
export const CustomizeSceneName = "CustomizeScene";
|
||||
|
||||
export class CustomizeScene extends AbstractCharacterScene {
|
||||
private Rectangle!: Rectangle;
|
||||
|
||||
private customWokaPreviewer: CustomWokaPreviewer;
|
||||
|
||||
private selectedLayers: number[] = [0];
|
||||
private containersRow: CustomizedCharacter[][] = [];
|
||||
private layers: BodyResourceDescriptionInterface[][] = [];
|
||||
@ -40,7 +43,7 @@ export class CustomizeScene extends AbstractCharacterScene {
|
||||
this.loader = new Loader(this);
|
||||
}
|
||||
|
||||
preload() {
|
||||
public preload(): void {
|
||||
const wokaMetadataKey = "woka-list";
|
||||
this.cache.json.remove(wokaMetadataKey);
|
||||
// FIXME: window.location.href is wrong. We need the URL of the main room (so we need to apply any redirect before!)
|
||||
@ -82,13 +85,8 @@ export class CustomizeScene extends AbstractCharacterScene {
|
||||
});
|
||||
}
|
||||
|
||||
create() {
|
||||
customCharacterSceneVisibleStore.set(true);
|
||||
this.events.addListener("wake", () => {
|
||||
waScaleManager.saveZoom();
|
||||
waScaleManager.zoomModifier = isMediaBreakpointUp("md") ? 3 : 1;
|
||||
customCharacterSceneVisibleStore.set(true);
|
||||
});
|
||||
public create(): void {
|
||||
console.log(this.layers);
|
||||
|
||||
waScaleManager.saveZoom();
|
||||
waScaleManager.zoomModifier = isMediaBreakpointUp("md") ? 3 : 1;
|
||||
@ -100,7 +98,6 @@ export class CustomizeScene extends AbstractCharacterScene {
|
||||
33
|
||||
);
|
||||
this.Rectangle.setStrokeStyle(2, 0xffffff);
|
||||
this.add.existing(this.Rectangle);
|
||||
|
||||
this.createCustomizeLayer(0, 0, 0);
|
||||
this.createCustomizeLayer(0, 0, 1);
|
||||
@ -110,6 +107,91 @@ export class CustomizeScene extends AbstractCharacterScene {
|
||||
this.createCustomizeLayer(0, 0, 5);
|
||||
|
||||
this.moveLayers();
|
||||
|
||||
const customCursorPosition = localUserStore.getCustomCursorPosition();
|
||||
if (customCursorPosition) {
|
||||
activeRowStore.set(customCursorPosition.activeRow);
|
||||
this.selectedLayers = customCursorPosition.selectedLayers;
|
||||
this.moveLayers();
|
||||
this.updateSelectedLayer();
|
||||
}
|
||||
|
||||
this.customWokaPreviewer = new CustomWokaPreviewer(this, 300, 300);
|
||||
|
||||
this.onResize();
|
||||
|
||||
this.bindEventHandlers();
|
||||
}
|
||||
|
||||
public update(time: number, dt: number): void {
|
||||
this.customWokaPreviewer.update();
|
||||
if (this.lazyloadingAttempt) {
|
||||
this.moveLayers();
|
||||
this.doMoveCursorHorizontally(this.moveHorizontally);
|
||||
this.lazyloadingAttempt = false;
|
||||
}
|
||||
|
||||
if (this.moveHorizontally !== 0) {
|
||||
this.doMoveCursorHorizontally(this.moveHorizontally);
|
||||
this.moveHorizontally = 0;
|
||||
}
|
||||
if (this.moveVertically !== 0) {
|
||||
this.doMoveCursorVertically(this.moveVertically);
|
||||
this.moveVertically = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public moveCursorHorizontally(index: number): void {
|
||||
this.moveHorizontally = index;
|
||||
}
|
||||
|
||||
public moveCursorVertically(index: number): void {
|
||||
this.moveVertically = index;
|
||||
}
|
||||
|
||||
public onResize(): void {
|
||||
this.moveLayers();
|
||||
|
||||
this.Rectangle.x = this.cameras.main.worldView.x + this.cameras.main.width / 2;
|
||||
this.Rectangle.y = this.cameras.main.worldView.y + this.cameras.main.height / 3;
|
||||
|
||||
this.customWokaPreviewer.x = this.cameras.main.worldView.x + this.cameras.main.width / 2;
|
||||
this.customWokaPreviewer.y = this.cameras.main.worldView.y + this.cameras.main.height / 2;
|
||||
}
|
||||
|
||||
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 bindEventHandlers(): void {
|
||||
this.events.addListener("wake", () => {
|
||||
waScaleManager.saveZoom();
|
||||
waScaleManager.zoomModifier = isMediaBreakpointUp("md") ? 3 : 1;
|
||||
});
|
||||
|
||||
this.input.keyboard.on("keyup-ENTER", () => {
|
||||
this.nextSceneToCamera();
|
||||
});
|
||||
@ -124,24 +206,6 @@ export class CustomizeScene extends AbstractCharacterScene {
|
||||
this.input.keyboard.on("keyup-LEFT", () => (this.moveHorizontally = -1));
|
||||
this.input.keyboard.on("keyup-DOWN", () => (this.moveVertically = 1));
|
||||
this.input.keyboard.on("keyup-UP", () => (this.moveVertically = -1));
|
||||
|
||||
const customCursorPosition = localUserStore.getCustomCursorPosition();
|
||||
if (customCursorPosition) {
|
||||
activeRowStore.set(customCursorPosition.activeRow);
|
||||
this.selectedLayers = customCursorPosition.selectedLayers;
|
||||
this.moveLayers();
|
||||
this.updateSelectedLayer();
|
||||
}
|
||||
|
||||
this.onResize();
|
||||
}
|
||||
|
||||
public moveCursorHorizontally(index: number): void {
|
||||
this.moveHorizontally = index;
|
||||
}
|
||||
|
||||
public moveCursorVertically(index: number): void {
|
||||
this.moveVertically = index;
|
||||
}
|
||||
|
||||
private doMoveCursorHorizontally(index: number): void {
|
||||
@ -246,17 +310,6 @@ export class CustomizeScene extends AbstractCharacterScene {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param x, the sprite's vertical position
|
||||
* @param y, the sprites's horizontal position
|
||||
* @param name, the sprite's name
|
||||
* @return a new sprite
|
||||
*/
|
||||
private generateLayers(x: number, y: number, name: string): Sprite {
|
||||
//return new Sprite(this, x, y, name);
|
||||
return this.add.sprite(0, 0, name);
|
||||
}
|
||||
|
||||
private updateSelectedLayer() {
|
||||
for (let i = 0; i < this.containersRow.length; i++) {
|
||||
for (let j = 0; j < this.containersRow[i].length; j++) {
|
||||
@ -265,57 +318,4 @@ export class CustomizeScene extends AbstractCharacterScene {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update(time: number, delta: number): void {
|
||||
if (this.lazyloadingAttempt) {
|
||||
this.moveLayers();
|
||||
this.doMoveCursorHorizontally(this.moveHorizontally);
|
||||
this.lazyloadingAttempt = false;
|
||||
}
|
||||
|
||||
if (this.moveHorizontally !== 0) {
|
||||
this.doMoveCursorHorizontally(this.moveHorizontally);
|
||||
this.moveHorizontally = 0;
|
||||
}
|
||||
if (this.moveVertically !== 0) {
|
||||
this.doMoveCursorVertically(this.moveVertically);
|
||||
this.moveVertically = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public onResize(): void {
|
||||
this.moveLayers();
|
||||
|
||||
this.Rectangle.x = this.cameras.main.worldView.x + this.cameras.main.width / 2;
|
||||
this.Rectangle.y = this.cameras.main.worldView.y + this.cameras.main.height / 3;
|
||||
}
|
||||
|
||||
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);
|
||||
customCharacterSceneVisibleStore.set(false);
|
||||
}
|
||||
|
||||
public backToPreviousScene() {
|
||||
this.scene.stop(CustomizeSceneName);
|
||||
waScaleManager.restoreZoom();
|
||||
this.scene.run(SelectCharacterSceneName);
|
||||
customCharacterSceneVisibleStore.set(false);
|
||||
}
|
||||
}
|
||||
|
@ -7,8 +7,8 @@ import { ReconnectingTextures } from "../Reconnecting/ReconnectingScene";
|
||||
import LL from "../../i18n/i18n-svelte";
|
||||
import { get } from "svelte/store";
|
||||
import { localeDetector } from "../../i18n/locales";
|
||||
import { PlayerTextures } from "../Entity/PlayerTextures";
|
||||
import { PUSHER_URL } from "../../Enum/EnvironmentVariable";
|
||||
import { CustomizeSceneName } from "./CustomizeScene";
|
||||
import { SelectCharacterSceneName } from "./SelectCharacterScene";
|
||||
|
||||
export const EntrySceneName = "EntryScene";
|
||||
|
||||
@ -46,7 +46,9 @@ export class EntryScene extends Scene {
|
||||
// Let's rescale before starting the game
|
||||
// We can do it at this stage.
|
||||
waScaleManager.applyNewSize();
|
||||
this.scene.start(nextSceneName);
|
||||
// this.scene.start(nextSceneName);
|
||||
// this.scene.start(CustomizeSceneName);
|
||||
this.scene.start(SelectCharacterSceneName);
|
||||
})
|
||||
.catch((err) => {
|
||||
const $LL = get(LL);
|
||||
|
@ -149,9 +149,9 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
||||
}
|
||||
|
||||
createCurrentPlayer(): void {
|
||||
console.log("CREATE CURRENT PLAYER");
|
||||
for (let i = 0; i < this.playerModels.length; i++) {
|
||||
const playerResource = this.playerModels[i];
|
||||
|
||||
//check already exist texture
|
||||
if (this.players.find((c) => c.texture.key === playerResource.id)) {
|
||||
continue;
|
||||
|
@ -8,3 +8,72 @@ export enum PlayerAnimationTypes {
|
||||
Walk = "walk",
|
||||
Idle = "idle",
|
||||
}
|
||||
|
||||
export interface AnimationData {
|
||||
key: string;
|
||||
frameRate: number;
|
||||
repeat: number;
|
||||
frameModel: string; //todo use an enum
|
||||
frames: number[];
|
||||
}
|
||||
|
||||
export function getPlayerAnimations(name: string): AnimationData[] {
|
||||
return [
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Walk}`,
|
||||
frameModel: name,
|
||||
frames: [0, 1, 2, 1],
|
||||
frameRate: 10,
|
||||
repeat: -1,
|
||||
},
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Walk}`,
|
||||
frameModel: name,
|
||||
frames: [3, 4, 5, 4],
|
||||
frameRate: 10,
|
||||
repeat: -1,
|
||||
},
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Walk}`,
|
||||
frameModel: name,
|
||||
frames: [6, 7, 8, 7],
|
||||
frameRate: 10,
|
||||
repeat: -1,
|
||||
},
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Walk}`,
|
||||
frameModel: name,
|
||||
frames: [9, 10, 11, 10],
|
||||
frameRate: 10,
|
||||
repeat: -1,
|
||||
},
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Idle}`,
|
||||
frameModel: name,
|
||||
frames: [1],
|
||||
frameRate: 10,
|
||||
repeat: 1,
|
||||
},
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Idle}`,
|
||||
frameModel: name,
|
||||
frames: [4],
|
||||
frameRate: 10,
|
||||
repeat: 1,
|
||||
},
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Idle}`,
|
||||
frameModel: name,
|
||||
frames: [7],
|
||||
frameRate: 10,
|
||||
repeat: 1,
|
||||
},
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Idle}`,
|
||||
frameModel: name,
|
||||
frames: [10],
|
||||
frameRate: 10,
|
||||
repeat: 1,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user