Merge branch 'develop' of github.com:thecodingmachine/workadventure into feature/animated-tiles

# Conflicts:
#	front/package.json
#	front/tsconfig.json
#	front/yarn.lock
This commit is contained in:
David Négrier 2021-06-21 17:26:00 +02:00
commit e4dab5fd0d
555 changed files with 22975 additions and 17853 deletions

View file

@ -0,0 +1,220 @@
import Sprite = Phaser.GameObjects.Sprite;
import Container = Phaser.GameObjects.Container;
import { PlayerAnimationDirections, PlayerAnimationTypes } from "../Player/Animation";
export interface CompanionStatus {
x: number;
y: number;
name: string;
moving: boolean;
direction: PlayerAnimationDirections;
}
export class Companion extends Container {
public sprites: Map<string, Sprite>;
private delta: number;
private invisible: boolean;
private updateListener: Function;
private target: { x: number, y: number, direction: PlayerAnimationDirections };
private companionName: string;
private direction: PlayerAnimationDirections;
private animationType: PlayerAnimationTypes;
constructor(scene: Phaser.Scene, x: number, y: number, name: string, texturePromise: Promise<string>) {
super(scene, x + 14, y + 4);
this.sprites = new Map<string, Sprite>();
this.delta = 0;
this.invisible = true;
this.target = { x, y, direction: PlayerAnimationDirections.Down };
this.direction = PlayerAnimationDirections.Down;
this.animationType = PlayerAnimationTypes.Idle;
this.companionName = name;
texturePromise.then(resource => {
this.addResource(resource);
this.invisible = false;
})
this.scene.physics.world.enableBody(this);
this.getBody().setImmovable(true);
this.getBody().setCollideWorldBounds(false);
this.setSize(16, 16);
this.getBody().setSize(16, 16);
this.getBody().setOffset(0, 8);
this.setDepth(-1);
this.updateListener = this.step.bind(this);
this.scene.events.addListener('update', this.updateListener);
this.scene.add.existing(this);
}
public setTarget(x: number, y: number, direction: PlayerAnimationDirections) {
this.target = { x, y: y + 4, direction };
}
public step(time: number, delta: number) {
if (typeof this.target === 'undefined') return;
this.delta += delta;
if (this.delta < 128) {
return;
}
this.delta = 0;
const xDist = this.target.x - this.x;
const yDist = this.target.y - this.y;
const distance = Math.pow(xDist, 2) + Math.pow(yDist, 2);
if (distance < 650) {
this.animationType = PlayerAnimationTypes.Idle;
this.direction = this.target.direction;
this.getBody().stop();
} else {
this.animationType = PlayerAnimationTypes.Walk;
const xDir = xDist / Math.max(Math.abs(xDist), 1);
const yDir = yDist / Math.max(Math.abs(yDist), 1);
const speed = 256;
this.getBody().setVelocity(Math.min(Math.abs(xDist * 2.5), speed) * xDir, Math.min(Math.abs(yDist * 2.5), speed) * yDir);
if (Math.abs(xDist) > Math.abs(yDist)) {
if (xDist < 0) {
this.direction = PlayerAnimationDirections.Left;
} else {
this.direction = PlayerAnimationDirections.Right;
}
} else {
if (yDist < 0) {
this.direction = PlayerAnimationDirections.Up;
} else {
this.direction = PlayerAnimationDirections.Down;
}
}
}
this.setDepth(this.y);
this.playAnimation(this.direction, this.animationType);
}
public getStatus(): CompanionStatus {
const { x, y, direction, animationType, companionName } = this;
return {
x,
y,
direction,
moving: animationType === PlayerAnimationTypes.Walk,
name: companionName
}
}
private playAnimation(direction: PlayerAnimationDirections, type: PlayerAnimationTypes): void {
if (this.invisible) return;
for (const [resource, sprite] of this.sprites.entries()) {
sprite.play(`${resource}-${direction}-${type}`, true);
}
}
private addResource(resource: string, frame?: string | number): void {
const sprite = new Sprite(this.scene, 0, 0, resource, frame);
this.add(sprite);
this.getAnimations(resource).forEach(animation => {
this.scene.anims.create(animation);
});
this.scene.sys.updateList.add(sprite);
this.sprites.set(resource, sprite);
}
private getAnimations(resource: string): Phaser.Types.Animations.Animation[] {
return [
{
key: `${resource}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Idle}`,
frames: this.scene.anims.generateFrameNumbers(resource, {frames: [1]}),
frameRate: 10,
repeat: 1
},
{
key: `${resource}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Idle}`,
frames: this.scene.anims.generateFrameNumbers(resource, {frames: [4]}),
frameRate: 10,
repeat: 1
},
{
key: `${resource}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Idle}`,
frames: this.scene.anims.generateFrameNumbers(resource, {frames: [7]}),
frameRate: 10,
repeat: 1
},
{
key: `${resource}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Idle}`,
frames: this.scene.anims.generateFrameNumbers(resource, {frames: [10]}),
frameRate: 10,
repeat: 1
},
{
key: `${resource}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Walk}`,
frames: this.scene.anims.generateFrameNumbers(resource, {frames: [0, 1, 2]}),
frameRate: 15,
repeat: -1
},
{
key: `${resource}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Walk}`,
frames: this.scene.anims.generateFrameNumbers(resource, {frames: [3, 4, 5]}),
frameRate: 15,
repeat: -1
},
{
key: `${resource}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Walk}`,
frames: this.scene.anims.generateFrameNumbers(resource, {frames: [6, 7, 8]}),
frameRate: 15,
repeat: -1
},
{
key: `${resource}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Walk}`,
frames: this.scene.anims.generateFrameNumbers(resource, {frames: [9, 10, 11]}),
frameRate: 15,
repeat: -1
}
]
}
private getBody(): Phaser.Physics.Arcade.Body {
const body = this.body;
if (!(body instanceof Phaser.Physics.Arcade.Body)) {
throw new Error('Container does not have arcade body');
}
return body;
}
public destroy(): void {
for (const sprite of this.sprites.values()) {
if (this.scene) {
this.scene.sys.updateList.remove(sprite);
}
}
if (this.scene) {
this.scene.events.removeListener('update', this.updateListener);
}
super.destroy();
}
}

View file

@ -0,0 +1,14 @@
export interface CompanionResourceDescriptionInterface {
name: string,
img: string,
behaviour: "dog" | "cat"
}
export const COMPANION_RESOURCES: CompanionResourceDescriptionInterface[] = [
{ name: "dog1", img: "resources/characters/pipoya/Dog 01-1.png", behaviour: "dog" },
{ name: "dog2", img: "resources/characters/pipoya/Dog 01-2.png", behaviour: "dog" },
{ name: "dog3", img: "resources/characters/pipoya/Dog 01-3.png", behaviour: "dog" },
{ name: "cat1", img: "resources/characters/pipoya/Cat 01-1.png", behaviour: "cat" },
{ name: "cat2", img: "resources/characters/pipoya/Cat 01-2.png", behaviour: "cat" },
{ name: "cat3", img: "resources/characters/pipoya/Cat 01-3.png", behaviour: "cat" },
]

View file

@ -0,0 +1,29 @@
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
import { COMPANION_RESOURCES, CompanionResourceDescriptionInterface } from "./CompanionTextures";
export const getAllCompanionResources = (loader: LoaderPlugin): CompanionResourceDescriptionInterface[] => {
COMPANION_RESOURCES.forEach((resource: CompanionResourceDescriptionInterface) => {
lazyLoadCompanionResource(loader, resource.name);
});
return COMPANION_RESOURCES;
}
export const lazyLoadCompanionResource = (loader: LoaderPlugin, name: string): Promise<string> => {
return new Promise((resolve, reject) => {
const resource = COMPANION_RESOURCES.find(item => item.name === name);
if (typeof resource === 'undefined') {
return reject(`Texture '${name}' not found!`);
}
if (loader.textureManager.exists(resource.name)) {
return resolve(resource.name);
}
loader.spritesheet(resource.name, resource.img, { frameWidth: 32, frameHeight: 32, endFrame: 12 });
loader.once(`filecomplete-spritesheet-${resource.name}`, () => resolve(resource.name));
loader.start(); // It's only automatically started during the Scene preload.
});
}

View file

@ -1,3 +1,5 @@
import { DEPTH_INGAME_TEXT_INDEX } from "../Game/DepthIndexes";
export class ChatModeIcon extends Phaser.GameObjects.Sprite {
constructor(scene: Phaser.Scene, x: number, y: number) {
super(scene, x, y, 'layout_modes', 3);
@ -6,6 +8,6 @@ export class ChatModeIcon extends Phaser.GameObjects.Sprite {
this.setOrigin(0, 1);
this.setInteractive();
this.setVisible(false);
this.setDepth(99999);
this.setDepth(DEPTH_INGAME_TEXT_INDEX);
}
}

View file

@ -0,0 +1,54 @@
import ImageFrameConfig = Phaser.Types.Loader.FileTypes.ImageFrameConfig;
const LogoNameIndex: string = 'logoLoading';
const TextName: string = 'Loading...';
const LogoResource: string = 'resources/logos/logo.png';
const LogoFrame: ImageFrameConfig = {frameWidth: 307, frameHeight: 59};
export const addLoader = (scene: Phaser.Scene): void => {
// If there is nothing to load, do not display the loader.
if (scene.load.list.entries.length === 0) {
return;
}
let loadingText: Phaser.GameObjects.Text|null = null;
const loadingBarWidth: number = Math.floor(scene.game.renderer.width / 3);
const loadingBarHeight: number = 16;
const padding: number = 5;
const promiseLoadLogoTexture = new Promise<Phaser.GameObjects.Image>((res) => {
if(scene.load.textureManager.exists(LogoNameIndex)){
return res(scene.add.image(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 150, LogoNameIndex));
}else{
//add loading if logo image is not ready
loadingText = scene.add.text(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 50, TextName);
}
scene.load.spritesheet(LogoNameIndex, LogoResource, LogoFrame);
scene.load.once(`filecomplete-spritesheet-${LogoNameIndex}`, () => {
if(loadingText){
loadingText.destroy();
}
return res(scene.add.image(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 150, LogoNameIndex));
});
});
const progressContainer = scene.add.graphics();
const progress = scene.add.graphics();
progressContainer.fillStyle(0x444444, 0.8);
progressContainer.fillRect((scene.game.renderer.width - loadingBarWidth) / 2 - padding, scene.game.renderer.height / 2 + 50 - padding, loadingBarWidth + padding * 2, loadingBarHeight + padding * 2);
scene.load.on('progress', (value: number) => {
progress.clear();
progress.fillStyle(0xBBBBBB, 1);
progress.fillRect((scene.game.renderer.width - loadingBarWidth) / 2, scene.game.renderer.height / 2 + 50, loadingBarWidth * value, loadingBarHeight);
});
scene.load.on('complete', () => {
if(loadingText){
loadingText.destroy();
}
promiseLoadLogoTexture.then((resLoadingImage: Phaser.GameObjects.Image) => {
resLoadingImage.destroy();
});
progress.destroy();
progressContainer.destroy();
});
}

View file

@ -0,0 +1,65 @@
import VirtualJoystick from 'phaser3-rex-plugins/plugins/virtualjoystick.js';
import {waScaleManager} from "../Services/WaScaleManager";
import {DEPTH_INGAME_TEXT_INDEX} from "../Game/DepthIndexes";
//the assets were found here: https://hannemann.itch.io/virtual-joystick-pack-free
export const joystickBaseKey = 'joystickBase';
export const joystickBaseImg = 'resources/objects/joystickSplitted.png';
export const joystickThumbKey = 'joystickThumb';
export const joystickThumbImg = 'resources/objects/smallHandleFilledGrey.png';
const baseSize = 50;
const thumbSize = 25;
const radius = 17.5;
export class MobileJoystick extends VirtualJoystick {
private resizeCallback: () => void;
constructor(scene: Phaser.Scene) {
super(scene, {
x: -1000,
y: -1000,
radius: radius * window.devicePixelRatio,
base: scene.add.image(0, 0, joystickBaseKey).setDisplaySize(baseSize * window.devicePixelRatio, baseSize * window.devicePixelRatio).setDepth(DEPTH_INGAME_TEXT_INDEX),
thumb: scene.add.image(0, 0, joystickThumbKey).setDisplaySize(thumbSize * window.devicePixelRatio, thumbSize * window.devicePixelRatio).setDepth(DEPTH_INGAME_TEXT_INDEX),
enable: true,
dir: "8dir",
});
this.visible = false;
this.enable = false;
this.scene.input.on('pointerdown', (pointer: Phaser.Input.Pointer) => {
if (!pointer.wasTouch) {
return;
}
// Let's only display the joystick if there is one finger on the screen
if ((pointer.event as TouchEvent).touches.length === 1) {
this.x = pointer.x;
this.y = pointer.y;
this.visible = true;
this.enable = true;
} else {
this.visible = false;
this.enable = false;
}
});
this.scene.input.on('pointerup', () => {
this.visible = false;
this.enable = false;
});
this.resizeCallback = this.resize.bind(this);
this.scene.scale.on(Phaser.Scale.Events.RESIZE, this.resizeCallback);
}
private resize() {
this.base.setDisplaySize(baseSize / waScaleManager.zoomModifier * window.devicePixelRatio, baseSize / waScaleManager.zoomModifier * window.devicePixelRatio);
this.thumb.setDisplaySize(thumbSize / waScaleManager.zoomModifier * window.devicePixelRatio, thumbSize / waScaleManager.zoomModifier * window.devicePixelRatio);
this.setRadius(radius / waScaleManager.zoomModifier * window.devicePixelRatio);
}
public destroy() {
super.destroy();
this.scene.scale.removeListener(Phaser.Scale.Events.RESIZE, this.resizeCallback);
}
}

View file

@ -1,17 +1,16 @@
import {discussionManager} from "../../WebRtc/DiscussionManager";
import {DEPTH_INGAME_TEXT_INDEX} from "../Game/DepthIndexes";
export const openChatIconName = 'openChatIcon';
export class OpenChatIcon extends Phaser.GameObjects.Image {
constructor(scene: Phaser.Scene, x: number, y: number) {
super(scene, x, y, openChatIconName);
super(scene, x, y, openChatIconName, 3);
scene.add.existing(this);
this.setScrollFactor(0, 0);
this.setOrigin(0, 1);
this.displayWidth = 30;
this.displayHeight = 30;
this.setInteractive();
this.setVisible(false)
this.setDepth(99999);
this.setVisible(false);
this.setDepth(DEPTH_INGAME_TEXT_INDEX);
this.on("pointerup", () => discussionManager.showDiscussionPart());
}

View file

@ -1,3 +1,5 @@
import {DEPTH_INGAME_TEXT_INDEX} from "../Game/DepthIndexes";
export class PresentationModeIcon extends Phaser.GameObjects.Sprite {
constructor(scene: Phaser.Scene, x: number, y: number) {
super(scene, x, y, 'layout_modes', 0);
@ -6,6 +8,6 @@ export class PresentationModeIcon extends Phaser.GameObjects.Sprite {
this.setOrigin(0, 1);
this.setInteractive();
this.setVisible(false);
this.setDepth(99999);
this.setDepth(DEPTH_INGAME_TEXT_INDEX);
}
}

View file

@ -0,0 +1,74 @@
import Sprite = Phaser.GameObjects.Sprite;
import {DEPTH_UI_INDEX} from "../Game/DepthIndexes";
import {waScaleManager} from "../Services/WaScaleManager";
export interface RadialMenuItem {
image: string,
name: string,
}
export const RadialMenuClickEvent = 'radialClick';
export class RadialMenu extends Phaser.GameObjects.Container {
private resizeCallback: OmitThisParameter<() => void>;
constructor(scene: Phaser.Scene, x: number, y: number, private items: RadialMenuItem[]) {
super(scene, x, y);
this.setDepth(DEPTH_UI_INDEX)
this.scene.add.existing(this);
this.initItems();
this.resize();
this.resizeCallback = this.resize.bind(this);
this.scene.scale.on(Phaser.Scale.Events.RESIZE, this.resizeCallback);
}
private initItems() {
const itemsNumber = this.items.length;
const menuRadius = 70 + (waScaleManager.uiScalingFactor - 1) * 20;
this.items.forEach((item, index) => this.createRadialElement(item, index, itemsNumber, menuRadius))
}
private createRadialElement(item: RadialMenuItem, index: number, itemsNumber: number, menuRadius: number) {
const image = new Sprite(this.scene, 0, menuRadius, item.image);
this.add(image);
this.scene.sys.updateList.add(image);
const scalingFactor = waScaleManager.uiScalingFactor * 0.075;
image.setScale(scalingFactor)
image.setInteractive({
useHandCursor: true,
});
image.on('pointerdown', () => this.emit(RadialMenuClickEvent, item));
image.on('pointerover', () => {
this.scene.tweens.add({
targets: image,
props: {
scale: 2 * scalingFactor,
},
duration: 500,
ease: 'Power3',
})
});
image.on('pointerout', () => {
this.scene.tweens.add({
targets: image,
props: {
scale: scalingFactor,
},
duration: 500,
ease: 'Power3',
})
});
const angle = 2 * Math.PI * index / itemsNumber;
Phaser.Actions.RotateAroundDistance([image], {x: 0, y: 0}, angle, menuRadius);
}
private resize() {
this.setScale(waScaleManager.uiScalingFactor);
}
public destroy() {
this.scene.scale.removeListener(Phaser.Scale.Events.RESIZE, this.resizeCallback);
super.destroy();
}
}

View file

@ -1,3 +1,5 @@
import type {IAnalyserNode, IAudioContext, IMediaStreamAudioSourceNode} from 'standardized-audio-context';
/**
* Class to measure the sound volume of a media stream
*/
@ -5,10 +7,10 @@ export class SoundMeter {
private instant: number;
private clip: number;
//private script: ScriptProcessorNode;
private analyser: AnalyserNode|undefined;
private analyser: IAnalyserNode<IAudioContext>|undefined;
private dataArray: Uint8Array|undefined;
private context: AudioContext|undefined;
private source: MediaStreamAudioSourceNode|undefined;
private context: IAudioContext|undefined;
private source: IMediaStreamAudioSourceNode<IAudioContext>|undefined;
constructor() {
this.instant = 0.0;
@ -16,19 +18,21 @@ export class SoundMeter {
//this.script = context.createScriptProcessor(2048, 1, 1);
}
private init(context: AudioContext) {
if (this.context === undefined) {
this.context = context;
this.analyser = this.context.createAnalyser();
private init(context: IAudioContext) {
this.context = context;
this.analyser = this.context.createAnalyser();
this.analyser.fftSize = 2048;
const bufferLength = this.analyser.fftSize;
this.dataArray = new Uint8Array(bufferLength);
}
this.analyser.fftSize = 2048;
const bufferLength = this.analyser.fftSize;
this.dataArray = new Uint8Array(bufferLength);
}
public connectToSource(stream: MediaStream, context: AudioContext): void
public connectToSource(stream: MediaStream, context: IAudioContext): void
{
if (this.source !== undefined) {
this.stop();
}
this.init(context);
this.source = this.context?.createMediaStreamSource(stream);
@ -83,56 +87,3 @@ export class SoundMeter {
}
// Meter class that generates a number correlated to audio volume.
// The meter class itself displays nothing, but it makes the
// instantaneous and time-decaying volumes available for inspection.
// It also reports on the fraction of samples that were at or near
// the top of the measurement range.
/*function SoundMeter(context) {
this.context = context;
this.instant = 0.0;
this.slow = 0.0;
this.clip = 0.0;
this.script = context.createScriptProcessor(2048, 1, 1);
const that = this;
this.script.onaudioprocess = function(event) {
const input = event.inputBuffer.getChannelData(0);
let i;
let sum = 0.0;
let clipcount = 0;
for (i = 0; i < input.length; ++i) {
sum += input[i] * input[i];
if (Math.abs(input[i]) > 0.99) {
clipcount += 1;
}
}
that.instant = Math.sqrt(sum / input.length);
that.slow = 0.95 * that.slow + 0.05 * that.instant;
that.clip = clipcount / input.length;
};
}
SoundMeter.prototype.connectToSource = function(stream, callback) {
console.log('SoundMeter connecting');
try {
this.mic = this.context.createMediaStreamSource(stream);
this.mic.connect(this.script);
// necessary to make sample run, but should not be.
this.script.connect(this.context.destination);
if (typeof callback !== 'undefined') {
callback(null);
}
} catch (e) {
console.error(e);
if (typeof callback !== 'undefined') {
callback(e);
}
}
};
SoundMeter.prototype.stop = function() {
this.mic.disconnect();
this.script.disconnect();
};
*/

View file

@ -1,44 +0,0 @@
import Container = Phaser.GameObjects.Container;
import {Scene} from "phaser";
import GameObject = Phaser.GameObjects.GameObject;
import Rectangle = Phaser.GameObjects.Rectangle;
export class SoundMeterSprite extends Container {
private rectangles: Rectangle[] = new Array<Rectangle>();
private static readonly NB_BARS = 20;
constructor(scene: Scene, x?: number, y?: number, children?: GameObject[]) {
super(scene, x, y, children);
for (let i = 0; i < SoundMeterSprite.NB_BARS; i++) {
const rectangle = new Rectangle(scene, i * 13, 0, 10, 20, (Math.round(255 - i * 255 / SoundMeterSprite.NB_BARS) << 8) + (Math.round(i * 255 / SoundMeterSprite.NB_BARS) << 16));
this.add(rectangle);
this.rectangles.push(rectangle);
}
}
/**
* A number between 0 and 100
*
* @param volume
*/
public setVolume(volume: number): void {
const normalizedVolume = volume / 100 * SoundMeterSprite.NB_BARS;
for (let i = 0; i < SoundMeterSprite.NB_BARS; i++) {
if (normalizedVolume < i) {
this.rectangles[i].alpha = 0.5;
} else {
this.rectangles[i].alpha = 1;
}
}
}
public getWidth(): number {
return SoundMeterSprite.NB_BARS * 13;
}
}

View file

@ -1,46 +1,68 @@
const IGNORED_KEYS = new Set([
'Esc',
'Escape',
'Alt',
'Meta',
'Control',
'Ctrl',
'Space',
'Backspace'
])
export class TextInput extends Phaser.GameObjects.BitmapText {
private minUnderLineLength = 4;
private underLine: Phaser.GameObjects.Text;
private domInput = document.createElement('input');
constructor(scene: Phaser.Scene, x: number, y: number, maxLength: number, text: string, onChange: (text: string) => void) {
constructor(scene: Phaser.Scene, x: number, y: number, maxLength: number, text: string,
onChange: (text: string) => void) {
super(scene, x, y, 'main_font', text, 32);
this.setOrigin(0.5).setCenterAlign()
this.setOrigin(0.5).setCenterAlign();
this.scene.add.existing(this);
this.underLine = this.scene.add.text(x, y+1, this.getUnderLineBody(text.length), { fontFamily: 'Arial', fontSize: "32px", color: '#ffffff'})
this.underLine.setOrigin(0.5)
const style = {fontFamily: 'Arial', fontSize: "32px", color: '#ffffff'};
this.underLine = this.scene.add.text(x, y+1, this.getUnderLineBody(text.length), style);
this.underLine.setOrigin(0.5);
this.domInput.maxLength = maxLength;
this.domInput.style.opacity = "0";
if (text) {
this.domInput.value = text;
}
this.scene.input.keyboard.on('keydown', (event: KeyboardEvent) => {
if (event.keyCode === 8 && this.text.length > 0) {
this.deleteLetter();
} else if ((event.keyCode === 32 || (event.keyCode >= 48 && event.keyCode <= 90)) && this.text.length < maxLength) {
this.addLetter(event.key);
this.domInput.addEventListener('keydown', event => {
if (IGNORED_KEYS.has(event.key)) {
return;
}
if (!/[a-zA-Z0-9:.!&?()+-]/.exec(event.key)) {
event.preventDefault();
}
});
this.domInput.addEventListener('input', (event) => {
if (event.defaultPrevented) {
return;
}
this.text = this.domInput.value;
this.underLine.text = this.getUnderLineBody(this.text.length);
onChange(this.text);
});
document.body.append(this.domInput);
this.focus();
}
private getUnderLineBody(textLength:number): string {
if (textLength < this.minUnderLineLength) textLength = this.minUnderLineLength;
let text = '_______';
for (let i = this.minUnderLineLength; i < textLength; i++) {
text += '__'
text += '__';
}
return text;
}
private deleteLetter() {
this.text = this.text.substr(0, this.text.length - 1);
}
private addLetter(letter: string) {
this.text += letter;
}
getText(): string {
return this.text;
}
@ -56,4 +78,13 @@ export class TextInput extends Phaser.GameObjects.BitmapText {
this.underLine.y = y+1;
return this;
}
focus() {
this.domInput.focus();
}
destroy(): void {
super.destroy();
this.domInput.remove();
}
}

View file

@ -0,0 +1,51 @@
import type {ITiledMapObject} from "../Map/ITiledMap";
import type {GameScene} from "../Game/GameScene";
export class TextUtils {
public static createTextFromITiledMapObject(scene: GameScene, object: ITiledMapObject): void {
if (object.text === undefined) {
throw new Error('This object has not textual representation.');
}
const options: {
fontStyle?: string,
fontSize?: string,
fontFamily?: string,
color?: string,
align?: string,
wordWrap?: {
width: number,
useAdvancedWrap?: boolean
}
} = {};
if (object.text.italic) {
options.fontStyle = 'italic';
}
// Note: there is no support for "strikeout" and "underline"
let fontSize: number = 16;
if (object.text.pixelsize) {
fontSize = object.text.pixelsize;
}
options.fontSize = fontSize + 'px';
if (object.text.fontfamily) {
options.fontFamily = '"'+object.text.fontfamily+'"';
}
let color = '#000000';
if (object.text.color !== undefined) {
color = object.text.color;
}
options.color = color;
if (object.text.wrap === true) {
options.wordWrap = {
width: object.width,
//useAdvancedWrap: true
}
}
if (object.text.halign !== undefined) {
options.align = object.text.halign;
}
console.warn(options);
const textElem = scene.add.text(object.x, object.y, object.text.text, options);
textElem.setAngle(object.rotation);
}
}

View file

@ -0,0 +1,14 @@
export const warningContainerKey = 'warningContainer';
export const warningContainerHtml = 'resources/html/warningContainer.html';
export class WarningContainer extends Phaser.GameObjects.DOMElement {
constructor(scene: Phaser.Scene) {
super(scene, 100, 0);
this.setOrigin(0, 0);
this.createFromCache(warningContainerKey);
this.scene.add.existing(this);
}
}

View file

@ -1,83 +1,74 @@
import {PlayerAnimationNames} from "../Player/Animation";
import {PlayerAnimationDirections, PlayerAnimationTypes} from "../Player/Animation";
import {SpeechBubble} from "./SpeechBubble";
import BitmapText = Phaser.GameObjects.BitmapText;
import Text = Phaser.GameObjects.Text;
import Container = Phaser.GameObjects.Container;
import Sprite = Phaser.GameObjects.Sprite;
import {TextureError} from "../../Exception/TextureError";
import {Companion} from "../Companion/Companion";
import type {GameScene} from "../Game/GameScene";
import {DEPTH_INGAME_TEXT_INDEX} from "../Game/DepthIndexes";
import {waScaleManager} from "../Services/WaScaleManager";
export interface PlayerResourceDescriptionInterface {
name: string,
img: string
}
export const PLAYER_RESOURCES: Array<PlayerResourceDescriptionInterface> = [
{name: "male1", img: "resources/characters/pipoya/Male 01-1.png" /*, x: 32, y: 32*/},
{name: "male2", img: "resources/characters/pipoya/Male 02-2.png"/*, x: 64, y: 32*/},
{name: "male3", img: "resources/characters/pipoya/Male 03-4.png"/*, x: 96, y: 32*/},
{name: "male4", img: "resources/characters/pipoya/Male 09-1.png"/*, x: 128, y: 32*/},
{name: "male5", img: "resources/characters/pipoya/Male 10-3.png"/*, x: 32, y: 64*/},
{name: "male6", img: "resources/characters/pipoya/Male 17-2.png"/*, x: 64, y: 64*/},
{name: "male7", img: "resources/characters/pipoya/Male 18-1.png"/*, x: 96, y: 64*/},
{name: "male8", img: "resources/characters/pipoya/Male 16-4.png"/*, x: 128, y: 64*/},
{name: "Female1", img: "resources/characters/pipoya/Female 01-1.png"/*, x: 32, y: 96*/},
{name: "Female2", img: "resources/characters/pipoya/Female 02-2.png"/*, x: 64, y: 96*/},
{name: "Female3", img: "resources/characters/pipoya/Female 03-4.png"/*, x: 96, y: 96*/},
{name: "Female4", img: "resources/characters/pipoya/Female 09-1.png"/*, x: 128, y: 96*/},
{name: "Female5", img: "resources/characters/pipoya/Female 10-3.png"/*, x: 32, y: 128*/},
{name: "Female6", img: "resources/characters/pipoya/Female 17-2.png"/*, x: 64, y: 128*/},
{name: "Female7", img: "resources/characters/pipoya/Female 18-1.png"/*, x: 96, y: 128*/},
{name: "Female8", img: "resources/characters/pipoya/Female 16-4.png"/*, x: 128, y: 128*/}
];
const playerNameY = - 25;
interface AnimationData {
key: string;
frameRate: number;
repeat: number;
frameModel: string; //todo use an enum
frameStart: number;
frameEnd: number;
frames : number[]
}
const interactiveRadius = 35;
export abstract class Character extends Container {
private bubble: SpeechBubble|null = null;
private readonly playerName: BitmapText;
private readonly playerName: Text;
public PlayerValue: string;
public sprites: Map<string, Sprite>;
private lastDirection: string = PlayerAnimationNames.WalkDown;
private lastDirection: PlayerAnimationDirections = PlayerAnimationDirections.Down;
//private teleportation: Sprite;
private invisible: boolean;
public companion?: Companion;
private emote: Phaser.GameObjects.Sprite | null = null;
private emoteTween: Phaser.Tweens.Tween|null = null;
constructor(scene: Phaser.Scene,
constructor(scene: GameScene,
x: number,
y: number,
textures: string[],
texturesPromise: Promise<string[]>,
name: string,
direction: string,
direction: PlayerAnimationDirections,
moving: boolean,
frame?: string | number
frame: string | number,
isClickable: boolean,
companion: string|null,
companionTexturePromise?: Promise<string>
) {
super(scene, x, y/*, texture, frame*/);
this.PlayerValue = name;
this.invisible = true
this.sprites = new Map<string, Sprite>();
this.addTextures(textures, frame);
//textures are inside a Promise in case they need to be lazyloaded before use.
texturesPromise.then((textures) => {
this.addTextures(textures, frame);
this.invisible = false
})
/*this.teleportation = new Sprite(scene, -20, -10, 'teleportation', 3);
this.teleportation.setInteractive();
this.teleportation.visible = false;
this.teleportation.on('pointerup', () => {
this.report.visible = false;
this.teleportation.visible = false;
});
this.add(this.teleportation);*/
this.playerName = new BitmapText(scene, 0, - 25, 'main_font', name, 7);
this.playerName.setOrigin(0.5).setCenterAlign().setDepth(99999);
this.playerName = new Text(scene, 0, playerNameY, name, {fontFamily: '"Press Start 2P"', fontSize: '8px', strokeThickness: 2, stroke: "gray"});
this.playerName.setOrigin(0.5).setDepth(DEPTH_INGAME_TEXT_INDEX);
this.add(this.playerName);
if (isClickable) {
this.setInteractive({
hitArea: new Phaser.Geom.Circle(0, 0, interactiveRadius),
hitAreaCallback: Phaser.Geom.Circle.Contains, //eslint-disable-line @typescript-eslint/unbound-method
useHandCursor: true,
});
}
scene.add.existing(this);
this.scene.physics.world.enableBody(this);
@ -89,20 +80,29 @@ export abstract class Character extends Container {
this.setDepth(-1);
this.playAnimation(direction, moving);
if (typeof companion === 'string') {
this.addCompanion(companion, companionTexturePromise);
}
}
public addCompanion(name: string, texturePromise?: Promise<string>): void {
if (typeof texturePromise !== 'undefined') {
this.companion = new Companion(this.scene, this.x, this.y, name, texturePromise);
}
}
public addTextures(textures: string[], frame?: string | number): void {
for (const texture of textures) {
if(!this.scene.textures.exists(texture)){
if(this.scene && !this.scene.textures.exists(texture)){
throw new TextureError('texture not found');
}
const sprite = new Sprite(this.scene, 0, 0, texture, frame);
sprite.setInteractive({useHandCursor: true});
this.add(sprite);
this.getPlayerAnimations(texture).forEach(d => {
this.scene.anims.create({
key: d.key,
frames: this.scene.anims.generateFrameNumbers(d.frameModel, {start: d.frameStart, end: d.frameEnd}),
frames: this.scene.anims.generateFrameNumbers(d.frameModel, {frames: d.frames}),
frameRate: d.frameRate,
repeat: d.repeat
});
@ -117,47 +117,67 @@ export abstract class Character extends Container {
private getPlayerAnimations(name: string): AnimationData[] {
return [{
key: `${name}-${PlayerAnimationNames.WalkDown}`,
key: `${name}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Walk}`,
frameModel: name,
frameStart: 0,
frameEnd: 2,
frames: [0, 1, 2, 1],
frameRate: 10,
repeat: -1
}, {
key: `${name}-${PlayerAnimationNames.WalkLeft}`,
key: `${name}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Walk}`,
frameModel: name,
frameStart: 3,
frameEnd: 5,
frames: [3, 4, 5, 4],
frameRate: 10,
repeat: -1
}, {
key: `${name}-${PlayerAnimationNames.WalkRight}`,
key: `${name}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Walk}`,
frameModel: name,
frameStart: 6,
frameEnd: 8,
frames: [6, 7, 8, 7],
frameRate: 10,
repeat: -1
}, {
key: `${name}-${PlayerAnimationNames.WalkUp}`,
key: `${name}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Walk}`,
frameModel: name,
frameStart: 9,
frameEnd: 11,
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 : string, moving: boolean): void {
protected playAnimation(direction : PlayerAnimationDirections, moving: boolean): void {
if (this.invisible) return;
for (const [texture, sprite] of this.sprites.entries()) {
if (!sprite.anims) {
console.error('ANIMS IS NOT DEFINED!!!');
return;
}
if (moving && (!sprite.anims.currentAnim || sprite.anims.currentAnim.key !== direction)) {
sprite.play(texture+'-'+direction, true);
sprite.play(texture+'-'+direction+'-'+PlayerAnimationTypes.Walk, true);
} else if (!moving) {
sprite.anims.play(texture + '-' + direction, true);
sprite.anims.stop();
sprite.anims.play(texture + '-' + direction + '-'+PlayerAnimationTypes.Idle, true);
}
}
}
@ -177,21 +197,24 @@ export abstract class Character extends Container {
// up or down animations are prioritized over left and right
if (body.velocity.y < 0) { //moving up
this.lastDirection = PlayerAnimationNames.WalkUp;
this.playAnimation(PlayerAnimationNames.WalkUp, true);
this.lastDirection = PlayerAnimationDirections.Up;
this.playAnimation(PlayerAnimationDirections.Up, true);
} else if (body.velocity.y > 0) { //moving down
this.lastDirection = PlayerAnimationNames.WalkDown;
this.playAnimation(PlayerAnimationNames.WalkDown, true);
this.lastDirection = PlayerAnimationDirections.Down;
this.playAnimation(PlayerAnimationDirections.Down, true);
} else if (body.velocity.x > 0) { //moving right
this.lastDirection = PlayerAnimationNames.WalkRight;
this.playAnimation(PlayerAnimationNames.WalkRight, true);
this.lastDirection = PlayerAnimationDirections.Right;
this.playAnimation(PlayerAnimationDirections.Right, true);
} else if (body.velocity.x < 0) { //moving left
this.lastDirection = PlayerAnimationNames.WalkLeft;
this.playAnimation(PlayerAnimationNames.WalkLeft, true);
this.lastDirection = PlayerAnimationDirections.Left;
this.playAnimation(PlayerAnimationDirections.Left, true);
}
//update depth user
this.setDepth(this.y);
if (this.companion) {
this.companion.setTarget(this.x, this.y, this.lastDirection);
}
}
stop(){
@ -216,7 +239,84 @@ export abstract class Character extends Container {
this.scene.sys.updateList.remove(sprite);
}
}
this.list.forEach(objectContaining => objectContaining.destroy())
super.destroy();
this.playerName.destroy();
}
playEmote(emoteKey: string) {
this.cancelPreviousEmote();
const scalingFactor = waScaleManager.uiScalingFactor * 0.05;
const emoteY = -30 - scalingFactor * 10;
this.playerName.setVisible(false);
this.emote = new Sprite(this.scene, 0, 0, emoteKey);
this.emote.setAlpha(0);
this.emote.setScale(0.1 * scalingFactor);
this.add(this.emote);
this.scene.sys.updateList.add(this.emote);
this.createStartTransition(scalingFactor, emoteY);
}
private createStartTransition(scalingFactor: number, emoteY: number) {
this.emoteTween = this.scene?.tweens.add({
targets: this.emote,
props: {
scale: scalingFactor,
alpha: 1,
y: emoteY,
},
ease: 'Power2',
duration: 500,
onComplete: () => {
this.startPulseTransition(emoteY, scalingFactor);
}
});
}
private startPulseTransition(emoteY: number, scalingFactor: number) {
this.emoteTween = this.scene?.tweens.add({
targets: this.emote,
props: {
y: emoteY * 1.3,
scale: scalingFactor * 1.1
},
duration: 250,
yoyo: true,
repeat: 1,
completeDelay: 200,
onComplete: () => {
this.startExitTransition(emoteY);
}
});
}
private startExitTransition(emoteY: number) {
this.emoteTween = this.scene?.tweens.add({
targets: this.emote,
props: {
alpha: 0,
y: 2 * emoteY,
},
ease: 'Power2',
duration: 500,
onComplete: () => {
this.destroyEmote();
}
});
}
cancelPreviousEmote() {
if (!this.emote) return;
this.emoteTween?.remove();
this.destroyEmote()
}
private destroyEmote() {
this.emote?.destroy();
this.emote = null;
this.playerName.setVisible(true);
}
}

View file

@ -0,0 +1,20 @@
import Container = Phaser.GameObjects.Container;
import type {Scene} from "phaser";
import Sprite = Phaser.GameObjects.Sprite;
/**
* A sprite of a customized character (used in the Customize Scene only)
*/
export class CustomizedCharacter extends Container {
public constructor(scene: Scene, x: number, y: number, layers: string[]) {
super(scene, x, y);
this.updateSprites(layers);
}
public updateSprites(layers: string[]): void {
this.removeAll(true);
for (const layer of layers) {
this.add(new Sprite(this.scene, 0, 0, layer));
}
}
}

View file

@ -0,0 +1,344 @@
//The list of all the player textures, both the default models and the partial textures used for customization
export interface BodyResourceDescriptionListInterface {
[key: string]: BodyResourceDescriptionInterface
}
export interface BodyResourceDescriptionInterface {
name: string,
img: string,
level?: number
}
export const PLAYER_RESOURCES: BodyResourceDescriptionListInterface = {
"male1": {name: "male1", img: "resources/characters/pipoya/Male 01-1.png"},
"male2": {name: "male2", img: "resources/characters/pipoya/Male 02-2.png"},
"male3": {name: "male3", img: "resources/characters/pipoya/Male 03-4.png"},
"male4": {name: "male4", img: "resources/characters/pipoya/Male 09-1.png"},
"male5": {name: "male5", img: "resources/characters/pipoya/Male 10-3.png"},
"male6": {name: "male6", img: "resources/characters/pipoya/Male 17-2.png"},
"male7": {name: "male7", img: "resources/characters/pipoya/Male 18-1.png"},
"male8": {name: "male8", img: "resources/characters/pipoya/Male 16-4.png"},
"male9": {name: "male9", img: "resources/characters/pipoya/Male 07-2.png"},
"male10": {name: "male10", img: "resources/characters/pipoya/Male 05-3.png"},
"male11": {name: "male11", img: "resources/characters/pipoya/Teacher male 02.png"},
"male12": {name: "male12", img: "resources/characters/pipoya/su4 Student male 12.png"},
"Female1": {name: "Female1", img: "resources/characters/pipoya/Female 01-1.png"},
"Female2": {name: "Female2", img: "resources/characters/pipoya/Female 02-2.png"},
"Female3": {name: "Female3", img: "resources/characters/pipoya/Female 03-4.png"},
"Female4": {name: "Female4", img: "resources/characters/pipoya/Female 09-1.png"},
"Female5": {name: "Female5", img: "resources/characters/pipoya/Female 10-3.png"},
"Female6": {name: "Female6", img: "resources/characters/pipoya/Female 17-2.png"},
"Female7": {name: "Female7", img: "resources/characters/pipoya/Female 18-1.png"},
"Female8": {name: "Female8", img: "resources/characters/pipoya/Female 16-4.png"},
"Female9": {name: "Female9", img: "resources/characters/pipoya/Female 07-2.png"},
"Female10": {name: "Female10", img: "resources/characters/pipoya/Female 05-3.png"},
"Female11": {name: "Female11", img: "resources/characters/pipoya/Teacher fmale 02.png"},
"Female12": {name: "Female12", img: "resources/characters/pipoya/su4 Student fmale 12.png"},
};
export const COLOR_RESOURCES: BodyResourceDescriptionListInterface = {
"color_1": {name: "color_1", img: "resources/customisation/character_color/character_color0.png"},
"color_2": {name: "color_2", img: "resources/customisation/character_color/character_color1.png"},
"color_3": {name: "color_3", img: "resources/customisation/character_color/character_color2.png"},
"color_4": {name: "color_4", img: "resources/customisation/character_color/character_color3.png"},
"color_5": {name: "color_5", img: "resources/customisation/character_color/character_color4.png"},
"color_6": {name: "color_6", img: "resources/customisation/character_color/character_color5.png"},
"color_7": {name: "color_7", img: "resources/customisation/character_color/character_color6.png"},
"color_8": {name: "color_8", img: "resources/customisation/character_color/character_color7.png"},
"color_9": {name: "color_9", img: "resources/customisation/character_color/character_color8.png"},
"color_10": {name: "color_10", img: "resources/customisation/character_color/character_color9.png"},
"color_11": {name: "color_11", img: "resources/customisation/character_color/character_color10.png"},
"color_12": {name: "color_12", img: "resources/customisation/character_color/character_color11.png"},
"color_13": {name: "color_13", img: "resources/customisation/character_color/character_color12.png"},
"color_14": {name: "color_14", img: "resources/customisation/character_color/character_color13.png"},
"color_15": {name: "color_15", img: "resources/customisation/character_color/character_color14.png"},
"color_16": {name: "color_16", img: "resources/customisation/character_color/character_color15.png"},
"color_17": {name: "color_17", img: "resources/customisation/character_color/character_color16.png"},
"color_18": {name: "color_18", img: "resources/customisation/character_color/character_color17.png"},
"color_19": {name: "color_19", img: "resources/customisation/character_color/character_color18.png"},
"color_20": {name: "color_20", img: "resources/customisation/character_color/character_color19.png"},
"color_21": {name: "color_21", img: "resources/customisation/character_color/character_color20.png"},
"color_22": {name: "color_22", img: "resources/customisation/character_color/character_color21.png"},
"color_23": {name: "color_23", img: "resources/customisation/character_color/character_color22.png"},
"color_24": {name: "color_24", img: "resources/customisation/character_color/character_color23.png"},
"color_25": {name: "color_25", img: "resources/customisation/character_color/character_color24.png"},
"color_26": {name: "color_26", img: "resources/customisation/character_color/character_color25.png"},
"color_27": {name: "color_27", img: "resources/customisation/character_color/character_color26.png"},
"color_28": {name: "color_28", img: "resources/customisation/character_color/character_color27.png"},
"color_29": {name: "color_29", img: "resources/customisation/character_color/character_color28.png"},
"color_30": {name: "color_30", img: "resources/customisation/character_color/character_color29.png"},
"color_31": {name: "color_31", img: "resources/customisation/character_color/character_color30.png"},
"color_32": {name: "color_32", img: "resources/customisation/character_color/character_color31.png"},
"color_33": {name: "color_33", img: "resources/customisation/character_color/character_color32.png"}
};
export const EYES_RESOURCES: BodyResourceDescriptionListInterface = {
"eyes_1": {name: "eyes_1", img: "resources/customisation/character_eyes/character_eyes1.png"},
"eyes_2": {name: "eyes_2", img: "resources/customisation/character_eyes/character_eyes2.png"},
"eyes_3": {name: "eyes_3", img: "resources/customisation/character_eyes/character_eyes3.png"},
"eyes_4": {name: "eyes_4", img: "resources/customisation/character_eyes/character_eyes4.png"},
"eyes_5": {name: "eyes_5", img: "resources/customisation/character_eyes/character_eyes5.png"},
"eyes_6": {name: "eyes_6", img: "resources/customisation/character_eyes/character_eyes6.png"},
"eyes_7": {name: "eyes_7", img: "resources/customisation/character_eyes/character_eyes7.png"},
"eyes_8": {name: "eyes_8", img: "resources/customisation/character_eyes/character_eyes8.png"},
"eyes_9": {name: "eyes_9", img: "resources/customisation/character_eyes/character_eyes9.png"},
"eyes_10": {name: "eyes_10", img: "resources/customisation/character_eyes/character_eyes10.png"},
"eyes_11": {name: "eyes_11", img: "resources/customisation/character_eyes/character_eyes11.png"},
"eyes_12": {name: "eyes_12", img: "resources/customisation/character_eyes/character_eyes12.png"},
"eyes_13": {name: "eyes_13", img: "resources/customisation/character_eyes/character_eyes13.png"},
"eyes_14": {name: "eyes_14", img: "resources/customisation/character_eyes/character_eyes14.png"},
"eyes_15": {name: "eyes_15", img: "resources/customisation/character_eyes/character_eyes15.png"},
"eyes_16": {name: "eyes_16", img: "resources/customisation/character_eyes/character_eyes16.png"},
"eyes_17": {name: "eyes_17", img: "resources/customisation/character_eyes/character_eyes17.png"},
"eyes_18": {name: "eyes_18", img: "resources/customisation/character_eyes/character_eyes18.png"},
"eyes_19": {name: "eyes_19", img: "resources/customisation/character_eyes/character_eyes19.png"},
"eyes_20": {name: "eyes_20", img: "resources/customisation/character_eyes/character_eyes20.png"},
"eyes_21": {name: "eyes_21", img: "resources/customisation/character_eyes/character_eyes21.png"},
"eyes_22": {name: "eyes_22", img: "resources/customisation/character_eyes/character_eyes22.png"},
"eyes_23": {name: "eyes_23", img: "resources/customisation/character_eyes/character_eyes23.png"},
"eyes_24": {name: "eyes_24", img: "resources/customisation/character_eyes/character_eyes24.png"},
"eyes_25": {name: "eyes_25", img: "resources/customisation/character_eyes/character_eyes25.png"},
"eyes_26": {name: "eyes_26", img: "resources/customisation/character_eyes/character_eyes26.png"},
"eyes_27": {name: "eyes_27", img: "resources/customisation/character_eyes/character_eyes27.png"},
"eyes_28": {name: "eyes_28", img: "resources/customisation/character_eyes/character_eyes28.png"},
"eyes_29": {name: "eyes_29", img: "resources/customisation/character_eyes/character_eyes29.png"},
"eyes_30": {name: "eyes_30", img: "resources/customisation/character_eyes/character_eyes30.png"}
};
export const HAIR_RESOURCES: BodyResourceDescriptionListInterface = {
"hair_1": {name:"hair_1", img: "resources/customisation/character_hairs/character_hairs0.png"},
"hair_2": {name:"hair_2", img: "resources/customisation/character_hairs/character_hairs1.png"},
"hair_3": {name:"hair_3", img: "resources/customisation/character_hairs/character_hairs2.png"},
"hair_4": {name:"hair_4", img: "resources/customisation/character_hairs/character_hairs3.png"},
"hair_5": {name:"hair_5", img: "resources/customisation/character_hairs/character_hairs4.png"},
"hair_6": {name:"hair_6", img: "resources/customisation/character_hairs/character_hairs5.png"},
"hair_7": {name:"hair_7", img: "resources/customisation/character_hairs/character_hairs6.png"},
"hair_8": {name:"hair_8", img: "resources/customisation/character_hairs/character_hairs7.png"},
"hair_9": {name:"hair_9", img: "resources/customisation/character_hairs/character_hairs8.png"},
"hair_10": {name:"hair_10",img: "resources/customisation/character_hairs/character_hairs9.png"},
"hair_11": {name:"hair_11",img: "resources/customisation/character_hairs/character_hairs10.png"},
"hair_12": {name:"hair_12",img: "resources/customisation/character_hairs/character_hairs11.png"},
"hair_13": {name:"hair_13",img: "resources/customisation/character_hairs/character_hairs12.png"},
"hair_14": {name:"hair_14",img: "resources/customisation/character_hairs/character_hairs13.png"},
"hair_15": {name:"hair_15",img: "resources/customisation/character_hairs/character_hairs14.png"},
"hair_16": {name:"hair_16",img: "resources/customisation/character_hairs/character_hairs15.png"},
"hair_17": {name:"hair_17",img: "resources/customisation/character_hairs/character_hairs16.png"},
"hair_18": {name:"hair_18",img: "resources/customisation/character_hairs/character_hairs17.png"},
"hair_19": {name:"hair_19",img: "resources/customisation/character_hairs/character_hairs18.png"},
"hair_20": {name:"hair_20",img: "resources/customisation/character_hairs/character_hairs19.png"},
"hair_21": {name:"hair_21",img: "resources/customisation/character_hairs/character_hairs20.png"},
"hair_22": {name:"hair_22",img: "resources/customisation/character_hairs/character_hairs21.png"},
"hair_23": {name:"hair_23",img: "resources/customisation/character_hairs/character_hairs22.png"},
"hair_24": {name:"hair_24",img: "resources/customisation/character_hairs/character_hairs23.png"},
"hair_25": {name:"hair_25",img: "resources/customisation/character_hairs/character_hairs24.png"},
"hair_26": {name:"hair_26",img: "resources/customisation/character_hairs/character_hairs25.png"},
"hair_27": {name:"hair_27",img: "resources/customisation/character_hairs/character_hairs26.png"},
"hair_28": {name:"hair_28",img: "resources/customisation/character_hairs/character_hairs27.png"},
"hair_29": {name:"hair_29",img: "resources/customisation/character_hairs/character_hairs28.png"},
"hair_30": {name:"hair_30",img: "resources/customisation/character_hairs/character_hairs29.png"},
"hair_31": {name:"hair_31",img: "resources/customisation/character_hairs/character_hairs30.png"},
"hair_32": {name:"hair_32",img: "resources/customisation/character_hairs/character_hairs31.png"},
"hair_33": {name:"hair_33",img: "resources/customisation/character_hairs/character_hairs32.png"},
"hair_34": {name:"hair_34",img: "resources/customisation/character_hairs/character_hairs33.png"},
"hair_35": {name:"hair_35",img: "resources/customisation/character_hairs/character_hairs34.png"},
"hair_36": {name:"hair_36",img: "resources/customisation/character_hairs/character_hairs35.png"},
"hair_37": {name:"hair_37",img: "resources/customisation/character_hairs/character_hairs36.png"},
"hair_38": {name:"hair_38",img: "resources/customisation/character_hairs/character_hairs37.png"},
"hair_39": {name:"hair_39",img: "resources/customisation/character_hairs/character_hairs38.png"},
"hair_40": {name:"hair_40",img: "resources/customisation/character_hairs/character_hairs39.png"},
"hair_41": {name:"hair_41",img: "resources/customisation/character_hairs/character_hairs40.png"},
"hair_42": {name:"hair_42",img: "resources/customisation/character_hairs/character_hairs41.png"},
"hair_43": {name:"hair_43",img: "resources/customisation/character_hairs/character_hairs42.png"},
"hair_44": {name:"hair_44",img: "resources/customisation/character_hairs/character_hairs43.png"},
"hair_45": {name:"hair_45",img: "resources/customisation/character_hairs/character_hairs44.png"},
"hair_46": {name:"hair_46",img: "resources/customisation/character_hairs/character_hairs45.png"},
"hair_47": {name:"hair_47",img: "resources/customisation/character_hairs/character_hairs46.png"},
"hair_48": {name:"hair_48",img: "resources/customisation/character_hairs/character_hairs47.png"},
"hair_49": {name:"hair_49",img: "resources/customisation/character_hairs/character_hairs48.png"},
"hair_50": {name:"hair_50",img: "resources/customisation/character_hairs/character_hairs49.png"},
"hair_51": {name:"hair_51",img: "resources/customisation/character_hairs/character_hairs50.png"},
"hair_52": {name:"hair_52",img: "resources/customisation/character_hairs/character_hairs51.png"},
"hair_53": {name:"hair_53",img: "resources/customisation/character_hairs/character_hairs52.png"},
"hair_54": {name:"hair_54",img: "resources/customisation/character_hairs/character_hairs53.png"},
"hair_55": {name:"hair_55",img: "resources/customisation/character_hairs/character_hairs54.png"},
"hair_56": {name:"hair_56",img: "resources/customisation/character_hairs/character_hairs55.png"},
"hair_57": {name:"hair_57",img: "resources/customisation/character_hairs/character_hairs56.png"},
"hair_58": {name:"hair_58",img: "resources/customisation/character_hairs/character_hairs57.png"},
"hair_59": {name:"hair_59",img: "resources/customisation/character_hairs/character_hairs58.png"},
"hair_60": {name:"hair_60",img: "resources/customisation/character_hairs/character_hairs59.png"},
"hair_61": {name:"hair_61",img: "resources/customisation/character_hairs/character_hairs60.png"},
"hair_62": {name:"hair_62",img: "resources/customisation/character_hairs/character_hairs61.png"},
"hair_63": {name:"hair_63",img: "resources/customisation/character_hairs/character_hairs62.png"},
"hair_64": {name:"hair_64",img: "resources/customisation/character_hairs/character_hairs63.png"},
"hair_65": {name:"hair_65",img: "resources/customisation/character_hairs/character_hairs64.png"},
"hair_66": {name:"hair_66",img: "resources/customisation/character_hairs/character_hairs65.png"},
"hair_67": {name:"hair_67",img: "resources/customisation/character_hairs/character_hairs66.png"},
"hair_68": {name:"hair_68",img: "resources/customisation/character_hairs/character_hairs67.png"},
"hair_69": {name:"hair_69",img: "resources/customisation/character_hairs/character_hairs68.png"},
"hair_70": {name:"hair_70",img: "resources/customisation/character_hairs/character_hairs69.png"},
"hair_71": {name:"hair_71",img: "resources/customisation/character_hairs/character_hairs70.png"},
"hair_72": {name:"hair_72",img: "resources/customisation/character_hairs/character_hairs71.png"},
"hair_73": {name:"hair_73",img: "resources/customisation/character_hairs/character_hairs72.png"},
"hair_74": {name:"hair_74",img: "resources/customisation/character_hairs/character_hairs73.png"}
};
export const CLOTHES_RESOURCES: BodyResourceDescriptionListInterface = {
"clothes_1": {name:"clothes_1", img: "resources/customisation/character_clothes/character_clothes0.png"},
"clothes_2": {name:"clothes_2", img: "resources/customisation/character_clothes/character_clothes1.png"},
"clothes_3": {name:"clothes_3", img: "resources/customisation/character_clothes/character_clothes2.png"},
"clothes_4": {name:"clothes_4", img: "resources/customisation/character_clothes/character_clothes3.png"},
"clothes_5": {name:"clothes_5", img: "resources/customisation/character_clothes/character_clothes4.png"},
"clothes_6": {name:"clothes_6", img: "resources/customisation/character_clothes/character_clothes5.png"},
"clothes_7": {name:"clothes_7", img: "resources/customisation/character_clothes/character_clothes6.png"},
"clothes_8": {name:"clothes_8", img: "resources/customisation/character_clothes/character_clothes7.png"},
"clothes_9": {name:"clothes_9", img: "resources/customisation/character_clothes/character_clothes8.png"},
"clothes_10": {name:"clothes_10",img: "resources/customisation/character_clothes/character_clothes9.png"},
"clothes_11": {name:"clothes_11",img: "resources/customisation/character_clothes/character_clothes10.png"},
"clothes_12": {name:"clothes_12",img: "resources/customisation/character_clothes/character_clothes11.png"},
"clothes_13": {name:"clothes_13",img: "resources/customisation/character_clothes/character_clothes12.png"},
"clothes_14": {name:"clothes_14",img: "resources/customisation/character_clothes/character_clothes13.png"},
"clothes_15": {name:"clothes_15",img: "resources/customisation/character_clothes/character_clothes14.png"},
"clothes_16": {name:"clothes_16",img: "resources/customisation/character_clothes/character_clothes15.png"},
"clothes_17": {name:"clothes_17",img: "resources/customisation/character_clothes/character_clothes16.png"},
"clothes_18": {name:"clothes_18",img: "resources/customisation/character_clothes/character_clothes17.png"},
"clothes_19": {name:"clothes_19",img: "resources/customisation/character_clothes/character_clothes18.png"},
"clothes_20": {name:"clothes_20",img: "resources/customisation/character_clothes/character_clothes19.png"},
"clothes_21": {name:"clothes_21",img: "resources/customisation/character_clothes/character_clothes20.png"},
"clothes_22": {name:"clothes_22",img: "resources/customisation/character_clothes/character_clothes21.png"},
"clothes_23": {name:"clothes_23",img: "resources/customisation/character_clothes/character_clothes22.png"},
"clothes_24": {name:"clothes_24",img: "resources/customisation/character_clothes/character_clothes23.png"},
"clothes_25": {name:"clothes_25",img: "resources/customisation/character_clothes/character_clothes24.png"},
"clothes_26": {name:"clothes_26",img: "resources/customisation/character_clothes/character_clothes25.png"},
"clothes_27": {name:"clothes_27",img: "resources/customisation/character_clothes/character_clothes26.png"},
"clothes_28": {name:"clothes_28",img: "resources/customisation/character_clothes/character_clothes27.png"},
"clothes_29": {name:"clothes_29",img: "resources/customisation/character_clothes/character_clothes28.png"},
"clothes_30": {name:"clothes_30",img: "resources/customisation/character_clothes/character_clothes29.png"},
"clothes_31": {name:"clothes_31",img: "resources/customisation/character_clothes/character_clothes30.png"},
"clothes_32": {name:"clothes_32",img: "resources/customisation/character_clothes/character_clothes31.png"},
"clothes_33": {name:"clothes_33",img: "resources/customisation/character_clothes/character_clothes32.png"},
"clothes_34": {name:"clothes_34",img: "resources/customisation/character_clothes/character_clothes33.png"},
"clothes_35": {name:"clothes_35",img: "resources/customisation/character_clothes/character_clothes34.png"},
"clothes_36": {name:"clothes_36",img: "resources/customisation/character_clothes/character_clothes35.png"},
"clothes_37": {name:"clothes_37",img: "resources/customisation/character_clothes/character_clothes36.png"},
"clothes_38": {name:"clothes_38",img: "resources/customisation/character_clothes/character_clothes37.png"},
"clothes_39": {name:"clothes_39",img: "resources/customisation/character_clothes/character_clothes38.png"},
"clothes_40": {name:"clothes_40",img: "resources/customisation/character_clothes/character_clothes39.png"},
"clothes_41": {name:"clothes_41",img: "resources/customisation/character_clothes/character_clothes40.png"},
"clothes_42": {name:"clothes_42",img: "resources/customisation/character_clothes/character_clothes41.png"},
"clothes_43": {name:"clothes_43",img: "resources/customisation/character_clothes/character_clothes42.png"},
"clothes_44": {name:"clothes_44",img: "resources/customisation/character_clothes/character_clothes43.png"},
"clothes_45": {name:"clothes_45",img: "resources/customisation/character_clothes/character_clothes44.png"},
"clothes_46": {name:"clothes_46",img: "resources/customisation/character_clothes/character_clothes45.png"},
"clothes_47": {name:"clothes_47",img: "resources/customisation/character_clothes/character_clothes46.png"},
"clothes_48": {name:"clothes_48",img: "resources/customisation/character_clothes/character_clothes47.png"},
"clothes_49": {name:"clothes_49",img: "resources/customisation/character_clothes/character_clothes48.png"},
"clothes_50": {name:"clothes_50",img: "resources/customisation/character_clothes/character_clothes49.png"},
"clothes_51": {name:"clothes_51",img: "resources/customisation/character_clothes/character_clothes50.png"},
"clothes_52": {name:"clothes_52",img: "resources/customisation/character_clothes/character_clothes51.png"},
"clothes_53": {name:"clothes_53",img: "resources/customisation/character_clothes/character_clothes52.png"},
"clothes_54": {name:"clothes_54",img: "resources/customisation/character_clothes/character_clothes53.png"},
"clothes_55": {name:"clothes_55",img: "resources/customisation/character_clothes/character_clothes54.png"},
"clothes_56": {name:"clothes_56",img: "resources/customisation/character_clothes/character_clothes55.png"},
"clothes_57": {name:"clothes_57",img: "resources/customisation/character_clothes/character_clothes56.png"},
"clothes_58": {name:"clothes_58",img: "resources/customisation/character_clothes/character_clothes57.png"},
"clothes_59": {name:"clothes_59",img: "resources/customisation/character_clothes/character_clothes58.png"},
"clothes_60": {name:"clothes_60",img: "resources/customisation/character_clothes/character_clothes59.png"},
"clothes_61": {name:"clothes_61",img: "resources/customisation/character_clothes/character_clothes60.png"},
"clothes_62": {name:"clothes_62",img: "resources/customisation/character_clothes/character_clothes61.png"},
"clothes_63": {name:"clothes_63",img: "resources/customisation/character_clothes/character_clothes62.png"},
"clothes_64": {name:"clothes_64",img: "resources/customisation/character_clothes/character_clothes63.png"},
"clothes_65": {name:"clothes_65",img: "resources/customisation/character_clothes/character_clothes64.png"},
"clothes_66": {name:"clothes_66",img: "resources/customisation/character_clothes/character_clothes65.png"},
"clothes_67": {name:"clothes_67",img: "resources/customisation/character_clothes/character_clothes66.png"},
"clothes_68": {name:"clothes_68",img: "resources/customisation/character_clothes/character_clothes67.png"},
"clothes_69": {name:"clothes_69",img: "resources/customisation/character_clothes/character_clothes68.png"},
"clothes_70": {name:"clothes_70",img: "resources/customisation/character_clothes/character_clothes69.png"},
"clothes_pride_shirt": {name:"clothes_pride_shirt",img: "resources/customisation/character_clothes/pride_shirt.png"},
"clothes_black_hoodie": {name:"clothes_black_hoodie",img: "resources/customisation/character_clothes/black_hoodie.png"},
"clothes_white_hoodie": {name:"clothes_white_hoodie",img: "resources/customisation/character_clothes/white_hoodie.png"},
"clothes_engelbert": {name:"clothes_engelbert",img: "resources/customisation/character_clothes/engelbert.png"}
};
export const HATS_RESOURCES: BodyResourceDescriptionListInterface = {
"hats_1": {name: "hats_1", img: "resources/customisation/character_hats/character_hats1.png"},
"hats_2": {name: "hats_2", img: "resources/customisation/character_hats/character_hats2.png"},
"hats_3": {name: "hats_3", img: "resources/customisation/character_hats/character_hats3.png"},
"hats_4": {name: "hats_4", img: "resources/customisation/character_hats/character_hats4.png"},
"hats_5": {name: "hats_5", img: "resources/customisation/character_hats/character_hats5.png"},
"hats_6": {name: "hats_6", img: "resources/customisation/character_hats/character_hats6.png"},
"hats_7": {name: "hats_7", img: "resources/customisation/character_hats/character_hats7.png"},
"hats_8": {name: "hats_8", img: "resources/customisation/character_hats/character_hats8.png"},
"hats_9": {name: "hats_9", img: "resources/customisation/character_hats/character_hats9.png"},
"hats_10": {name: "hats_10", img: "resources/customisation/character_hats/character_hats10.png"},
"hats_11": {name: "hats_11", img: "resources/customisation/character_hats/character_hats11.png"},
"hats_12": {name: "hats_12", img: "resources/customisation/character_hats/character_hats12.png"},
"hats_13": {name: "hats_13", img: "resources/customisation/character_hats/character_hats13.png"},
"hats_14": {name: "hats_14", img: "resources/customisation/character_hats/character_hats14.png"},
"hats_15": {name: "hats_15", img: "resources/customisation/character_hats/character_hats15.png"},
"hats_16": {name: "hats_16", img: "resources/customisation/character_hats/character_hats16.png"},
"hats_17": {name: "hats_17", img: "resources/customisation/character_hats/character_hats17.png"},
"hats_18": {name: "hats_18", img: "resources/customisation/character_hats/character_hats18.png"},
"hats_19": {name: "hats_19", img: "resources/customisation/character_hats/character_hats19.png"},
"hats_20": {name: "hats_20", img: "resources/customisation/character_hats/character_hats20.png"},
"hats_21": {name: "hats_21", img: "resources/customisation/character_hats/character_hats21.png"},
"hats_22": {name: "hats_22", img: "resources/customisation/character_hats/character_hats22.png"},
"hats_23": {name: "hats_23", img: "resources/customisation/character_hats/character_hats23.png"},
"hats_24": {name: "hats_24", img: "resources/customisation/character_hats/character_hats24.png"},
"hats_25": {name: "hats_25", img: "resources/customisation/character_hats/character_hats25.png"},
"hats_26": {name: "hats_26", img: "resources/customisation/character_hats/character_hats26.png"},
"tinfoil_hat1": {name: "tinfoil_hat1", img: "resources/customisation/character_hats/tinfoil_hat1.png"}
};
export const ACCESSORIES_RESOURCES: BodyResourceDescriptionListInterface = {
"accessory_1": {name: "accessory_1", img: "resources/customisation/character_accessories/character_accessories1.png"},
"accessory_2": {name: "accessory_2", img: "resources/customisation/character_accessories/character_accessories2.png"},
"accessory_3": {name: "accessory_3", img: "resources/customisation/character_accessories/character_accessories3.png"},
"accessory_4": {name: "accessory_4", img: "resources/customisation/character_accessories/character_accessories4.png"},
"accessory_5": {name: "accessory_5", img: "resources/customisation/character_accessories/character_accessories5.png"},
"accessory_6": {name: "accessory_6", img: "resources/customisation/character_accessories/character_accessories6.png"},
"accessory_7": {name: "accessory_7", img: "resources/customisation/character_accessories/character_accessories7.png"},
"accessory_8": {name: "accessory_8", img: "resources/customisation/character_accessories/character_accessories8.png"},
"accessory_9": {name: "accessory_9", img: "resources/customisation/character_accessories/character_accessories9.png"},
"accessory_10": {name: "accessory_10", img: "resources/customisation/character_accessories/character_accessories10.png"},
"accessory_11": {name: "accessory_11", img: "resources/customisation/character_accessories/character_accessories11.png"},
"accessory_12": {name: "accessory_12", img: "resources/customisation/character_accessories/character_accessories12.png"},
"accessory_13": {name: "accessory_13", img: "resources/customisation/character_accessories/character_accessories13.png"},
"accessory_14": {name: "accessory_14", img: "resources/customisation/character_accessories/character_accessories14.png"},
"accessory_15": {name: "accessory_15", img: "resources/customisation/character_accessories/character_accessories15.png"},
"accessory_16": {name: "accessory_16", img: "resources/customisation/character_accessories/character_accessories16.png"},
"accessory_17": {name: "accessory_17", img: "resources/customisation/character_accessories/character_accessories17.png"},
"accessory_18": {name: "accessory_18", img: "resources/customisation/character_accessories/character_accessories18.png"},
"accessory_19": {name: "accessory_19", img: "resources/customisation/character_accessories/character_accessories19.png"},
"accessory_20": {name: "accessory_20", img: "resources/customisation/character_accessories/character_accessories20.png"},
"accessory_21": {name: "accessory_21", img: "resources/customisation/character_accessories/character_accessories21.png"},
"accessory_22": {name: "accessory_22", img: "resources/customisation/character_accessories/character_accessories22.png"},
"accessory_23": {name: "accessory_23", img: "resources/customisation/character_accessories/character_accessories23.png"},
"accessory_24": {name: "accessory_24", img: "resources/customisation/character_accessories/character_accessories24.png"},
"accessory_25": {name: "accessory_25", img: "resources/customisation/character_accessories/character_accessories25.png"},
"accessory_26": {name: "accessory_26", img: "resources/customisation/character_accessories/character_accessories26.png"},
"accessory_27": {name: "accessory_27", img: "resources/customisation/character_accessories/character_accessories27.png"},
"accessory_28": {name: "accessory_28", img: "resources/customisation/character_accessories/character_accessories28.png"},
"accessory_29": {name: "accessory_29", img: "resources/customisation/character_accessories/character_accessories29.png"},
"accessory_30": {name: "accessory_30", img: "resources/customisation/character_accessories/character_accessories30.png"},
"accessory_31": {name: "accessory_31", img: "resources/customisation/character_accessories/character_accessories31.png"},
"accessory_32": {name: "accessory_32", img: "resources/customisation/character_accessories/character_accessories32.png"},
"accessory_mate_bottle": {name: "accessory_mate_bottle", img: "resources/customisation/character_accessories/mate_bottle1.png"},
"accessory_mask": {name: "accessory_mask", img: "resources/customisation/character_accessories/mask.png"}
};
export const LAYERS: BodyResourceDescriptionListInterface[] = [
COLOR_RESOURCES,
EYES_RESOURCES,
HAIR_RESOURCES,
CLOTHES_RESOURCES,
HATS_RESOURCES,
ACCESSORIES_RESOURCES
];
export const OBJECTS: BodyResourceDescriptionInterface[] = [
{name:'teleportation', img:'resources/objects/teleportation.png'},
];

View file

@ -0,0 +1,90 @@
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
import type {CharacterTexture} from "../../Connexion/LocalUser";
import {BodyResourceDescriptionInterface, LAYERS, PLAYER_RESOURCES} from "./PlayerTextures";
export interface FrameConfig {
frameWidth: number,
frameHeight: number,
}
export const loadAllLayers = (load: LoaderPlugin): BodyResourceDescriptionInterface[][] => {
const returnArray:BodyResourceDescriptionInterface[][] = [];
LAYERS.forEach(layer => {
const layerArray:BodyResourceDescriptionInterface[] = [];
Object.values(layer).forEach((textureDescriptor) => {
layerArray.push(textureDescriptor);
load.spritesheet(textureDescriptor.name,textureDescriptor.img,{frameWidth: 32, frameHeight: 32});
})
returnArray.push(layerArray)
});
return returnArray;
}
export const loadAllDefaultModels = (load: LoaderPlugin): BodyResourceDescriptionInterface[] => {
const returnArray = Object.values(PLAYER_RESOURCES);
returnArray.forEach((playerResource: BodyResourceDescriptionInterface) => {
load.spritesheet(playerResource.name, playerResource.img, {frameWidth: 32, frameHeight: 32});
});
return returnArray;
}
export const loadCustomTexture = (loaderPlugin: LoaderPlugin, texture: CharacterTexture) : Promise<BodyResourceDescriptionInterface> => {
const name = 'customCharacterTexture'+texture.id;
const playerResourceDescriptor: BodyResourceDescriptionInterface = {name, img: texture.url, level: texture.level}
return createLoadingPromise(loaderPlugin, playerResourceDescriptor, {
frameWidth: 32,
frameHeight: 32
});
}
export const lazyLoadPlayerCharacterTextures = (loadPlugin: LoaderPlugin, texturekeys:Array<string|BodyResourceDescriptionInterface>): Promise<string[]> => {
const promisesList:Promise<unknown>[] = [];
texturekeys.forEach((textureKey: string|BodyResourceDescriptionInterface) => {
try {
//TODO refactor
const playerResourceDescriptor = getRessourceDescriptor(textureKey);
if (playerResourceDescriptor && !loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
promisesList.push(createLoadingPromise(loadPlugin, playerResourceDescriptor, {
frameWidth: 32,
frameHeight: 32
}));
}
}catch (err){
console.error(err);
}
});
let returnPromise:Promise<Array<string|BodyResourceDescriptionInterface>>;
if (promisesList.length > 0) {
loadPlugin.start();
returnPromise = Promise.all(promisesList).then(() => texturekeys);
} else {
returnPromise = Promise.resolve(texturekeys);
}
return returnPromise.then((keys) => keys.map((key) => {
return typeof key !== 'string' ? key.name : key;
}))
}
export const getRessourceDescriptor = (textureKey: string|BodyResourceDescriptionInterface): BodyResourceDescriptionInterface => {
if (typeof textureKey !== 'string' && textureKey.img) {
return textureKey;
}
const textureName:string = typeof textureKey === 'string' ? textureKey : textureKey.name;
const playerResource = PLAYER_RESOURCES[textureName];
if (playerResource !== undefined) return playerResource;
for (let i=0; i<LAYERS.length;i++) {
const playerResource = LAYERS[i][textureName];
if (playerResource !== undefined) return playerResource;
}
throw 'Could not find a data for texture '+textureName;
}
export const createLoadingPromise = (loadPlugin: LoaderPlugin, playerResourceDescriptor: BodyResourceDescriptionInterface, frameConfig: FrameConfig) => {
return new Promise<BodyResourceDescriptionInterface>((res) => {
if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
return res(playerResourceDescriptor);
}
loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, frameConfig);
loadPlugin.once('filecomplete-spritesheet-' + playerResourceDescriptor.name, () => res(playerResourceDescriptor));
});
}

View file

@ -1,13 +1,16 @@
import {GameScene} from "../Game/GameScene";
import {PointInterface} from "../../Connexion/ConnexionModels";
import type {GameScene} from "../Game/GameScene";
import type {PointInterface} from "../../Connexion/ConnexionModels";
import {Character} from "../Entity/Character";
import {Sprite} from "./Sprite";
import type {PlayerAnimationDirections} from "../Player/Animation";
import {requestVisitCardsStore} from "../../Stores/GameStore";
/**
* Class representing the sprite of a remote player (a player that plays on another computer)
*/
export class RemotePlayer extends Character {
userId: number;
private visitCardUrl: string|null;
constructor(
userId: number,
@ -15,21 +18,33 @@ export class RemotePlayer extends Character {
x: number,
y: number,
name: string,
PlayerTextures: string[],
direction: string,
moving: boolean
texturesPromise: Promise<string[]>,
direction: PlayerAnimationDirections,
moving: boolean,
visitCardUrl: string|null,
companion: string|null,
companionTexturePromise?: Promise<string>
) {
super(Scene, x, y, PlayerTextures, name, direction, moving, 1);
super(Scene, x, y, texturesPromise, name, direction, moving, 1, !!visitCardUrl, companion, companionTexturePromise);
//set data
this.userId = userId;
this.visitCardUrl = visitCardUrl;
this.on('pointerdown', () => {
requestVisitCardsStore.set(this.visitCardUrl);
})
}
updatePosition(position: PointInterface): void {
this.playAnimation(position.direction, position.moving);
this.playAnimation(position.direction as PlayerAnimationDirections, position.moving);
this.setX(position.x);
this.setY(position.y);
this.setDepth(position.y); //this is to make sure the perspective (player models closer the bottom of the screen will appear in front of models nearer the top of the screen).
if (this.companion) {
this.companion.setTarget(position.x, position.y, position.direction as PlayerAnimationDirections);
}
}
}

View file

@ -1,12 +1,12 @@
import Scene = Phaser.Scene;
import {Character} from "./Character";
import type {Character} from "./Character";
//todo: improve this WIP
export class SpeechBubble {
private bubble: Phaser.GameObjects.Graphics;
private content: Phaser.GameObjects.Text;
constructor(scene: Scene, player: Character, text: string = "") {
const bubbleHeight = 50;
@ -14,7 +14,7 @@ export class SpeechBubble {
const bubbleWidth = bubblePadding * 2 + text.length * 10;
const arrowHeight = bubbleHeight / 4;
this.bubble = scene.add.graphics({ x: player.x + 16, y: player.y - 80 });
this.bubble = scene.add.graphics({ x: 16, y: -80 });
player.add(this.bubble);
// Bubble shadow

View file

@ -1,355 +0,0 @@
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
import {PLAYER_RESOURCES, PlayerResourceDescriptionInterface} from "./Character";
import {CharacterTexture} from "../../Connexion/LocalUser";
export interface BodyResourceDescriptionInterface {
name: string,
img: string
}
export const COLOR_RESOURCES: Array<BodyResourceDescriptionInterface> = [
{name:"color_1", img: "resources/customisation/character_color/character_color0.png"},
{name:"color_2", img: "resources/customisation/character_color/character_color1.png"},
{name:"color_3", img: "resources/customisation/character_color/character_color2.png"},
{name:"color_4", img: "resources/customisation/character_color/character_color3.png"},
{name:"color_5", img: "resources/customisation/character_color/character_color4.png"},
{name:"color_6", img: "resources/customisation/character_color/character_color5.png"},
{name:"color_7", img: "resources/customisation/character_color/character_color6.png"},
{name:"color_8", img: "resources/customisation/character_color/character_color7.png"},
{name:"color_9", img: "resources/customisation/character_color/character_color8.png"},
{name:"color_10",img: "resources/customisation/character_color/character_color9.png"},
{name:"color_11",img: "resources/customisation/character_color/character_color10.png"},
{name:"color_12",img: "resources/customisation/character_color/character_color11.png"},
{name:"color_13",img: "resources/customisation/character_color/character_color12.png"},
{name:"color_14",img: "resources/customisation/character_color/character_color13.png"},
{name:"color_15",img: "resources/customisation/character_color/character_color14.png"},
{name:"color_16",img: "resources/customisation/character_color/character_color15.png"},
{name:"color_17",img: "resources/customisation/character_color/character_color16.png"},
{name:"color_18",img: "resources/customisation/character_color/character_color17.png"},
{name:"color_19",img: "resources/customisation/character_color/character_color18.png"},
{name:"color_20",img: "resources/customisation/character_color/character_color19.png"},
{name:"color_21",img: "resources/customisation/character_color/character_color20.png"},
{name:"color_22",img: "resources/customisation/character_color/character_color21.png"},
{name:"color_23",img: "resources/customisation/character_color/character_color22.png"},
{name:"color_24",img: "resources/customisation/character_color/character_color23.png"},
{name:"color_25",img: "resources/customisation/character_color/character_color24.png"},
{name:"color_26",img: "resources/customisation/character_color/character_color25.png"},
{name:"color_27",img: "resources/customisation/character_color/character_color26.png"},
{name:"color_28",img: "resources/customisation/character_color/character_color27.png"},
{name:"color_29",img: "resources/customisation/character_color/character_color28.png"},
{name:"color_30",img: "resources/customisation/character_color/character_color29.png"},
{name:"color_31",img: "resources/customisation/character_color/character_color30.png"},
{name:"color_32",img: "resources/customisation/character_color/character_color31.png"},
{name:"color_33",img: "resources/customisation/character_color/character_color32.png"}
];
export const EYES_RESOURCES: Array<BodyResourceDescriptionInterface> = [
{name: "eyes_1", img: "resources/customisation/character_eyes/character_eyes1.png"},
{name: "eyes_2", img: "resources/customisation/character_eyes/character_eyes2.png"},
{name: "eyes_3", img: "resources/customisation/character_eyes/character_eyes3.png"},
{name: "eyes_4", img: "resources/customisation/character_eyes/character_eyes4.png"},
{name: "eyes_5", img: "resources/customisation/character_eyes/character_eyes5.png"},
{name: "eyes_6", img: "resources/customisation/character_eyes/character_eyes6.png"},
{name: "eyes_7", img: "resources/customisation/character_eyes/character_eyes7.png"},
{name: "eyes_8", img: "resources/customisation/character_eyes/character_eyes8.png"},
{name: "eyes_9", img: "resources/customisation/character_eyes/character_eyes9.png"},
{name: "eyes_10", img: "resources/customisation/character_eyes/character_eyes10.png"},
{name: "eyes_11", img: "resources/customisation/character_eyes/character_eyes11.png"},
{name: "eyes_12", img: "resources/customisation/character_eyes/character_eyes12.png"},
{name: "eyes_13", img: "resources/customisation/character_eyes/character_eyes13.png"},
{name: "eyes_14", img: "resources/customisation/character_eyes/character_eyes14.png"},
{name: "eyes_15", img: "resources/customisation/character_eyes/character_eyes15.png"},
{name: "eyes_16", img: "resources/customisation/character_eyes/character_eyes16.png"},
{name: "eyes_17", img: "resources/customisation/character_eyes/character_eyes17.png"},
{name: "eyes_18", img: "resources/customisation/character_eyes/character_eyes18.png"},
{name: "eyes_19", img: "resources/customisation/character_eyes/character_eyes19.png"},
{name: "eyes_20", img: "resources/customisation/character_eyes/character_eyes20.png"},
{name: "eyes_21", img: "resources/customisation/character_eyes/character_eyes21.png"},
{name: "eyes_22", img: "resources/customisation/character_eyes/character_eyes22.png"},
{name: "eyes_23", img: "resources/customisation/character_eyes/character_eyes23.png"},
{name: "eyes_24", img: "resources/customisation/character_eyes/character_eyes24.png"},
{name: "eyes_25", img: "resources/customisation/character_eyes/character_eyes25.png"},
{name: "eyes_26", img: "resources/customisation/character_eyes/character_eyes26.png"},
{name: "eyes_27", img: "resources/customisation/character_eyes/character_eyes27.png"},
{name: "eyes_28", img: "resources/customisation/character_eyes/character_eyes28.png"},
{name: "eyes_29", img: "resources/customisation/character_eyes/character_eyes29.png"},
{name: "eyes_30", img: "resources/customisation/character_eyes/character_eyes30.png"}
]
export const HAIR_RESOURCES: Array<BodyResourceDescriptionInterface> = [
{name:"hair_1", img: "resources/customisation/character_hairs/character_hairs0.png"},
{name:"hair_2", img: "resources/customisation/character_hairs/character_hairs1.png"},
{name:"hair_3", img: "resources/customisation/character_hairs/character_hairs2.png"},
{name:"hair_4", img: "resources/customisation/character_hairs/character_hairs3.png"},
{name:"hair_5", img: "resources/customisation/character_hairs/character_hairs4.png"},
{name:"hair_6", img: "resources/customisation/character_hairs/character_hairs5.png"},
{name:"hair_7", img: "resources/customisation/character_hairs/character_hairs6.png"},
{name:"hair_8", img: "resources/customisation/character_hairs/character_hairs7.png"},
{name:"hair_9", img: "resources/customisation/character_hairs/character_hairs8.png"},
{name:"hair_10",img: "resources/customisation/character_hairs/character_hairs9.png"},
{name:"hair_11",img: "resources/customisation/character_hairs/character_hairs10.png"},
{name:"hair_12",img: "resources/customisation/character_hairs/character_hairs11.png"},
{name:"hair_13",img: "resources/customisation/character_hairs/character_hairs12.png"},
{name:"hair_14",img: "resources/customisation/character_hairs/character_hairs13.png"},
{name:"hair_15",img: "resources/customisation/character_hairs/character_hairs14.png"},
{name:"hair_16",img: "resources/customisation/character_hairs/character_hairs15.png"},
{name:"hair_17",img: "resources/customisation/character_hairs/character_hairs16.png"},
{name:"hair_18",img: "resources/customisation/character_hairs/character_hairs17.png"},
{name:"hair_19",img: "resources/customisation/character_hairs/character_hairs18.png"},
{name:"hair_20",img: "resources/customisation/character_hairs/character_hairs19.png"},
{name:"hair_21",img: "resources/customisation/character_hairs/character_hairs20.png"},
{name:"hair_22",img: "resources/customisation/character_hairs/character_hairs21.png"},
{name:"hair_23",img: "resources/customisation/character_hairs/character_hairs22.png"},
{name:"hair_24",img: "resources/customisation/character_hairs/character_hairs23.png"},
{name:"hair_25",img: "resources/customisation/character_hairs/character_hairs24.png"},
{name:"hair_26",img: "resources/customisation/character_hairs/character_hairs25.png"},
{name:"hair_27",img: "resources/customisation/character_hairs/character_hairs26.png"},
{name:"hair_28",img: "resources/customisation/character_hairs/character_hairs27.png"},
{name:"hair_29",img: "resources/customisation/character_hairs/character_hairs28.png"},
{name:"hair_30",img: "resources/customisation/character_hairs/character_hairs29.png"},
{name:"hair_31",img: "resources/customisation/character_hairs/character_hairs30.png"},
{name:"hair_32",img: "resources/customisation/character_hairs/character_hairs31.png"},
{name:"hair_33",img: "resources/customisation/character_hairs/character_hairs32.png"},
{name:"hair_34",img: "resources/customisation/character_hairs/character_hairs33.png"},
{name:"hair_35",img: "resources/customisation/character_hairs/character_hairs34.png"},
{name:"hair_36",img: "resources/customisation/character_hairs/character_hairs35.png"},
{name:"hair_37",img: "resources/customisation/character_hairs/character_hairs36.png"},
{name:"hair_38",img: "resources/customisation/character_hairs/character_hairs37.png"},
{name:"hair_39",img: "resources/customisation/character_hairs/character_hairs38.png"},
{name:"hair_40",img: "resources/customisation/character_hairs/character_hairs39.png"},
{name:"hair_41",img: "resources/customisation/character_hairs/character_hairs40.png"},
{name:"hair_42",img: "resources/customisation/character_hairs/character_hairs41.png"},
{name:"hair_43",img: "resources/customisation/character_hairs/character_hairs42.png"},
{name:"hair_44",img: "resources/customisation/character_hairs/character_hairs43.png"},
{name:"hair_45",img: "resources/customisation/character_hairs/character_hairs44.png"},
{name:"hair_46",img: "resources/customisation/character_hairs/character_hairs45.png"},
{name:"hair_47",img: "resources/customisation/character_hairs/character_hairs46.png"},
{name:"hair_48",img: "resources/customisation/character_hairs/character_hairs47.png"},
{name:"hair_49",img: "resources/customisation/character_hairs/character_hairs48.png"},
{name:"hair_50",img: "resources/customisation/character_hairs/character_hairs49.png"},
{name:"hair_51",img: "resources/customisation/character_hairs/character_hairs50.png"},
{name:"hair_52",img: "resources/customisation/character_hairs/character_hairs51.png"},
{name:"hair_53",img: "resources/customisation/character_hairs/character_hairs52.png"},
{name:"hair_54",img: "resources/customisation/character_hairs/character_hairs53.png"},
{name:"hair_55",img: "resources/customisation/character_hairs/character_hairs54.png"},
{name:"hair_56",img: "resources/customisation/character_hairs/character_hairs55.png"},
{name:"hair_57",img: "resources/customisation/character_hairs/character_hairs56.png"},
{name:"hair_58",img: "resources/customisation/character_hairs/character_hairs57.png"},
{name:"hair_59",img: "resources/customisation/character_hairs/character_hairs58.png"},
{name:"hair_60",img: "resources/customisation/character_hairs/character_hairs59.png"},
{name:"hair_61",img: "resources/customisation/character_hairs/character_hairs60.png"},
{name:"hair_62",img: "resources/customisation/character_hairs/character_hairs61.png"},
{name:"hair_63",img: "resources/customisation/character_hairs/character_hairs62.png"},
{name:"hair_64",img: "resources/customisation/character_hairs/character_hairs63.png"},
{name:"hair_65",img: "resources/customisation/character_hairs/character_hairs64.png"},
{name:"hair_66",img: "resources/customisation/character_hairs/character_hairs65.png"},
{name:"hair_67",img: "resources/customisation/character_hairs/character_hairs66.png"},
{name:"hair_68",img: "resources/customisation/character_hairs/character_hairs67.png"},
{name:"hair_69",img: "resources/customisation/character_hairs/character_hairs68.png"},
{name:"hair_70",img: "resources/customisation/character_hairs/character_hairs69.png"},
{name:"hair_71",img: "resources/customisation/character_hairs/character_hairs70.png"},
{name:"hair_72",img: "resources/customisation/character_hairs/character_hairs71.png"},
{name:"hair_73",img: "resources/customisation/character_hairs/character_hairs72.png"},
{name:"hair_74",img: "resources/customisation/character_hairs/character_hairs73.png"}
];
export const CLOTHES_RESOURCES: Array<BodyResourceDescriptionInterface> = [
{name:"clothes_1", img: "resources/customisation/character_clothes/character_clothes0.png"},
{name:"clothes_2", img: "resources/customisation/character_clothes/character_clothes1.png"},
{name:"clothes_3", img: "resources/customisation/character_clothes/character_clothes2.png"},
{name:"clothes_4", img: "resources/customisation/character_clothes/character_clothes3.png"},
{name:"clothes_5", img: "resources/customisation/character_clothes/character_clothes4.png"},
{name:"clothes_6", img: "resources/customisation/character_clothes/character_clothes5.png"},
{name:"clothes_7", img: "resources/customisation/character_clothes/character_clothes6.png"},
{name:"clothes_8", img: "resources/customisation/character_clothes/character_clothes7.png"},
{name:"clothes_9", img: "resources/customisation/character_clothes/character_clothes8.png"},
{name:"clothes_10",img: "resources/customisation/character_clothes/character_clothes9.png"},
{name:"clothes_11",img: "resources/customisation/character_clothes/character_clothes10.png"},
{name:"clothes_12",img: "resources/customisation/character_clothes/character_clothes11.png"},
{name:"clothes_13",img: "resources/customisation/character_clothes/character_clothes12.png"},
{name:"clothes_14",img: "resources/customisation/character_clothes/character_clothes13.png"},
{name:"clothes_15",img: "resources/customisation/character_clothes/character_clothes14.png"},
{name:"clothes_16",img: "resources/customisation/character_clothes/character_clothes15.png"},
{name:"clothes_17",img: "resources/customisation/character_clothes/character_clothes16.png"},
{name:"clothes_18",img: "resources/customisation/character_clothes/character_clothes17.png"},
{name:"clothes_19",img: "resources/customisation/character_clothes/character_clothes18.png"},
{name:"clothes_20",img: "resources/customisation/character_clothes/character_clothes19.png"},
{name:"clothes_21",img: "resources/customisation/character_clothes/character_clothes20.png"},
{name:"clothes_22",img: "resources/customisation/character_clothes/character_clothes21.png"},
{name:"clothes_23",img: "resources/customisation/character_clothes/character_clothes22.png"},
{name:"clothes_24",img: "resources/customisation/character_clothes/character_clothes23.png"},
{name:"clothes_25",img: "resources/customisation/character_clothes/character_clothes24.png"},
{name:"clothes_26",img: "resources/customisation/character_clothes/character_clothes25.png"},
{name:"clothes_27",img: "resources/customisation/character_clothes/character_clothes26.png"},
{name:"clothes_28",img: "resources/customisation/character_clothes/character_clothes27.png"},
{name:"clothes_29",img: "resources/customisation/character_clothes/character_clothes28.png"},
{name:"clothes_30",img: "resources/customisation/character_clothes/character_clothes29.png"},
{name:"clothes_31",img: "resources/customisation/character_clothes/character_clothes30.png"},
{name:"clothes_32",img: "resources/customisation/character_clothes/character_clothes31.png"},
{name:"clothes_33",img: "resources/customisation/character_clothes/character_clothes32.png"},
{name:"clothes_34",img: "resources/customisation/character_clothes/character_clothes33.png"},
{name:"clothes_35",img: "resources/customisation/character_clothes/character_clothes34.png"},
{name:"clothes_36",img: "resources/customisation/character_clothes/character_clothes35.png"},
{name:"clothes_37",img: "resources/customisation/character_clothes/character_clothes36.png"},
{name:"clothes_38",img: "resources/customisation/character_clothes/character_clothes37.png"},
{name:"clothes_39",img: "resources/customisation/character_clothes/character_clothes38.png"},
{name:"clothes_40",img: "resources/customisation/character_clothes/character_clothes39.png"},
{name:"clothes_41",img: "resources/customisation/character_clothes/character_clothes40.png"},
{name:"clothes_42",img: "resources/customisation/character_clothes/character_clothes41.png"},
{name:"clothes_43",img: "resources/customisation/character_clothes/character_clothes42.png"},
{name:"clothes_44",img: "resources/customisation/character_clothes/character_clothes43.png"},
{name:"clothes_45",img: "resources/customisation/character_clothes/character_clothes44.png"},
{name:"clothes_46",img: "resources/customisation/character_clothes/character_clothes45.png"},
{name:"clothes_47",img: "resources/customisation/character_clothes/character_clothes46.png"},
{name:"clothes_48",img: "resources/customisation/character_clothes/character_clothes47.png"},
{name:"clothes_49",img: "resources/customisation/character_clothes/character_clothes48.png"},
{name:"clothes_50",img: "resources/customisation/character_clothes/character_clothes49.png"},
{name:"clothes_51",img: "resources/customisation/character_clothes/character_clothes50.png"},
{name:"clothes_52",img: "resources/customisation/character_clothes/character_clothes51.png"},
{name:"clothes_53",img: "resources/customisation/character_clothes/character_clothes52.png"},
{name:"clothes_54",img: "resources/customisation/character_clothes/character_clothes53.png"},
{name:"clothes_55",img: "resources/customisation/character_clothes/character_clothes54.png"},
{name:"clothes_56",img: "resources/customisation/character_clothes/character_clothes55.png"},
{name:"clothes_57",img: "resources/customisation/character_clothes/character_clothes56.png"},
{name:"clothes_58",img: "resources/customisation/character_clothes/character_clothes57.png"},
{name:"clothes_59",img: "resources/customisation/character_clothes/character_clothes58.png"},
{name:"clothes_60",img: "resources/customisation/character_clothes/character_clothes59.png"},
{name:"clothes_61",img: "resources/customisation/character_clothes/character_clothes60.png"},
{name:"clothes_62",img: "resources/customisation/character_clothes/character_clothes61.png"},
{name:"clothes_63",img: "resources/customisation/character_clothes/character_clothes62.png"},
{name:"clothes_64",img: "resources/customisation/character_clothes/character_clothes63.png"},
{name:"clothes_65",img: "resources/customisation/character_clothes/character_clothes64.png"},
{name:"clothes_66",img: "resources/customisation/character_clothes/character_clothes65.png"},
{name:"clothes_67",img: "resources/customisation/character_clothes/character_clothes66.png"},
{name:"clothes_68",img: "resources/customisation/character_clothes/character_clothes67.png"},
{name:"clothes_69",img: "resources/customisation/character_clothes/character_clothes68.png"},
{name:"clothes_70",img: "resources/customisation/character_clothes/character_clothes69.png"},
{name:"clothes_pride_shirt",img: "resources/customisation/character_clothes/pride_shirt.png"},
{name:"clothes_black_hoodie",img: "resources/customisation/character_clothes/black_hoodie.png"},
{name:"clothes_white_hoodie",img: "resources/customisation/character_clothes/white_hoodie.png"},
{name:"clothes_engelbert",img: "resources/customisation/character_clothes/engelbert.png"}
];
export const HATS_RESOURCES: Array<BodyResourceDescriptionInterface> = [
{name: "hats_1", img: "resources/customisation/character_hats/character_hats1.png"},
{name: "hats_2", img: "resources/customisation/character_hats/character_hats2.png"},
{name: "hats_3", img: "resources/customisation/character_hats/character_hats3.png"},
{name: "hats_4", img: "resources/customisation/character_hats/character_hats4.png"},
{name: "hats_5", img: "resources/customisation/character_hats/character_hats5.png"},
{name: "hats_6", img: "resources/customisation/character_hats/character_hats6.png"},
{name: "hats_7", img: "resources/customisation/character_hats/character_hats7.png"},
{name: "hats_8", img: "resources/customisation/character_hats/character_hats8.png"},
{name: "hats_9", img: "resources/customisation/character_hats/character_hats9.png"},
{name: "hats_10", img: "resources/customisation/character_hats/character_hats10.png"},
{name: "hats_11", img: "resources/customisation/character_hats/character_hats11.png"},
{name: "hats_12", img: "resources/customisation/character_hats/character_hats12.png"},
{name: "hats_13", img: "resources/customisation/character_hats/character_hats13.png"},
{name: "hats_14", img: "resources/customisation/character_hats/character_hats14.png"},
{name: "hats_15", img: "resources/customisation/character_hats/character_hats15.png"},
{name: "hats_16", img: "resources/customisation/character_hats/character_hats16.png"},
{name: "hats_17", img: "resources/customisation/character_hats/character_hats17.png"},
{name: "hats_18", img: "resources/customisation/character_hats/character_hats18.png"},
{name: "hats_19", img: "resources/customisation/character_hats/character_hats19.png"},
{name: "hats_20", img: "resources/customisation/character_hats/character_hats20.png"},
{name: "hats_21", img: "resources/customisation/character_hats/character_hats21.png"},
{name: "hats_22", img: "resources/customisation/character_hats/character_hats22.png"},
{name: "hats_23", img: "resources/customisation/character_hats/character_hats23.png"},
{name: "hats_24", img: "resources/customisation/character_hats/character_hats24.png"},
{name: "hats_25", img: "resources/customisation/character_hats/character_hats25.png"},
{name: "hats_26", img: "resources/customisation/character_hats/character_hats26.png"},
{name: "tinfoil_hat1", img: "resources/customisation/character_hats/tinfoil_hat1.png"}
];
export const ACCESSORIES_RESOURCES: Array<BodyResourceDescriptionInterface> = [
{name: "accessory_1", img: "resources/customisation/character_accessories/character_accessories1.png"},
{name: "accessory_2", img: "resources/customisation/character_accessories/character_accessories2.png"},
{name: "accessory_3", img: "resources/customisation/character_accessories/character_accessories3.png"},
{name: "accessory_4", img: "resources/customisation/character_accessories/character_accessories4.png"},
{name: "accessory_5", img: "resources/customisation/character_accessories/character_accessories5.png"},
{name: "accessory_6", img: "resources/customisation/character_accessories/character_accessories6.png"},
{name: "accessory_7", img: "resources/customisation/character_accessories/character_accessories7.png"},
{name: "accessory_8", img: "resources/customisation/character_accessories/character_accessories8.png"},
{name: "accessory_9", img: "resources/customisation/character_accessories/character_accessories9.png"},
{name: "accessory_10", img: "resources/customisation/character_accessories/character_accessories10.png"},
{name: "accessory_11", img: "resources/customisation/character_accessories/character_accessories11.png"},
{name: "accessory_12", img: "resources/customisation/character_accessories/character_accessories12.png"},
{name: "accessory_13", img: "resources/customisation/character_accessories/character_accessories13.png"},
{name: "accessory_14", img: "resources/customisation/character_accessories/character_accessories14.png"},
{name: "accessory_15", img: "resources/customisation/character_accessories/character_accessories15.png"},
{name: "accessory_16", img: "resources/customisation/character_accessories/character_accessories16.png"},
{name: "accessory_17", img: "resources/customisation/character_accessories/character_accessories17.png"},
{name: "accessory_18", img: "resources/customisation/character_accessories/character_accessories18.png"},
{name: "accessory_19", img: "resources/customisation/character_accessories/character_accessories19.png"},
{name: "accessory_20", img: "resources/customisation/character_accessories/character_accessories20.png"},
{name: "accessory_21", img: "resources/customisation/character_accessories/character_accessories21.png"},
{name: "accessory_22", img: "resources/customisation/character_accessories/character_accessories22.png"},
{name: "accessory_23", img: "resources/customisation/character_accessories/character_accessories23.png"},
{name: "accessory_24", img: "resources/customisation/character_accessories/character_accessories24.png"},
{name: "accessory_25", img: "resources/customisation/character_accessories/character_accessories25.png"},
{name: "accessory_26", img: "resources/customisation/character_accessories/character_accessories26.png"},
{name: "accessory_27", img: "resources/customisation/character_accessories/character_accessories27.png"},
{name: "accessory_28", img: "resources/customisation/character_accessories/character_accessories28.png"},
{name: "accessory_29", img: "resources/customisation/character_accessories/character_accessories29.png"},
{name: "accessory_30", img: "resources/customisation/character_accessories/character_accessories30.png"},
{name: "accessory_31", img: "resources/customisation/character_accessories/character_accessories31.png"},
{name: "accessory_32", img: "resources/customisation/character_accessories/character_accessories32.png"},
{name: "accessory_mate_bottle", img: "resources/customisation/character_accessories/mate_bottle1.png"},
{name: "accessory_mask", img: "resources/customisation/character_accessories/mask.png"}
];
export const LAYERS: Array<Array<BodyResourceDescriptionInterface>> = [
COLOR_RESOURCES,
EYES_RESOURCES,
HAIR_RESOURCES,
CLOTHES_RESOURCES,
HATS_RESOURCES,
ACCESSORIES_RESOURCES
];
export const loadAllLayers = (load: LoaderPlugin) => {
for (let j = 0; j < LAYERS.length; j++) {
for (let i = 0; i < LAYERS[j].length; i++) {
load.spritesheet(
LAYERS[j][i].name,
LAYERS[j][i].img,
{frameWidth: 32, frameHeight: 32}
)
}
}
}
export const loadCustomTexture = (load: LoaderPlugin, texture: CharacterTexture) => {
const name = 'customCharacterTexture'+texture.id;
load.spritesheet(
name,
texture.url,
{frameWidth: 32, frameHeight: 32}
);
}
export const OBJECTS: Array<PlayerResourceDescriptionInterface> = [
{name:'layout_modes', img:'resources/objects/layout_modes.png'},
{name:'teleportation', img:'resources/objects/teleportation.png'},
];
export const loadObject = (load: LoaderPlugin) => {
for (let j = 0; j < OBJECTS.length; j++) {
load.spritesheet(
OBJECTS[j].name,
OBJECTS[j].img,
{frameWidth: 32, frameHeight: 32}
)
}
}
export const loadPlayerCharacters = (load: LoaderPlugin) => {
PLAYER_RESOURCES.forEach((playerResource: PlayerResourceDescriptionInterface) => {
load.spritesheet(
playerResource.name,
playerResource.img,
{frameWidth: 32, frameHeight: 32}
);
});
}

View file

@ -1,9 +1,11 @@
import {PointInterface} from "../../Connexion/ConnexionModels";
import {BodyResourceDescriptionInterface} from "../Entity/body_character";
import type {PointInterface} from "../../Connexion/ConnexionModels";
import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
export interface AddPlayerInterface {
userId: number;
name: string;
characterLayers: BodyResourceDescriptionInterface[];
position: PointInterface;
visitCardUrl: string|null;
companion: string|null;
}

View file

@ -0,0 +1,8 @@
//this file contains all the depth indexes which will be used in our game
export const DEPTH_TILE_INDEX = 0;
//Note: Player characters use their y coordinate as their depth to simulate a perspective.
//See the Character class.
export const DEPTH_OVERLAY_INDEX = 10000;
export const DEPTH_INGAME_TEXT_INDEX = 100000;
export const DEPTH_UI_INDEX = 1000000;

View file

@ -0,0 +1,75 @@
import {ResizableScene} from "../Login/ResizableScene";
import GameObject = Phaser.GameObjects.GameObject;
import Events = Phaser.Scenes.Events;
import AnimationEvents = Phaser.Animations.Events;
import StructEvents = Phaser.Structs.Events;
import {SKIP_RENDER_OPTIMIZATIONS} from "../../Enum/EnvironmentVariable";
/**
* A scene that can track its dirty/pristine state.
*/
export abstract class DirtyScene extends ResizableScene {
private isAlreadyTracking: boolean = false;
protected dirty:boolean = true;
private objectListChanged:boolean = true;
private physicsEnabled: boolean = false;
/**
* Track all objects added to the scene and adds a callback each time an animation is added.
* Whenever an object is added, removed, or when an animation is played, the dirty state is set to true.
*
* Note: this does not work with animations from sprites inside containers.
*/
protected trackDirtyAnims(): void {
if (this.isAlreadyTracking || SKIP_RENDER_OPTIMIZATIONS) {
return;
}
this.isAlreadyTracking = true;
const trackAnimationFunction = this.trackAnimation.bind(this);
this.sys.updateList.on(StructEvents.PROCESS_QUEUE_ADD, (gameObject: GameObject) => {
this.objectListChanged = true;
gameObject.on(AnimationEvents.ANIMATION_UPDATE, trackAnimationFunction);
});
this.sys.updateList.on(StructEvents.PROCESS_QUEUE_REMOVE, (gameObject: GameObject) => {
this.objectListChanged = true;
gameObject.removeListener(AnimationEvents.ANIMATION_UPDATE, trackAnimationFunction);
});
this.events.on(Events.RENDER, () => {
this.objectListChanged = false;
});
this.physics.disableUpdate();
this.events.on(Events.POST_UPDATE, () => {
let objectMoving = false;
for (const body of this.physics.world.bodies.entries) {
if (body.velocity.x !== 0 || body.velocity.y !== 0) {
this.objectListChanged = true;
objectMoving = true;
if (!this.physicsEnabled) {
this.physics.enableUpdate();
this.physicsEnabled = true;
}
break;
}
}
if (!objectMoving && this.physicsEnabled) {
this.physics.disableUpdate();
this.physicsEnabled = false;
}
});
}
private trackAnimation(): void {
this.objectListChanged = true;
}
public isDirty(): boolean {
return this.dirty || this.objectListChanged;
}
public onResize(): void {
this.objectListChanged = true;
}
}

View file

@ -0,0 +1,73 @@
import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
import {emoteEventStream} from "../../Connexion/EmoteEventStream";
import type {GameScene} from "./GameScene";
import type {RadialMenuItem} from "../Components/RadialMenu";
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
import type {Subscription} from "rxjs";
interface RegisteredEmote extends BodyResourceDescriptionInterface {
name: string;
img: string;
}
export const emotes: {[key: string]: RegisteredEmote} = {
'emote-heart': {name: 'emote-heart', img: 'resources/emotes/heart-emote.png'},
'emote-clap': {name: 'emote-clap', img: 'resources/emotes/clap-emote.png'},
'emote-hand': {name: 'emote-hand', img: 'resources/emotes/hand-emote.png'},
'emote-thanks': {name: 'emote-thanks', img: 'resources/emotes/thanks-emote.png'},
'emote-thumb-up': {name: 'emote-thumb-up', img: 'resources/emotes/thumb-up-emote.png'},
'emote-thumb-down': {name: 'emote-thumb-down', img: 'resources/emotes/thumb-down-emote.png'},
};
export class EmoteManager {
private subscription: Subscription;
constructor(private scene: GameScene) {
this.subscription = emoteEventStream.stream.subscribe((event) => {
const actor = this.scene.MapPlayersByKey.get(event.userId);
if (actor) {
this.lazyLoadEmoteTexture(event.emoteName).then(emoteKey => {
actor.playEmote(emoteKey);
})
}
})
}
createLoadingPromise(loadPlugin: LoaderPlugin, playerResourceDescriptor: BodyResourceDescriptionInterface) {
return new Promise<string>((res) => {
if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
return res(playerResourceDescriptor.name);
}
loadPlugin.image(playerResourceDescriptor.name, playerResourceDescriptor.img);
loadPlugin.once('filecomplete-image-' + playerResourceDescriptor.name, () => res(playerResourceDescriptor.name));
});
}
lazyLoadEmoteTexture(textureKey: string): Promise<string> {
const emoteDescriptor = emotes[textureKey];
if (emoteDescriptor === undefined) {
throw 'Emote not found!';
}
const loadPromise = this.createLoadingPromise(this.scene.load, emoteDescriptor);
this.scene.load.start();
return loadPromise
}
getMenuImages(): Promise<RadialMenuItem[]> {
const promises = [];
for (const key in emotes) {
const promise = this.lazyLoadEmoteTexture(key).then((textureKey) => {
return {
image: textureKey,
name: textureKey,
}
});
promises.push(promise);
}
return Promise.all(promises);
}
destroy() {
this.subscription.unsubscribe();
}
}

View file

@ -0,0 +1,132 @@
import {SKIP_RENDER_OPTIMIZATIONS} from "../../Enum/EnvironmentVariable";
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
import {waScaleManager} from "../Services/WaScaleManager";
import {ResizableScene} from "../Login/ResizableScene";
const Events = Phaser.Core.Events;
/**
* A specialization of the main Phaser Game scene.
* It comes with an optimization to skip rendering.
*
* Beware, the "step" function might vary in future versions of Phaser.
*
* It also automatically calls "onResize" on any scenes extending ResizableScene.
*/
export class Game extends Phaser.Game {
private _isDirty = false;
constructor(GameConfig: Phaser.Types.Core.GameConfig) {
super(GameConfig);
this.scale.on(Phaser.Scale.Events.RESIZE, () => {
for (const scene of this.scene.getScenes(true)) {
if (scene instanceof ResizableScene) {
scene.onResize();
}
}
})
/*window.addEventListener('resize', (event) => {
// Let's trigger the onResize method of any active scene that is a ResizableScene
for (const scene of this.scene.getScenes(true)) {
if (scene instanceof ResizableScene) {
scene.onResize(event);
}
}
});*/
}
public step(time: number, delta: number)
{
// @ts-ignore
if (this.pendingDestroy)
{
// @ts-ignore
return this.runDestroy();
}
const eventEmitter = this.events;
// Global Managers like Input and Sound update in the prestep
eventEmitter.emit(Events.PRE_STEP, time, delta);
// This is mostly meant for user-land code and plugins
eventEmitter.emit(Events.STEP, time, delta);
// Update the Scene Manager and all active Scenes
this.scene.update(time, delta);
// Our final event before rendering starts
eventEmitter.emit(Events.POST_STEP, time, delta);
// This "if" is the changed introduced by the new "Game" class to avoid rendering unnecessarily.
if (SKIP_RENDER_OPTIMIZATIONS || this.isDirty()) {
const renderer = this.renderer;
// Run the Pre-render (clearing the canvas, setting background colors, etc)
renderer.preRender();
eventEmitter.emit(Events.PRE_RENDER, renderer, time, delta);
// The main render loop. Iterates all Scenes and all Cameras in those scenes, rendering to the renderer instance.
this.scene.render(renderer);
// The Post-Render call. Tidies up loose end, takes snapshots, etc.
renderer.postRender();
// The final event before the step repeats. Your last chance to do anything to the canvas before it all starts again.
eventEmitter.emit(Events.POST_RENDER, renderer, time, delta);
} else {
// @ts-ignore
this.scene.isProcessing = false;
}
}
private isDirty(): boolean {
if (this._isDirty) {
this._isDirty = false;
return true;
}
// Loop through the scenes in forward order
for (let i = 0; i < this.scene.scenes.length; i++)
{
const scene = this.scene.scenes[i];
const sys = scene.sys;
if (sys.settings.visible && sys.settings.status >= Phaser.Scenes.LOADING && sys.settings.status < Phaser.Scenes.SLEEPING)
{
// @ts-ignore
if(typeof scene.isDirty === 'function') {
// @ts-ignore
const isDirty = scene.isDirty() || scene.tweens.getAllTweens().length > 0;
if (isDirty) {
return true;
}
} else {
return true;
}
}
}
return false;
}
/**
* Marks the game as needing to be redrawn.
*/
public markDirty(): void {
this._isDirty = true;
}
}

View file

@ -1,11 +1,14 @@
import {GameScene} from "./GameScene";
import {connectionManager} from "../../Connexion/ConnectionManager";
import {Room} from "../../Connexion/Room";
import type {Room} from "../../Connexion/Room";
import {MenuScene, MenuSceneName} from "../Menu/MenuScene";
import {LoginSceneName} from "../Login/LoginScene";
import {SelectCharacterSceneName} from "../Login/SelectCharacterScene";
import {EnableCameraSceneName} from "../Login/EnableCameraScene";
import {localUserStore} from "../../Connexion/LocalUserStore";
import {get} from "svelte/store";
import {requestedCameraState, requestedMicrophoneState} from "../../Stores/MediaStore";
import {helpCameraSettingsVisibleStore} from "../../Stores/HelpCameraSettingsStore";
export interface HasMovedEvent {
direction: string;
@ -20,21 +23,23 @@ export interface HasMovedEvent {
export class GameManager {
private playerName: string|null;
private characterLayers: string[]|null;
private companion: string|null;
private startRoom!:Room;
currentGameSceneName: string|null = null;
constructor() {
this.playerName = localUserStore.getName();
this.characterLayers = localUserStore.getCharacterLayers();
this.companion = localUserStore.getCompanion();
}
public async init(scenePlugin: Phaser.Scenes.ScenePlugin): Promise<string> {
this.startRoom = await connectionManager.initGameConnexion();
await this.loadMap(this.startRoom, scenePlugin);
if (!this.playerName) {
return LoginSceneName;
} else if (!this.characterLayers) {
} else if (!this.characterLayers || !this.characterLayers.length) {
return SelectCharacterSceneName;
} else {
return EnableCameraSceneName;
@ -55,18 +60,29 @@ export class GameManager {
return this.playerName;
}
getCharacterLayers(): string[]|null {
getCharacterLayers(): string[] {
if (!this.characterLayers) {
throw 'characterLayers are not set';
}
return this.characterLayers;
}
setCompanion(companion: string|null): void {
this.companion = companion;
}
getCompanion(): string|null {
return this.companion;
}
public async loadMap(room: Room, scenePlugin: Phaser.Scenes.ScenePlugin): Promise<void> {
const roomID = room.id;
const mapUrl = await room.getMapUrl();
const mapDetail = await room.getMapDetail();
const gameIndex = scenePlugin.getIndex(roomID);
if(gameIndex === -1){
const game : Phaser.Scene = new GameScene(room, mapUrl);
const game : Phaser.Scene = new GameScene(room, mapDetail.mapUrl);
scenePlugin.add(roomID, game, false);
}
}
@ -75,8 +91,13 @@ export class GameManager {
console.log('starting '+ (this.currentGameSceneName || this.startRoom.id))
scenePlugin.start(this.currentGameSceneName || this.startRoom.id);
scenePlugin.launch(MenuSceneName);
if(!localUserStore.getHelpCameraSettingsShown() && (!get(requestedMicrophoneState) || !get(requestedCameraState))){
helpCameraSettingsVisibleStore.set(true);
localUserStore.setHelpCameraSettingsShown();
}
}
public gameSceneIsCreated(scene: GameScene) {
this.currentGameSceneName = scene.scene.key;
const menuScene: MenuScene = scene.scene.get(MenuSceneName) as MenuScene;
@ -110,7 +131,7 @@ export class GameManager {
scene.scene.run(fallbackSceneName)
}
}
public getCurrentGameScene(scene: Phaser.Scene): GameScene {
if (this.currentGameSceneName === null) throw 'No current scene id set!';
return scene.scene.get(this.currentGameSceneName) as GameScene

View file

@ -1,4 +1,5 @@
import {ITiledMap} from "../Map/ITiledMap";
import type {ITiledMap, ITiledMapLayer} from "../Map/ITiledMap";
import {LayersIterator} from "../Map/LayersIterator";
export type PropertyChangeCallback = (newValue: string | number | boolean | undefined, oldValue: string | number | boolean | undefined, allProps: Map<string, string | boolean | number>) => void;
@ -10,8 +11,10 @@ export class GameMap {
private key: number|undefined;
private lastProperties = new Map<string, string|boolean|number>();
private callbacks = new Map<string, Array<PropertyChangeCallback>>();
public readonly layersIterator: LayersIterator;
public constructor(private map: ITiledMap) {
this.layersIterator = new LayersIterator(map);
}
/**
@ -29,6 +32,7 @@ export class GameMap {
const newProps = this.getProperties(key);
const oldProps = this.lastProperties;
this.lastProperties = newProps;
// Let's compare the 2 maps:
// First new properties vs oldProperties
@ -45,14 +49,16 @@ export class GameMap {
this.trigger(oldPropName, oldPropValue, undefined, newProps);
}
}
}
this.lastProperties = newProps;
public getCurrentProperties(): Map<string, string|boolean|number> {
return this.lastProperties;
}
private getProperties(key: number): Map<string, string|boolean|number> {
const properties = new Map<string, string|boolean|number>();
for (const layer of this.map.layers) {
for (const layer of this.layersIterator) {
if (layer.type !== 'tilelayer') {
continue;
}

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
import {HasMovedEvent} from "./GameManager";
import type {HasMovedEvent} from "./GameManager";
import {MAX_EXTRAPOLATION_TIME} from "../../Enum/EnvironmentVariable";
import {PositionInterface} from "../../Connexion/ConnexionModels";
import type {PositionInterface} from "../../Connexion/ConnexionModels";
export class PlayerMovement {
public constructor(private startPosition: PositionInterface, private startTick: number, private endPosition: HasMovedEvent, private endTick: number) {

View file

@ -2,8 +2,8 @@
* This class is in charge of computing the position of all players.
* Player movement is delayed by 200ms so position depends on ticks.
*/
import {PlayerMovement} from "./PlayerMovement";
import {HasMovedEvent} from "./GameManager";
import type {PlayerMovement} from "./PlayerMovement";
import type {HasMovedEvent} from "./GameManager";
export class PlayersPositionInterpolator {
playerMovements: Map<number, PlayerMovement> = new Map<number, PlayerMovement>();

View file

@ -0,0 +1,39 @@
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
import BaseSoundManager = Phaser.Sound.BaseSoundManager;
import BaseSound = Phaser.Sound.BaseSound;
import SoundConfig = Phaser.Types.Sound.SoundConfig;
class SoundManager {
private soundPromises : Map<string,Promise<BaseSound>> = new Map<string, Promise<Phaser.Sound.BaseSound>>();
public loadSound (loadPlugin: LoaderPlugin, soundManager : BaseSoundManager, soundUrl: string) : Promise<BaseSound> {
let soundPromise = this.soundPromises.get(soundUrl);
if (soundPromise !== undefined) {
return soundPromise;
}
soundPromise = new Promise<BaseSound>((res) => {
const sound = soundManager.get(soundUrl);
if (sound !== null) {
return res(sound);
}
loadPlugin.audio(soundUrl, soundUrl);
loadPlugin.once('filecomplete-audio-' + soundUrl, () => {
res(soundManager.add(soundUrl));
});
loadPlugin.start();
});
this.soundPromises.set(soundUrl,soundPromise);
return soundPromise;
}
public async playSound(loadPlugin: LoaderPlugin, soundManager : BaseSoundManager, soundUrl: string, config: SoundConfig|undefined) : Promise<void> {
const sound = await this.loadSound(loadPlugin,soundManager,soundUrl);
if (config === undefined) sound.play();
else sound.play(config);
}
public stopSound(soundManager : BaseSoundManager,soundUrl : string){
soundManager.get(soundUrl).stop();
}
}
export const soundManager = new SoundManager();

View file

@ -4,7 +4,7 @@
*/
import Sprite = Phaser.GameObjects.Sprite;
import {OutlinePipeline} from "../Shaders/OutlinePipeline";
import {GameScene} from "../Game/GameScene";
import type {GameScene} from "../Game/GameScene";
type EventCallback = (state: unknown, parameters: unknown) => void;
@ -42,8 +42,11 @@ export class ActionableItem {
return;
}
this.isSelectable = true;
this.sprite.setPipeline(OutlinePipeline.KEY);
this.sprite.pipeline.set2f('uTextureSize', this.sprite.texture.getSourceImage().width, this.sprite.texture.getSourceImage().height);
if (this.sprite.pipeline) {
// Commented out to try to fix MacOS issue
/*this.sprite.setPipeline(OutlinePipeline.KEY);
this.sprite.pipeline.set2f('uTextureSize', this.sprite.texture.getSourceImage().width, this.sprite.texture.getSourceImage().height);*/
}
}
/**
@ -54,7 +57,8 @@ export class ActionableItem {
return;
}
this.isSelectable = false;
this.sprite.resetPipeline();
// Commented out to try to fix MacOS issue
//this.sprite.resetPipeline();
}
/**

View file

@ -1,9 +1,9 @@
import * as Phaser from 'phaser';
import {Scene} from "phaser";
import Sprite = Phaser.GameObjects.Sprite;
import {ITiledMapObject} from "../../Map/ITiledMap";
import {ItemFactoryInterface} from "../ItemFactoryInterface";
import {GameScene} from "../../Game/GameScene";
import type {ITiledMapObject} from "../../Map/ITiledMap";
import type {ItemFactoryInterface} from "../ItemFactoryInterface";
import type {GameScene} from "../../Game/GameScene";
import {ActionableItem} from "../ActionableItem";
import * as tg from "generic-type-guard";

View file

@ -1,7 +1,7 @@
import type {GameScene} from "../Game/GameScene";
import type {ITiledMapObject} from "../Map/ITiledMap";
import type {ActionableItem} from "./ActionableItem";
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
import {GameScene} from "../Game/GameScene";
import {ITiledMapObject} from "../Map/ITiledMap";
import {ActionableItem} from "./ActionableItem";
export interface ItemFactoryInterface {
preload: (loader: LoaderPlugin) => void;

View file

@ -0,0 +1,41 @@
import {ResizableScene} from "./ResizableScene";
import {localUserStore} from "../../Connexion/LocalUserStore";
import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
import {loadCustomTexture} from "../Entity/PlayerTexturesLoadingManager";
import type {CharacterTexture} from "../../Connexion/LocalUser";
export abstract class AbstractCharacterScene extends ResizableScene {
loadCustomSceneSelectCharacters() : Promise<BodyResourceDescriptionInterface[]> {
const textures = this.getTextures();
const promises : Promise<BodyResourceDescriptionInterface>[] = [];
if (textures) {
for (const texture of textures) {
if (texture.level === -1) {
continue;
}
promises.push(loadCustomTexture(this.load, texture));
}
}
return Promise.all(promises)
}
loadSelectSceneCharacters() : Promise<BodyResourceDescriptionInterface[]> {
const textures = this.getTextures();
const promises: Promise<BodyResourceDescriptionInterface>[] = [];
if (textures) {
for (const texture of textures) {
if (texture.level !== -1) {
continue;
}
promises.push(loadCustomTexture(this.load, texture));
}
}
return Promise.all(promises)
}
private getTextures() : CharacterTexture[]|undefined{
const localUser = localUserStore.getLocalUser();
return localUser?.textures;
}
}

View file

@ -1,46 +1,34 @@
import {EnableCameraSceneName} from "./EnableCameraScene";
import {TextField} from "../Components/TextField";
import Image = Phaser.GameObjects.Image;
import Rectangle = Phaser.GameObjects.Rectangle;
import {BodyResourceDescriptionInterface, LAYERS, loadAllLayers, loadCustomTexture} from "../Entity/body_character";
import {loadAllLayers} from "../Entity/PlayerTexturesLoadingManager";
import Sprite = Phaser.GameObjects.Sprite;
import Container = Phaser.GameObjects.Container;
import {gameManager} from "../Game/GameManager";
import {ResizableScene} from "./ResizableScene";
import {localUserStore} from "../../Connexion/LocalUserStore";
import {PlayerResourceDescriptionInterface} from "../Entity/Character";
import {SelectCharacterSceneName} from "./SelectCharacterScene";
import {LoginSceneName} from "./LoginScene";
import {addLoader} from "../Components/Loader";
import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
import {AbstractCharacterScene} from "./AbstractCharacterScene";
import {areCharacterLayersValid} from "../../Connexion/LocalUser";
import { SelectCharacterSceneName } from "./SelectCharacterScene";
import {customCharacterSceneVisibleStore} from "../../Stores/CustomCharacterStore";
import {waScaleManager} from "../Services/WaScaleManager";
import {isMobile} from "../../Enum/EnvironmentVariable";
import {CustomizedCharacter} from "../Entity/CustomizedCharacter";
export const CustomizeSceneName = "CustomizeScene";
enum CustomizeTextures{
icon = "icon",
arrowRight = "arrow_right",
mainFont = "main_font",
arrowUp = "arrow_up",
}
export class CustomizeScene extends ResizableScene {
private textField!: TextField;
private enterField!: TextField;
private arrowRight!: Image;
private arrowLeft!: Image;
private arrowDown!: Image;
private arrowUp!: Image;
export class CustomizeScene extends AbstractCharacterScene {
private Rectangle!: Rectangle;
private logo!: Image;
private selectedLayers: number[] = [0];
private containersRow: Container[][] = [];
private activeRow:number = 0;
private containersRow: CustomizedCharacter[][] = [];
public activeRow:number = 0;
private layers: BodyResourceDescriptionInterface[][] = [];
protected lazyloadingAttempt = true; //permit to update texture loaded after renderer
private moveHorizontally: number = 0;
private moveVertically: number = 0;
constructor() {
super({
key: CustomizeSceneName
@ -48,60 +36,40 @@ export class CustomizeScene extends ResizableScene {
}
preload() {
this.load.image(CustomizeTextures.arrowRight, "resources/objects/arrow_right.png");
this.load.image(CustomizeTextures.icon, "resources/logos/tcm_full.png");
this.load.image(CustomizeTextures.arrowUp, "resources/objects/arrow_up.png");
this.load.bitmapFont(CustomizeTextures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
//load all the png files
loadAllLayers(this.load);
this.loadCustomSceneSelectCharacters().then((bodyResourceDescriptions) => {
bodyResourceDescriptions.forEach((bodyResourceDescription) => {
if(bodyResourceDescription.level == undefined || bodyResourceDescription.level < 0 || bodyResourceDescription.level > 5 ){
throw 'Texture level is null';
}
this.layers[bodyResourceDescription.level].unshift(bodyResourceDescription);
});
this.lazyloadingAttempt = true;
});
// load custom layers
this.layers = LAYERS;
this.layers = loadAllLayers(this.load);
this.lazyloadingAttempt = false;
const localUser = localUserStore.getLocalUser();
const textures = localUser?.textures;
if (textures) {
for (const texture of textures) {
loadCustomTexture(this.load, texture);
const name = 'customCharacterTexture'+texture.id;
this.layers[texture.level].unshift({
name,
img: texture.url
});
}
}
//this function must stay at the end of preload function
addLoader(this);
}
create() {
this.textField = new TextField(this, this.game.renderer.width / 2, 30, 'Customize your own Avatar!');
customCharacterSceneVisibleStore.set(true);
this.events.addListener('wake', () => {
waScaleManager.saveZoom();
waScaleManager.zoomModifier = isMobile() ? 3 : 1;
customCharacterSceneVisibleStore.set(true);
});
this.enterField = new TextField(this, this.game.renderer.width / 2, 40, 'you can start the game by pressing SPACE..');
waScaleManager.saveZoom();
waScaleManager.zoomModifier = isMobile() ? 3 : 1;
this.logo = new Image(this, this.game.renderer.width - 30, this.game.renderer.height - 20, CustomizeTextures.icon);
this.add.existing(this.logo);
this.arrowRight = new Image(this, this.game.renderer.width*0.9, this.game.renderer.height/2, CustomizeTextures.arrowRight);
this.add.existing(this.arrowRight);
this.arrowLeft = new Image(this, this.game.renderer.width/9, this.game.renderer.height/2, CustomizeTextures.arrowRight);
this.arrowLeft.flipX = true;
this.add.existing(this.arrowLeft);
this.Rectangle = this.add.rectangle(this.cameras.main.worldView.x + this.cameras.main.width / 2, this.cameras.main.worldView.y + this.cameras.main.height / 2, 32, 33)
this.Rectangle = this.add.rectangle(this.cameras.main.worldView.x + this.cameras.main.width / 2, this.cameras.main.worldView.y + this.cameras.main.height / 3, 32, 33)
this.Rectangle.setStrokeStyle(2, 0xFFFFFF);
this.add.existing(this.Rectangle);
this.arrowDown = new Image(this, this.game.renderer.width - 30, 100, CustomizeTextures.arrowUp);
this.arrowDown.flipY = true;
this.add.existing(this.arrowDown);
this.arrowUp = new Image(this, this.game.renderer.width - 30, 60, CustomizeTextures.arrowUp);
this.add.existing(this.arrowUp);
this.createCustomizeLayer(0, 0, 0);
this.createCustomizeLayer(0, 0, 1);
this.createCustomizeLayer(0, 0, 2);
@ -111,25 +79,19 @@ export class CustomizeScene extends ResizableScene {
this.moveLayers();
this.input.keyboard.on('keyup-ENTER', () => {
const layers: string[] = [];
let i = 0;
for (const layerItem of this.selectedLayers) {
if (layerItem !== undefined) {
layers.push(this.layers[i][layerItem].name);
}
i++;
}
gameManager.setCharacterLayers(layers);
this.scene.sleep(CustomizeSceneName);
gameManager.tryResumingGame(this, EnableCameraSceneName);
this.nextSceneToCamera();
});
this.input.keyboard.on('keyup-BACKSPACE', () => {
this.backToPreviousScene();
});
this.input.keyboard.on('keyup-RIGHT', () => this.moveCursorHorizontally(1));
this.input.keyboard.on('keyup-LEFT', () => this.moveCursorHorizontally(-1));
this.input.keyboard.on('keyup-DOWN', () => this.moveCursorVertically(1));
this.input.keyboard.on('keyup-UP', () => this.moveCursorVertically(-1));
// Note: the key bindings are not directly put on the moveCursorVertically or moveCursorHorizontally methods
// because if 2 such events are fired close to one another, it makes the whole application crawl to a halt (for a reason I cannot
// explain, the list of sprites managed by the update list become immense
this.input.keyboard.on('keyup-RIGHT', () => this.moveHorizontally = 1);
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) {
@ -138,9 +100,19 @@ export class CustomizeScene extends ResizableScene {
this.moveLayers();
this.updateSelectedLayer();
}
this.onResize();
}
private moveCursorHorizontally(index: number): void {
public moveCursorHorizontally(index: number): void {
this.moveHorizontally = index;
}
public moveCursorVertically(index: number): void {
this.moveVertically = index;
}
private doMoveCursorHorizontally(index: number): void {
this.selectedLayers[this.activeRow] += index;
if (this.selectedLayers[this.activeRow] < 0) {
this.selectedLayers[this.activeRow] = 0
@ -152,7 +124,8 @@ export class CustomizeScene extends ResizableScene {
this.saveInLocalStorage();
}
private moveCursorVertically(index:number): void {
private doMoveCursorVertically(index:number): void {
this.activeRow += index;
if (this.activeRow < 0) {
this.activeRow = 0
@ -167,11 +140,6 @@ export class CustomizeScene extends ResizableScene {
localUserStore.setCustomCursorPosition(this.activeRow, this.selectedLayers);
}
update(time: number, delta: number): void {
super.update(time, delta);
this.enterField.setVisible(!!(Math.floor(time / 500) % 2));
}
/**
* @param x, the layer's vertical position
* @param y, the layer's horizontal position
@ -205,20 +173,20 @@ export class CustomizeScene extends ResizableScene {
* @param selectedItem, The number of the item select (0 for black body...)
*/
private generateCharacter(x: number, y: number, layerNumber: number, selectedItem: number) {
return new Container(this, x, y,this.getContainerChildren(layerNumber,selectedItem));
return new CustomizedCharacter(this, x, y, this.getContainerChildren(layerNumber,selectedItem));
}
private getContainerChildren(layerNumber: number, selectedItem: number): Array<Sprite> {
const children: Array<Sprite> = new Array<Sprite>();
private getContainerChildren(layerNumber: number, selectedItem: number): Array<string> {
const children: Array<string> = new Array<string>();
for (let j = 0; j <= layerNumber; j++) {
if (j === layerNumber) {
children.push(this.generateLayers(0, 0, this.layers[j][selectedItem].name));
children.push(this.layers[j][selectedItem].name);
} else {
const layer = this.selectedLayers[j];
if (layer === undefined) {
continue;
}
children.push(this.generateLayers(0, 0, this.layers[j][layer].name));
children.push(this.layers[j][layer].name);
}
}
return children;
@ -229,7 +197,7 @@ export class CustomizeScene extends ResizableScene {
*/
private moveLayers(): void {
const screenCenterX = this.cameras.main.worldView.x + this.cameras.main.width / 2;
const screenCenterY = this.cameras.main.worldView.y + this.cameras.main.height / 2;
const screenCenterY = this.cameras.main.worldView.y + this.cameras.main.height / 3;
const screenWidth = this.game.renderer.width;
const screenHeight = this.game.renderer.height;
for (let i = 0; i < this.containersRow.length; i++) {
@ -255,40 +223,71 @@ export class CustomizeScene extends ResizableScene {
* @return a new sprite
*/
private generateLayers(x: number, y: number, name: string): Sprite {
return new Sprite(this, x, y, name);
//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++){
const children = this.getContainerChildren(i, j);
this.containersRow[i][j].removeAll(true);
this.containersRow[i][j].add(children);
const children = this.getContainerChildren(i, j);
this.containersRow[i][j].updateSprites(children);
}
}
}
}
update(time: number, delta: number): void {
if(this.lazyloadingAttempt){
this.moveLayers();
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 / 2;
this.textField.x = this.game.renderer.width/2;
this.logo.x = this.game.renderer.width - 30;
this.logo.y = this.game.renderer.height - 20;
this.arrowUp.x = this.game.renderer.width - 30;
this.arrowUp.y = 60;
this.arrowDown.x = this.game.renderer.width - 30;
this.arrowDown.y = 100;
this.arrowLeft.x = this.game.renderer.width/9;
this.arrowLeft.y = this.game.renderer.height/2;
this.arrowRight.x = this.game.renderer.width*0.9;
this.arrowRight.y = this.game.renderer.height/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].name);
}
i++;
}
if (!areCharacterLayersValid(layers)) {
return;
}
gameManager.setCharacterLayers(layers);
this.scene.sleep(CustomizeSceneName);
waScaleManager.restoreZoom();
this.events.removeListener('wake');
gameManager.tryResumingGame(this, EnableCameraSceneName);
customCharacterSceneVisibleStore.set(false);
}
public backToPreviousScene(){
this.scene.sleep(CustomizeSceneName);
waScaleManager.restoreZoom();
this.scene.run(SelectCharacterSceneName);
customCharacterSceneVisibleStore.set(false);
}
}

View file

@ -1,281 +1,50 @@
import {gameManager} from "../Game/GameManager";
import {TextField} from "../Components/TextField";
import Image = Phaser.GameObjects.Image;
import {GameSceneInitInterface} from "../Game/GameScene";
import {StartMapInterface} from "../../Connexion/ConnexionModels";
import {mediaManager} from "../../WebRtc/MediaManager";
import {RESOLUTION} from "../../Enum/EnvironmentVariable";
import {SoundMeter} from "../Components/SoundMeter";
import {SoundMeterSprite} from "../Components/SoundMeterSprite";
import {HtmlUtils} from "../../WebRtc/HtmlUtils";
import {touchScreenManager} from "../../Touch/TouchScreenManager";
import {PinchManager} from "../UserInput/PinchManager";
import Zone = Phaser.GameObjects.Zone;
import { MenuScene } from "../Menu/MenuScene";
import {ResizableScene} from "./ResizableScene";
import {
enableCameraSceneVisibilityStore,
} from "../../Stores/MediaStore";
export const EnableCameraSceneName = "EnableCameraScene";
enum LoginTextures {
playButton = "play_button",
icon = "icon",
mainFont = "main_font",
arrowRight = "arrow_right",
arrowUp = "arrow_up"
}
export class EnableCameraScene extends Phaser.Scene {
private textField!: TextField;
private pressReturnField!: TextField;
private cameraNameField!: TextField;
private logo!: Image;
private arrowLeft!: Image;
private arrowRight!: Image;
private arrowDown!: Image;
private arrowUp!: Image;
private microphonesList: MediaDeviceInfo[] = new Array<MediaDeviceInfo>();
private camerasList: MediaDeviceInfo[] = new Array<MediaDeviceInfo>();
private cameraSelected: number = 0;
private microphoneSelected: number = 0;
private soundMeter: SoundMeter;
private soundMeterSprite!: SoundMeterSprite;
private microphoneNameField!: TextField;
private repositionCallback!: (this: Window, ev: UIEvent) => void;
export class EnableCameraScene extends ResizableScene {
constructor() {
super({
key: EnableCameraSceneName
});
this.soundMeter = new SoundMeter();
}
preload() {
this.load.image(LoginTextures.playButton, "resources/objects/play_button.png");
this.load.image(LoginTextures.icon, "resources/logos/tcm_full.png");
this.load.image(LoginTextures.arrowRight, "resources/objects/arrow_right.png");
this.load.image(LoginTextures.arrowUp, "resources/objects/arrow_up.png");
// Note: arcade.png from the Phaser 3 examples at: https://github.com/photonstorm/phaser3-examples/tree/master/public/assets/fonts/bitmap
this.load.bitmapFont(LoginTextures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
}
create() {
this.textField = new TextField(this, this.game.renderer.width / 2, 20, 'Turn on your camera and microphone');
this.pressReturnField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height - 30, 'Press enter to start');
this.cameraNameField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height - 60, '');
this.microphoneNameField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height - 40, '');
this.arrowRight = new Image(this, 0, 0, LoginTextures.arrowRight);
this.arrowRight.setOrigin(0.5, 0.5);
this.arrowRight.setVisible(false);
this.arrowRight.setInteractive().on('pointerdown', this.nextCam.bind(this));
this.add.existing(this.arrowRight);
this.arrowLeft = new Image(this, 0, 0, LoginTextures.arrowRight);
this.arrowLeft.setOrigin(0.5, 0.5);
this.arrowLeft.setVisible(false);
this.arrowLeft.flipX = true;
this.arrowLeft.setInteractive().on('pointerdown', this.previousCam.bind(this));
this.add.existing(this.arrowLeft);
this.arrowUp = new Image(this, 0, 0, LoginTextures.arrowUp);
this.arrowUp.setOrigin(0.5, 0.5);
this.arrowUp.setVisible(false);
this.arrowUp.setInteractive().on('pointerdown', this.previousMic.bind(this));
this.add.existing(this.arrowUp);
this.arrowDown = new Image(this, 0, 0, LoginTextures.arrowUp);
this.arrowDown.setOrigin(0.5, 0.5);
this.arrowDown.setVisible(false);
this.arrowDown.flipY = true;
this.arrowDown.setInteractive().on('pointerdown', this.nextMic.bind(this));
this.add.existing(this.arrowDown);
this.logo = new Image(this, this.game.renderer.width - 30, this.game.renderer.height - 20, LoginTextures.icon);
this.add.existing(this.logo);
this.input.keyboard.on('keyup-ENTER', () => {
this.login();
});
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('webRtcSetup').classList.add('active');
const mediaPromise = mediaManager.getCamera();
mediaPromise.then(this.getDevices.bind(this));
mediaPromise.then(this.setupStream.bind(this));
this.input.keyboard.on('keydown-RIGHT', this.nextCam.bind(this));
this.input.keyboard.on('keydown-LEFT', this.previousCam.bind(this));
this.input.keyboard.on('keydown-DOWN', this.nextMic.bind(this));
this.input.keyboard.on('keydown-UP', this.previousMic.bind(this));
this.soundMeterSprite = new SoundMeterSprite(this, 50, 50);
this.soundMeterSprite.setVisible(false);
this.add.existing(this.soundMeterSprite);
this.repositionCallback = this.reposition.bind(this);
window.addEventListener('resize', this.repositionCallback);
enableCameraSceneVisibilityStore.showEnableCameraScene();
}
private previousCam(): void {
if (this.cameraSelected === 0 || this.camerasList.length === 0) {
return;
}
this.cameraSelected--;
mediaManager.setCamera(this.camerasList[this.cameraSelected].deviceId).then(this.setupStream.bind(this));
}
private nextCam(): void {
if (this.cameraSelected === this.camerasList.length - 1 || this.camerasList.length === 0) {
return;
}
this.cameraSelected++;
// TODO: the change of camera should be OBSERVED (reactive)
mediaManager.setCamera(this.camerasList[this.cameraSelected].deviceId).then(this.setupStream.bind(this));
}
private previousMic(): void {
if (this.microphoneSelected === 0 || this.microphonesList.length === 0) {
return;
}
this.microphoneSelected--;
mediaManager.setMicrophone(this.microphonesList[this.microphoneSelected].deviceId).then(this.setupStream.bind(this));
}
private nextMic(): void {
if (this.microphoneSelected === this.microphonesList.length - 1 || this.microphonesList.length === 0) {
return;
}
this.microphoneSelected++;
// TODO: the change of camera should be OBSERVED (reactive)
mediaManager.setMicrophone(this.microphonesList[this.microphoneSelected].deviceId).then(this.setupStream.bind(this));
}
/**
* Function called each time a camera is changed
*/
private setupStream(stream: MediaStream): void {
const img = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('webRtcSetupNoVideo');
img.style.display = 'none';
const div = HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('myCamVideoSetup');
div.srcObject = stream;
this.soundMeter.connectToSource(stream, new window.AudioContext());
this.soundMeterSprite.setVisible(true);
this.updateWebCamName();
}
private updateWebCamName(): void {
if (this.camerasList.length > 1) {
const div = HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('myCamVideoSetup');
let label = this.camerasList[this.cameraSelected].label;
// remove text in parenthesis
label = label.replace(/\([^()]*\)/g, '').trim();
// remove accents
label = label.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
this.cameraNameField.text = label;
if (this.cameraSelected < this.camerasList.length - 1) {
this.arrowRight.setVisible(true);
} else {
this.arrowRight.setVisible(false);
}
if (this.cameraSelected > 0) {
this.arrowLeft.setVisible(true);
} else {
this.arrowLeft.setVisible(false);
}
}
if (this.microphonesList.length > 1) {
let label = this.microphonesList[this.microphoneSelected].label;
// remove text in parenthesis
label = label.replace(/\([^()]*\)/g, '').trim();
// remove accents
label = label.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
this.microphoneNameField.text = label;
if (this.microphoneSelected < this.microphonesList.length - 1) {
this.arrowDown.setVisible(true);
} else {
this.arrowDown.setVisible(false);
}
if (this.microphoneSelected > 0) {
this.arrowUp.setVisible(true);
} else {
this.arrowUp.setVisible(false);
}
}
this.reposition();
}
private reposition(): void {
let div = HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('myCamVideoSetup');
let bounds = div.getBoundingClientRect();
if (!div.srcObject) {
div = HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('webRtcSetup');
bounds = div.getBoundingClientRect();
}
this.textField.x = this.game.renderer.width / 2;
this.cameraNameField.x = this.game.renderer.width / 2;
this.microphoneNameField.x = this.game.renderer.width / 2;
this.pressReturnField.x = this.game.renderer.width / 2;
this.pressReturnField.x = this.game.renderer.width / 2;
this.cameraNameField.y = bounds.top / RESOLUTION - 8;
this.soundMeterSprite.x = this.game.renderer.width / 2 - this.soundMeterSprite.getWidth() / 2;
this.soundMeterSprite.y = bounds.bottom / RESOLUTION + 16;
this.microphoneNameField.y = this.soundMeterSprite.y + 22;
this.arrowRight.x = bounds.right / RESOLUTION + 16;
this.arrowRight.y = (bounds.top + bounds.height / 2) / RESOLUTION;
this.arrowLeft.x = bounds.left / RESOLUTION - 16;
this.arrowLeft.y = (bounds.top + bounds.height / 2) / RESOLUTION;
this.arrowDown.x = this.microphoneNameField.x + this.microphoneNameField.width / 2 + 16;
this.arrowDown.y = this.microphoneNameField.y;
this.arrowUp.x = this.microphoneNameField.x - this.microphoneNameField.width / 2 - 16;
this.arrowUp.y = this.microphoneNameField.y;
this.pressReturnField.y = Math.max(this.game.renderer.height - 30, this.microphoneNameField.y + 20);
this.logo.x = this.game.renderer.width - 30;
this.logo.y = Math.max(this.game.renderer.height - 20, this.microphoneNameField.y + 30);
public onResize(): void {
}
update(time: number, delta: number): void {
this.pressReturnField.setVisible(!!(Math.floor(time / 500) % 2));
this.soundMeterSprite.setVolume(this.soundMeter.getVolume());
}
private login(): void {
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('webRtcSetup').style.display = 'none';
this.soundMeter.stop();
window.removeEventListener('resize', this.repositionCallback);
public login(): void {
enableCameraSceneVisibilityStore.hideEnableCameraScene();
mediaManager.stopCamera();
mediaManager.stopMicrophone();
this.scene.sleep(EnableCameraSceneName)
this.scene.sleep(EnableCameraSceneName);
gameManager.goToStartingMap(this.scene);
}
private async getDevices() {
const mediaDeviceInfos = await navigator.mediaDevices.enumerateDevices();
for (const mediaDeviceInfo of mediaDeviceInfos) {
if (mediaDeviceInfo.kind === 'audioinput') {
this.microphonesList.push(mediaDeviceInfo);
} else if (mediaDeviceInfo.kind === 'videoinput') {
this.camerasList.push(mediaDeviceInfo);
}
}
this.updateWebCamName();
}
}

View file

@ -1,7 +1,8 @@
import {gameManager} from "../Game/GameManager";
import {Scene} from "phaser";
import {LoginSceneName} from "./LoginScene";
import {FourOFourSceneName} from "../Reconnecting/FourOFourScene";
import {ErrorScene} from "../Reconnecting/ErrorScene";
import {WAError} from "../Reconnecting/WAError";
import {waScaleManager} from "../Services/WaScaleManager";
export const EntrySceneName = "EntryScene";
@ -17,13 +18,21 @@ export class EntryScene extends Scene {
}
create() {
gameManager.init(this.scene).then((nextSceneName) => {
// Let's rescale before starting the game
// We can do it at this stage.
waScaleManager.applyNewSize();
this.scene.start(nextSceneName);
}).catch((err) => {
console.error(err)
this.scene.start(FourOFourSceneName, {
url: window.location.pathname.toString()
});
if (err.response && err.response.status == 404) {
ErrorScene.showError(new WAError(
'Access link incorrect',
'Could not find map. Please check your access link.',
'If you want more information, you may contact administrator or contact us at: workadventure@thecodingmachine.com'), this.scene);
} else {
ErrorScene.showError(err, this.scene);
}
});
}
}

View file

@ -1,26 +1,12 @@
import {gameManager} from "../Game/GameManager";
import {TextField} from "../Components/TextField";
import {TextInput} from "../Components/TextInput";
import Image = Phaser.GameObjects.Image;
import {PLAYER_RESOURCES, PlayerResourceDescriptionInterface} from "../Entity/Character";
import {cypressAsserter} from "../../Cypress/CypressAsserter";
import {SelectCharacterSceneName} from "./SelectCharacterScene";
import {ResizableScene} from "./ResizableScene";
import {EnableCameraSceneName} from "./EnableCameraScene";
import {loginSceneVisibleStore} from "../../Stores/LoginSceneStore";
//todo: put this constants in a dedicated file
export const LoginSceneName = "LoginScene";
enum LoginTextures {
icon = "icon",
mainFont = "main_font"
}
export class LoginScene extends ResizableScene {
private nameInput!: TextInput;
private textField!: TextField;
private infoTextField!: TextField;
private pressReturnField!: TextField;
private logo!: Image;
private name: string = '';
constructor() {
@ -31,71 +17,25 @@ export class LoginScene extends ResizableScene {
}
preload() {
cypressAsserter.preloadStarted();
//this.load.image(LoginTextures.playButton, "resources/objects/play_button.png");
this.load.image(LoginTextures.icon, "resources/logos/tcm_full.png");
// Note: arcade.png from the Phaser 3 examples at: https://github.com/photonstorm/phaser3-examples/tree/master/public/assets/fonts/bitmap
this.load.bitmapFont(LoginTextures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
cypressAsserter.preloadFinished();
//add player png
PLAYER_RESOURCES.forEach((playerResource: PlayerResourceDescriptionInterface) => {
this.load.spritesheet(
playerResource.name,
playerResource.img,
{frameWidth: 32, frameHeight: 32}
);
});
}
create() {
cypressAsserter.initStarted();
this.textField = new TextField(this, this.game.renderer.width / 2, 50, 'Enter your name:');
this.nameInput = new TextInput(this, this.game.renderer.width / 2, 70, 8, this.name,(text: string) => {
this.name = text;
});
this.pressReturnField = new TextField(this, this.game.renderer.width / 2, 130, 'Press enter to start');
this.logo = new Image(this, this.game.renderer.width - 30, this.game.renderer.height - 20, LoginTextures.icon);
this.add.existing(this.logo);
const infoText = "Commands: \n - Arrows or Z,Q,S,D to move\n - SHIFT to run";
this.infoTextField = new TextField(this, 10, this.game.renderer.height - 35, infoText, false);
this.input.keyboard.on('keyup-ENTER', () => {
if (this.name === '') {
return
}
this.login(this.name);
});
cypressAsserter.initFinished();
loginSceneVisibleStore.set(true);
}
update(time: number, delta: number): void {
if (this.name == '') {
this.pressReturnField?.setVisible(false);
} else {
this.pressReturnField?.setVisible(!!(Math.floor(time / 500) % 2));
}
}
private login(name: string): void {
public login(name: string): void {
name = name.trim();
gameManager.setPlayerName(name);
this.scene.stop(LoginSceneName)
gameManager.tryResumingGame(this, SelectCharacterSceneName);
this.scene.remove(LoginSceneName)
this.scene.remove(LoginSceneName);
loginSceneVisibleStore.set(false);
}
public onResize(ev: UIEvent): void {
this.textField.x = this.game.renderer.width / 2;
this.nameInput.setX(this.game.renderer.width / 2 - 64);
this.pressReturnField.x = this.game.renderer.width / 2;
this.logo.x = this.game.renderer.width - 30;
this.logo.y = this.game.renderer.height - 20;
this.infoTextField.y = this.game.renderer.height - 35;
update(time: number, delta: number): void {
}
public onResize(): void {
}
}

View file

@ -1,5 +1,23 @@
import {Scene} from "phaser";
import DOMElement = Phaser.GameObjects.DOMElement;
export abstract class ResizableScene extends Scene {
public abstract onResize(ev: UIEvent): void;
public abstract onResize(): void;
/**
* Centers the DOM element on the X axis.
*
* @param object
* @param defaultWidth The width of the DOM element. We try to compute it but it may not be available if called from "create".
*/
public centerXDomElement(object: DOMElement, defaultWidth: number): void {
object.x = (this.scale.width / 2) -
(
object
&& object.node
&& object.node.getBoundingClientRect().width > 0
? (object.node.getBoundingClientRect().width / 2 / this.scale.zoom)
: (defaultWidth / this.scale.zoom)
);
}
}

View file

@ -0,0 +1,63 @@
import { SelectCharacterScene } from "./SelectCharacterScene";
export class SelectCharacterMobileScene extends SelectCharacterScene {
create(){
super.create();
this.onResize();
this.selectedRectangle.destroy();
}
protected defineSetupPlayer(num: number){
const deltaX = 30;
const deltaY = 2;
let [playerX, playerY] = this.getCharacterPosition();
let playerVisible = true;
let playerScale = 1.5;
let playerOpacity = 1;
if( this.currentSelectUser !== num ){
playerVisible = false;
}
if( num === (this.currentSelectUser + 1) ){
playerY -= deltaY;
playerX += deltaX;
playerScale = 0.8;
playerOpacity = 0.6;
playerVisible = true;
}
if( num === (this.currentSelectUser + 2) ){
playerY -= deltaY;
playerX += (deltaX * 2);
playerScale = 0.8;
playerOpacity = 0.6;
playerVisible = true;
}
if( num === (this.currentSelectUser - 1) ){
playerY -= deltaY;
playerX -= deltaX;
playerScale = 0.8;
playerOpacity = 0.6;
playerVisible = true;
}
if( num === (this.currentSelectUser - 2) ){
playerY -= deltaY;
playerX -= (deltaX * 2);
playerScale = 0.8;
playerOpacity = 0.6;
playerVisible = true;
}
return {playerX, playerY, playerScale, playerOpacity, playerVisible}
}
/**
* Returns pixel position by on column and row number
*/
protected getCharacterPosition(): [number, number] {
return [
this.game.renderer.width / 2,
this.game.renderer.height / 3
];
}
}

View file

@ -1,227 +1,271 @@
import {gameManager} from "../Game/GameManager";
import {TextField} from "../Components/TextField";
import Image = Phaser.GameObjects.Image;
import Rectangle = Phaser.GameObjects.Rectangle;
import {PLAYER_RESOURCES, PlayerResourceDescriptionInterface} from "../Entity/Character";
import {EnableCameraSceneName} from "./EnableCameraScene";
import {CustomizeSceneName} from "./CustomizeScene";
import {ResizableScene} from "./ResizableScene";
import {localUserStore} from "../../Connexion/LocalUserStore";
import {loadAllDefaultModels} from "../Entity/PlayerTexturesLoadingManager";
import {addLoader} from "../Components/Loader";
import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
import {AbstractCharacterScene} from "./AbstractCharacterScene";
import {areCharacterLayersValid} from "../../Connexion/LocalUser";
import {touchScreenManager} from "../../Touch/TouchScreenManager";
import {PinchManager} from "../UserInput/PinchManager";
import {selectCharacterSceneVisibleStore} from "../../Stores/SelectCharacterStore";
import {waScaleManager} from "../Services/WaScaleManager";
import {isMobile} from "../../Enum/EnvironmentVariable";
//todo: put this constants in a dedicated file
export const SelectCharacterSceneName = "SelectCharacterScene";
enum LoginTextures {
playButton = "play_button",
icon = "icon",
mainFont = "main_font",
customizeButton = "customize_button",
customizeButtonSelected = "customize_button_selected"
}
export class SelectCharacterScene extends ResizableScene {
private readonly nbCharactersPerRow = 4;
private textField!: TextField;
private pressReturnField!: TextField;
private logo!: Image;
private customizeButton!: Image;
private customizeButtonSelected!: Image;
export class SelectCharacterScene extends AbstractCharacterScene {
protected readonly nbCharactersPerRow = 6;
protected selectedPlayer!: Phaser.Physics.Arcade.Sprite|null; // null if we are selecting the "customize" option
protected players: Array<Phaser.Physics.Arcade.Sprite> = new Array<Phaser.Physics.Arcade.Sprite>();
protected playerModels!: BodyResourceDescriptionInterface[];
private selectedRectangle!: Rectangle;
private selectedRectangleXPos = 0; // Number of the character selected in the rows
private selectedRectangleYPos = 0; // Number of the character selected in the columns
private selectedPlayer!: Phaser.Physics.Arcade.Sprite|null; // null if we are selecting the "customize" option
private players: Array<Phaser.Physics.Arcade.Sprite> = new Array<Phaser.Physics.Arcade.Sprite>();
protected selectedRectangle!: Rectangle;
protected currentSelectUser = 0;
protected pointerClicked: boolean = false;
protected pointerTimer: number = 0;
protected lazyloadingAttempt = true; //permit to update texture loaded after renderer
constructor() {
super({
key: SelectCharacterSceneName
key: SelectCharacterSceneName,
});
}
preload() {
this.load.image(LoginTextures.playButton, "resources/objects/play_button.png");
this.load.image(LoginTextures.icon, "resources/logos/tcm_full.png");
// Note: arcade.png from the Phaser 3 examples at: https://github.com/photonstorm/phaser3-examples/tree/master/public/assets/fonts/bitmap
this.load.bitmapFont(LoginTextures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
//add player png
PLAYER_RESOURCES.forEach((playerResource: PlayerResourceDescriptionInterface) => {
this.load.spritesheet(
playerResource.name,
playerResource.img,
{frameWidth: 32, frameHeight: 32}
);
this.loadSelectSceneCharacters().then((bodyResourceDescriptions) => {
bodyResourceDescriptions.forEach((bodyResourceDescription) => {
this.playerModels.push(bodyResourceDescription);
});
this.lazyloadingAttempt = true;
});
this.load.image(LoginTextures.customizeButton, 'resources/objects/customize.png');
this.load.image(LoginTextures.customizeButtonSelected, 'resources/objects/customize_selected.png');
this.playerModels = loadAllDefaultModels(this.load);
this.lazyloadingAttempt = false;
//this function must stay at the end of preload function
addLoader(this);
}
create() {
this.textField = new TextField(this, this.game.renderer.width / 2, 50, 'Select your character');
selectCharacterSceneVisibleStore.set(true);
this.events.addListener('wake', () => {
waScaleManager.saveZoom();
waScaleManager.zoomModifier = isMobile() ? 2 : 1;
selectCharacterSceneVisibleStore.set(true);
});
this.pressReturnField = new TextField(this, this.game.renderer.width / 2, 256, 'Press enter to start');
if (touchScreenManager.supportTouchScreen) {
new PinchManager(this);
}
waScaleManager.saveZoom();
waScaleManager.zoomModifier = isMobile() ? 2 : 1;
const rectangleXStart = this.game.renderer.width / 2 - (this.nbCharactersPerRow / 2) * 32 + 16;
this.selectedRectangle = this.add.rectangle(rectangleXStart, 90, 32, 32).setStrokeStyle(2, 0xFFFFFF);
this.logo = new Image(this, this.game.renderer.width - 30, this.game.renderer.height - 20, LoginTextures.icon);
this.add.existing(this.logo);
this.input.keyboard.on('keyup-ENTER', () => {
return this.nextScene();
});
this.input.keyboard.on('keydown-RIGHT', () => {
if (this.selectedRectangleXPos < this.nbCharactersPerRow - 1) {
this.selectedRectangleXPos++;
}
this.updateSelectedPlayer();
});
this.input.keyboard.on('keydown-LEFT', () => {
if (this.selectedRectangleXPos > 0) {
this.selectedRectangleXPos--;
}
this.updateSelectedPlayer();
});
this.input.keyboard.on('keydown-DOWN', () => {
if (this.selectedRectangleYPos < Math.ceil(PLAYER_RESOURCES.length / this.nbCharactersPerRow)) {
this.selectedRectangleYPos++;
}
this.updateSelectedPlayer();
});
this.input.keyboard.on('keydown-UP', () => {
if (this.selectedRectangleYPos > 0) {
this.selectedRectangleYPos--;
}
this.updateSelectedPlayer();
});
this.selectedRectangle.setDepth(2);
/*create user*/
this.createCurrentPlayer();
const playerNumber = localUserStore.getPlayerCharacterIndex();
if (playerNumber && playerNumber !== -1) {
this.selectedRectangleXPos = playerNumber % this.nbCharactersPerRow;
this.selectedRectangleYPos = Math.floor(playerNumber / this.nbCharactersPerRow);
this.updateSelectedPlayer();
} else if (playerNumber === -1) {
this.selectedRectangleYPos = Math.ceil(PLAYER_RESOURCES.length / this.nbCharactersPerRow);
this.updateSelectedPlayer();
this.input.keyboard.on('keyup-ENTER', () => {
return this.nextSceneToCameraScene();
});
this.input.keyboard.on('keydown-RIGHT', () => {
this.moveToRight();
});
this.input.keyboard.on('keydown-LEFT', () => {
this.moveToLeft();
});
this.input.keyboard.on('keydown-UP', () => {
this.moveToUp();
});
this.input.keyboard.on('keydown-DOWN', () => {
this.moveToDown();
});
}
public nextSceneToCameraScene(): void {
if (this.selectedPlayer !== null && !areCharacterLayersValid([this.selectedPlayer.texture.key])) {
return;
}
if(!this.selectedPlayer){
return;
}
}
update(time: number, delta: number): void {
this.pressReturnField.setVisible(!!(Math.floor(time / 500) % 2));
}
private nextScene(): void {
this.scene.stop(SelectCharacterSceneName);
if (this.selectedPlayer !== null) {
gameManager.setCharacterLayers([this.selectedPlayer.texture.key]);
gameManager.tryResumingGame(this, EnableCameraSceneName);
} else {
this.scene.run(CustomizeSceneName);
waScaleManager.restoreZoom();
gameManager.setCharacterLayers([this.selectedPlayer.texture.key]);
gameManager.tryResumingGame(this, EnableCameraSceneName);
this.players = [];
selectCharacterSceneVisibleStore.set(false);
this.events.removeListener('wake');
}
public nextSceneToCustomizeScene(): void {
if (this.selectedPlayer !== null && !areCharacterLayersValid([this.selectedPlayer.texture.key])) {
return;
}
this.scene.remove(SelectCharacterSceneName);
this.scene.sleep(SelectCharacterSceneName);
waScaleManager.restoreZoom();
this.scene.run(CustomizeSceneName);
selectCharacterSceneVisibleStore.set(false);
}
createCurrentPlayer(): void {
for (let i = 0; i <PLAYER_RESOURCES.length; i++) {
const playerResource = PLAYER_RESOURCES[i];
for (let i = 0; i <this.playerModels.length; i++) {
const playerResource = this.playerModels[i];
const col = i % this.nbCharactersPerRow;
const row = Math.floor(i / this.nbCharactersPerRow);
//check already exist texture
if(this.players.find((c) => c.texture.key === playerResource.name)){
continue;
}
const [x, y] = this.getCharacterPosition(col, row);
const player = this.physics.add.sprite(x, y, playerResource.name, 0);
player.setBounce(0.2);
player.setCollideWorldBounds(true);
const [middleX, middleY] = this.getCharacterPosition();
const player = this.physics.add.sprite(middleX, middleY, playerResource.name, 0);
this.setUpPlayer(player, i);
this.anims.create({
key: playerResource.name,
frames: this.anims.generateFrameNumbers(playerResource.name, {start: 0, end: 2,}),
frameRate: 10,
frames: this.anims.generateFrameNumbers(playerResource.name, {start: 0, end: 11}),
frameRate: 8,
repeat: -1
});
player.setInteractive().on("pointerdown", () => {
this.selectedRectangleXPos = col;
this.selectedRectangleYPos = row;
this.updateSelectedPlayer();
if (this.pointerClicked) {
return;
}
if (this.currentSelectUser === i) {
return;
}
//To not trigger two time the pointerdown events :
// We set a boolean to true so that pointerdown events does nothing when the boolean is true
// We set a timer that we decrease in update function to not trigger the pointerdown events twice
this.pointerClicked = true;
this.pointerTimer = 250;
this.currentSelectUser = i;
this.moveUser();
});
this.players.push(player);
}
this.selectedPlayer = this.players[this.currentSelectUser];
this.selectedPlayer.play(this.playerModels[this.currentSelectUser].name);
}
this.customizeButton = new Image(this, this.game.renderer.width / 2, 90 + 32 * 4 + 6, LoginTextures.customizeButton);
this.customizeButton.setOrigin(0.5, 0.5);
this.add.existing(this.customizeButton);
this.customizeButtonSelected = new Image(this, this.game.renderer.width / 2, 90 + 32 * 4 + 6, LoginTextures.customizeButtonSelected);
this.customizeButtonSelected.setOrigin(0.5, 0.5);
this.customizeButtonSelected.setVisible(false);
this.add.existing(this.customizeButtonSelected);
protected moveUser(){
for(let i = 0; i < this.players.length; i++){
const player = this.players[i];
this.setUpPlayer(player, i);
}
this.updateSelectedPlayer();
}
this.customizeButton.setInteractive().on("pointerdown", () => {
this.selectedRectangleYPos = Math.ceil(PLAYER_RESOURCES.length / this.nbCharactersPerRow);
this.updateSelectedPlayer();
});
public moveToLeft(){
if(this.currentSelectUser === 0){
return;
}
this.currentSelectUser -= 1;
this.moveUser();
}
this.selectedPlayer = this.players[0];
this.selectedPlayer.play(PLAYER_RESOURCES[0].name);
public moveToRight(){
if(this.currentSelectUser === (this.players.length - 1)){
return;
}
this.currentSelectUser += 1;
this.moveUser();
}
protected moveToUp(){
if(this.currentSelectUser < this.nbCharactersPerRow){
return;
}
this.currentSelectUser -= this.nbCharactersPerRow;
this.moveUser();
}
protected moveToDown(){
if((this.currentSelectUser + this.nbCharactersPerRow) > (this.players.length - 1)){
return;
}
this.currentSelectUser += this.nbCharactersPerRow;
this.moveUser();
}
protected defineSetupPlayer(num: number){
const deltaX = 32;
const deltaY = 32;
let [playerX, playerY] = this.getCharacterPosition(); // player X and player y are middle of the
playerX = ( (playerX - (deltaX * 2.5)) + ((deltaX) * (num % this.nbCharactersPerRow)) ); // calcul position on line users
playerY = ( (playerY - (deltaY * 2)) + ((deltaY) * ( Math.floor(num / this.nbCharactersPerRow) )) ); // calcul position on column users
const playerVisible = true;
const playerScale = 1;
const playerOpacity = 1;
// if selected
if( num === this.currentSelectUser ){
this.selectedRectangle.setX(playerX);
this.selectedRectangle.setY(playerY);
}
return {playerX, playerY, playerScale, playerOpacity, playerVisible}
}
protected setUpPlayer(player: Phaser.Physics.Arcade.Sprite, num: number){
const {playerX, playerY, playerScale, playerOpacity, playerVisible} = this.defineSetupPlayer(num);
player.setBounce(0.2);
player.setCollideWorldBounds(false);
player.setVisible( playerVisible );
player.setScale(playerScale, playerScale);
player.setAlpha(playerOpacity);
player.setX(playerX);
player.setY(playerY);
}
/**
* Returns pixel position by on column and row number
*/
private getCharacterPosition(x: number, y: number): [number, number] {
protected getCharacterPosition(): [number, number] {
return [
this.game.renderer.width / 2 + 16 + (x - this.nbCharactersPerRow / 2) * 32,
y * 32 + 90
this.game.renderer.width / 2,
this.game.renderer.height / 2.5
];
}
private updateSelectedPlayer(): void {
this.selectedPlayer?.anims.pause();
// If we selected the customize button
if (this.selectedRectangleYPos === Math.ceil(PLAYER_RESOURCES.length / this.nbCharactersPerRow)) {
this.selectedPlayer = null;
this.selectedRectangle.setVisible(false);
this.customizeButtonSelected.setVisible(true);
this.customizeButton.setVisible(false);
localUserStore.setPlayerCharacterIndex(-1);
return;
}
this.customizeButtonSelected.setVisible(false);
this.customizeButton.setVisible(true);
const [x, y] = this.getCharacterPosition(this.selectedRectangleXPos, this.selectedRectangleYPos);
this.selectedRectangle.setVisible(true);
this.selectedRectangle.setX(x);
this.selectedRectangle.setY(y);
this.selectedRectangle.setSize(32, 32);
const playerNumber = this.selectedRectangleXPos + this.selectedRectangleYPos * this.nbCharactersPerRow;
const player = this.players[playerNumber];
player.play(PLAYER_RESOURCES[playerNumber].name);
protected updateSelectedPlayer(): void {
this.selectedPlayer?.anims.pause(this.selectedPlayer?.anims.currentAnim.frames[0]);
const player = this.players[this.currentSelectUser];
player.play(this.playerModels[this.currentSelectUser].name);
this.selectedPlayer = player;
localUserStore.setPlayerCharacterIndex(playerNumber);
localUserStore.setPlayerCharacterIndex(this.currentSelectUser);
}
public onResize(ev: UIEvent): void {
this.textField.x = this.game.renderer.width / 2;
this.pressReturnField.x = this.game.renderer.width / 2;
this.logo.x = this.game.renderer.width - 30;
this.logo.y = this.game.renderer.height - 20;
this.customizeButton.x = this.game.renderer.width / 2;
this.customizeButtonSelected.x = this.game.renderer.width / 2;
for (let i = 0; i <PLAYER_RESOURCES.length; i++) {
const player = this.players[i];
const col = i % this.nbCharactersPerRow;
const row = Math.floor(i / this.nbCharactersPerRow);
const [x, y] = this.getCharacterPosition(col, row);
player.x = x;
player.y = y;
update(time: number, delta: number): void {
// pointerTimer is set to 250 when pointerdown events is trigger
// After 250ms, pointerClicked is set to false and the pointerdown events can be trigger again
this.pointerTimer -= delta;
if (this.pointerTimer <= 0) {
this.pointerClicked = false;
}
if(this.lazyloadingAttempt){
//re-render players list
this.createCurrentPlayer();
this.moveUser();
this.lazyloadingAttempt = false;
}
this.updateSelectedPlayer();
}
public onResize(): void {
//move position of user
this.moveUser();
}
}

View file

@ -0,0 +1,229 @@
import Image = Phaser.GameObjects.Image;
import Rectangle = Phaser.GameObjects.Rectangle;
import { addLoader } from "../Components/Loader";
import { gameManager} from "../Game/GameManager";
import { ResizableScene } from "./ResizableScene";
import { EnableCameraSceneName } from "./EnableCameraScene";
import { localUserStore } from "../../Connexion/LocalUserStore";
import type { CompanionResourceDescriptionInterface } from "../Companion/CompanionTextures";
import { getAllCompanionResources } from "../Companion/CompanionTexturesLoadingManager";
import {touchScreenManager} from "../../Touch/TouchScreenManager";
import {PinchManager} from "../UserInput/PinchManager";
import { MenuScene } from "../Menu/MenuScene";
import {selectCompanionSceneVisibleStore} from "../../Stores/SelectCompanionStore";
import {waScaleManager} from "../Services/WaScaleManager";
import {isMobile} from "../../Enum/EnvironmentVariable";
export const SelectCompanionSceneName = "SelectCompanionScene";
export class SelectCompanionScene extends ResizableScene {
private selectedCompanion!: Phaser.Physics.Arcade.Sprite;
private companions: Array<Phaser.Physics.Arcade.Sprite> = new Array<Phaser.Physics.Arcade.Sprite>();
private companionModels: Array<CompanionResourceDescriptionInterface> = [];
private saveZoom: number = 0;
private currentCompanion = 0;
private pointerClicked: boolean = false;
private pointerTimer: number = 0;
constructor() {
super({
key: SelectCompanionSceneName
});
}
preload() {
getAllCompanionResources(this.load).forEach(model => {
this.companionModels.push(model);
});
//this function must stay at the end of preload function
addLoader(this);
}
create() {
selectCompanionSceneVisibleStore.set(true);
waScaleManager.saveZoom();
waScaleManager.zoomModifier = isMobile() ? 2 : 1;
if (touchScreenManager.supportTouchScreen) {
new PinchManager(this);
}
// input events
this.input.keyboard.on('keyup-ENTER', this.selectCompanion.bind(this));
this.input.keyboard.on('keydown-RIGHT', this.moveToRight.bind(this));
this.input.keyboard.on('keydown-LEFT', this.moveToLeft.bind(this));
if(localUserStore.getCompanion()){
const companionIndex = this.companionModels.findIndex((companion) => companion.name === localUserStore.getCompanion());
if(companionIndex > -1 || companionIndex < this.companions.length){
this.currentCompanion = companionIndex;
this.selectedCompanion = this.companions[companionIndex];
}
}
localUserStore.setCompanion(null);
gameManager.setCompanion(null);
this.createCurrentCompanion();
this.updateSelectedCompanion();
}
update(time: number, delta: number): void {
// pointerTimer is set to 250 when pointerdown events is trigger
// After 250ms, pointerClicked is set to false and the pointerdown events can be trigger again
this.pointerTimer -= delta;
if (this.pointerTimer <= 0) {
this.pointerClicked = false;
}
}
public selectCompanion(): void {
localUserStore.setCompanion(this.companionModels[this.currentCompanion].name);
gameManager.setCompanion(this.companionModels[this.currentCompanion].name);
this.closeScene();
}
public closeScene(){
// next scene
this.scene.stop(SelectCompanionSceneName);
waScaleManager.restoreZoom();
gameManager.tryResumingGame(this, EnableCameraSceneName);
this.scene.remove(SelectCompanionSceneName);
selectCompanionSceneVisibleStore.set(false);
}
private createCurrentCompanion(): void {
for (let i = 0; i < this.companionModels.length; i++) {
const companionResource = this.companionModels[i]
const [middleX, middleY] = this.getCompanionPosition();
const companion = this.physics.add.sprite(middleX, middleY, companionResource.name, 0);
this.setUpCompanion(companion, i);
this.anims.create({
key: companionResource.name,
frames: this.anims.generateFrameNumbers(companionResource.name, {start: 0, end: 2,}),
frameRate: 10,
repeat: -1
});
companion.setInteractive().on("pointerdown", () => {
if (this.pointerClicked) {
return;
}
//To not trigger two time the pointerdown events :
// We set a boolean to true so that pointerdown events does nothing when the boolean is true
// We set a timer that we decrease in update function to not trigger the pointerdown events twice
this.pointerClicked = true;
this.pointerTimer = 250;
this.currentCompanion = i;
this.moveCompanion();
});
this.companions.push(companion);
}
this.selectedCompanion = this.companions[this.currentCompanion];
}
public onResize(): void {
this.moveCompanion();
}
private updateSelectedCompanion(): void {
this.selectedCompanion?.anims.pause();
const companion = this.companions[this.currentCompanion];
companion.play(this.companionModels[this.currentCompanion].name);
this.selectedCompanion = companion;
}
private moveCompanion(){
for(let i = 0; i < this.companions.length; i++){
const companion = this.companions[i];
this.setUpCompanion(companion, i);
}
this.updateSelectedCompanion();
}
public moveToRight(){
if(this.currentCompanion === (this.companions.length - 1)){
return;
}
this.currentCompanion += 1;
this.moveCompanion();
}
public moveToLeft(){
if(this.currentCompanion === 0){
return;
}
this.currentCompanion -= 1;
this.moveCompanion();
}
private defineSetupCompanion(num: number){
const deltaX = 30;
const deltaY = 2;
let [companionX, companionY] = this.getCompanionPosition();
let companionVisible = true;
let companionScale = 1.5;
let companionOpactity = 1;
if( this.currentCompanion !== num ){
companionVisible = false;
}
if( num === (this.currentCompanion + 1) ){
companionY -= deltaY;
companionX += deltaX;
companionScale = 0.8;
companionOpactity = 0.6;
companionVisible = true;
}
if( num === (this.currentCompanion + 2) ){
companionY -= deltaY;
companionX += (deltaX * 2);
companionScale = 0.8;
companionOpactity = 0.6;
companionVisible = true;
}
if( num === (this.currentCompanion - 1) ){
companionY -= deltaY;
companionX -= deltaX;
companionScale = 0.8;
companionOpactity = 0.6;
companionVisible = true;
}
if( num === (this.currentCompanion - 2) ){
companionY -= deltaY;
companionX -= (deltaX * 2);
companionScale = 0.8;
companionOpactity = 0.6;
companionVisible = true;
}
return {companionX, companionY, companionScale, companionOpactity, companionVisible}
}
/**
* Returns pixel position by on column and row number
*/
private getCompanionPosition(): [number, number] {
return [
this.game.renderer.width / 2,
this.game.renderer.height / 3
];
}
private setUpCompanion(companion: Phaser.Physics.Arcade.Sprite, numero: number){
const {companionX, companionY, companionScale, companionOpactity, companionVisible} = this.defineSetupCompanion(numero);
companion.setBounce(0.2);
companion.setCollideWorldBounds(true);
companion.setVisible( companionVisible );
companion.setScale(companionScale, companionScale);
companion.setAlpha(companionOpactity);
companion.setX(companionX);
companion.setY(companionY);
}
}

View file

@ -14,7 +14,7 @@ export interface ITiledMap {
* Map orientation (orthogonal)
*/
orientation: string;
properties: {[key: string]: string};
properties?: ITiledMapLayerProperty[];
/**
* Render order (right-down)
@ -24,6 +24,11 @@ export interface ITiledMap {
tilewidth: number;
tilesets: ITiledTileSet[];
version: number;
compressionlevel?: number;
infinite?: boolean;
nextlayerid?: number;
tiledversion?: string;
type?: string;
}
export interface ITiledMapLayerProperty {
@ -38,19 +43,35 @@ export interface ITiledMapLayerProperty {
value: boolean
}*/
export interface ITiledMapLayer {
export type ITiledMapLayer = ITiledMapGroupLayer | ITiledMapObjectLayer | ITiledMapTileLayer;
export interface ITiledMapGroupLayer {
id?: number,
name: string;
opacity: number;
properties?: ITiledMapLayerProperty[];
type: "group";
visible: boolean;
x: number;
y: number;
/**
* Layers for group layer
*/
layers: ITiledMapLayer[];
}
export interface ITiledMapTileLayer {
id?: number,
data: number[]|string;
height: number;
name: string;
opacity: number;
properties: ITiledMapLayerProperty[];
encoding: string;
properties?: ITiledMapLayerProperty[];
encoding?: string;
compression?: string;
/**
* Type of layer (tilelayer, objectgroup)
*/
type: string;
type: "tilelayer";
visible: boolean;
width: number;
x: number;
@ -59,7 +80,28 @@ export interface ITiledMapLayer {
/**
* Draw order (topdown (default), index)
*/
draworder: string;
draworder?: string;
}
export interface ITiledMapObjectLayer {
id?: number,
height: number;
name: string;
opacity: number;
properties?: ITiledMapLayerProperty[];
encoding?: string;
compression?: string;
type: "objectgroup";
visible: boolean;
width: number;
x: number;
y: number;
/**
* Draw order (topdown (default), index)
*/
draworder?: string;
objects: ITiledMapObject[];
}
@ -94,6 +136,20 @@ export interface ITiledMapObject {
* Polyline points
*/
polyline: {x: number, y: number}[];
text?: ITiledText
}
export interface ITiledText {
text: string,
wrap?: boolean,
fontfamily?: string,
pixelsize?: number,
color?: string,
underline?: boolean,
italic?: boolean,
strikeout?: boolean,
halign?: "center"|"right"|"justify"|"left"
}
export interface ITiledTileSet {

View file

@ -0,0 +1,44 @@
import type {ITiledMap, ITiledMapLayer} from "./ITiledMap";
/**
* Iterates over the layers of a map, flattening the grouped layers
*/
export class LayersIterator implements IterableIterator<ITiledMapLayer> {
private layers: ITiledMapLayer[] = [];
private pointer: number = 0;
constructor(private map: ITiledMap) {
this.initLayersList(map.layers, '');
}
private initLayersList(layers : ITiledMapLayer[], prefix : string) {
for (const layer of layers) {
if (layer.type === 'group') {
this.initLayersList(layer.layers, prefix + layer.name + '/');
} else {
const layerWithNewName = { ...layer };
layerWithNewName.name = prefix+layerWithNewName.name;
this.layers.push(layerWithNewName);
}
}
}
public next(): IteratorResult<ITiledMapLayer> {
if (this.pointer < this.layers.length) {
return {
done: false,
value: this.layers[this.pointer++]
}
} else {
return {
done: true,
value: null
}
}
}
[Symbol.iterator](): IterableIterator<ITiledMapLayer> {
return new LayersIterator(this.map);
}
}

View file

@ -1,9 +1,16 @@
import {LoginScene, LoginSceneName} from "../Login/LoginScene";
import {SelectCharacterScene, SelectCharacterSceneName} from "../Login/SelectCharacterScene";
import {SelectCompanionScene, SelectCompanionSceneName} from "../Login/SelectCompanionScene";
import {gameManager} from "../Game/GameManager";
import {localUserStore} from "../../Connexion/LocalUserStore";
import {mediaManager} from "../../WebRtc/MediaManager";
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
import {gameReportKey, gameReportRessource, ReportMenu} from "./ReportMenu";
import {connectionManager} from "../../Connexion/ConnectionManager";
import {GameConnexionTypes} from "../../Url/UrlManager";
import {WarningContainer, warningContainerHtml, warningContainerKey} from "../Components/WarningContainer";
import {worldFullWarningStream} from "../../Connexion/WorldFullWarningStream";
import {menuIconVisible} from "../../Stores/MenuStore";
import {videoConstraintStore} from "../../Stores/MediaStore";
export const MenuSceneName = 'MenuScene';
const gameMenuKey = 'gameMenu';
@ -11,7 +18,7 @@ const gameMenuIconKey = 'gameMenuIcon';
const gameSettingsMenuKey = 'gameSettingsMenu';
const gameShare = 'gameShare';
const closedSideMenuX = -200;
const closedSideMenuX = -1000;
const openedSideMenuX = 0;
/**
@ -21,12 +28,15 @@ export class MenuScene extends Phaser.Scene {
private menuElement!: Phaser.GameObjects.DOMElement;
private gameQualityMenuElement!: Phaser.GameObjects.DOMElement;
private gameShareElement!: Phaser.GameObjects.DOMElement;
private gameReportElement!: ReportMenu;
private sideMenuOpened = false;
private settingsMenuOpened = false;
private gameShareOpened = false;
private gameQualityValue: number;
private videoQualityValue: number;
private menuButton!: Phaser.GameObjects.DOMElement;
private warningContainer: WarningContainer | null = null;
private warningContainerTimeout: NodeJS.Timeout | null = null;
constructor() {
super({key: MenuSceneName});
@ -40,20 +50,23 @@ export class MenuScene extends Phaser.Scene {
this.load.html(gameMenuIconKey, 'resources/html/gameMenuIcon.html');
this.load.html(gameSettingsMenuKey, 'resources/html/gameQualityMenu.html');
this.load.html(gameShare, 'resources/html/gameShare.html');
this.load.html(gameReportKey, gameReportRessource);
this.load.html(warningContainerKey, warningContainerHtml);
}
create() {
menuIconVisible.set(true);
this.menuElement = this.add.dom(closedSideMenuX, 30).createFromCache(gameMenuKey);
this.menuElement.setOrigin(0);
this.revealMenusAfterInit(this.menuElement, 'gameMenu');
MenuScene.revealMenusAfterInit(this.menuElement, 'gameMenu');
const middleX = (window.innerWidth / 3) - 298;
this.gameQualityMenuElement = this.add.dom(middleX, -400).createFromCache(gameSettingsMenuKey);
this.revealMenusAfterInit(this.gameQualityMenuElement, 'gameQuality');
MenuScene.revealMenusAfterInit(this.gameQualityMenuElement, 'gameQuality');
this.gameShareElement = this.add.dom(middleX, -400).createFromCache(gameShare);
this.revealMenusAfterInit(this.gameShareElement, gameShare);
MenuScene.revealMenusAfterInit(this.gameShareElement, gameShare);
this.gameShareElement.addListener('click');
this.gameShareElement.on('click', (event:MouseEvent) => {
event.preventDefault();
@ -64,6 +77,12 @@ export class MenuScene extends Phaser.Scene {
}
});
this.gameReportElement = new ReportMenu(this, connectionManager.getConnexionType === GameConnexionTypes.anonymous);
mediaManager.setShowReportModalCallBacks((userId, userName) => {
this.closeAll();
this.gameReportElement.open(parseInt(userId), userName);
});
this.input.keyboard.on('keyup-TAB', () => {
this.sideMenuOpened ? this.closeSideMenu() : this.openSideMenu();
});
@ -75,9 +94,12 @@ export class MenuScene extends Phaser.Scene {
this.menuElement.addListener('click');
this.menuElement.on('click', this.onMenuClick.bind(this));
worldFullWarningStream.stream.subscribe(() => this.showWorldCapacityWarning());
}
private revealMenusAfterInit(menuElement: Phaser.GameObjects.DOMElement, rootDomId: string) {
//todo put this method in a parent menuElement class
static revealMenusAfterInit(menuElement: Phaser.GameObjects.DOMElement, rootDomId: string) {
//Dom elements will appear inside the viewer screen when creating before being moved out of it, which create a flicker effect.
//To prevent this, we put a 'hidden' attribute on the root element, we remove it only after the init is done.
setTimeout(() => {
@ -86,7 +108,12 @@ export class MenuScene extends Phaser.Scene {
}
public revealMenuIcon(): void {
(this.menuButton.getChildByID('menuIcon') as HTMLElement).hidden = false
//TODO fix me: add try catch because at the same time, 'this.menuButton' variable doesn't exist and there is error on 'getChildByID' function
try {
(this.menuButton.getChildByID('menuIcon') as HTMLElement).hidden = false;
} catch (err) {
console.error(err);
}
}
openSideMenu() {
@ -94,10 +121,16 @@ export class MenuScene extends Phaser.Scene {
this.closeAll();
this.sideMenuOpened = true;
this.menuButton.getChildByID('openMenuButton').innerHTML = 'X';
if (gameManager.getCurrentGameScene(this).connection && gameManager.getCurrentGameScene(this).connection.isAdmin()) {
const connection = gameManager.getCurrentGameScene(this).connection;
if (connection && connection.isAdmin()) {
const adminSection = this.menuElement.getChildByID('adminConsoleSection') as HTMLElement;
adminSection.hidden = false;
}
//TODO bind with future metadata of card
//if (connectionManager.getConnexionType === GameConnexionTypes.anonymous){
const adminSection = this.menuElement.getChildByID('socialLinks') as HTMLElement;
adminSection.hidden = false;
//}
this.tweens.add({
targets: this.menuElement,
x: openedSideMenuX,
@ -106,10 +139,26 @@ export class MenuScene extends Phaser.Scene {
});
}
private showWorldCapacityWarning() {
if (!this.warningContainer) {
this.warningContainer = new WarningContainer(this);
}
if (this.warningContainerTimeout) {
clearTimeout(this.warningContainerTimeout);
}
this.warningContainerTimeout = setTimeout(() => {
this.warningContainer?.destroy();
this.warningContainer = null
this.warningContainerTimeout = null
}, 120000);
}
private closeSideMenu(): void {
if (!this.sideMenuOpened) return;
this.sideMenuOpened = false;
this.closeAll();
this.menuButton.getChildByID('openMenuButton').innerHTML = `<img src="/static/images/menu.svg">`;
gameManager.getCurrentGameScene(this).ConsoleGlobalMessageManager.disabledMessageConsole();
this.tweens.add({
targets: this.menuElement,
@ -121,6 +170,7 @@ export class MenuScene extends Phaser.Scene {
private openGameSettingsMenu(): void {
if (this.settingsMenuOpened) {
this.closeGameQualityMenu();
return;
}
//close all
@ -145,11 +195,11 @@ export class MenuScene extends Phaser.Scene {
}
});
let middleY = (window.innerHeight / 3) - (257);
let middleY = this.scale.height / 2 - 392/2;
if(middleY < 0){
middleY = 0;
}
let middleX = (window.innerWidth / 3) - 298;
let middleX = this.scale.width / 2 - 457/2;
if(middleX < 0){
middleX = 0;
}
@ -189,11 +239,11 @@ export class MenuScene extends Phaser.Scene {
this.gameShareOpened = true;
let middleY = (window.innerHeight / 3) - (257);
let middleY = this.scale.height / 2 - 85;
if(middleY < 0){
middleY = 0;
}
let middleX = (window.innerWidth / 3) - 298;
let middleX = this.scale.width / 2 - 200;
if(middleX < 0){
middleX = 0;
}
@ -220,6 +270,9 @@ export class MenuScene extends Phaser.Scene {
}
private onMenuClick(event:MouseEvent) {
if((event?.target as HTMLInputElement).classList.contains('not-button')){
return;
}
event.preventDefault();
switch ((event?.target as HTMLInputElement).id) {
@ -234,6 +287,10 @@ export class MenuScene extends Phaser.Scene {
this.closeSideMenu();
gameManager.leaveGame(this, SelectCharacterSceneName, new SelectCharacterScene());
break;
case 'changeCompanionButton':
this.closeSideMenu();
gameManager.leaveGame(this, SelectCompanionSceneName, new SelectCompanionScene());
break;
case 'closeButton':
this.closeSideMenu();
break;
@ -243,6 +300,9 @@ export class MenuScene extends Phaser.Scene {
case 'editGameSettingsButton':
this.openGameSettingsMenu();
break;
case 'toggleFullscreen':
this.toggleFullscreen();
break;
case 'adminConsoleButton':
gameManager.getCurrentGameScene(this).ConsoleGlobalMessageManager.activeMessageConsole();
break;
@ -265,19 +325,36 @@ export class MenuScene extends Phaser.Scene {
if (valueVideo !== this.videoQualityValue) {
this.videoQualityValue = valueVideo;
localUserStore.setVideoQualityValue(valueVideo);
mediaManager.updateCameraQuality(valueVideo);
videoConstraintStore.setFrameRate(valueVideo);
}
this.closeGameQualityMenu();
}
private gotToCreateMapPage() {
const sparkHost = 'https://'+window.location.host.replace('play.', '')+'/choose-map.html';
//const sparkHost = 'https://'+window.location.host.replace('play.', '')+'/choose-map.html';
//TODO fix me: this button can to send us on WorkAdventure BO.
const sparkHost = 'https://workadventu.re/getting-started';
window.open(sparkHost, '_blank');
}
private closeAll(){
this.closeGameQualityMenu();
this.closeGameShare();
this.menuButton.getChildByID('openMenuButton').innerHTML = `<img src="/static/images/menu.svg">`;
this.gameReportElement.close();
}
private toggleFullscreen() {
const body = document.querySelector('body')
if (body) {
if (document.fullscreenElement ?? document.fullscreen) {
document.exitFullscreen()
} else {
body.requestFullscreen();
}
}
}
public isDirty(): boolean {
return false;
}
}

View file

@ -0,0 +1,118 @@
import {MenuScene} from "./MenuScene";
import {gameManager} from "../Game/GameManager";
import {blackListManager} from "../../WebRtc/BlackListManager";
export const gameReportKey = 'gameReport';
export const gameReportRessource = 'resources/html/gameReport.html';
export class ReportMenu extends Phaser.GameObjects.DOMElement {
private opened: boolean = false;
private userId!: number;
private userName!: string|undefined;
private anonymous: boolean;
constructor(scene: Phaser.Scene, anonymous: boolean) {
super(scene, -2000, -2000);
this.anonymous = anonymous;
this.createFromCache(gameReportKey);
if (this.anonymous) {
const divToHide = this.getChildByID('reportSection') as HTMLElement;
divToHide.hidden = true;
const textToHide = this.getChildByID('askActionP') as HTMLElement;
textToHide.hidden = true;
}
scene.add.existing(this);
MenuScene.revealMenusAfterInit(this, gameReportKey);
this.addListener('click');
this.on('click', (event:MouseEvent) => {
event.preventDefault();
if ((event?.target as HTMLInputElement).id === 'gameReportFormSubmit') {
this.submitReport();
} else if((event?.target as HTMLInputElement).id === 'gameReportFormCancel') {
this.close();
} else if((event?.target as HTMLInputElement).id === 'toggleBlockButton') {
this.toggleBlock();
}
});
}
public open(userId: number, userName: string|undefined): void {
if (this.opened) {
this.close();
return;
}
this.userId = userId;
this.userName = userName;
const mainEl = this.getChildByID('gameReport') as HTMLElement;
this.x = this.getCenteredX(mainEl);
this.y = this.getHiddenY(mainEl);
const gameTitleReport = this.getChildByID('nameReported') as HTMLElement;
gameTitleReport.innerText = userName || '';
const blockButton = this.getChildByID('toggleBlockButton') as HTMLElement;
blockButton.innerText = blackListManager.isBlackListed(this.userId) ? 'Unblock this user' : 'Block this user';
this.opened = true;
gameManager.getCurrentGameScene(this.scene).userInputManager.disableControls();
this.scene.tweens.add({
targets: this,
y: this.getCenteredY(mainEl),
duration: 1000,
ease: 'Power3'
});
}
public close(): void {
gameManager.getCurrentGameScene(this.scene).userInputManager.restoreControls();
this.opened = false;
const mainEl = this.getChildByID('gameReport') as HTMLElement;
this.scene.tweens.add({
targets: this,
y: this.getHiddenY(mainEl),
duration: 1000,
ease: 'Power3'
});
}
//todo: into a parent class?
private getCenteredX(mainEl: HTMLElement): number {
return window.innerWidth / 4 - mainEl.clientWidth / 2;
}
private getHiddenY(mainEl: HTMLElement): number {
return - mainEl.clientHeight - 50;
}
private getCenteredY(mainEl: HTMLElement): number {
return window.innerHeight / 4 - mainEl.clientHeight / 2;
}
private toggleBlock(): void {
!blackListManager.isBlackListed(this.userId) ? blackListManager.blackList(this.userId) : blackListManager.cancelBlackList(this.userId);
this.close();
}
private submitReport(): void{
const gamePError = this.getChildByID('gameReportErr') as HTMLParagraphElement;
gamePError.innerText = '';
gamePError.style.display = 'none';
const gameTextArea = this.getChildByID('gameReportInput') as HTMLInputElement;
if(!gameTextArea || !gameTextArea.value){
gamePError.innerText = 'Report message cannot to be empty.';
gamePError.style.display = 'block';
return;
}
gameManager.getCurrentGameScene(this.scene).connection?.emitReportPlayerMessage(
this.userId,
gameTextArea.value
);
this.close();
}
}

View file

@ -1,9 +1,13 @@
export enum PlayerAnimationNames {
WalkDown = 'down',
WalkLeft = 'left',
WalkUp = 'up',
WalkRight = 'right',
export enum PlayerAnimationDirections {
Down = 'down',
Left = 'left',
Up = 'up',
Right = 'right',
}
export enum PlayerAnimationTypes {
Walk = 'walk',
Idle = 'idle',
}

View file

@ -1,33 +1,43 @@
import {PlayerAnimationNames} from "./Animation";
import {GameScene} from "../Game/GameScene";
import {PlayerAnimationDirections} from "./Animation";
import type {GameScene} from "../Game/GameScene";
import {UserInputEvent, UserInputManager} from "../UserInput/UserInputManager";
import {Character} from "../Entity/Character";
import {userMovingStore} from "../../Stores/GameStore";
import {RadialMenu, RadialMenuClickEvent, RadialMenuItem} from "../Components/RadialMenu";
export const hasMovedEventName = "hasMoved";
export interface CurrentGamerInterface extends Character{
moveUser(delta: number) : void;
say(text : string) : void;
}
export const requestEmoteEventName = "requestEmote";
export class Player extends Character implements CurrentGamerInterface {
private previousDirection: string = PlayerAnimationNames.WalkDown;
export class Player extends Character {
private previousDirection: string = PlayerAnimationDirections.Down;
private wasMoving: boolean = false;
private emoteMenu: RadialMenu|null = null;
private updateListener: () => void;
constructor(
Scene: GameScene,
x: number,
y: number,
name: string,
PlayerTextures: string[],
direction: string,
texturesPromise: Promise<string[]>,
direction: PlayerAnimationDirections,
moving: boolean,
private userInputManager: UserInputManager
private userInputManager: UserInputManager,
companion: string|null,
companionTexturePromise?: Promise<string>
) {
super(Scene, x, y, PlayerTextures, name, direction, moving, 1);
super(Scene, x, y, texturesPromise, name, direction, moving, 1, true, companion, companionTexturePromise);
//the current player model should be push away by other players to prevent conflict
this.getBody().setImmovable(false);
this.updateListener = () => {
if (this.emoteMenu) {
this.emoteMenu.x = this.x;
this.emoteMenu.y = this.y;
}
};
this.scene.events.addListener('postupdate', this.updateListener);
}
moveUser(delta: number): void {
@ -42,37 +52,74 @@ export class Player extends Character implements CurrentGamerInterface {
let x = 0;
let y = 0;
if (activeEvents.get(UserInputEvent.MoveUp)) {
y = - moveAmount;
direction = PlayerAnimationNames.WalkUp;
y = -moveAmount;
direction = PlayerAnimationDirections.Up;
moving = true;
} else if (activeEvents.get(UserInputEvent.MoveDown)) {
y = moveAmount;
direction = PlayerAnimationNames.WalkDown;
direction = PlayerAnimationDirections.Down;
moving = true;
}
if (activeEvents.get(UserInputEvent.MoveLeft)) {
x = -moveAmount;
direction = PlayerAnimationNames.WalkLeft;
direction = PlayerAnimationDirections.Left;
moving = true;
} else if (activeEvents.get(UserInputEvent.MoveRight)) {
x = moveAmount;
direction = PlayerAnimationNames.WalkRight;
direction = PlayerAnimationDirections.Right;
moving = true;
}
moving = moving || activeEvents.get(UserInputEvent.JoystickMove);
if (x !== 0 || y !== 0) {
this.move(x, y);
this.emit(hasMovedEventName, {moving, direction, x: this.x, y: this.y});
} else {
if (this.wasMoving) {
//direction = PlayerAnimationNames.None;
this.stop();
this.emit(hasMovedEventName, {moving, direction: this.previousDirection, x: this.x, y: this.y});
}
} else if (this.wasMoving && moving) {
// slow joystick movement
this.move(0, 0);
this.emit(hasMovedEventName, {moving, direction: this.previousDirection, x: this.x, y: this.y});
} else if (this.wasMoving && !moving) {
this.stop();
this.emit(hasMovedEventName, {moving, direction: this.previousDirection, x: this.x, y: this.y});
}
if (direction !== null) {
this.previousDirection = direction;
}
this.wasMoving = moving;
userMovingStore.set(moving);
}
public isMoving(): boolean {
return this.wasMoving;
}
openOrCloseEmoteMenu(emotes:RadialMenuItem[]) {
if(this.emoteMenu) {
this.closeEmoteMenu();
} else {
this.openEmoteMenu(emotes);
}
}
openEmoteMenu(emotes:RadialMenuItem[]): void {
this.cancelPreviousEmote();
this.emoteMenu = new RadialMenu(this.scene, this.x, this.y, emotes)
this.emoteMenu.on(RadialMenuClickEvent, (item: RadialMenuItem) => {
this.closeEmoteMenu();
this.emit(requestEmoteEventName, item.name);
this.playEmote(item.name);
});
}
closeEmoteMenu(): void {
if (!this.emoteMenu) return;
this.emoteMenu.destroy();
this.emoteMenu = null;
}
destroy() {
this.scene.events.removeListener('postupdate', this.updateListener);
super.destroy();
}
}

View file

@ -0,0 +1,120 @@
import {TextField} from "../Components/TextField";
import Image = Phaser.GameObjects.Image;
import Sprite = Phaser.GameObjects.Sprite;
import Text = Phaser.GameObjects.Text;
import ScenePlugin = Phaser.Scenes.ScenePlugin;
import {WAError} from "./WAError";
export const ErrorSceneName = "ErrorScene";
enum Textures {
icon = "icon",
mainFont = "main_font"
}
export class ErrorScene extends Phaser.Scene {
private titleField!: TextField;
private subTitleField!: TextField;
private messageField!: Text;
private logo!: Image;
private cat!: Sprite;
private title!: string;
private subTitle!: string;
private message!: string;
constructor() {
super({
key: ErrorSceneName
});
}
init({title, subTitle, message}: { title?: string, subTitle?: string, message?: string }) {
this.title = title ? title : '';
this.subTitle = subTitle ? subTitle : '';
this.message = message ? message : '';
}
preload() {
this.load.image(Textures.icon, "resources/logos/tcm_full.png");
// Note: arcade.png from the Phaser 3 examples at: https://github.com/photonstorm/phaser3-examples/tree/master/public/assets/fonts/bitmap
this.load.bitmapFont(Textures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
this.load.spritesheet(
'cat',
'resources/characters/pipoya/Cat 01-1.png',
{frameWidth: 32, frameHeight: 32}
);
}
create() {
this.logo = new Image(this, this.game.renderer.width - 30, this.game.renderer.height - 20, Textures.icon);
this.add.existing(this.logo);
this.titleField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height / 2, this.title);
this.subTitleField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height / 2 + 24, this.subTitle);
this.messageField = this.add.text(this.game.renderer.width / 2, this.game.renderer.height / 2 + 48, this.message, {
fontFamily: 'Georgia, "Goudy Bookletter 1911", Times, serif',
fontSize: '10px'
});
this.messageField.setOrigin(0.5, 0.5);
this.cat = this.physics.add.sprite(this.game.renderer.width / 2, this.game.renderer.height / 2 - 32, 'cat', 6);
this.cat.flipY = true;
}
/**
* Displays the error page, with an error message matching the "error" parameters passed in.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public static showError(error: any, scene: ScenePlugin): void {
console.error(error);
if (typeof error === 'string' || error instanceof String) {
scene.start(ErrorSceneName, {
title: 'An error occurred',
subTitle: error
});
} else if (error instanceof WAError) {
scene.start(ErrorSceneName, {
title: error.title,
subTitle: error.subTitle,
message: error.details
});
} else if (error.response) {
// Axios HTTP error
// client received an error response (5xx, 4xx)
scene.start(ErrorSceneName, {
title: 'HTTP ' + error.response.status + ' - ' + error.response.statusText,
subTitle: 'An error occurred while accessing URL:',
message: error.response.config.url
});
} else if (error.request) {
// Axios HTTP error
// client never received a response, or request never left
scene.start(ErrorSceneName, {
title: 'Network error',
subTitle: error.message
});
} else if (error instanceof Error) {
// Error
scene.start(ErrorSceneName, {
title: 'An error occurred',
subTitle: error.name,
message: error.message
});
} else {
throw error;
}
}
/**
* Displays the error page, with an error message matching the "error" parameters passed in.
*/
public static startErrorPage(title: string, subTitle: string, message: string, scene: ScenePlugin): void {
scene.start(ErrorSceneName, {
title,
subTitle,
message
});
}
}

View file

@ -1,68 +0,0 @@
import {TextField} from "../Components/TextField";
import Image = Phaser.GameObjects.Image;
import Sprite = Phaser.GameObjects.Sprite;
import Text = Phaser.GameObjects.Text;
export const FourOFourSceneName = "FourOFourScene";
enum Textures {
icon = "icon",
mainFont = "main_font"
}
export class FourOFourScene extends Phaser.Scene {
private mapNotFoundField!: TextField;
private couldNotFindField!: TextField;
private fileNameField!: Text;
private logo!: Image;
private cat!: Sprite;
private file: string|undefined;
private url: string|undefined;
constructor() {
super({
key: FourOFourSceneName
});
}
init({ file, url }: { file?: string, url?: string }) {
this.file = file;
this.url = url;
}
preload() {
this.load.image(Textures.icon, "resources/logos/tcm_full.png");
// Note: arcade.png from the Phaser 3 examples at: https://github.com/photonstorm/phaser3-examples/tree/master/public/assets/fonts/bitmap
this.load.bitmapFont(Textures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
this.load.spritesheet(
'cat',
'resources/characters/pipoya/Cat 01-1.png',
{frameWidth: 32, frameHeight: 32}
);
}
create() {
this.logo = new Image(this, this.game.renderer.width - 30, this.game.renderer.height - 20, Textures.icon);
this.add.existing(this.logo);
this.mapNotFoundField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height / 2, "404 - File not found");
let text: string = '';
if (this.file !== undefined) {
text = "Could not load map"
}
if (this.url !== undefined) {
text = "Invalid URL"
}
this.couldNotFindField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height / 2 + 24, text);
const url = this.file ? this.file : this.url;
if (url !== undefined) {
this.fileNameField = this.add.text(this.game.renderer.width / 2, this.game.renderer.height / 2 + 38, url, { fontFamily: 'Georgia, "Goudy Bookletter 1911", Times, serif', fontSize: '10px' });
this.fileNameField.setOrigin(0.5, 0.5);
}
this.cat = this.physics.add.sprite(this.game.renderer.width / 2, this.game.renderer.height / 2 - 32, 'cat', 6);
this.cat.flipY=true;
}
}

View file

@ -0,0 +1,26 @@
export class WAError extends Error {
private _title: string;
private _subTitle: string;
private _details: string;
constructor (title: string, subTitle: string, details: string) {
super(title+' - '+subTitle+' - '+details);
this._title = title;
this._subTitle = subTitle;
this._details = details;
// Set the prototype explicitly.
Object.setPrototypeOf (this, WAError.prototype);
}
get title(): string {
return this._title;
}
get subTitle(): string {
return this._subTitle;
}
get details(): string {
return this._details;
}
}

View file

@ -0,0 +1,111 @@
interface Size {
width: number;
height: number;
}
export class HdpiManager {
private _zoomModifier: number = 1;
/**
*
* @param minRecommendedGamePixelsNumber The minimum number of pixels we want to display "by default" to the user
* @param absoluteMinPixelNumber The very minimum of game pixels to display. Below, we forbid zooming more
*/
public constructor(private minRecommendedGamePixelsNumber: number, private absoluteMinPixelNumber: number) {}
/**
* Returns the optimal size in "game pixels" based on the screen size in "real pixels".
*
* Note: the function is returning the optimal size in "game pixels" in the "game" property,
* but also recommends resizing the "real" pixel screen size of the canvas.
* The proposed new real size is a few pixels bigger than the real size available (if the size is not a multiple of the pixel size) and should overflow.
*
* @param realPixelScreenSize
*/
public getOptimalGameSize(realPixelScreenSize: Size): { game: Size, real: Size } {
const realPixelNumber = realPixelScreenSize.width * realPixelScreenSize.height;
// If the screen has not a definition small enough to match the minimum number of pixels we want to display,
// let's make the canvas the size of the screen (in real pixels)
if (realPixelNumber <= this.minRecommendedGamePixelsNumber) {
return {
game: realPixelScreenSize,
real: realPixelScreenSize
};
}
const optimalZoomLevel = this.getOptimalZoomLevel(realPixelNumber);
// Has the canvas more pixels than the screen? This is forbidden
if (optimalZoomLevel * this._zoomModifier < 1) {
// Let's reset the zoom modifier (WARNING this is a SIDE EFFECT in a getter)
this._zoomModifier = 1 / optimalZoomLevel;
return {
game: {
width: realPixelScreenSize.width,
height: realPixelScreenSize.height,
},
real: {
width: realPixelScreenSize.width,
height: realPixelScreenSize.height,
}
}
}
const gameWidth = Math.ceil(realPixelScreenSize.width / optimalZoomLevel / this._zoomModifier);
const gameHeight = Math.ceil(realPixelScreenSize.height / optimalZoomLevel / this._zoomModifier);
// Let's ensure we display a minimum of pixels, even if crazily zoomed in.
if (gameWidth * gameHeight < this.absoluteMinPixelNumber) {
const minGameHeight = Math.sqrt(this.absoluteMinPixelNumber * realPixelScreenSize.height / realPixelScreenSize.width);
const minGameWidth = Math.sqrt(this.absoluteMinPixelNumber * realPixelScreenSize.width / realPixelScreenSize.height);
// Let's reset the zoom modifier (WARNING this is a SIDE EFFECT in a getter)
this._zoomModifier = realPixelScreenSize.width / minGameWidth / optimalZoomLevel;
return {
game: {
width: minGameWidth,
height: minGameHeight,
},
real: {
width: realPixelScreenSize.width,
height: realPixelScreenSize.height,
}
}
}
return {
game: {
width: gameWidth,
height: gameHeight,
},
real: {
width: Math.ceil(realPixelScreenSize.width / optimalZoomLevel) * optimalZoomLevel,
height: Math.ceil(realPixelScreenSize.height / optimalZoomLevel) * optimalZoomLevel,
}
}
}
/**
* We only accept integer but we make an exception for 1.5
*/
private getOptimalZoomLevel(realPixelNumber: number): number {
const result = Math.sqrt(realPixelNumber / this.minRecommendedGamePixelsNumber);
if (1.5 <= result && result < 2) {
return 1.5
} else {
return Math.floor(result);
}
}
public get zoomModifier(): number {
return this._zoomModifier;
}
public set zoomModifier(zoomModifier: number) {
this._zoomModifier = zoomModifier;
}
}

View file

@ -0,0 +1,87 @@
import {HdpiManager} from "./HdpiManager";
import ScaleManager = Phaser.Scale.ScaleManager;
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
import type {Game} from "../Game/Game";
import {ResizableScene} from "../Login/ResizableScene";
import {HtmlUtils} from "../../WebRtc/HtmlUtils";
class WaScaleManager {
private hdpiManager: HdpiManager;
private scaleManager!: ScaleManager;
private game!: Game;
private actualZoom: number = 1;
private _saveZoom: number = 1;
public constructor(private minGamePixelsNumber: number, private absoluteMinPixelNumber: number) {
this.hdpiManager = new HdpiManager(minGamePixelsNumber, absoluteMinPixelNumber);
}
public setGame(game: Game): void {
this.scaleManager = game.scale;
this.game = game;
}
public applyNewSize() {
const {width, height} = coWebsiteManager.getGameSize();
let devicePixelRatio = 1;
if (window.devicePixelRatio) {
devicePixelRatio = window.devicePixelRatio;
}
const { game: gameSize, real: realSize } = this.hdpiManager.getOptimalGameSize({width: width * devicePixelRatio, height: height * devicePixelRatio});
this.actualZoom = realSize.width / gameSize.width / devicePixelRatio;
this.scaleManager.setZoom(realSize.width / gameSize.width / devicePixelRatio)
this.scaleManager.resize(gameSize.width, gameSize.height);
// Override bug in canvas resizing in Phaser. Let's resize the canvas ourselves
const style = this.scaleManager.canvas.style;
style.width = Math.ceil(realSize.width / devicePixelRatio) + 'px';
style.height = Math.ceil(realSize.height / devicePixelRatio) + 'px';
// Resize the game element at the same size at the canvas
const gameStyle = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('game').style;
gameStyle.width = style.width;
gameStyle.height = style.height;
// Note: onResize will be called twice (once here and once in Game.ts), but we have no better way.
for (const scene of this.game.scene.getScenes(true)) {
if (scene instanceof ResizableScene) {
// We are delaying the call to the "render" event because otherwise, the "camera" coordinates are not correctly updated.
scene.events.once(Phaser.Scenes.Events.RENDER, () => scene.onResize());
}
}
this.game.markDirty();
}
public get zoomModifier(): number {
return this.hdpiManager.zoomModifier;
}
public set zoomModifier(zoomModifier: number) {
this.hdpiManager.zoomModifier = zoomModifier;
this.applyNewSize();
}
public saveZoom(): void {
this._saveZoom = this.hdpiManager.zoomModifier;
}
public restoreZoom(): void{
this.hdpiManager.zoomModifier = this._saveZoom;
this.applyNewSize();
}
/**
* This is used to scale back the ui components to counter-act the zoom.
*/
public get uiScalingFactor(): number {
return this.actualZoom > 1 ? 1 : 1.2;
}
}
export const waScaleManager = new WaScaleManager(640*480, 196*196);

View file

@ -0,0 +1,41 @@
import {Pinch} from "phaser3-rex-plugins/plugins/gestures.js";
import {waScaleManager} from "../Services/WaScaleManager";
import {GameScene} from "../Game/GameScene";
export class PinchManager {
private scene: Phaser.Scene;
private pinch!: any; // eslint-disable-line
constructor(scene: Phaser.Scene) {
this.scene = scene;
this.pinch = new Pinch(scene);
this.pinch.setDragThreshold(10);
// The "pinch.scaleFactor" value is very sensitive and causes the screen to flicker.
// We are smoothing its value with previous values to prevent the flicking.
let smoothPinch = 1;
this.pinch.on('pinchstart', () => {
smoothPinch = 1;
});
this.pinch.on('pinch', (pinch:any) => { // eslint-disable-line
if (pinch.scaleFactor > 1.2 || pinch.scaleFactor < 0.8) {
// Pinch too fast! Probably a bad measure.
return;
}
smoothPinch = 3/5*smoothPinch + 2/5*pinch.scaleFactor;
if (this.scene instanceof GameScene) {
this.scene.zoomByFactor(smoothPinch);
} else {
waScaleManager.zoomModifier *= smoothPinch;
}
});
}
destroy() {
this.pinch.removeAllListeners();
}
}

View file

@ -1,4 +1,7 @@
import {GameScene} from "../Game/GameScene";
import type { Direction } from "../../types";
import type {GameScene} from "../Game/GameScene";
import {touchScreenManager} from "../../Touch/TouchScreenManager";
import {MobileJoystick} from "../Components/MobileJoystick";
interface UserInputManagerDatum {
keyInstance: Phaser.Input.Keyboard.Key;
@ -13,17 +16,25 @@ export enum UserInputEvent {
SpeedUp,
Interact,
Shout,
JoystickMove,
}
//we cannot the map structure so we have to create a replacment
//we cannot use a map structure so we have to create a replacment
export class ActiveEventList {
private KeysCode : Map<UserInputEvent, boolean> = new Map<UserInputEvent, boolean>();
private eventMap : Map<UserInputEvent, boolean> = new Map<UserInputEvent, boolean>();
get(event: UserInputEvent): boolean {
return this.KeysCode.get(event) || false;
return this.eventMap.get(event) || false;
}
set(event: UserInputEvent, value: boolean): void {
this.KeysCode.set(event, value);
this.eventMap.set(event, value);
}
forEach(callback: (value: boolean, key: UserInputEvent) => void): void {
this.eventMap.forEach(callback);
}
any(): boolean {
return Array.from(this.eventMap.values()).reduce((accu, curr) => accu || curr, false);
}
}
@ -31,10 +42,48 @@ export class ActiveEventList {
export class UserInputManager {
private KeysCode!: UserInputManagerDatum[];
private Scene: GameScene;
private isInputDisabled : boolean;
constructor(Scene : GameScene) {
private joystick!: MobileJoystick;
private joystickEvents = new ActiveEventList();
private joystickForceThreshold = 60;
private joystickForceAccuX = 0;
private joystickForceAccuY = 0;
constructor(Scene: GameScene) {
this.Scene = Scene;
this.isInputDisabled = false;
this.initKeyBoardEvent();
this.initMouseWheel();
if (touchScreenManager.supportTouchScreen) {
this.initVirtualJoystick();
}
}
initVirtualJoystick() {
this.joystick = new MobileJoystick(this.Scene);
this.joystick.on("update", () => {
this.joystickForceAccuX = this.joystick.forceX ? this.joystickForceAccuX : 0;
this.joystickForceAccuY = this.joystick.forceY ? this.joystickForceAccuY : 0;
const cursorKeys = this.joystick.createCursorKeys();
for (const name in cursorKeys) {
const key = cursorKeys[name as Direction];
switch (name) {
case "up":
this.joystickEvents.set(UserInputEvent.MoveUp, key.isDown);
break;
case "left":
this.joystickEvents.set(UserInputEvent.MoveLeft, key.isDown);
break;
case "down":
this.joystickEvents.set(UserInputEvent.MoveDown, key.isDown);
break;
case "right":
this.joystickEvents.set(UserInputEvent.MoveRight, key.isDown);
break;
}
}
});
}
initKeyBoardEvent(){
@ -59,14 +108,50 @@ export class UserInputManager {
];
}
clearAllInputKeyboard(){
this.Scene.input.keyboard.removeAllKeys();
clearAllListeners(){
this.Scene.input.keyboard.removeAllListeners();
}
//todo: should we also disable the joystick?
disableControls(){
this.Scene.input.keyboard.removeAllKeys();
this.isInputDisabled = true;
}
restoreControls(){
this.initKeyBoardEvent();
this.isInputDisabled = false;
}
getEventListForGameTick(): ActiveEventList {
const eventsMap = new ActiveEventList();
if (this.isInputDisabled) {
return eventsMap;
}
this.joystickEvents.forEach((value, key) => {
if (value) {
switch (key) {
case UserInputEvent.MoveUp:
case UserInputEvent.MoveDown:
this.joystickForceAccuY += this.joystick.forceY;
if (Math.abs(this.joystickForceAccuY) > this.joystickForceThreshold) {
eventsMap.set(key, value);
this.joystickForceAccuY = 0;
}
break;
case UserInputEvent.MoveLeft:
case UserInputEvent.MoveRight:
this.joystickForceAccuX += this.joystick.forceX;
if (Math.abs(this.joystickForceAccuX) > this.joystickForceThreshold) {
eventsMap.set(key, value);
this.joystickForceAccuX = 0;
}
break;
}
}
});
eventsMap.set(UserInputEvent.JoystickMove, this.joystickEvents.any());
this.KeysCode.forEach(d => {
if (d. keyInstance.isDown) {
if (d.keyInstance.isDown) {
eventsMap.set(d.event, true);
}
});
@ -86,4 +171,14 @@ export class UserInputManager {
removeSpaceEventListner(callback : Function){
this.Scene.input.keyboard.removeListener('keyup-SPACE', callback);
}
destroy(): void {
this.joystick?.destroy();
}
private initMouseWheel() {
this.Scene.input.on('wheel', (pointer: unknown, gameObjects: unknown, deltaX: number, deltaY: number, deltaZ: number) => {
this.Scene.zoomByFactor(1 - deltaY / 53 * 0.1);
});
}
}