From 69ff15ff7155fd4cec62adff81b2474a8d11860f Mon Sep 17 00:00:00 2001 From: Malte Riechmann Date: Fri, 29 Jan 2021 21:45:01 +0100 Subject: [PATCH 001/184] Run containers in the background using detached mode --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index faafed98..1ca5a48a 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Install Docker. Run: ``` -docker-compose up +docker-compose up -d ``` The environment will start. From 31a0d08c7681f67c48b46733ced48d2ba071ae9a Mon Sep 17 00:00:00 2001 From: TabascoEye Date: Sat, 30 Jan 2021 02:13:03 +0100 Subject: [PATCH 002/184] disable automatic gain control on microphone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AGC really makes the soundqulity way worse than it could be. Especially since it is so noticeable when entering a Jitsi where AGC is disabled (and suddenly some people sound much better). In the long run, this should probably be configurable. On the other hand the setting changes the audio _going out_ from a user, so it might be hard for him to judge which settings are a good or a bad idea… --- front/src/WebRtc/MediaManager.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 1a84d4a9..10a505cd 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -18,6 +18,12 @@ let videoConstraint: boolean|MediaTrackConstraints = { resizeMode: 'crop-and-scale', aspectRatio: 1.777777778 }; +let audioConstraint: boolean|MediaTrackConstraints = { + //TODO: make these values configurable in the game settings menu and store them in localstorage + autoGainControl: false, + echoCancellation: true, + noiseSuppression: false +}; export type UpdatedLocalStreamCallback = (media: MediaStream|null) => void; export type StartScreenSharingCallback = (media: MediaStream) => void; @@ -40,7 +46,7 @@ export class MediaManager { webrtcInAudio: HTMLAudioElement; private webrtcOutAudio: HTMLAudioElement; constraintsMedia : MediaStreamConstraints = { - audio: true, + audio: audioConstraint, video: videoConstraint }; updatedLocalStreamCallBacks : Set = new Set(); From 58b7d85bf3037f1b8de2b88e578da6dc37cfd3dc Mon Sep 17 00:00:00 2001 From: TabascoEye Date: Sat, 30 Jan 2021 02:24:36 +0100 Subject: [PATCH 003/184] bugfix and linting * use 'const' on constraints which never change * also re-enable constraints ater mic was re-enabled (after mute/unmute) --- front/src/WebRtc/MediaManager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 10a505cd..57879ca6 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -18,7 +18,7 @@ let videoConstraint: boolean|MediaTrackConstraints = { resizeMode: 'crop-and-scale', aspectRatio: 1.777777778 }; -let audioConstraint: boolean|MediaTrackConstraints = { +const audioConstraint: boolean|MediaTrackConstraints = { //TODO: make these values configurable in the game settings menu and store them in localstorage autoGainControl: false, echoCancellation: true, @@ -243,7 +243,7 @@ export class MediaManager { } public enableMicrophone() { - this.constraintsMedia.audio = true; + this.constraintsMedia.audio = audioConstraint; this.getCamera().then((stream) => { //TODO show error message tooltip upper of camera button From 80a5d2e30e29b340f25bd364caa24e7c3278e5e8 Mon Sep 17 00:00:00 2001 From: Johannes Berthel Date: Thu, 1 Apr 2021 00:33:05 +0200 Subject: [PATCH 004/184] added random companion to player --- front/src/Phaser/Companion/Companion.ts | 196 ++++++++++++++++++ .../src/Phaser/Companion/CompanionTextures.ts | 14 ++ .../CompanionTexturesLoadingManager.ts | 30 +++ front/src/Phaser/Player/Player.ts | 17 +- 4 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 front/src/Phaser/Companion/Companion.ts create mode 100644 front/src/Phaser/Companion/CompanionTextures.ts create mode 100644 front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts diff --git a/front/src/Phaser/Companion/Companion.ts b/front/src/Phaser/Companion/Companion.ts new file mode 100644 index 00000000..ab9b8be8 --- /dev/null +++ b/front/src/Phaser/Companion/Companion.ts @@ -0,0 +1,196 @@ +import Sprite = Phaser.GameObjects.Sprite; +import Container = Phaser.GameObjects.Container; +import { lazyLoadResource } from "./CompanionTexturesLoadingManager"; +import { PlayerAnimationDirections, PlayerAnimationTypes } from "../Player/Animation"; + +export class Companion extends Container { + public sprites: Map; + + private delta: number; + private invisible: boolean; + private target: { x: number, y: number }; + + constructor( + scene: Phaser.Scene, + x: number, + y: number + ) { + super(scene, x, y); + + this.delta = 0; + this.invisible = true; + this.target = { x, y }; + this.sprites = new Map(); + + const animal = ["dog1", "dog2", "dog3", "cat1", "cat2", "cat3"]; + const random = Math.floor(Math.random() * animal.length); + + lazyLoadResource(this.scene.load, animal[random]).then(resource => { + this.addResource(resource); + this.invisible = false; + }) + + this.scene.physics.world.enableBody(this); + + this.getBody().setImmovable(true); + this.getBody().setCollideWorldBounds(true); + this.setSize(16, 16); + this.getBody().setSize(16, 16); + this.getBody().setOffset(0, 8); + + this.setDepth(-1); + + scene.game.events.addListener('step', this.step.bind(this)); + scene.add.existing(this); + } + + public setTarget(x: number, y: number) { + this.target = { x, y }; + } + + private step(time: any, delta: any) { + if (typeof this.target === 'undefined') return; + + this.delta += delta; + if (this.delta < 256) { + return; + } + this.delta = 0; + + const xDist = this.target.x - this.x; + const yDist = this.target.y - this.y; + + let direction: PlayerAnimationDirections; + let type: PlayerAnimationTypes; + + const distance = Math.sqrt(Math.pow(Math.abs(xDist), 2) + Math.pow(Math.abs(yDist), 2)); + + if (distance < 16) { + type = PlayerAnimationTypes.Idle; + this.getBody().stop(); + } else { + type = 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), speed) * xDir, Math.min(Math.abs(yDist * 2), speed) * yDir); + } + + if (Math.abs(xDist) > Math.abs(yDist)) { + if (xDist < 0) { + direction = PlayerAnimationDirections.Left; + } else { + direction = PlayerAnimationDirections.Right; + } + } else { + if (yDist < 0) { + direction = PlayerAnimationDirections.Up; + } else { + direction = PlayerAnimationDirections.Down; + } + } + + this.setDepth(this.y); + this.playAnimation(direction, type); + } + + 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.game.events.removeListener('step', this.step.bind(this)); + } + + super.destroy(); + } +} diff --git a/front/src/Phaser/Companion/CompanionTextures.ts b/front/src/Phaser/Companion/CompanionTextures.ts new file mode 100644 index 00000000..84eaf38f --- /dev/null +++ b/front/src/Phaser/Companion/CompanionTextures.ts @@ -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" }, +] diff --git a/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts b/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts new file mode 100644 index 00000000..7023a34d --- /dev/null +++ b/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts @@ -0,0 +1,30 @@ +import LoaderPlugin = Phaser.Loader.LoaderPlugin; +import { TextureError } from "../../Exception/TextureError"; +import { COMPANION_RESOURCES, CompanionResourceDescriptionInterface } from "./CompanionTextures"; + +export const loadAll = (loader: LoaderPlugin): CompanionResourceDescriptionInterface[] => { + const resources = COMPANION_RESOURCES; + + resources.forEach((resource: CompanionResourceDescriptionInterface) => { + loader.spritesheet(resource.name, resource.img, { frameWidth: 32, frameHeight: 32, endFrame: 12 }); + }); + + return resources; +} + +export const lazyLoadResource = (loader: LoaderPlugin, name: string): Promise => { + const resource = COMPANION_RESOURCES.find(item => item.name === name); + + if (typeof resource === 'undefined') { + throw new TextureError(`Texture '${name}' not found!`); + } + + if (loader.textureManager.exists(resource.name)) { + return Promise.resolve(resource.name); + } + + return new Promise(resolve => { + loader.spritesheet(resource.name, resource.img, { frameWidth: 32, frameHeight: 32, endFrame: 12 }); + loader.once(`filecomplete-spritesheet-${resource.name}`, () => resolve(resource.name)); + }); +} diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index 64ba56d0..b7a2f7b4 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -2,7 +2,7 @@ import {PlayerAnimationDirections} from "./Animation"; import {GameScene} from "../Game/GameScene"; import {UserInputEvent, UserInputManager} from "../UserInput/UserInputManager"; import {Character} from "../Entity/Character"; - +import {Companion} from "../Companion/Companion"; export const hasMovedEventName = "hasMoved"; export interface CurrentGamerInterface extends Character{ @@ -13,6 +13,7 @@ export interface CurrentGamerInterface extends Character{ export class Player extends Character implements CurrentGamerInterface { private previousDirection: string = PlayerAnimationDirections.Down; private wasMoving: boolean = false; + private companion?: Companion; constructor( Scene: GameScene, @@ -28,6 +29,20 @@ export class Player extends Character implements CurrentGamerInterface { //the current player model should be push away by other players to prevent conflict this.getBody().setImmovable(false); + + this.addCompanion(); + } + + addCompanion(): void { + this.companion = new Companion(this.scene, this.x, this.y); + } + + move(x: number, y: number) { + super.move(x, y); + + if (this.companion) { + this.companion.setTarget(this.x, this.y); + } } moveUser(delta: number): void { From 5a91e1558088ecd2c2e1c8914e5c48f8d6e49c59 Mon Sep 17 00:00:00 2001 From: Johannes Berthel Date: Thu, 1 Apr 2021 18:51:51 +0200 Subject: [PATCH 005/184] add companion to remote player --- front/src/Phaser/Companion/Companion.ts | 47 ++++++++++++++----------- front/src/Phaser/Entity/Character.ts | 16 +++++++++ front/src/Phaser/Entity/RemotePlayer.ts | 4 +++ front/src/Phaser/Player/Player.ts | 16 --------- 4 files changed, 46 insertions(+), 37 deletions(-) diff --git a/front/src/Phaser/Companion/Companion.ts b/front/src/Phaser/Companion/Companion.ts index ab9b8be8..065b3a8d 100644 --- a/front/src/Phaser/Companion/Companion.ts +++ b/front/src/Phaser/Companion/Companion.ts @@ -8,18 +8,19 @@ export class Companion extends Container { private delta: number; private invisible: boolean; - private target: { x: number, y: number }; + private stepListener: Function; + private target: { x: number, y: number, direction: PlayerAnimationDirections }; constructor( scene: Phaser.Scene, x: number, y: number ) { - super(scene, x, y); + super(scene, x + 8, y + 8); this.delta = 0; this.invisible = true; - this.target = { x, y }; + this.target = { x, y, direction: PlayerAnimationDirections.Down }; this.sprites = new Map(); const animal = ["dog1", "dog2", "dog3", "cat1", "cat2", "cat3"]; @@ -40,19 +41,21 @@ export class Companion extends Container { this.setDepth(-1); - scene.game.events.addListener('step', this.step.bind(this)); + this.stepListener = this.step.bind(this); + + scene.game.events.addListener('step', this.stepListener); scene.add.existing(this); } - public setTarget(x: number, y: number) { - this.target = { x, y }; + public setTarget(x: number, y: number, direction: PlayerAnimationDirections) { + this.target = { x, y, direction }; } private step(time: any, delta: any) { if (typeof this.target === 'undefined') return; this.delta += delta; - if (this.delta < 256) { + if (this.delta < 128) { return; } this.delta = 0; @@ -63,10 +66,12 @@ export class Companion extends Container { let direction: PlayerAnimationDirections; let type: PlayerAnimationTypes; - const distance = Math.sqrt(Math.pow(Math.abs(xDist), 2) + Math.pow(Math.abs(yDist), 2)); + const distance = Math.sqrt(Math.pow(xDist, 2) + Math.pow(yDist, 2)); if (distance < 16) { type = PlayerAnimationTypes.Idle; + direction = this.target.direction; + this.getBody().stop(); } else { type = PlayerAnimationTypes.Walk; @@ -76,22 +81,22 @@ export class Companion extends Container { const speed = 256; this.getBody().setVelocity(Math.min(Math.abs(xDist * 2), speed) * xDir, Math.min(Math.abs(yDist * 2), speed) * yDir); - } - if (Math.abs(xDist) > Math.abs(yDist)) { - if (xDist < 0) { - direction = PlayerAnimationDirections.Left; + if (Math.abs(xDist) > Math.abs(yDist)) { + if (xDist < 0) { + direction = PlayerAnimationDirections.Left; + } else { + direction = PlayerAnimationDirections.Right; + } } else { - direction = PlayerAnimationDirections.Right; - } - } else { - if (yDist < 0) { - direction = PlayerAnimationDirections.Up; - } else { - direction = PlayerAnimationDirections.Down; + if (yDist < 0) { + direction = PlayerAnimationDirections.Up; + } else { + direction = PlayerAnimationDirections.Down; + } } } - + this.setDepth(this.y); this.playAnimation(direction, type); } @@ -188,7 +193,7 @@ export class Companion extends Container { } if (this.scene) { - this.scene.game.events.removeListener('step', this.step.bind(this)); + this.scene.game.events.removeListener('step', this.stepListener); } super.destroy(); diff --git a/front/src/Phaser/Entity/Character.ts b/front/src/Phaser/Entity/Character.ts index 797616f8..352494f8 100644 --- a/front/src/Phaser/Entity/Character.ts +++ b/front/src/Phaser/Entity/Character.ts @@ -4,6 +4,7 @@ import BitmapText = Phaser.GameObjects.BitmapText; import Container = Phaser.GameObjects.Container; import Sprite = Phaser.GameObjects.Sprite; import {TextureError} from "../../Exception/TextureError"; +import {Companion} from "../Companion/Companion"; interface AnimationData { key: string; @@ -21,6 +22,7 @@ export abstract class Character extends Container { private lastDirection: PlayerAnimationDirections = PlayerAnimationDirections.Down; //private teleportation: Sprite; private invisible: boolean; + public companion?: Companion; constructor(scene: Phaser.Scene, x: number, @@ -67,6 +69,12 @@ export abstract class Character extends Container { this.setDepth(-1); this.playAnimation(direction, moving); + + this.addCompanion(); + } + + private addCompanion(): void { + this.companion = new Companion(this.scene, this.x, this.y); } public addTextures(textures: string[], frame?: string | number): void { @@ -189,6 +197,10 @@ export abstract class Character extends Container { } this.setDepth(this.y); + + if (this.companion) { + this.companion.setTarget(this.x, this.y, this.lastDirection); + } } stop(){ @@ -215,5 +227,9 @@ export abstract class Character extends Container { } super.destroy(); this.playerName.destroy(); + + if (this.companion) { + this.companion.destroy(); + } } } diff --git a/front/src/Phaser/Entity/RemotePlayer.ts b/front/src/Phaser/Entity/RemotePlayer.ts index a6bd4f40..4fe18fc6 100644 --- a/front/src/Phaser/Entity/RemotePlayer.ts +++ b/front/src/Phaser/Entity/RemotePlayer.ts @@ -31,5 +31,9 @@ export class RemotePlayer extends Character { 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); + } } } diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index b7a2f7b4..ac8942a5 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -2,7 +2,6 @@ import {PlayerAnimationDirections} from "./Animation"; import {GameScene} from "../Game/GameScene"; import {UserInputEvent, UserInputManager} from "../UserInput/UserInputManager"; import {Character} from "../Entity/Character"; -import {Companion} from "../Companion/Companion"; export const hasMovedEventName = "hasMoved"; export interface CurrentGamerInterface extends Character{ @@ -13,7 +12,6 @@ export interface CurrentGamerInterface extends Character{ export class Player extends Character implements CurrentGamerInterface { private previousDirection: string = PlayerAnimationDirections.Down; private wasMoving: boolean = false; - private companion?: Companion; constructor( Scene: GameScene, @@ -29,20 +27,6 @@ export class Player extends Character implements CurrentGamerInterface { //the current player model should be push away by other players to prevent conflict this.getBody().setImmovable(false); - - this.addCompanion(); - } - - addCompanion(): void { - this.companion = new Companion(this.scene, this.x, this.y); - } - - move(x: number, y: number) { - super.move(x, y); - - if (this.companion) { - this.companion.setTarget(this.x, this.y); - } } moveUser(delta: number): void { From 2ad712807ba13e9f32d974a284ba2961fb3b15ae Mon Sep 17 00:00:00 2001 From: Johannes Berthel Date: Fri, 2 Apr 2021 17:14:34 +0200 Subject: [PATCH 006/184] add companion only on local player --- front/src/Phaser/Companion/Companion.ts | 70 ++++++++++++++++--------- front/src/Phaser/Entity/Character.ts | 4 +- front/src/Phaser/Player/Player.ts | 7 +++ 3 files changed, 53 insertions(+), 28 deletions(-) diff --git a/front/src/Phaser/Companion/Companion.ts b/front/src/Phaser/Companion/Companion.ts index 065b3a8d..60e58995 100644 --- a/front/src/Phaser/Companion/Companion.ts +++ b/front/src/Phaser/Companion/Companion.ts @@ -3,14 +3,25 @@ import Container = Phaser.GameObjects.Container; import { lazyLoadResource } from "./CompanionTexturesLoadingManager"; 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; private delta: number; private invisible: boolean; - private stepListener: Function; private target: { x: number, y: number, direction: PlayerAnimationDirections }; + private companionName: string; + private direction: PlayerAnimationDirections; + private animationType: PlayerAnimationTypes; + constructor( scene: Phaser.Scene, x: number, @@ -18,15 +29,22 @@ export class Companion extends Container { ) { super(scene, x + 8, y + 8); + this.sprites = new Map(); + this.delta = 0; this.invisible = true; this.target = { x, y, direction: PlayerAnimationDirections.Down }; - this.sprites = new Map(); + this.direction = PlayerAnimationDirections.Down; + this.animationType = PlayerAnimationTypes.Idle; + + // select random animal const animal = ["dog1", "dog2", "dog3", "cat1", "cat2", "cat3"]; const random = Math.floor(Math.random() * animal.length); - lazyLoadResource(this.scene.load, animal[random]).then(resource => { + this.companionName = animal[random]; + + lazyLoadResource(this.scene.load, this.companionName).then(resource => { this.addResource(resource); this.invisible = false; }) @@ -34,16 +52,13 @@ export class Companion extends Container { this.scene.physics.world.enableBody(this); this.getBody().setImmovable(true); - this.getBody().setCollideWorldBounds(true); + this.getBody().setCollideWorldBounds(false); this.setSize(16, 16); this.getBody().setSize(16, 16); this.getBody().setOffset(0, 8); this.setDepth(-1); - this.stepListener = this.step.bind(this); - - scene.game.events.addListener('step', this.stepListener); scene.add.existing(this); } @@ -51,7 +66,7 @@ export class Companion extends Container { this.target = { x, y, direction }; } - private step(time: any, delta: any) { + public step(delta: number) { if (typeof this.target === 'undefined') return; this.delta += delta; @@ -63,18 +78,15 @@ export class Companion extends Container { const xDist = this.target.x - this.x; const yDist = this.target.y - this.y; - let direction: PlayerAnimationDirections; - let type: PlayerAnimationTypes; + const distance = Math.pow(xDist, 2) + Math.pow(yDist, 2); - const distance = Math.sqrt(Math.pow(xDist, 2) + Math.pow(yDist, 2)); - - if (distance < 16) { - type = PlayerAnimationTypes.Idle; - direction = this.target.direction; + if (distance < 576) { // 24^2 + this.animationType = PlayerAnimationTypes.Idle; + this.direction = this.target.direction; this.getBody().stop(); } else { - type = PlayerAnimationTypes.Walk; + this.animationType = PlayerAnimationTypes.Walk; const xDir = xDist / Math.max(Math.abs(xDist), 1); const yDir = yDist / Math.max(Math.abs(yDist), 1); @@ -84,21 +96,33 @@ export class Companion extends Container { if (Math.abs(xDist) > Math.abs(yDist)) { if (xDist < 0) { - direction = PlayerAnimationDirections.Left; + this.direction = PlayerAnimationDirections.Left; } else { - direction = PlayerAnimationDirections.Right; + this.direction = PlayerAnimationDirections.Right; } } else { if (yDist < 0) { - direction = PlayerAnimationDirections.Up; + this.direction = PlayerAnimationDirections.Up; } else { - direction = PlayerAnimationDirections.Down; + this.direction = PlayerAnimationDirections.Down; } } } this.setDepth(this.y); - this.playAnimation(direction, type); + 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 { @@ -192,10 +216,6 @@ export class Companion extends Container { } } - if (this.scene) { - this.scene.game.events.removeListener('step', this.stepListener); - } - super.destroy(); } } diff --git a/front/src/Phaser/Entity/Character.ts b/front/src/Phaser/Entity/Character.ts index 352494f8..f329192c 100644 --- a/front/src/Phaser/Entity/Character.ts +++ b/front/src/Phaser/Entity/Character.ts @@ -69,11 +69,9 @@ export abstract class Character extends Container { this.setDepth(-1); this.playAnimation(direction, moving); - - this.addCompanion(); } - private addCompanion(): void { + public addCompanion(): void { this.companion = new Companion(this.scene, this.x, this.y); } diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index ac8942a5..af63ddb3 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -27,6 +27,8 @@ export class Player extends Character implements CurrentGamerInterface { //the current player model should be push away by other players to prevent conflict this.getBody().setImmovable(false); + + this.addCompanion(); } moveUser(delta: number): void { @@ -58,6 +60,11 @@ export class Player extends Character implements CurrentGamerInterface { direction = PlayerAnimationDirections.Right; moving = true; } + + if (this.companion) { + this.companion.step(delta); + } + if (x !== 0 || y !== 0) { this.move(x, y); this.emit(hasMovedEventName, {moving, direction, x: this.x, y: this.y}); From c07079051a39d0cddf40e1d4846d8490840630a2 Mon Sep 17 00:00:00 2001 From: Johannes Berthel Date: Fri, 2 Apr 2021 21:21:11 +0200 Subject: [PATCH 007/184] transmit companion to remote players --- back/src/Model/GameRoom.ts | 3 ++- back/src/Model/User.ts | 5 +++-- back/src/Services/SocketManager.ts | 2 ++ front/src/Connexion/ConnectionManager.ts | 6 +++--- front/src/Connexion/ConnexionModels.ts | 4 +++- front/src/Connexion/LocalUserStore.ts | 8 ++++++++ front/src/Connexion/RoomConnection.ts | 11 ++++++++-- front/src/Phaser/Companion/Companion.ts | 20 ++++++++----------- front/src/Phaser/Entity/Character.ts | 4 ++-- front/src/Phaser/Entity/RemotePlayer.ts | 9 +++++++-- front/src/Phaser/Game/AddPlayerInterface.ts | 1 + front/src/Phaser/Game/GameManager.ts | 5 +++++ front/src/Phaser/Game/GameScene.ts | 16 ++++++++++----- front/src/Phaser/Player/Player.ts | 11 +++++----- messages/protos/messages.proto | 7 +++++++ pusher/src/Controller/IoSocketController.ts | 12 ++++++++++- .../src/Model/Websocket/ExSocketInterface.ts | 2 ++ pusher/src/Model/Zone.ts | 8 +++++--- pusher/src/Services/SocketManager.ts | 2 ++ 19 files changed, 96 insertions(+), 40 deletions(-) diff --git a/back/src/Model/GameRoom.ts b/back/src/Model/GameRoom.ts index 6a592ed0..430dbc48 100644 --- a/back/src/Model/GameRoom.ts +++ b/back/src/Model/GameRoom.ts @@ -110,7 +110,8 @@ export class GameRoom { socket, joinRoomMessage.getTagList(), joinRoomMessage.getName(), - ProtobufUtils.toCharacterLayerObjects(joinRoomMessage.getCharacterlayerList()) + ProtobufUtils.toCharacterLayerObjects(joinRoomMessage.getCharacterlayerList()), + joinRoomMessage.getCompanion() ); this.nextUserId++; this.users.set(user.id, user); diff --git a/back/src/Model/User.ts b/back/src/Model/User.ts index 51a1a617..370781e5 100644 --- a/back/src/Model/User.ts +++ b/back/src/Model/User.ts @@ -4,7 +4,7 @@ import {Zone} from "_Model/Zone"; import {Movable} from "_Model/Movable"; import {PositionNotifier} from "_Model/PositionNotifier"; import {ServerDuplexStream} from "grpc"; -import {BatchMessage, PusherToBackMessage, ServerToClientMessage, SubMessage} from "../Messages/generated/messages_pb"; +import {BatchMessage, Companion, PusherToBackMessage, ServerToClientMessage, SubMessage} from "../Messages/generated/messages_pb"; import {CharacterLayer} from "_Model/Websocket/CharacterLayer"; export type UserSocket = ServerDuplexStream; @@ -23,7 +23,8 @@ export class User implements Movable { public readonly socket: UserSocket, public readonly tags: string[], public readonly name: string, - public readonly characterLayers: CharacterLayer[] + public readonly characterLayers: CharacterLayer[], + public readonly companion?: Companion ) { this.listenedZones = new Set(); diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index c03f4773..166622f9 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -325,6 +325,7 @@ export class SocketManager { userJoinedZoneMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers)); userJoinedZoneMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition())); userJoinedZoneMessage.setFromzone(this.toProtoZone(fromZone)); + userJoinedZoneMessage.setCompanion(thing.companion); const subMessage = new SubToPusherMessage(); subMessage.setUserjoinedzonemessage(userJoinedZoneMessage); @@ -634,6 +635,7 @@ export class SocketManager { userJoinedMessage.setName(thing.name); userJoinedMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers)); userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition())); + userJoinedMessage.setCompanion(thing.companion); const subMessage = new SubToPusherMessage(); subMessage.setUserjoinedzonemessage(userJoinedMessage); diff --git a/front/src/Connexion/ConnectionManager.ts b/front/src/Connexion/ConnectionManager.ts index ed920aa0..e1e0db0a 100644 --- a/front/src/Connexion/ConnectionManager.ts +++ b/front/src/Connexion/ConnectionManager.ts @@ -88,9 +88,9 @@ class ConnectionManager { this.localUser = new LocalUser('', 'test', []); } - public connectToRoomSocket(roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface): Promise { + public connectToRoomSocket(roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface, companion: string|null): Promise { return new Promise((resolve, reject) => { - const connection = new RoomConnection(this.localUser.jwtToken, roomId, name, characterLayers, position, viewport); + const connection = new RoomConnection(this.localUser.jwtToken, roomId, name, characterLayers, position, viewport, companion); connection.onConnectError((error: object) => { console.log('An error occurred while connecting to socket server. Retrying'); reject(error); @@ -111,7 +111,7 @@ class ConnectionManager { this.reconnectingTimeout = setTimeout(() => { //todo: allow a way to break recursion? //todo: find a way to avoid recursive function. Otherwise, the call stack will grow indefinitely. - this.connectToRoomSocket(roomId, name, characterLayers, position, viewport).then((connection) => resolve(connection)); + this.connectToRoomSocket(roomId, name, characterLayers, position, viewport, companion).then((connection) => resolve(connection)); }, 4000 + Math.floor(Math.random() * 2000) ); }); }); diff --git a/front/src/Connexion/ConnexionModels.ts b/front/src/Connexion/ConnexionModels.ts index 519afcd3..477e86e3 100644 --- a/front/src/Connexion/ConnexionModels.ts +++ b/front/src/Connexion/ConnexionModels.ts @@ -47,6 +47,7 @@ export interface MessageUserPositionInterface { name: string; characterLayers: BodyResourceDescriptionInterface[]; position: PointInterface; + companion: string|null; } export interface MessageUserMovedInterface { @@ -58,7 +59,8 @@ export interface MessageUserJoined { userId: number; name: string; characterLayers: BodyResourceDescriptionInterface[]; - position: PointInterface + position: PointInterface; + companion: string|null; } export interface PositionInterface { diff --git a/front/src/Connexion/LocalUserStore.ts b/front/src/Connexion/LocalUserStore.ts index 702df561..4989a9ea 100644 --- a/front/src/Connexion/LocalUserStore.ts +++ b/front/src/Connexion/LocalUserStore.ts @@ -4,6 +4,7 @@ const playerNameKey = 'playerName'; const selectedPlayerKey = 'selectedPlayer'; const customCursorPositionKey = 'customCursorPosition'; const characterLayersKey = 'characterLayers'; +const companionKey = 'companion'; const gameQualityKey = 'gameQuality'; const videoQualityKey = 'videoQuality'; const audioPlayerVolumeKey = 'audioVolume'; @@ -47,6 +48,13 @@ class LocalUserStore { return JSON.parse(localStorage.getItem(characterLayersKey) || "null"); } + setCompanion(companion: string): void { + localStorage.setItem(companionKey, companion); + } + getCompanion(): string|null { + return localStorage.getItem(companionKey); + } + setGameQualityValue(value: number): void { localStorage.setItem(gameQualityKey, '' + value); } diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index 584dae06..391d227d 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -66,7 +66,7 @@ export class RoomConnection implements RoomConnection { * @param token A JWT token containing the UUID of the user * @param roomId The ID of the room in the form "_/[instance]/[map_url]" or "@/[org]/[event]/[map]" */ - public constructor(token: string|null, roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface) { + public constructor(token: string|null, roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface, companion: string|null) { let url = API_URL.replace('http://', 'ws://').replace('https://', 'wss://'); url += '/room'; url += '?roomId='+(roomId ?encodeURIComponent(roomId):''); @@ -81,6 +81,10 @@ export class RoomConnection implements RoomConnection { url += '&bottom='+Math.floor(viewport.bottom); url += '&left='+Math.floor(viewport.left); url += '&right='+Math.floor(viewport.right); + + if (typeof companion === 'string') { + url += '&companion='+encodeURIComponent(companion); + } if (RoomConnection.websocketFactory) { this.socket = RoomConnection.websocketFactory(url); @@ -316,11 +320,14 @@ export class RoomConnection implements RoomConnection { } }) + const companion = message.getCompanion(); + return { userId: message.getUserid(), name: message.getName(), characterLayers, - position: ProtobufClientUtils.toPointInterface(position) + position: ProtobufClientUtils.toPointInterface(position), + companion: companion ? companion.getName() : null } } diff --git a/front/src/Phaser/Companion/Companion.ts b/front/src/Phaser/Companion/Companion.ts index 60e58995..196da09e 100644 --- a/front/src/Phaser/Companion/Companion.ts +++ b/front/src/Phaser/Companion/Companion.ts @@ -22,11 +22,7 @@ export class Companion extends Container { private direction: PlayerAnimationDirections; private animationType: PlayerAnimationTypes; - constructor( - scene: Phaser.Scene, - x: number, - y: number - ) { + constructor(scene: Phaser.Scene, x: number, y: number, name: string) { super(scene, x + 8, y + 8); this.sprites = new Map(); @@ -38,11 +34,7 @@ export class Companion extends Container { this.direction = PlayerAnimationDirections.Down; this.animationType = PlayerAnimationTypes.Idle; - // select random animal - const animal = ["dog1", "dog2", "dog3", "cat1", "cat2", "cat3"]; - const random = Math.floor(Math.random() * animal.length); - - this.companionName = animal[random]; + this.companionName = name; lazyLoadResource(this.scene.load, this.companionName).then(resource => { this.addResource(resource); @@ -59,14 +51,16 @@ export class Companion extends Container { this.setDepth(-1); - scene.add.existing(this); + this.scene.events.addListener('update', this.step, this); + + this.scene.add.existing(this); } public setTarget(x: number, y: number, direction: PlayerAnimationDirections) { this.target = { x, y, direction }; } - public step(delta: number) { + public step(time: number, delta: number) { if (typeof this.target === 'undefined') return; this.delta += delta; @@ -216,6 +210,8 @@ export class Companion extends Container { } } + this.scene.events.removeListener('update', this.step, this); + super.destroy(); } } diff --git a/front/src/Phaser/Entity/Character.ts b/front/src/Phaser/Entity/Character.ts index f329192c..eb8e45fd 100644 --- a/front/src/Phaser/Entity/Character.ts +++ b/front/src/Phaser/Entity/Character.ts @@ -71,8 +71,8 @@ export abstract class Character extends Container { this.playAnimation(direction, moving); } - public addCompanion(): void { - this.companion = new Companion(this.scene, this.x, this.y); + public addCompanion(name: string): void { + this.companion = new Companion(this.scene, this.x, this.y, name); } public addTextures(textures: string[], frame?: string | number): void { diff --git a/front/src/Phaser/Entity/RemotePlayer.ts b/front/src/Phaser/Entity/RemotePlayer.ts index 4fe18fc6..b405d8df 100644 --- a/front/src/Phaser/Entity/RemotePlayer.ts +++ b/front/src/Phaser/Entity/RemotePlayer.ts @@ -17,12 +17,17 @@ export class RemotePlayer extends Character { name: string, texturesPromise: Promise, direction: PlayerAnimationDirections, - moving: boolean + moving: boolean, + companion: string|null ) { super(Scene, x, y, texturesPromise, name, direction, moving, 1); - + //set data this.userId = userId; + + if (typeof companion === 'string') { + this.addCompanion(companion); + } } updatePosition(position: PointInterface): void { diff --git a/front/src/Phaser/Game/AddPlayerInterface.ts b/front/src/Phaser/Game/AddPlayerInterface.ts index 0edf197e..96762a66 100644 --- a/front/src/Phaser/Game/AddPlayerInterface.ts +++ b/front/src/Phaser/Game/AddPlayerInterface.ts @@ -6,4 +6,5 @@ export interface AddPlayerInterface { name: string; characterLayers: BodyResourceDescriptionInterface[]; position: PointInterface; + companion: string|null; } diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts index 8751796f..fbe5102c 100644 --- a/front/src/Phaser/Game/GameManager.ts +++ b/front/src/Phaser/Game/GameManager.ts @@ -21,12 +21,14 @@ 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 { @@ -63,6 +65,9 @@ export class GameManager { return this.characterLayers; } + getCompanion(): string|null { + return this.companion; + } public async loadMap(room: Room, scenePlugin: Phaser.Scenes.ScenePlugin): Promise { const roomID = room.id; diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 51388169..222f7ed5 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -159,6 +159,7 @@ export class GameScene extends ResizableScene implements CenterListener { private openChatIcon!: OpenChatIcon; private playerName!: string; private characterLayers!: string[]; + private companion!: string|null; private messageSubscription: Subscription|null = null; private popUpElements : Map = new Map(); @@ -335,7 +336,7 @@ export class GameScene extends ResizableScene implements CenterListener { } this.playerName = playerName; this.characterLayers = gameManager.getCharacterLayers(); - + this.companion = gameManager.getCompanion(); //initalise map this.Map = this.add.tilemap(this.MapUrlFile); @@ -459,7 +460,9 @@ export class GameScene extends ResizableScene implements CenterListener { top: camera.scrollY, right: camera.scrollX + camera.width, bottom: camera.scrollY + camera.height, - }).then((onConnect: OnConnectInterface) => { + }, + this.companion + ).then((onConnect: OnConnectInterface) => { this.connection = onConnect.connection; this.connection.onUserJoins((message: MessageUserJoined) => { @@ -467,7 +470,8 @@ export class GameScene extends ResizableScene implements CenterListener { userId: message.userId, characterLayers: message.characterLayers, name: message.name, - position: message.position + position: message.position, + companion: message.companion } this.addPlayer(userMessage); }); @@ -1019,7 +1023,8 @@ ${escapedMessage} texturesPromise, PlayerAnimationDirections.Down, false, - this.userInputManager + this.userInputManager, + this.companion ); }catch (err){ if(err instanceof TextureError) { @@ -1211,7 +1216,8 @@ ${escapedMessage} addPlayerData.name, texturesPromise, addPlayerData.position.direction as PlayerAnimationDirections, - addPlayerData.position.moving + addPlayerData.position.moving, + addPlayerData.companion ); this.MapPlayers.add(player); this.MapPlayersByKey.set(player.userId, player); diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index af63ddb3..d018a41f 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -21,14 +21,17 @@ export class Player extends Character implements CurrentGamerInterface { texturesPromise: Promise, direction: PlayerAnimationDirections, moving: boolean, - private userInputManager: UserInputManager + private userInputManager: UserInputManager, + companion: string|null ) { super(Scene, x, y, texturesPromise, name, direction, moving, 1); //the current player model should be push away by other players to prevent conflict this.getBody().setImmovable(false); - this.addCompanion(); + if (typeof companion === 'string') { + this.addCompanion(companion); + } } moveUser(delta: number): void { @@ -61,10 +64,6 @@ export class Player extends Character implements CurrentGamerInterface { moving = true; } - if (this.companion) { - this.companion.step(delta); - } - if (x !== 0 || y !== 0) { this.move(x, y); this.emit(hasMovedEventName, {moving, direction, x: this.x, y: this.y}); diff --git a/messages/protos/messages.proto b/messages/protos/messages.proto index cc23ed24..eee3be86 100644 --- a/messages/protos/messages.proto +++ b/messages/protos/messages.proto @@ -36,6 +36,10 @@ message CharacterLayerMessage { string name = 2; } +message Companion { + string name = 1; +} + /*********** CLIENT TO SERVER MESSAGES *************/ message PingMessage { @@ -141,6 +145,7 @@ message UserJoinedMessage { string name = 2; repeated CharacterLayerMessage characterLayers = 3; PositionMessage position = 4; + Companion companion = 5; } message UserLeftMessage { @@ -243,6 +248,7 @@ message JoinRoomMessage { string roomId = 5; repeated string tag = 6; string IPAddress = 7; + Companion companion = 8; } message UserJoinedZoneMessage { @@ -251,6 +257,7 @@ message UserJoinedZoneMessage { repeated CharacterLayerMessage characterLayers = 3; PositionMessage position = 4; Zone fromZone = 5; + Companion companion = 6; } message UserLeftZoneMessage { diff --git a/pusher/src/Controller/IoSocketController.ts b/pusher/src/Controller/IoSocketController.ts index 85a80e11..55a8e032 100644 --- a/pusher/src/Controller/IoSocketController.ts +++ b/pusher/src/Controller/IoSocketController.ts @@ -12,7 +12,7 @@ import { WebRtcSignalToServerMessage, PlayGlobalMessage, ReportPlayerMessage, - QueryJitsiJwtMessage, SendUserMessage, ServerToClientMessage + QueryJitsiJwtMessage, SendUserMessage, ServerToClientMessage, Companion } from "../Messages/generated/messages_pb"; import {UserMovesMessage} from "../Messages/generated/messages_pb"; import {TemplatedApp} from "uWebSockets.js" @@ -138,6 +138,14 @@ export class IoSocketController { const left = Number(query.left); const right = Number(query.right); const name = query.name; + + let companion: Companion|undefined = undefined; + + if (typeof query.companion === 'string') { + companion = new Companion(); + companion.setName(query.companion); + } + if (typeof name !== 'string') { throw new Error('Expecting name'); } @@ -221,6 +229,7 @@ export class IoSocketController { IPAddress, roomId, name, + companion, characterLayers: characterLayerObjs, messages: memberMessages, tags: memberTags, @@ -350,6 +359,7 @@ export class IoSocketController { client.tags = ws.tags; client.textures = ws.textures; client.characterLayers = ws.characterLayers; + client.companion = ws.companion; client.roomId = ws.roomId; client.listenedZones = new Set(); return client; diff --git a/pusher/src/Model/Websocket/ExSocketInterface.ts b/pusher/src/Model/Websocket/ExSocketInterface.ts index 56e7e5ca..135c6f10 100644 --- a/pusher/src/Model/Websocket/ExSocketInterface.ts +++ b/pusher/src/Model/Websocket/ExSocketInterface.ts @@ -3,6 +3,7 @@ import {Identificable} from "./Identificable"; import {ViewportInterface} from "_Model/Websocket/ViewportMessage"; import { BatchMessage, + Companion, PusherToBackMessage, ServerToClientMessage, SubMessage @@ -29,6 +30,7 @@ export interface ExSocketInterface extends WebSocket, Identificable { characterLayers: CharacterLayer[]; position: PointInterface; viewport: ViewportInterface; + companion?: Companion; /** * Pushes an event that will be sent in the next batch of events */ diff --git a/pusher/src/Model/Zone.ts b/pusher/src/Model/Zone.ts index 12d27ff3..a54481a5 100644 --- a/pusher/src/Model/Zone.ts +++ b/pusher/src/Model/Zone.ts @@ -5,7 +5,8 @@ import { CharacterLayerMessage, GroupLeftZoneMessage, GroupUpdateMessage, GroupUpdateZoneMessage, PointMessage, PositionMessage, UserJoinedMessage, UserJoinedZoneMessage, UserLeftZoneMessage, UserMovedMessage, - ZoneMessage + ZoneMessage, + Companion } from "../Messages/generated/messages_pb"; import * as messages_pb from "../Messages/generated/messages_pb"; import {ClientReadableStream} from "grpc"; @@ -30,7 +31,7 @@ export type MovesCallback = (thing: Movable, position: PositionInterface, listen export type LeavesCallback = (thing: Movable, listener: User) => void;*/ export class UserDescriptor { - private constructor(public readonly userId: number, private name: string, private characterLayers: CharacterLayerMessage[], private position: PositionMessage) { + private constructor(public readonly userId: number, private name: string, private characterLayers: CharacterLayerMessage[], private position: PositionMessage, private companion?: Companion) { if (!Number.isInteger(this.userId)) { throw new Error('UserDescriptor.userId is not an integer: '+this.userId); } @@ -41,7 +42,7 @@ export class UserDescriptor { if (position === undefined) { throw new Error('Missing position'); } - return new UserDescriptor(message.getUserid(), message.getName(), message.getCharacterlayersList(), position); + return new UserDescriptor(message.getUserid(), message.getName(), message.getCharacterlayersList(), position, message.getCompanion()); } public update(userMovedMessage: UserMovedMessage) { @@ -59,6 +60,7 @@ export class UserDescriptor { userJoinedMessage.setName(this.name); userJoinedMessage.setCharacterlayersList(this.characterLayers); userJoinedMessage.setPosition(this.position); + userJoinedMessage.setCompanion(this.companion) return userJoinedMessage; } diff --git a/pusher/src/Services/SocketManager.ts b/pusher/src/Services/SocketManager.ts index 9b698e38..d555a59c 100644 --- a/pusher/src/Services/SocketManager.ts +++ b/pusher/src/Services/SocketManager.ts @@ -153,6 +153,8 @@ export class SocketManager implements ZoneEventListener { joinRoomMessage.setName(client.name); joinRoomMessage.setPositionmessage(ProtobufUtils.toPositionMessage(client.position)); joinRoomMessage.setTagList(client.tags); + joinRoomMessage.setCompanion(client.companion); + for (const characterLayer of client.characterLayers) { const characterLayerMessage = new CharacterLayerMessage(); characterLayerMessage.setName(characterLayer.name); From fc3a503bcf1e4c35847cc89077a96adecb4fb3a9 Mon Sep 17 00:00:00 2001 From: Johannes Berthel Date: Fri, 2 Apr 2021 21:26:24 +0200 Subject: [PATCH 008/184] don't fail if companion texture is not found --- front/src/Phaser/Companion/Companion.ts | 10 ++++++---- .../CompanionTexturesLoadingManager.ts | 20 +++++++++---------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/front/src/Phaser/Companion/Companion.ts b/front/src/Phaser/Companion/Companion.ts index 196da09e..407b7326 100644 --- a/front/src/Phaser/Companion/Companion.ts +++ b/front/src/Phaser/Companion/Companion.ts @@ -36,10 +36,12 @@ export class Companion extends Container { this.companionName = name; - lazyLoadResource(this.scene.load, this.companionName).then(resource => { - this.addResource(resource); - this.invisible = false; - }) + lazyLoadResource(this.scene.load, this.companionName) + .then(resource => { + this.addResource(resource); + this.invisible = false; + }) + .catch(error => console.error(error)); this.scene.physics.world.enableBody(this); diff --git a/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts b/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts index 7023a34d..126af219 100644 --- a/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts +++ b/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts @@ -13,17 +13,17 @@ export const loadAll = (loader: LoaderPlugin): CompanionResourceDescriptionInter } export const lazyLoadResource = (loader: LoaderPlugin, name: string): Promise => { - const resource = COMPANION_RESOURCES.find(item => item.name === name); + return new Promise((resolve, reject) => { + const resource = COMPANION_RESOURCES.find(item => item.name === name); - if (typeof resource === 'undefined') { - throw new TextureError(`Texture '${name}' not found!`); - } - - if (loader.textureManager.exists(resource.name)) { - return Promise.resolve(resource.name); - } - - return new Promise(resolve => { + 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)); }); From e4d324e5fab5df40e621467d2fcc2cf63d4d45b3 Mon Sep 17 00:00:00 2001 From: Johannes Berthel Date: Fri, 2 Apr 2021 21:26:42 +0200 Subject: [PATCH 009/184] removed unused import --- front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts b/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts index 126af219..bc260c69 100644 --- a/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts +++ b/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts @@ -1,5 +1,4 @@ import LoaderPlugin = Phaser.Loader.LoaderPlugin; -import { TextureError } from "../../Exception/TextureError"; import { COMPANION_RESOURCES, CompanionResourceDescriptionInterface } from "./CompanionTextures"; export const loadAll = (loader: LoaderPlugin): CompanionResourceDescriptionInterface[] => { From 4cfce15695fef93e02799575cb7e4822f7c5d7c4 Mon Sep 17 00:00:00 2001 From: Johannes Berthel Date: Fri, 2 Apr 2021 21:29:45 +0200 Subject: [PATCH 010/184] only remove listener if scene was not already destroyed --- front/src/Phaser/Companion/Companion.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/front/src/Phaser/Companion/Companion.ts b/front/src/Phaser/Companion/Companion.ts index 407b7326..2d11110e 100644 --- a/front/src/Phaser/Companion/Companion.ts +++ b/front/src/Phaser/Companion/Companion.ts @@ -212,7 +212,9 @@ export class Companion extends Container { } } - this.scene.events.removeListener('update', this.step, this); + if (this.scene) { + this.scene.events.removeListener('update', this.step, this); + } super.destroy(); } From 52303c0bd69612e649acfe9a95523736e31237e2 Mon Sep 17 00:00:00 2001 From: Johannes Berthel Date: Fri, 2 Apr 2021 23:00:51 +0200 Subject: [PATCH 011/184] added select companion scene --- front/src/Connexion/LocalUserStore.ts | 9 +- .../CompanionTexturesLoadingManager.ts | 12 +- front/src/Phaser/Game/GameManager.ts | 8 + .../src/Phaser/Login/SelectCompanionScene.ts | 248 ++++++++++++++++++ front/src/index.ts | 3 +- 5 files changed, 271 insertions(+), 9 deletions(-) create mode 100644 front/src/Phaser/Login/SelectCompanionScene.ts diff --git a/front/src/Connexion/LocalUserStore.ts b/front/src/Connexion/LocalUserStore.ts index 4989a9ea..a766f5dc 100644 --- a/front/src/Connexion/LocalUserStore.ts +++ b/front/src/Connexion/LocalUserStore.ts @@ -48,11 +48,14 @@ class LocalUserStore { return JSON.parse(localStorage.getItem(characterLayersKey) || "null"); } - setCompanion(companion: string): void { - localStorage.setItem(companionKey, companion); + setCompanion(companion: string|null): void { + return localStorage.setItem(companionKey, JSON.stringify(companion)); } getCompanion(): string|null { - return localStorage.getItem(companionKey); + return JSON.parse(localStorage.getItem(companionKey) || "null"); + } + wasCompanionSet(): boolean { + return localStorage.getItem(companionKey) ? true : false; } setGameQualityValue(value: number): void { diff --git a/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts b/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts index bc260c69..b7859a70 100644 --- a/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts +++ b/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts @@ -1,14 +1,16 @@ import LoaderPlugin = Phaser.Loader.LoaderPlugin; import { COMPANION_RESOURCES, CompanionResourceDescriptionInterface } from "./CompanionTextures"; -export const loadAll = (loader: LoaderPlugin): CompanionResourceDescriptionInterface[] => { - const resources = COMPANION_RESOURCES; +export const loadAll = (loader: LoaderPlugin): Promise => { + const promises: Promise[] = []; - resources.forEach((resource: CompanionResourceDescriptionInterface) => { - loader.spritesheet(resource.name, resource.img, { frameWidth: 32, frameHeight: 32, endFrame: 12 }); + COMPANION_RESOURCES.forEach((resource: CompanionResourceDescriptionInterface) => { + promises.push(lazyLoadResource(loader, resource.name)); }); - return resources; + return Promise.all(promises).then(() => { + return COMPANION_RESOURCES; + }); } export const lazyLoadResource = (loader: LoaderPlugin, name: string): Promise => { diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts index fbe5102c..70625b24 100644 --- a/front/src/Phaser/Game/GameManager.ts +++ b/front/src/Phaser/Game/GameManager.ts @@ -5,6 +5,7 @@ import {MenuScene, MenuSceneName} from "../Menu/MenuScene"; import {HelpCameraSettingsScene, HelpCameraSettingsSceneName} from "../Menu/HelpCameraSettingsScene"; import {LoginSceneName} from "../Login/LoginScene"; import {SelectCharacterSceneName} from "../Login/SelectCharacterScene"; +import {SelectCompanionSceneName} from "../Login/SelectCompanionScene"; import {EnableCameraSceneName} from "../Login/EnableCameraScene"; import {localUserStore} from "../../Connexion/LocalUserStore"; @@ -39,6 +40,8 @@ export class GameManager { return LoginSceneName; } else if (!this.characterLayers) { return SelectCharacterSceneName; + } else if (!localUserStore.wasCompanionSet()) { + return SelectCompanionSceneName; } else { return EnableCameraSceneName; } @@ -65,6 +68,11 @@ export class GameManager { return this.characterLayers; } + + setCompanion(companion: string|null): void { + this.companion = companion; + } + getCompanion(): string|null { return this.companion; } diff --git a/front/src/Phaser/Login/SelectCompanionScene.ts b/front/src/Phaser/Login/SelectCompanionScene.ts new file mode 100644 index 00000000..9b1cb09a --- /dev/null +++ b/front/src/Phaser/Login/SelectCompanionScene.ts @@ -0,0 +1,248 @@ +import Image = Phaser.GameObjects.Image; +import Rectangle = Phaser.GameObjects.Rectangle; +import {gameManager} from "../Game/GameManager"; +import { addLoader } from "../Components/Loader"; +import {TextField} from "../Components/TextField"; +import { ResizableScene } from "./ResizableScene"; +import {EnableCameraSceneName} from "./EnableCameraScene"; +import {localUserStore} from "../../Connexion/LocalUserStore"; +import { loadAll } from "../Companion/CompanionTexturesLoadingManager"; +import { CompanionResourceDescriptionInterface } from "../Companion/CompanionTextures"; + +export const SelectCompanionSceneName = "SelectCompanionScene"; + +enum LoginTextures { + playButton = "play_button", + icon = "icon", + mainFont = "main_font" +} + +export class SelectCompanionScene extends ResizableScene { + private logo!: Image; + private textField!: TextField; + private pressReturnField!: TextField; + private readonly nbCharactersPerRow = 7; + + private selectedRectangle!: Rectangle; + private selectedRectangleXPos = 0; + private selectedRectangleYPos = 0; + + private selectedCompanion!: Phaser.Physics.Arcade.Sprite; + private companions: Array = new Array(); + private companionModels: Array = [null]; + + constructor() { + super({ + key: SelectCompanionSceneName + }); + } + + preload() { + loadAll(this.load).then(resourceDescriptions => { + resourceDescriptions.forEach(resourceDescription => { + this.companionModels.push(resourceDescription); + }); + }); + + this.load.image(LoginTextures.icon, "resources/logos/tcm_full.png"); + this.load.image(LoginTextures.playButton, "resources/objects/play_button.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'); + + addLoader(this); + } + + create() { + this.textField = new TextField(this, this.game.renderer.width / 2, 50, 'Select your companion'); + + this.pressReturnField = new TextField( + this, + this.game.renderer.width / 2, + 90 + 32 * Math.ceil(this.companionModels.length / this.nbCharactersPerRow), + 'Press enter to start' + ); + + 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); + + // input events + this.input.keyboard.on('keyup-ENTER', () => { + return this.nextScene(); + }); + + this.input.keyboard.on('keydown-RIGHT', () => { + if(this.selectedRectangleYPos * this.nbCharactersPerRow + (this.selectedRectangleXPos + 2)) + if ( + this.selectedRectangleXPos < this.nbCharactersPerRow - 1 + && ((this.selectedRectangleYPos * this.nbCharactersPerRow) + (this.selectedRectangleXPos + 1) + 1) <= this.companionModels.length + ) { + this.selectedRectangleXPos++; + } + this.updateSelectedCompanion(); + }); + + this.input.keyboard.on('keydown-LEFT', () => { + if ( + this.selectedRectangleXPos > 0 + && ((this.selectedRectangleYPos * this.nbCharactersPerRow) + (this.selectedRectangleXPos - 1) + 1) <= this.companionModels.length + ) { + this.selectedRectangleXPos--; + } + this.updateSelectedCompanion(); + }); + + this.input.keyboard.on('keydown-DOWN', () => { + if ( + this.selectedRectangleYPos + 1 < Math.ceil(this.companionModels.length / this.nbCharactersPerRow) + && ( + (((this.selectedRectangleYPos + 1) * this.nbCharactersPerRow) + this.selectedRectangleXPos + 1) <= this.companionModels.length // check if companion isn't empty + || (this.selectedRectangleYPos + 1) === Math.ceil(this.companionModels.length / this.nbCharactersPerRow) // check if is custom rectangle + ) + ) { + this.selectedRectangleYPos++; + } + this.updateSelectedCompanion(); + }); + + this.input.keyboard.on('keydown-UP', () => { + if ( + this.selectedRectangleYPos > 0 + && (((this.selectedRectangleYPos - 1) * this.nbCharactersPerRow) + this.selectedRectangleXPos + 1) <= this.companionModels.length + ) { + this.selectedRectangleYPos--; + } + this.updateSelectedCompanion(); + }); + + this.createCurrentCompanion(); + + const companionNumber = this.getCompanionIndex(); + + this.selectedRectangleXPos = companionNumber % this.nbCharactersPerRow; + this.selectedRectangleYPos = Math.floor(companionNumber / this.nbCharactersPerRow); + + this.updateSelectedCompanion(); + } + + update(time: number, delta: number): void { + this.pressReturnField.setVisible(!!(Math.floor(time / 500) % 2)); + } + + private nextScene(): void { + // store companion + const companionNumber = this.selectedRectangleXPos + this.selectedRectangleYPos * this.nbCharactersPerRow; + const model = this.companionModels[companionNumber]; + const companion = model === null ? null : model.name; + + localUserStore.setCompanion(companion); + + // next scene + this.scene.stop(SelectCompanionSceneName); + + gameManager.setCompanion(companion); + gameManager.tryResumingGame(this, EnableCameraSceneName); + + this.scene.remove(SelectCompanionSceneName); + } + + private createCurrentCompanion(): void { + for (let i = 0; i < this.companionModels.length; i++) { + const companionResource = this.companionModels[i]; + + const col = i % this.nbCharactersPerRow; + const row = Math.floor(i / this.nbCharactersPerRow); + + const [x, y] = this.getCharacterPosition(col, row); + + let name = "null"; + if (companionResource !== null) { + name = companionResource.name; + } + + const companion = this.physics.add.sprite(x, y, name, 0); + companion.setBounce(0.2); + companion.setCollideWorldBounds(true); + + if (companionResource !== null) { + this.anims.create({ + key: name, + frames: this.anims.generateFrameNumbers(name, {start: 0, end: 2,}), + frameRate: 10, + repeat: -1 + }); + } + + companion.setInteractive().on("pointerdown", () => { + this.selectedRectangleXPos = col; + this.selectedRectangleYPos = row; + this.updateSelectedCompanion(); + }); + + this.companions.push(companion); + } + + this.selectedCompanion = this.companions[0]; + } + + private getCharacterPosition(x: number, y: number): [number, number] { + return [ + this.game.renderer.width / 2 + 16 + (x - this.nbCharactersPerRow / 2) * 32, + y * 32 + 90 + ]; + } + + private updateSelectedCompanion(): void { + this.selectedCompanion?.anims.pause(); + + 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 companionNumber = this.selectedRectangleXPos + this.selectedRectangleYPos * this.nbCharactersPerRow; + const model = this.companionModels[companionNumber]; + + const companion = this.companions[companionNumber]; + + if (model !== null) { + companion.play(model.name); + } + + this.selectedCompanion = companion; + } + + 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; + + for (let i = 0; i model !== null && model.name === companion); + } +} diff --git a/front/src/index.ts b/front/src/index.ts index c0663acd..aab45a9b 100644 --- a/front/src/index.ts +++ b/front/src/index.ts @@ -6,6 +6,7 @@ import {DEBUG_MODE, JITSI_URL, RESOLUTION} from "./Enum/EnvironmentVariable"; import {LoginScene} from "./Phaser/Login/LoginScene"; import {ReconnectingScene} from "./Phaser/Reconnecting/ReconnectingScene"; import {SelectCharacterScene} from "./Phaser/Login/SelectCharacterScene"; +import {SelectCompanionScene} from "./Phaser/Login/SelectCompanionScene"; import {EnableCameraScene} from "./Phaser/Login/EnableCameraScene"; import {CustomizeScene} from "./Phaser/Login/CustomizeScene"; import {ResizableScene} from "./Phaser/Login/ResizableScene"; @@ -74,7 +75,7 @@ const config: GameConfig = { width: width / RESOLUTION, height: height / RESOLUTION, parent: "game", - scene: [EntryScene, LoginScene, SelectCharacterScene, EnableCameraScene, ReconnectingScene, ErrorScene, CustomizeScene, MenuScene, HelpCameraSettingsScene], + scene: [EntryScene, LoginScene, SelectCharacterScene, SelectCompanionScene, EnableCameraScene, ReconnectingScene, ErrorScene, CustomizeScene, MenuScene, HelpCameraSettingsScene], zoom: RESOLUTION, fps: fps, dom: { From 6ee488977370e6abca60a05762ea98e8d0ae3947 Mon Sep 17 00:00:00 2001 From: Johannes Berthel Date: Fri, 2 Apr 2021 23:13:03 +0200 Subject: [PATCH 012/184] added change companion button to menu --- front/dist/resources/html/gameMenu.html | 3 +++ .../CompanionTexturesLoadingManager.ts | 6 +++++- .../src/Phaser/Login/SelectCompanionScene.ts | 20 ++++++++++--------- front/src/Phaser/Menu/MenuScene.ts | 5 +++++ 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/front/dist/resources/html/gameMenu.html b/front/dist/resources/html/gameMenu.html index f0faf5c5..d5e9ad7e 100644 --- a/front/dist/resources/html/gameMenu.html +++ b/front/dist/resources/html/gameMenu.html @@ -30,6 +30,9 @@
+
+ +
diff --git a/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts b/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts index b7859a70..1c74b64f 100644 --- a/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts +++ b/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts @@ -1,7 +1,11 @@ import LoaderPlugin = Phaser.Loader.LoaderPlugin; import { COMPANION_RESOURCES, CompanionResourceDescriptionInterface } from "./CompanionTextures"; -export const loadAll = (loader: LoaderPlugin): Promise => { +export const getAllResources = (): CompanionResourceDescriptionInterface[] => { + return COMPANION_RESOURCES; +} + +export const lazyLoadAllResources = (loader: LoaderPlugin): Promise => { const promises: Promise[] = []; COMPANION_RESOURCES.forEach((resource: CompanionResourceDescriptionInterface) => { diff --git a/front/src/Phaser/Login/SelectCompanionScene.ts b/front/src/Phaser/Login/SelectCompanionScene.ts index 9b1cb09a..5a6cc160 100644 --- a/front/src/Phaser/Login/SelectCompanionScene.ts +++ b/front/src/Phaser/Login/SelectCompanionScene.ts @@ -1,13 +1,13 @@ import Image = Phaser.GameObjects.Image; import Rectangle = Phaser.GameObjects.Rectangle; -import {gameManager} from "../Game/GameManager"; import { addLoader } from "../Components/Loader"; -import {TextField} from "../Components/TextField"; +import { gameManager} from "../Game/GameManager"; import { ResizableScene } from "./ResizableScene"; -import {EnableCameraSceneName} from "./EnableCameraScene"; -import {localUserStore} from "../../Connexion/LocalUserStore"; -import { loadAll } from "../Companion/CompanionTexturesLoadingManager"; +import { TextField } from "../Components/TextField"; +import { EnableCameraSceneName } from "./EnableCameraScene"; +import { localUserStore } from "../../Connexion/LocalUserStore"; import { CompanionResourceDescriptionInterface } from "../Companion/CompanionTextures"; +import { getAllResources, lazyLoadAllResources } from "../Companion/CompanionTexturesLoadingManager"; export const SelectCompanionSceneName = "SelectCompanionScene"; @@ -38,10 +38,12 @@ export class SelectCompanionScene extends ResizableScene { } preload() { - loadAll(this.load).then(resourceDescriptions => { - resourceDescriptions.forEach(resourceDescription => { - this.companionModels.push(resourceDescription); - }); + lazyLoadAllResources(this.load).then(() => { + console.log("Loaded all companion textures."); + }); + + getAllResources().forEach(model => { + this.companionModels.push(model); }); this.load.image(LoginTextures.icon, "resources/logos/tcm_full.png"); diff --git a/front/src/Phaser/Menu/MenuScene.ts b/front/src/Phaser/Menu/MenuScene.ts index 58e7f0a6..f29fd39d 100644 --- a/front/src/Phaser/Menu/MenuScene.ts +++ b/front/src/Phaser/Menu/MenuScene.ts @@ -1,5 +1,6 @@ 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"; @@ -277,6 +278,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; From 38c06ce8ff634edb387f0e480ee0e3a9e39fc11b Mon Sep 17 00:00:00 2001 From: Johannes Berthel Date: Fri, 2 Apr 2021 23:17:04 +0200 Subject: [PATCH 013/184] navigate from select character scene to select companion scene --- front/src/Phaser/Login/SelectCharacterScene.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/front/src/Phaser/Login/SelectCharacterScene.ts b/front/src/Phaser/Login/SelectCharacterScene.ts index e47cf38a..1d595494 100644 --- a/front/src/Phaser/Login/SelectCharacterScene.ts +++ b/front/src/Phaser/Login/SelectCharacterScene.ts @@ -2,6 +2,7 @@ import {gameManager} from "../Game/GameManager"; import {TextField} from "../Components/TextField"; import Image = Phaser.GameObjects.Image; import Rectangle = Phaser.GameObjects.Rectangle; +import {SelectCompanionSceneName} from "./SelectCompanionScene"; import {EnableCameraSceneName} from "./EnableCameraScene"; import {CustomizeSceneName} from "./CustomizeScene"; import {ResizableScene} from "./ResizableScene"; @@ -145,7 +146,7 @@ export class SelectCharacterScene extends AbstractCharacterScene { this.scene.stop(SelectCharacterSceneName); if (this.selectedPlayer !== null) { gameManager.setCharacterLayers([this.selectedPlayer.texture.key]); - gameManager.tryResumingGame(this, EnableCameraSceneName); + gameManager.tryResumingGame(this, localUserStore.wasCompanionSet() ? EnableCameraSceneName : SelectCompanionSceneName); } else { this.scene.run(CustomizeSceneName); } From 3187520e7b4bc8c8b54098e6b6bc0496d0483b3f Mon Sep 17 00:00:00 2001 From: Johannes Berthel Date: Fri, 2 Apr 2021 23:36:06 +0200 Subject: [PATCH 014/184] companion behaviour fine tuning --- front/src/Phaser/Companion/Companion.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/front/src/Phaser/Companion/Companion.ts b/front/src/Phaser/Companion/Companion.ts index 2d11110e..cb932cbb 100644 --- a/front/src/Phaser/Companion/Companion.ts +++ b/front/src/Phaser/Companion/Companion.ts @@ -23,7 +23,7 @@ export class Companion extends Container { private animationType: PlayerAnimationTypes; constructor(scene: Phaser.Scene, x: number, y: number, name: string) { - super(scene, x + 8, y + 8); + super(scene, x + 14, y + 4); this.sprites = new Map(); @@ -59,7 +59,7 @@ export class Companion extends Container { } public setTarget(x: number, y: number, direction: PlayerAnimationDirections) { - this.target = { x, y, direction }; + this.target = { x, y: y + 4, direction }; } public step(time: number, delta: number) { @@ -76,7 +76,7 @@ export class Companion extends Container { const distance = Math.pow(xDist, 2) + Math.pow(yDist, 2); - if (distance < 576) { // 24^2 + if (distance < 650) { this.animationType = PlayerAnimationTypes.Idle; this.direction = this.target.direction; @@ -88,7 +88,7 @@ export class Companion extends Container { const yDir = yDist / Math.max(Math.abs(yDist), 1); const speed = 256; - this.getBody().setVelocity(Math.min(Math.abs(xDist * 2), speed) * xDir, Math.min(Math.abs(yDist * 2), speed) * yDir); + 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) { From 8c35860f2abde025bcbe66ea2978f2f89d4147bd Mon Sep 17 00:00:00 2001 From: Johannes Berthel Date: Fri, 2 Apr 2021 23:49:25 +0200 Subject: [PATCH 015/184] show select companion scene after customize scene --- front/src/Phaser/Login/CustomizeScene.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/front/src/Phaser/Login/CustomizeScene.ts b/front/src/Phaser/Login/CustomizeScene.ts index 4f5b2860..87e5aac9 100644 --- a/front/src/Phaser/Login/CustomizeScene.ts +++ b/front/src/Phaser/Login/CustomizeScene.ts @@ -1,4 +1,5 @@ import {EnableCameraSceneName} from "./EnableCameraScene"; +import {SelectCompanionSceneName} from "./SelectCompanionScene"; import {TextField} from "../Components/TextField"; import Image = Phaser.GameObjects.Image; import Rectangle = Phaser.GameObjects.Rectangle; @@ -115,7 +116,7 @@ export class CustomizeScene extends AbstractCharacterScene { gameManager.setCharacterLayers(layers); this.scene.sleep(CustomizeSceneName); - gameManager.tryResumingGame(this, EnableCameraSceneName); + gameManager.tryResumingGame(this, localUserStore.wasCompanionSet() ? EnableCameraSceneName : SelectCompanionSceneName); }); this.input.keyboard.on('keyup-RIGHT', () => this.moveCursorHorizontally(1)); From 6c512fb7cd9d4b0f9391489b9e0919cba38ea88a Mon Sep 17 00:00:00 2001 From: Johannes Berthel Date: Sat, 3 Apr 2021 00:18:08 +0200 Subject: [PATCH 016/184] fixed linting --- front/src/Phaser/Companion/Companion.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/front/src/Phaser/Companion/Companion.ts b/front/src/Phaser/Companion/Companion.ts index cb932cbb..e578dd3d 100644 --- a/front/src/Phaser/Companion/Companion.ts +++ b/front/src/Phaser/Companion/Companion.ts @@ -16,6 +16,7 @@ export class Companion extends Container { private delta: number; private invisible: boolean; + private updateListener: Function; private target: { x: number, y: number, direction: PlayerAnimationDirections }; private companionName: string; @@ -53,7 +54,8 @@ export class Companion extends Container { this.setDepth(-1); - this.scene.events.addListener('update', this.step, this); + this.updateListener = this.step.bind(this); + this.scene.events.addListener('update', this.updateListener); this.scene.add.existing(this); } @@ -213,7 +215,7 @@ export class Companion extends Container { } if (this.scene) { - this.scene.events.removeListener('update', this.step, this); + this.scene.events.removeListener('update', this.updateListener); } super.destroy(); From 7c6b73efdbc8b7d178b93c4d26c1d8c900bf990a Mon Sep 17 00:00:00 2001 From: Johannes Berthel Date: Tue, 6 Apr 2021 18:36:46 +0200 Subject: [PATCH 017/184] don't show companion selection during first connexion flow --- front/src/Phaser/Login/CustomizeScene.ts | 3 +-- front/src/Phaser/Login/SelectCharacterScene.ts | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/front/src/Phaser/Login/CustomizeScene.ts b/front/src/Phaser/Login/CustomizeScene.ts index 87e5aac9..4f5b2860 100644 --- a/front/src/Phaser/Login/CustomizeScene.ts +++ b/front/src/Phaser/Login/CustomizeScene.ts @@ -1,5 +1,4 @@ import {EnableCameraSceneName} from "./EnableCameraScene"; -import {SelectCompanionSceneName} from "./SelectCompanionScene"; import {TextField} from "../Components/TextField"; import Image = Phaser.GameObjects.Image; import Rectangle = Phaser.GameObjects.Rectangle; @@ -116,7 +115,7 @@ export class CustomizeScene extends AbstractCharacterScene { gameManager.setCharacterLayers(layers); this.scene.sleep(CustomizeSceneName); - gameManager.tryResumingGame(this, localUserStore.wasCompanionSet() ? EnableCameraSceneName : SelectCompanionSceneName); + gameManager.tryResumingGame(this, EnableCameraSceneName); }); this.input.keyboard.on('keyup-RIGHT', () => this.moveCursorHorizontally(1)); diff --git a/front/src/Phaser/Login/SelectCharacterScene.ts b/front/src/Phaser/Login/SelectCharacterScene.ts index 1d595494..e47cf38a 100644 --- a/front/src/Phaser/Login/SelectCharacterScene.ts +++ b/front/src/Phaser/Login/SelectCharacterScene.ts @@ -2,7 +2,6 @@ import {gameManager} from "../Game/GameManager"; import {TextField} from "../Components/TextField"; import Image = Phaser.GameObjects.Image; import Rectangle = Phaser.GameObjects.Rectangle; -import {SelectCompanionSceneName} from "./SelectCompanionScene"; import {EnableCameraSceneName} from "./EnableCameraScene"; import {CustomizeSceneName} from "./CustomizeScene"; import {ResizableScene} from "./ResizableScene"; @@ -146,7 +145,7 @@ export class SelectCharacterScene extends AbstractCharacterScene { this.scene.stop(SelectCharacterSceneName); if (this.selectedPlayer !== null) { gameManager.setCharacterLayers([this.selectedPlayer.texture.key]); - gameManager.tryResumingGame(this, localUserStore.wasCompanionSet() ? EnableCameraSceneName : SelectCompanionSceneName); + gameManager.tryResumingGame(this, EnableCameraSceneName); } else { this.scene.run(CustomizeSceneName); } From e5a196a42bdbe0b601c139ddef9cde9cec6fc78c Mon Sep 17 00:00:00 2001 From: Johannes Berthel Date: Tue, 6 Apr 2021 18:54:45 +0200 Subject: [PATCH 018/184] implemented feedback --- back/src/Model/User.ts | 4 ++-- front/src/Connexion/LocalUserStore.ts | 8 +++++++- front/src/Phaser/Companion/Companion.ts | 4 ++-- .../CompanionTexturesLoadingManager.ts | 20 ++++++------------- .../src/Phaser/Login/SelectCompanionScene.ts | 8 +++----- messages/protos/messages.proto | 8 ++++---- pusher/src/Controller/IoSocketController.ts | 6 +++--- .../src/Model/Websocket/ExSocketInterface.ts | 4 ++-- pusher/src/Model/Zone.ts | 4 ++-- 9 files changed, 31 insertions(+), 35 deletions(-) diff --git a/back/src/Model/User.ts b/back/src/Model/User.ts index 370781e5..52a96b61 100644 --- a/back/src/Model/User.ts +++ b/back/src/Model/User.ts @@ -4,7 +4,7 @@ import {Zone} from "_Model/Zone"; import {Movable} from "_Model/Movable"; import {PositionNotifier} from "_Model/PositionNotifier"; import {ServerDuplexStream} from "grpc"; -import {BatchMessage, Companion, PusherToBackMessage, ServerToClientMessage, SubMessage} from "../Messages/generated/messages_pb"; +import {BatchMessage, CompanionMessage, PusherToBackMessage, ServerToClientMessage, SubMessage} from "../Messages/generated/messages_pb"; import {CharacterLayer} from "_Model/Websocket/CharacterLayer"; export type UserSocket = ServerDuplexStream; @@ -24,7 +24,7 @@ export class User implements Movable { public readonly tags: string[], public readonly name: string, public readonly characterLayers: CharacterLayer[], - public readonly companion?: Companion + public readonly companion?: CompanionMessage ) { this.listenedZones = new Set(); diff --git a/front/src/Connexion/LocalUserStore.ts b/front/src/Connexion/LocalUserStore.ts index a766f5dc..74e45049 100644 --- a/front/src/Connexion/LocalUserStore.ts +++ b/front/src/Connexion/LocalUserStore.ts @@ -52,7 +52,13 @@ class LocalUserStore { return localStorage.setItem(companionKey, JSON.stringify(companion)); } getCompanion(): string|null { - return JSON.parse(localStorage.getItem(companionKey) || "null"); + const companion = JSON.parse(localStorage.getItem(companionKey) || "null"); + + if (typeof companion !== "string" || companion === "") { + return null; + } + + return companion; } wasCompanionSet(): boolean { return localStorage.getItem(companionKey) ? true : false; diff --git a/front/src/Phaser/Companion/Companion.ts b/front/src/Phaser/Companion/Companion.ts index e578dd3d..48a3fa4b 100644 --- a/front/src/Phaser/Companion/Companion.ts +++ b/front/src/Phaser/Companion/Companion.ts @@ -1,6 +1,6 @@ import Sprite = Phaser.GameObjects.Sprite; import Container = Phaser.GameObjects.Container; -import { lazyLoadResource } from "./CompanionTexturesLoadingManager"; +import { lazyLoadCompanionResource } from "./CompanionTexturesLoadingManager"; import { PlayerAnimationDirections, PlayerAnimationTypes } from "../Player/Animation"; export interface CompanionStatus { @@ -37,7 +37,7 @@ export class Companion extends Container { this.companionName = name; - lazyLoadResource(this.scene.load, this.companionName) + lazyLoadCompanionResource(this.scene.load, this.companionName) .then(resource => { this.addResource(resource); this.invisible = false; diff --git a/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts b/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts index 1c74b64f..354c5740 100644 --- a/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts +++ b/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts @@ -1,23 +1,15 @@ import LoaderPlugin = Phaser.Loader.LoaderPlugin; import { COMPANION_RESOURCES, CompanionResourceDescriptionInterface } from "./CompanionTextures"; -export const getAllResources = (): CompanionResourceDescriptionInterface[] => { +export const getAllCompanionResources = (loader: LoaderPlugin): CompanionResourceDescriptionInterface[] => { + COMPANION_RESOURCES.forEach((resource: CompanionResourceDescriptionInterface) => { + lazyLoadCompanionResource(loader, resource.name); + }); + return COMPANION_RESOURCES; } -export const lazyLoadAllResources = (loader: LoaderPlugin): Promise => { - const promises: Promise[] = []; - - COMPANION_RESOURCES.forEach((resource: CompanionResourceDescriptionInterface) => { - promises.push(lazyLoadResource(loader, resource.name)); - }); - - return Promise.all(promises).then(() => { - return COMPANION_RESOURCES; - }); -} - -export const lazyLoadResource = (loader: LoaderPlugin, name: string): Promise => { +export const lazyLoadCompanionResource = (loader: LoaderPlugin, name: string): Promise => { return new Promise((resolve, reject) => { const resource = COMPANION_RESOURCES.find(item => item.name === name); diff --git a/front/src/Phaser/Login/SelectCompanionScene.ts b/front/src/Phaser/Login/SelectCompanionScene.ts index 5a6cc160..3d20d35a 100644 --- a/front/src/Phaser/Login/SelectCompanionScene.ts +++ b/front/src/Phaser/Login/SelectCompanionScene.ts @@ -7,7 +7,7 @@ import { TextField } from "../Components/TextField"; import { EnableCameraSceneName } from "./EnableCameraScene"; import { localUserStore } from "../../Connexion/LocalUserStore"; import { CompanionResourceDescriptionInterface } from "../Companion/CompanionTextures"; -import { getAllResources, lazyLoadAllResources } from "../Companion/CompanionTexturesLoadingManager"; +import { getAllCompanionResources } from "../Companion/CompanionTexturesLoadingManager"; export const SelectCompanionSceneName = "SelectCompanionScene"; @@ -38,11 +38,9 @@ export class SelectCompanionScene extends ResizableScene { } preload() { - lazyLoadAllResources(this.load).then(() => { - console.log("Loaded all companion textures."); - }); + addLoader(this); - getAllResources().forEach(model => { + getAllCompanionResources(this.load).forEach(model => { this.companionModels.push(model); }); diff --git a/messages/protos/messages.proto b/messages/protos/messages.proto index 372f00b7..b3d4e755 100644 --- a/messages/protos/messages.proto +++ b/messages/protos/messages.proto @@ -36,7 +36,7 @@ message CharacterLayerMessage { string name = 2; } -message Companion { +message CompanionMessage { string name = 1; } @@ -145,7 +145,7 @@ message UserJoinedMessage { string name = 2; repeated CharacterLayerMessage characterLayers = 3; PositionMessage position = 4; - Companion companion = 5; + CompanionMessage companion = 5; } message UserLeftMessage { @@ -256,7 +256,7 @@ message JoinRoomMessage { string roomId = 5; repeated string tag = 6; string IPAddress = 7; - Companion companion = 8; + CompanionMessage companion = 8; } message UserJoinedZoneMessage { @@ -265,7 +265,7 @@ message UserJoinedZoneMessage { repeated CharacterLayerMessage characterLayers = 3; PositionMessage position = 4; Zone fromZone = 5; - Companion companion = 6; + CompanionMessage companion = 6; } message UserLeftZoneMessage { diff --git a/pusher/src/Controller/IoSocketController.ts b/pusher/src/Controller/IoSocketController.ts index 2079548f..87051bbc 100644 --- a/pusher/src/Controller/IoSocketController.ts +++ b/pusher/src/Controller/IoSocketController.ts @@ -12,7 +12,7 @@ import { WebRtcSignalToServerMessage, PlayGlobalMessage, ReportPlayerMessage, - QueryJitsiJwtMessage, SendUserMessage, ServerToClientMessage, Companion + QueryJitsiJwtMessage, SendUserMessage, ServerToClientMessage, CompanionMessage } from "../Messages/generated/messages_pb"; import {UserMovesMessage} from "../Messages/generated/messages_pb"; import {TemplatedApp} from "uWebSockets.js" @@ -139,10 +139,10 @@ export class IoSocketController { const right = Number(query.right); const name = query.name; - let companion: Companion|undefined = undefined; + let companion: CompanionMessage|undefined = undefined; if (typeof query.companion === 'string') { - companion = new Companion(); + companion = new CompanionMessage(); companion.setName(query.companion); } diff --git a/pusher/src/Model/Websocket/ExSocketInterface.ts b/pusher/src/Model/Websocket/ExSocketInterface.ts index 135c6f10..5b9a4f7e 100644 --- a/pusher/src/Model/Websocket/ExSocketInterface.ts +++ b/pusher/src/Model/Websocket/ExSocketInterface.ts @@ -3,7 +3,7 @@ import {Identificable} from "./Identificable"; import {ViewportInterface} from "_Model/Websocket/ViewportMessage"; import { BatchMessage, - Companion, + CompanionMessage, PusherToBackMessage, ServerToClientMessage, SubMessage @@ -30,7 +30,7 @@ export interface ExSocketInterface extends WebSocket, Identificable { characterLayers: CharacterLayer[]; position: PointInterface; viewport: ViewportInterface; - companion?: Companion; + companion?: CompanionMessage; /** * Pushes an event that will be sent in the next batch of events */ diff --git a/pusher/src/Model/Zone.ts b/pusher/src/Model/Zone.ts index a54481a5..3f39a5ed 100644 --- a/pusher/src/Model/Zone.ts +++ b/pusher/src/Model/Zone.ts @@ -6,7 +6,7 @@ import { PointMessage, PositionMessage, UserJoinedMessage, UserJoinedZoneMessage, UserLeftZoneMessage, UserMovedMessage, ZoneMessage, - Companion + CompanionMessage } from "../Messages/generated/messages_pb"; import * as messages_pb from "../Messages/generated/messages_pb"; import {ClientReadableStream} from "grpc"; @@ -31,7 +31,7 @@ export type MovesCallback = (thing: Movable, position: PositionInterface, listen export type LeavesCallback = (thing: Movable, listener: User) => void;*/ export class UserDescriptor { - private constructor(public readonly userId: number, private name: string, private characterLayers: CharacterLayerMessage[], private position: PositionMessage, private companion?: Companion) { + private constructor(public readonly userId: number, private name: string, private characterLayers: CharacterLayerMessage[], private position: PositionMessage, private companion?: CompanionMessage) { if (!Number.isInteger(this.userId)) { throw new Error('UserDescriptor.userId is not an integer: '+this.userId); } From 187e21eed98c3b017eb366aa019920203c3207f0 Mon Sep 17 00:00:00 2001 From: Johannes Berthel Date: Tue, 6 Apr 2021 19:10:18 +0200 Subject: [PATCH 019/184] load texture inside game scene instead inside of inside companion class --- front/src/Phaser/Companion/Companion.ts | 12 +++++------- front/src/Phaser/Entity/Character.ts | 6 ++++-- front/src/Phaser/Entity/RemotePlayer.ts | 5 +++-- front/src/Phaser/Game/GameScene.ts | 7 +++++-- front/src/Phaser/Player/Player.ts | 5 +++-- 5 files changed, 20 insertions(+), 15 deletions(-) diff --git a/front/src/Phaser/Companion/Companion.ts b/front/src/Phaser/Companion/Companion.ts index 48a3fa4b..72491ae1 100644 --- a/front/src/Phaser/Companion/Companion.ts +++ b/front/src/Phaser/Companion/Companion.ts @@ -23,7 +23,7 @@ export class Companion extends Container { private direction: PlayerAnimationDirections; private animationType: PlayerAnimationTypes; - constructor(scene: Phaser.Scene, x: number, y: number, name: string) { + constructor(scene: Phaser.Scene, x: number, y: number, name: string, texturePromise: Promise) { super(scene, x + 14, y + 4); this.sprites = new Map(); @@ -37,12 +37,10 @@ export class Companion extends Container { this.companionName = name; - lazyLoadCompanionResource(this.scene.load, this.companionName) - .then(resource => { - this.addResource(resource); - this.invisible = false; - }) - .catch(error => console.error(error)); + texturePromise.then(resource => { + this.addResource(resource); + this.invisible = false; + }) this.scene.physics.world.enableBody(this); diff --git a/front/src/Phaser/Entity/Character.ts b/front/src/Phaser/Entity/Character.ts index eb8e45fd..5b541267 100644 --- a/front/src/Phaser/Entity/Character.ts +++ b/front/src/Phaser/Entity/Character.ts @@ -71,8 +71,10 @@ export abstract class Character extends Container { this.playAnimation(direction, moving); } - public addCompanion(name: string): void { - this.companion = new Companion(this.scene, this.x, this.y, name); + public addCompanion(name: string, texturePromise?: Promise): 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 { diff --git a/front/src/Phaser/Entity/RemotePlayer.ts b/front/src/Phaser/Entity/RemotePlayer.ts index b405d8df..41e2e2df 100644 --- a/front/src/Phaser/Entity/RemotePlayer.ts +++ b/front/src/Phaser/Entity/RemotePlayer.ts @@ -18,7 +18,8 @@ export class RemotePlayer extends Character { texturesPromise: Promise, direction: PlayerAnimationDirections, moving: boolean, - companion: string|null + companion: string|null, + companionTexturePromise?: Promise ) { super(Scene, x, y, texturesPromise, name, direction, moving, 1); @@ -26,7 +27,7 @@ export class RemotePlayer extends Character { this.userId = userId; if (typeof companion === 'string') { - this.addCompanion(companion); + this.addCompanion(companion, companionTexturePromise); } } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 222f7ed5..7cbefb39 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -69,6 +69,7 @@ import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR; import DOMElement = Phaser.GameObjects.DOMElement; import {Subscription} from "rxjs"; import {worldFullMessageStream} from "../../Connexion/WorldFullMessageStream"; +import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager"; export interface GameSceneInitInterface { initPosition: PointInterface|null, @@ -1024,7 +1025,8 @@ ${escapedMessage} PlayerAnimationDirections.Down, false, this.userInputManager, - this.companion + this.companion, + this.companion !== null ? lazyLoadCompanionResource(this.load, this.companion) : undefined ); }catch (err){ if(err instanceof TextureError) { @@ -1217,7 +1219,8 @@ ${escapedMessage} texturesPromise, addPlayerData.position.direction as PlayerAnimationDirections, addPlayerData.position.moving, - addPlayerData.companion + addPlayerData.companion, + addPlayerData.companion !== null ? lazyLoadCompanionResource(this.load, addPlayerData.companion) : undefined ); this.MapPlayers.add(player); this.MapPlayersByKey.set(player.userId, player); diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index d018a41f..bb961115 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -22,7 +22,8 @@ export class Player extends Character implements CurrentGamerInterface { direction: PlayerAnimationDirections, moving: boolean, private userInputManager: UserInputManager, - companion: string|null + companion: string|null, + companionTexturePromise?: Promise ) { super(Scene, x, y, texturesPromise, name, direction, moving, 1); @@ -30,7 +31,7 @@ export class Player extends Character implements CurrentGamerInterface { this.getBody().setImmovable(false); if (typeof companion === 'string') { - this.addCompanion(companion); + this.addCompanion(companion, companionTexturePromise); } } From fb90111cdf30c77740f0d027a34dc9558af76e55 Mon Sep 17 00:00:00 2001 From: Johannes Berthel Date: Tue, 6 Apr 2021 20:12:10 +0200 Subject: [PATCH 020/184] refactored companion selection scene --- .../src/Phaser/Login/SelectCompanionScene.ts | 154 ++++++++---------- 1 file changed, 69 insertions(+), 85 deletions(-) diff --git a/front/src/Phaser/Login/SelectCompanionScene.ts b/front/src/Phaser/Login/SelectCompanionScene.ts index 3d20d35a..34892a3c 100644 --- a/front/src/Phaser/Login/SelectCompanionScene.ts +++ b/front/src/Phaser/Login/SelectCompanionScene.ts @@ -24,8 +24,6 @@ export class SelectCompanionScene extends ResizableScene { private readonly nbCharactersPerRow = 7; private selectedRectangle!: Rectangle; - private selectedRectangleXPos = 0; - private selectedRectangleYPos = 0; private selectedCompanion!: Phaser.Physics.Arcade.Sprite; private companions: Array = new Array(); @@ -70,76 +68,85 @@ export class SelectCompanionScene extends ResizableScene { this.add.existing(this.logo); // input events - this.input.keyboard.on('keyup-ENTER', () => { - return this.nextScene(); - }); + this.input.keyboard.on('keyup-ENTER', this.nextScene.bind(this)); - this.input.keyboard.on('keydown-RIGHT', () => { - if(this.selectedRectangleYPos * this.nbCharactersPerRow + (this.selectedRectangleXPos + 2)) - if ( - this.selectedRectangleXPos < this.nbCharactersPerRow - 1 - && ((this.selectedRectangleYPos * this.nbCharactersPerRow) + (this.selectedRectangleXPos + 1) + 1) <= this.companionModels.length - ) { - this.selectedRectangleXPos++; - } - this.updateSelectedCompanion(); - }); - - this.input.keyboard.on('keydown-LEFT', () => { - if ( - this.selectedRectangleXPos > 0 - && ((this.selectedRectangleYPos * this.nbCharactersPerRow) + (this.selectedRectangleXPos - 1) + 1) <= this.companionModels.length - ) { - this.selectedRectangleXPos--; - } - this.updateSelectedCompanion(); - }); - - this.input.keyboard.on('keydown-DOWN', () => { - if ( - this.selectedRectangleYPos + 1 < Math.ceil(this.companionModels.length / this.nbCharactersPerRow) - && ( - (((this.selectedRectangleYPos + 1) * this.nbCharactersPerRow) + this.selectedRectangleXPos + 1) <= this.companionModels.length // check if companion isn't empty - || (this.selectedRectangleYPos + 1) === Math.ceil(this.companionModels.length / this.nbCharactersPerRow) // check if is custom rectangle - ) - ) { - this.selectedRectangleYPos++; - } - this.updateSelectedCompanion(); - }); - - this.input.keyboard.on('keydown-UP', () => { - if ( - this.selectedRectangleYPos > 0 - && (((this.selectedRectangleYPos - 1) * this.nbCharactersPerRow) + this.selectedRectangleXPos + 1) <= this.companionModels.length - ) { - this.selectedRectangleYPos--; - } - this.updateSelectedCompanion(); - }); + this.input.keyboard.on('keydown-RIGHT', this.selectNext.bind(this)); + this.input.keyboard.on('keydown-LEFT', this.selectPrevious.bind(this)); + this.input.keyboard.on('keydown-DOWN', this.jumpToNextRow.bind(this)); + this.input.keyboard.on('keydown-UP', this.jumpToPreviousRow.bind(this)); this.createCurrentCompanion(); - - const companionNumber = this.getCompanionIndex(); - - this.selectedRectangleXPos = companionNumber % this.nbCharactersPerRow; - this.selectedRectangleYPos = Math.floor(companionNumber / this.nbCharactersPerRow); - - this.updateSelectedCompanion(); + this.selectCompanion(this.getCompanionIndex()); } update(time: number, delta: number): void { this.pressReturnField.setVisible(!!(Math.floor(time / 500) % 2)); } - private nextScene(): void { - // store companion - const companionNumber = this.selectedRectangleXPos + this.selectedRectangleYPos * this.nbCharactersPerRow; - const model = this.companionModels[companionNumber]; + private jumpToPreviousRow(): void { + const index = this.companions.indexOf(this.selectedCompanion) - this.nbCharactersPerRow; + if (index >= 0) { + this.selectCompanion(index); + } + } + + private jumpToNextRow(): void { + const index = this.companions.indexOf(this.selectedCompanion) + this.nbCharactersPerRow; + if (index < this.companions.length) { + this.selectCompanion(index); + } + } + + private selectPrevious(): void { + const index = this.companions.indexOf(this.selectedCompanion); + this.selectCompanion(index - 1); + } + + private selectNext(): void { + const index = this.companions.indexOf(this.selectedCompanion); + this.selectCompanion(index + 1); + } + + private selectCompanion(index?: number): void { + if (typeof index === 'undefined') { + index = this.companions.indexOf(this.selectedCompanion); + } + + // make sure index is inside possible range + index = Math.min(this.companions.length - 1, Math.max(0, index)); + + if (this.selectedCompanion === this.companions[index]) { + return; + } + + this.selectedCompanion.anims.pause(); + this.selectedCompanion = this.companions[index]; + + this.selectedRectangle.setVisible(true); + this.selectedRectangle.setX(this.selectedCompanion.x); + this.selectedRectangle.setY(this.selectedCompanion.y); + this.selectedRectangle.setSize(32, 32); + + const model = this.companionModels[index]; + + if (model !== null) { + this.selectedCompanion.anims.play(model.name); + } + } + + private storeCompanionSelection(): string|null { + const index = this.companions.indexOf(this.selectedCompanion); + const model = this.companionModels[index]; const companion = model === null ? null : model.name; localUserStore.setCompanion(companion); + return companion; + } + + private nextScene(): void { + const companion = this.storeCompanionSelection(); + // next scene this.scene.stop(SelectCompanionSceneName); @@ -177,9 +184,7 @@ export class SelectCompanionScene extends ResizableScene { } companion.setInteractive().on("pointerdown", () => { - this.selectedRectangleXPos = col; - this.selectedRectangleYPos = row; - this.updateSelectedCompanion(); + this.selectCompanion(i); }); this.companions.push(companion); @@ -195,34 +200,13 @@ export class SelectCompanionScene extends ResizableScene { ]; } - private updateSelectedCompanion(): void { - this.selectedCompanion?.anims.pause(); - - 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 companionNumber = this.selectedRectangleXPos + this.selectedRectangleYPos * this.nbCharactersPerRow; - const model = this.companionModels[companionNumber]; - - const companion = this.companions[companionNumber]; - - if (model !== null) { - companion.play(model.name); - } - - this.selectedCompanion = companion; - } - 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; - for (let i = 0; i Date: Tue, 6 Apr 2021 20:31:08 +0200 Subject: [PATCH 021/184] fixed rectangle resize issue --- front/src/Phaser/Login/SelectCompanionScene.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/front/src/Phaser/Login/SelectCompanionScene.ts b/front/src/Phaser/Login/SelectCompanionScene.ts index 34892a3c..9b5c38fb 100644 --- a/front/src/Phaser/Login/SelectCompanionScene.ts +++ b/front/src/Phaser/Login/SelectCompanionScene.ts @@ -122,10 +122,7 @@ export class SelectCompanionScene extends ResizableScene { this.selectedCompanion.anims.pause(); this.selectedCompanion = this.companions[index]; - this.selectedRectangle.setVisible(true); - this.selectedRectangle.setX(this.selectedCompanion.x); - this.selectedRectangle.setY(this.selectedCompanion.y); - this.selectedRectangle.setSize(32, 32); + this.redrawSelectedRectangle(); const model = this.companionModels[index]; @@ -134,6 +131,13 @@ export class SelectCompanionScene extends ResizableScene { } } + private redrawSelectedRectangle(): void { + this.selectedRectangle.setVisible(true); + this.selectedRectangle.setX(this.selectedCompanion.x); + this.selectedRectangle.setY(this.selectedCompanion.y); + this.selectedRectangle.setSize(32, 32); + } + private storeCompanionSelection(): string|null { const index = this.companions.indexOf(this.selectedCompanion); const model = this.companionModels[index]; @@ -217,7 +221,7 @@ export class SelectCompanionScene extends ResizableScene { companion.y = y; } - this.selectCompanion(); + this.redrawSelectedRectangle(); } private getCompanionIndex(): number { From 73e515c2368fcb6667a8f94b12d93231a377b646 Mon Sep 17 00:00:00 2001 From: Johannes Berthel Date: Thu, 8 Apr 2021 18:42:05 +0200 Subject: [PATCH 022/184] don't show companion scene after first refresh --- front/src/Phaser/Game/GameManager.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts index 70625b24..41d827a3 100644 --- a/front/src/Phaser/Game/GameManager.ts +++ b/front/src/Phaser/Game/GameManager.ts @@ -5,7 +5,6 @@ import {MenuScene, MenuSceneName} from "../Menu/MenuScene"; import {HelpCameraSettingsScene, HelpCameraSettingsSceneName} from "../Menu/HelpCameraSettingsScene"; import {LoginSceneName} from "../Login/LoginScene"; import {SelectCharacterSceneName} from "../Login/SelectCharacterScene"; -import {SelectCompanionSceneName} from "../Login/SelectCompanionScene"; import {EnableCameraSceneName} from "../Login/EnableCameraScene"; import {localUserStore} from "../../Connexion/LocalUserStore"; @@ -40,8 +39,6 @@ export class GameManager { return LoginSceneName; } else if (!this.characterLayers) { return SelectCharacterSceneName; - } else if (!localUserStore.wasCompanionSet()) { - return SelectCompanionSceneName; } else { return EnableCameraSceneName; } From 8d4e0c3e88db92c142c2f424b93a46fe3a02a463 Mon Sep 17 00:00:00 2001 From: Johannes Berthel Date: Fri, 9 Apr 2021 18:30:30 +0200 Subject: [PATCH 023/184] added companion compatibility with phaser 3.54.0 --- .../Companion/CompanionTexturesLoadingManager.ts | 2 ++ front/src/Phaser/Entity/Character.ts | 4 ---- front/src/Phaser/Game/GameScene.ts | 10 ++++++++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts b/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts index 354c5740..75c20a48 100644 --- a/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts +++ b/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts @@ -23,5 +23,7 @@ export const lazyLoadCompanionResource = (loader: LoaderPlugin, name: string): P 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. }); } diff --git a/front/src/Phaser/Entity/Character.ts b/front/src/Phaser/Entity/Character.ts index 5b541267..9f2bd1fd 100644 --- a/front/src/Phaser/Entity/Character.ts +++ b/front/src/Phaser/Entity/Character.ts @@ -227,9 +227,5 @@ export abstract class Character extends Container { } super.destroy(); this.playerName.destroy(); - - if (this.companion) { - this.companion.destroy(); - } } } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 7cbefb39..5feb0607 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -854,6 +854,11 @@ ${escapedMessage} private removeAllRemotePlayers(): void { this.MapPlayersByKey.forEach((player: RemotePlayer) => { player.destroy(); + + if (player.companion) { + player.companion.destroy(); + } + this.MapPlayers.remove(player); }); this.MapPlayersByKey = new Map(); @@ -1243,6 +1248,11 @@ ${escapedMessage} console.error('Cannot find user with id ', userId); } else { player.destroy(); + + if (player.companion) { + player.companion.destroy(); + } + this.MapPlayers.remove(player); } this.MapPlayersByKey.delete(userId); From 6acf0ff047af449ec38f242265805aed2310c1a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Sat, 10 Apr 2021 18:42:42 +0200 Subject: [PATCH 024/184] Creating a dedicated package for iframe API typings If users are willing to use Typescript to develop scripts for maps, they will need typings for the `WA` object. This commit creates a new package (@workadventure/iframe-api-typings) that contains only the `iframe_api.d.ts` file. The file is generated from the build of the front and isolated in this package. This is necessary because the iframe_api.js file is supposed to always be loaded from WorkAdventure directly (and there is no @workadventure/iframe-api package and there will never be one) --- .github/workflows/push-to-npm.yml | 55 +++++++++++++++++++ front/packages/iframe-api-typings/.gitignore | 1 + front/packages/iframe-api-typings/README.md | 27 +++++++++ .../packages/iframe-api-typings/iframe_api.js | 1 + .../packages/iframe-api-typings/package.json | 13 +++++ front/tsconfig.json | 1 + 6 files changed, 98 insertions(+) create mode 100644 .github/workflows/push-to-npm.yml create mode 100644 front/packages/iframe-api-typings/.gitignore create mode 100644 front/packages/iframe-api-typings/README.md create mode 100644 front/packages/iframe-api-typings/iframe_api.js create mode 100644 front/packages/iframe-api-typings/package.json diff --git a/.github/workflows/push-to-npm.yml b/.github/workflows/push-to-npm.yml new file mode 100644 index 00000000..18404dc0 --- /dev/null +++ b/.github/workflows/push-to-npm.yml @@ -0,0 +1,55 @@ +name: Push @workadventure/iframe-api-typings to NPM +on: + # TODO remove action on push + push: ~ + release: + types: [created] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + # Setup .npmrc file to publish to npm + - uses: actions/setup-node@v2 + with: + node-version: '14.x' + registry-url: 'https://registry.npmjs.org' + # TODO: replace version with RELEASE tag + # TODO: enable declaration flag in tsconfig dynamically + - name: Install Protoc + uses: arduino/setup-protoc@v1 + with: + version: '3.x' + + - name: "Install dependencies" + run: yarn install + working-directory: "front" + + - name: "Install messages dependencies" + run: yarn install + working-directory: "messages" + + - name: "Build proto messages" + run: yarn run proto && yarn run copy-to-front + working-directory: "messages" + + - name: "Create index.html" + run: ./templater.sh + working-directory: "front" + + - name: "Build" + run: yarn run build + env: + API_URL: "localhost:8080" + working-directory: "front" + # We build the front to generate the typings of iframe_api, then we copy those typings in a separate package. + - name: Copy typings to package dir + run: cp front/dist/src/iframe_api.d.ts front/packages/iframe-api-typings/iframe_api.d.ts + - name: Install dependencies in package + run: yarn install + working-directory: "front/packages/iframe-api-typings" + - name: Publish package + run: yarn publish + working-directory: "front/packages/iframe-api-typings" + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/front/packages/iframe-api-typings/.gitignore b/front/packages/iframe-api-typings/.gitignore new file mode 100644 index 00000000..f850ebdb --- /dev/null +++ b/front/packages/iframe-api-typings/.gitignore @@ -0,0 +1 @@ +iframe_api.d.ts diff --git a/front/packages/iframe-api-typings/README.md b/front/packages/iframe-api-typings/README.md new file mode 100644 index 00000000..c5c16ded --- /dev/null +++ b/front/packages/iframe-api-typings/README.md @@ -0,0 +1,27 @@ +

WorkAdventure - IFrame API typings for Typescript

+ +

This package contains Typescript typings for WorkAdventure's map scripting API

+ +
+ +[WorkAdventure](https://workadventu.re) comes with a scripting API. Using this API, you can add some intelligence to your map. +You use this API by loading an external script directly from WorkAdventure (at https://play.workadventu.re/iframe_api.js), or this script is loaded +for you if you are using the "script" property of a map. + +This project contains Typescript typings for the `WA` object provided by this script. + +## Usage + +This package is only useful if you are using Typescript to script your WorkAdventure maps. + +## Download & Installation + +```shell +$ npm install @workadventure/iframe-api-typings +``` + +or + +```shell +$ yarn add @workadventure/iframe-api-typings +``` diff --git a/front/packages/iframe-api-typings/iframe_api.js b/front/packages/iframe-api-typings/iframe_api.js new file mode 100644 index 00000000..033bf208 --- /dev/null +++ b/front/packages/iframe-api-typings/iframe_api.js @@ -0,0 +1 @@ +// This file is voluntarily empty. diff --git a/front/packages/iframe-api-typings/package.json b/front/packages/iframe-api-typings/package.json new file mode 100644 index 00000000..61184c4e --- /dev/null +++ b/front/packages/iframe-api-typings/package.json @@ -0,0 +1,13 @@ +{ + "name": "@workadventure/iframe-api-typings", + "version": "1.2.0", + "description": "Typescript typings for WorkAdventure iFrame API", + "main": "iframe_api.js", + "types": "iframe_api.d.ts", + "repository": "https://github.com/thecodingmachine/workadventure/", + "author": "David Négrier ", + "license": "MIT", + "publishConfig": { + "access": "public" + } +} diff --git a/front/tsconfig.json b/front/tsconfig.json index 3fce57ea..f6cb384a 100644 --- a/front/tsconfig.json +++ b/front/tsconfig.json @@ -5,6 +5,7 @@ "moduleResolution": "node", "module": "CommonJS", "target": "ES2015", + "declaration": true, "downlevelIteration": true, "jsx": "react", "allowJs": true, From 0f7ac7ba8b3a6ad3958244868f7f50e14e54578e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Sat, 10 Apr 2021 19:14:36 +0200 Subject: [PATCH 025/184] Added empty .npmignore to override .gitignore --- .github/workflows/push-to-npm.yml | 3 +++ front/packages/iframe-api-typings/.npmignore | 0 2 files changed, 3 insertions(+) create mode 100644 front/packages/iframe-api-typings/.npmignore diff --git a/.github/workflows/push-to-npm.yml b/.github/workflows/push-to-npm.yml index 18404dc0..354cb651 100644 --- a/.github/workflows/push-to-npm.yml +++ b/.github/workflows/push-to-npm.yml @@ -42,12 +42,15 @@ jobs: env: API_URL: "localhost:8080" working-directory: "front" + # We build the front to generate the typings of iframe_api, then we copy those typings in a separate package. - name: Copy typings to package dir run: cp front/dist/src/iframe_api.d.ts front/packages/iframe-api-typings/iframe_api.d.ts + - name: Install dependencies in package run: yarn install working-directory: "front/packages/iframe-api-typings" + - name: Publish package run: yarn publish working-directory: "front/packages/iframe-api-typings" diff --git a/front/packages/iframe-api-typings/.npmignore b/front/packages/iframe-api-typings/.npmignore new file mode 100644 index 00000000..e69de29b From 728fd4779c03b976b04f75f4c3278f13d4be60cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Sun, 11 Apr 2021 11:15:59 +0200 Subject: [PATCH 026/184] Dynamically fetching correct version and generating declarations only for npm lib --- .github/workflows/push-to-npm.yml | 17 +++++++++++++---- front/packages/iframe-api-typings/package.json | 2 +- front/tsconfig.json | 2 +- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/.github/workflows/push-to-npm.yml b/.github/workflows/push-to-npm.yml index 354cb651..798e2530 100644 --- a/.github/workflows/push-to-npm.yml +++ b/.github/workflows/push-to-npm.yml @@ -1,7 +1,5 @@ name: Push @workadventure/iframe-api-typings to NPM on: - # TODO remove action on push - push: ~ release: types: [created] jobs: @@ -14,8 +12,19 @@ jobs: with: node-version: '14.x' registry-url: 'https://registry.npmjs.org' - # TODO: replace version with RELEASE tag - # TODO: enable declaration flag in tsconfig dynamically + + - name: Edit tsconfig.json to add declarations + run: "sed -i 's/\"declaration\": false/\"declaration\": true/g' tsconfig.json" + working-directory: "front" + + - name: Replace version number + run: 'sed -i "s#VERSION_PLACEHOLDER#${GITHUB_REF/refs\/tags\//}#g" package.json' + working-directory: "front/packages/iframe-api-typings" + + - name: Debug package.json + run: cat package.json + working-directory: "front/packages/iframe-api-typings" + - name: Install Protoc uses: arduino/setup-protoc@v1 with: diff --git a/front/packages/iframe-api-typings/package.json b/front/packages/iframe-api-typings/package.json index 61184c4e..fa35851c 100644 --- a/front/packages/iframe-api-typings/package.json +++ b/front/packages/iframe-api-typings/package.json @@ -1,6 +1,6 @@ { "name": "@workadventure/iframe-api-typings", - "version": "1.2.0", + "version": "VERSION_PLACEHOLDER", "description": "Typescript typings for WorkAdventure iFrame API", "main": "iframe_api.js", "types": "iframe_api.d.ts", diff --git a/front/tsconfig.json b/front/tsconfig.json index f6cb384a..b5c8c74d 100644 --- a/front/tsconfig.json +++ b/front/tsconfig.json @@ -5,7 +5,7 @@ "moduleResolution": "node", "module": "CommonJS", "target": "ES2015", - "declaration": true, + "declaration": false, "downlevelIteration": true, "jsx": "react", "allowJs": true, From 3dd2a634a0629eeede4d8c09653881f41569f1fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Sun, 11 Apr 2021 14:56:03 +0200 Subject: [PATCH 027/184] Allowing loading HTTP local resources from a HTTPS endpoint. By default, maps are loaded in HTTPS if WorkAdventure is running in HTTPS, and in HTTP is WorkAdventure is running in HTTP. Also, if WorkAdventure is running in HTTP and map loading fails, we try map loading in HTTPS (useful when we are working on WorkAdventure locally and want to load a map on a secure domain). This commit adds the last combination: If WorkAdventure is running in HTTPS, and map loading fails in HTTPS **AND** if the map URL is targetting "localhost", "*.localhost" or "127.0.0.1", then we attempt to load the resource in HTTP. Why? "localhost" is considered secure context by modern browsers. So even if a page is loaded in HTTPS, it can load resources from any secure context (including localhost in HTTP). This means that from "https://play.workadventu.re", I can now test a map running locally on my machine (served by a classic webserver without any certificate). This change should make map testing easier, since map developers will not have to install the whole WorkAdventure project to test their map locally. --- front/src/Phaser/Game/GameScene.ts | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 9a165eb5..6c4c6e3e 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -161,6 +161,7 @@ export class GameScene extends ResizableScene implements CenterListener { private characterLayers!: string[]; private messageSubscription: Subscription|null = null; private popUpElements : Map = new Map(); + private originalMapUrl: string|undefined; constructor(private room: Room, MapUrlFile: string, customKey?: string|undefined) { super({ @@ -195,7 +196,8 @@ export class GameScene extends ResizableScene implements CenterListener { this.load.image(openChatIconName, 'resources/objects/talk.png'); this.load.on(FILE_LOAD_ERROR, (file: {src: string}) => { // If we happen to be in HTTP and we are trying to load a URL in HTTPS only... (this happens only in dev environments) - if (window.location.protocol === 'http:' && file.src === this.MapUrlFile && file.src.startsWith('http:')) { + if (window.location.protocol === 'http:' && file.src === this.MapUrlFile && file.src.startsWith('http:') && this.originalMapUrl === undefined) { + this.originalMapUrl = this.MapUrlFile; this.MapUrlFile = this.MapUrlFile.replace('http://', 'https://'); this.load.tilemapTiledJSON(this.MapUrlFile, this.MapUrlFile); this.load.on('filecomplete-tilemapJSON-'+this.MapUrlFile, (key: string, type: string, data: unknown) => { @@ -203,10 +205,25 @@ export class GameScene extends ResizableScene implements CenterListener { }); return; } + // 127.0.0.1, localhost and *.localhost are considered secure, even on HTTP. + // So if we are in https, we can still try to load a HTTP local resource (can be useful for testing purposes) + // See https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure + const url = new URL(file.src); + const host = url.host.split(':')[0]; + if (window.location.protocol === 'https:' && file.src === this.MapUrlFile && (host === '127.0.0.1' || host === 'localhost' || host.endsWith('.localhost')) && this.originalMapUrl === undefined) { + this.originalMapUrl = this.MapUrlFile; + this.MapUrlFile = this.MapUrlFile.replace('https://', 'http://'); + this.load.tilemapTiledJSON(this.MapUrlFile, this.MapUrlFile); + this.load.on('filecomplete-tilemapJSON-'+this.MapUrlFile, (key: string, type: string, data: unknown) => { + this.onMapLoad(data); + }); + return; + } + this.scene.start(ErrorSceneName, { title: 'Network error', subTitle: 'An error occurred while loading resource:', - message: file.src + message: this.originalMapUrl ?? file.src }); }); this.load.on('filecomplete-tilemapJSON-'+this.MapUrlFile, (key: string, type: string, data: unknown) => { From 8e467f3e10c8cd2546bb63916a3fdf1269154e2b Mon Sep 17 00:00:00 2001 From: kharhamel Date: Wed, 7 Apr 2021 14:09:45 +0200 Subject: [PATCH 028/184] FIX: improved the validation from localstorage for username and characterLayers --- front/src/Connexion/LocalUser.ts | 19 ++++++++ front/src/Connexion/LocalUserStore.ts | 10 +++-- front/src/Enum/EnvironmentVariable.ts | 1 + front/src/Phaser/Game/GameManager.ts | 2 +- front/src/Phaser/Login/CustomizeScene.ts | 4 ++ front/src/Phaser/Login/LoginScene.ts | 8 ++-- .../src/Phaser/Login/SelectCharacterScene.ts | 4 ++ front/tests/Phaser/Connexion/LocalUserTest.ts | 45 +++++++++++++++++++ 8 files changed, 84 insertions(+), 9 deletions(-) create mode 100644 front/tests/Phaser/Connexion/LocalUserTest.ts diff --git a/front/src/Connexion/LocalUser.ts b/front/src/Connexion/LocalUser.ts index 06d98b70..b8e0a204 100644 --- a/front/src/Connexion/LocalUser.ts +++ b/front/src/Connexion/LocalUser.ts @@ -1,3 +1,5 @@ +import {MAX_USERNAME_LENGTH} from "../Enum/EnvironmentVariable"; + export interface CharacterTexture { id: number, level: number, @@ -5,6 +7,23 @@ export interface CharacterTexture { rights: string } +export const maxUserNameLength: number = MAX_USERNAME_LENGTH; + +export function isUserNameValid(value: string): boolean { + const regexp = new RegExp('^[A-Za-z]{1,'+maxUserNameLength+'}$'); + return regexp.test(value); +} + +export function areCharacterLayersValid(value: string[]): boolean { + if (!value.length) return false; + for (let i = 0; i < value.length; i++) { + if (/^\w+$/.exec(value[i]) === null) { + return false; + } + } + return true; +} + export class LocalUser { constructor(public readonly uuid:string, public readonly jwtToken: string, public readonly textures: CharacterTexture[]) { } diff --git a/front/src/Connexion/LocalUserStore.ts b/front/src/Connexion/LocalUserStore.ts index 702df561..48b5697b 100644 --- a/front/src/Connexion/LocalUserStore.ts +++ b/front/src/Connexion/LocalUserStore.ts @@ -1,4 +1,4 @@ -import {LocalUser} from "./LocalUser"; +import {areCharacterLayersValid, isUserNameValid, LocalUser} from "./LocalUser"; const playerNameKey = 'playerName'; const selectedPlayerKey = 'selectedPlayer'; @@ -22,8 +22,9 @@ class LocalUserStore { setName(name:string): void { localStorage.setItem(playerNameKey, name); } - getName(): string { - return localStorage.getItem(playerNameKey) || ''; + getName(): string|null { + const value = localStorage.getItem(playerNameKey) || ''; + return isUserNameValid(value) ? value : null; } setPlayerCharacterIndex(playerCharacterIndex: number): void { @@ -44,7 +45,8 @@ class LocalUserStore { localStorage.setItem(characterLayersKey, JSON.stringify(layers)); } getCharacterLayers(): string[]|null { - return JSON.parse(localStorage.getItem(characterLayersKey) || "null"); + const value = JSON.parse(localStorage.getItem(characterLayersKey) || "null"); + return areCharacterLayersValid(value) ? value : null; } setGameQualityValue(value: number): void { diff --git a/front/src/Enum/EnvironmentVariable.ts b/front/src/Enum/EnvironmentVariable.ts index ea2434af..5040e59f 100644 --- a/front/src/Enum/EnvironmentVariable.ts +++ b/front/src/Enum/EnvironmentVariable.ts @@ -14,6 +14,7 @@ const RESOLUTION = 2; const ZOOM_LEVEL = 1/*3/4*/; const POSITION_DELAY = 200; // Wait 200ms between sending position events const MAX_EXTRAPOLATION_TIME = 100; // Extrapolate a maximum of 250ms if no new movement is sent by the player +export const MAX_USERNAME_LENGTH = parseInt(process.env.MAX_USERNAME_LENGTH || '') || 8; export { DEBUG_MODE, diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts index 8751796f..e5ed7bba 100644 --- a/front/src/Phaser/Game/GameManager.ts +++ b/front/src/Phaser/Game/GameManager.ts @@ -35,7 +35,7 @@ export class GameManager { if (!this.playerName) { return LoginSceneName; - } else if (!this.characterLayers) { + } else if (!this.characterLayers || !this.characterLayers.length) { return SelectCharacterSceneName; } else { return EnableCameraSceneName; diff --git a/front/src/Phaser/Login/CustomizeScene.ts b/front/src/Phaser/Login/CustomizeScene.ts index 4f5b2860..6cf13a86 100644 --- a/front/src/Phaser/Login/CustomizeScene.ts +++ b/front/src/Phaser/Login/CustomizeScene.ts @@ -11,6 +11,7 @@ import {localUserStore} from "../../Connexion/LocalUserStore"; import {addLoader} from "../Components/Loader"; import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures"; import {AbstractCharacterScene} from "./AbstractCharacterScene"; +import {areCharacterLayersValid} from "../../Connexion/LocalUser"; export const CustomizeSceneName = "CustomizeScene"; @@ -111,6 +112,9 @@ export class CustomizeScene extends AbstractCharacterScene { } i++; } + if (!areCharacterLayersValid(layers)) { + return; + } gameManager.setCharacterLayers(layers); diff --git a/front/src/Phaser/Login/LoginScene.ts b/front/src/Phaser/Login/LoginScene.ts index 9ca6dcd2..ffdf1367 100644 --- a/front/src/Phaser/Login/LoginScene.ts +++ b/front/src/Phaser/Login/LoginScene.ts @@ -4,6 +4,7 @@ import {TextInput} from "../Components/TextInput"; import Image = Phaser.GameObjects.Image; import {SelectCharacterSceneName} from "./SelectCharacterScene"; import {ResizableScene} from "./ResizableScene"; +import {isUserNameValid, maxUserNameLength} from "../../Connexion/LocalUser"; //todo: put this constants in a dedicated file export const LoginSceneName = "LoginScene"; @@ -37,7 +38,7 @@ export class LoginScene extends ResizableScene { create() { 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.nameInput = new TextInput(this, this.game.renderer.width / 2, 70, maxUserNameLength, this.name,(text: string) => { this.name = text; }); @@ -50,10 +51,9 @@ export class LoginScene extends ResizableScene { this.infoTextField = new TextField(this, 10, this.game.renderer.height - 35, infoText, false); this.input.keyboard.on('keyup-ENTER', () => { - if (this.name === '') { - return + if (isUserNameValid(this.name)) { + this.login(this.name); } - this.login(this.name); }); } diff --git a/front/src/Phaser/Login/SelectCharacterScene.ts b/front/src/Phaser/Login/SelectCharacterScene.ts index e47cf38a..a1f3fd81 100644 --- a/front/src/Phaser/Login/SelectCharacterScene.ts +++ b/front/src/Phaser/Login/SelectCharacterScene.ts @@ -10,6 +10,7 @@ import {loadAllDefaultModels, loadCustomTexture} from "../Entity/PlayerTexturesL import {addLoader} from "../Components/Loader"; import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures"; import {AbstractCharacterScene} from "./AbstractCharacterScene"; +import {areCharacterLayersValid} from "../../Connexion/LocalUser"; //todo: put this constants in a dedicated file @@ -142,6 +143,9 @@ export class SelectCharacterScene extends AbstractCharacterScene { } private nextScene(): void { + if (this.selectedPlayer !== null && !areCharacterLayersValid([this.selectedPlayer.texture.key])) { + return; + } this.scene.stop(SelectCharacterSceneName); if (this.selectedPlayer !== null) { gameManager.setCharacterLayers([this.selectedPlayer.texture.key]); diff --git a/front/tests/Phaser/Connexion/LocalUserTest.ts b/front/tests/Phaser/Connexion/LocalUserTest.ts new file mode 100644 index 00000000..00d8f400 --- /dev/null +++ b/front/tests/Phaser/Connexion/LocalUserTest.ts @@ -0,0 +1,45 @@ +import "jasmine"; +import {areCharacterLayersValid, isUserNameValid, maxUserNameLength} from "../../../src/Connexion/LocalUser"; + +describe("isUserNameValid()", () => { + it("should validate name with letters", () => { + expect(isUserNameValid('toto')).toBe(true); + }); + + it("should not validate empty name", () => { + expect(isUserNameValid('')).toBe(false); + }); + it("should not validate string with too many letters", () => { + let testString = ''; + for (let i = 0; i < maxUserNameLength + 2; i++) { + testString += 'a'; + } + expect(isUserNameValid(testString)).toBe(false); + }); + it("should not validate spaces", () => { + expect(isUserNameValid(' ')).toBe(false); + }); + it("should not validate numbers", () => { + expect(isUserNameValid('a12')).toBe(false); + }); + it("should not validate special characters", () => { + expect(isUserNameValid('a&-')).toBe(false); + }); +}); + +describe("areCharacterLayersValid()", () => { + it("should validate default textures array", () => { + expect(areCharacterLayersValid(['male1', 'male2'])).toBe(true); + }); + + it("should not validate an empty array", () => { + expect(areCharacterLayersValid([])).toBe(false); + }); + it("should not validate space only strings", () => { + expect(areCharacterLayersValid([' ', 'male1'])).toBe(false); + }); + + it("should not validate empty strings", () => { + expect(areCharacterLayersValid(['', 'male1'])).toBe(false); + }); +}); \ No newline at end of file From 4fc9a12b950b3be3d09aae980cb1faec09a08134 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Mon, 12 Apr 2021 18:17:04 +0200 Subject: [PATCH 029/184] HotFix style WorkAdventure --- front/dist/resources/html/gameMenuIcon.html | 2 +- front/dist/resources/style/style.css | 39 +++++++++++---------- front/src/Phaser/Menu/MenuScene.ts | 6 +++- front/src/Phaser/Menu/ReportMenu.ts | 2 +- 4 files changed, 28 insertions(+), 21 deletions(-) diff --git a/front/dist/resources/html/gameMenuIcon.html b/front/dist/resources/html/gameMenuIcon.html index 0df8ff80..946cb60a 100644 --- a/front/dist/resources/html/gameMenuIcon.html +++ b/front/dist/resources/html/gameMenuIcon.html @@ -8,7 +8,7 @@ } #menuIcon button img{ width: 14px; - padding-top: 3px; + padding-top: 0; cursor: url('/resources/logos/cursor_pointer.png'), pointer; } #menuIcon section { diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index ba5b6c07..010da692 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -44,13 +44,13 @@ body .message-info.warning{ .video-container i{ position: absolute; width: 100px; - height: 65px; + height: 100px; left: calc(50% - 50px); top: calc(50% - 50px); background-color: black; border-radius: 50%; text-align: center; - padding-top: 35px; + padding-top: 32px; font-size: 28px; color: white; } @@ -58,8 +58,8 @@ body .message-info.warning{ .video-container img{ position: absolute; display: none; - width: 25px; - height: 25px; + width: 40px; + height: 40px; left: 5px; bottom: 5px; padding: 10px; @@ -108,10 +108,12 @@ body .message-info.warning{ margin: 0; padding: 0; cursor: url('/resources/logos/cursor_pointer.png'), pointer; + width: 25px; + height: 25px; } .video-container button.report span{ position: absolute; - bottom: 8px; + bottom: 6px; left: 36px; color: white; font-size: 16px; @@ -211,8 +213,8 @@ video#myCamVideo{ .connecting-spinner { /*display: inline-block;*/ position: absolute; - left: calc(50% - 68px); - top: calc(50% - 68px); + left: calc(50% - 62px); + top: calc(50% - 62px); width: 130px; @@ -850,10 +852,10 @@ div.modal-report-user{ .modal-report-user img{ position: absolute; - height: 50px; - width: 50px; + height: 24px; + width: 24px; z-index: 999; - left: calc(50% - 25px); + left: calc(50% - 12px); top: 10px; } @@ -1017,7 +1019,7 @@ div.modal-report-user{ background-color: #2d2d2dba; right: 34px; margin: 0px; - padding: 6px 0px; + padding: 2px 0px; border-radius: 15px; border: none; color: white; @@ -1073,18 +1075,18 @@ div.modal-report-user{ .discussion .send-message{ position: absolute; bottom: 45px; - width: 220px; + width: 200px; height: 26px; margin-bottom: 10px; } .discussion .send-message input{ position: absolute; - width: calc(100% - 10px); - height: 20px; + width: calc(100% - 20px); + height: 30px; background-color: #171717; color: white; - border-radius: 15px; + border-radius: 10px; border: none; padding: 6px; } @@ -1117,10 +1119,11 @@ div.action p.action-body{ padding: 10px; background-color: #2d2d2dba; color: #fff; - font-size: 12px; + font-size: 14px; + font-weight: 500; text-align: center; - max-width: 150px; - margin-left: calc(50% - 75px); + max-width: 250px; + margin-left: calc(50% - 125px); border-radius: 15px; } .popUpElement{ diff --git a/front/src/Phaser/Menu/MenuScene.ts b/front/src/Phaser/Menu/MenuScene.ts index 58e7f0a6..8899c62f 100644 --- a/front/src/Phaser/Menu/MenuScene.ts +++ b/front/src/Phaser/Menu/MenuScene.ts @@ -104,7 +104,11 @@ export class MenuScene extends Phaser.Scene { } public revealMenuIcon(): void { - (this.menuButton.getChildByID('menuIcon') as HTMLElement).hidden = false + try { + (this.menuButton.getChildByID('menuIcon') as HTMLElement).hidden = false; + } catch (err) { + console.error(err); + } } openSideMenu() { diff --git a/front/src/Phaser/Menu/ReportMenu.ts b/front/src/Phaser/Menu/ReportMenu.ts index d7d0658b..c5d59f38 100644 --- a/front/src/Phaser/Menu/ReportMenu.ts +++ b/front/src/Phaser/Menu/ReportMenu.ts @@ -72,8 +72,8 @@ export class ReportMenu extends Phaser.GameObjects.DOMElement { } public close(): void { + gameManager.getCurrentGameScene(this.scene).userInputManager.restoreControls(); this.opened = false; - gameManager.getCurrentGameScene(this.scene).userInputManager.initKeyBoardEvent(); const mainEl = this.getChildByID('gameReport') as HTMLElement; this.scene.tweens.add({ targets: this, From 6a08ca656f84d042b118e275593329f71554af0c Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Mon, 12 Apr 2021 18:26:36 +0200 Subject: [PATCH 030/184] Add TODO --- front/src/Phaser/Menu/MenuScene.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/front/src/Phaser/Menu/MenuScene.ts b/front/src/Phaser/Menu/MenuScene.ts index 8899c62f..6d09f247 100644 --- a/front/src/Phaser/Menu/MenuScene.ts +++ b/front/src/Phaser/Menu/MenuScene.ts @@ -104,6 +104,7 @@ export class MenuScene extends Phaser.Scene { } public revealMenuIcon(): void { + //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) { From 1693819b6acfd4d4becaca1904585f3c58713ac1 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Mon, 12 Apr 2021 20:23:00 +0200 Subject: [PATCH 031/184] HotFix Share Form height --- front/dist/resources/html/gameShare.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/dist/resources/html/gameShare.html b/front/dist/resources/html/gameShare.html index 85f63b80..60c2ee0f 100644 --- a/front/dist/resources/html/gameShare.html +++ b/front/dist/resources/html/gameShare.html @@ -5,7 +5,7 @@ border-radius: 6px; margin: 20px auto 0; width: 298px; - height: 150px; + height: 160px; } #gameShare h1 { background-image: linear-gradient(top, #f1f3f3, #d4dae0); From 270cdb5c8209e786f293caa23b754a9eba37757f Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Mon, 12 Apr 2021 20:44:29 +0200 Subject: [PATCH 032/184] Button "CREATE MAP" must redirect user on production server --- front/src/Phaser/Menu/MenuScene.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/front/src/Phaser/Menu/MenuScene.ts b/front/src/Phaser/Menu/MenuScene.ts index 6d09f247..9093c618 100644 --- a/front/src/Phaser/Menu/MenuScene.ts +++ b/front/src/Phaser/Menu/MenuScene.ts @@ -319,7 +319,9 @@ export class MenuScene extends Phaser.Scene { } 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/choose-map.html'; window.open(sparkHost, '_blank'); } From c7dcaec9408bfcba7b07423921fc36b022005dbc Mon Sep 17 00:00:00 2001 From: PizZaKatZe Date: Wed, 3 Feb 2021 23:11:19 +0100 Subject: [PATCH 033/184] Make login text input field touch capable (thx @TabscoEye) --- front/src/Phaser/Components/TextInput.ts | 71 +++++++++++++++++------- 1 file changed, 51 insertions(+), 20 deletions(-) diff --git a/front/src/Phaser/Components/TextInput.ts b/front/src/Phaser/Components/TextInput.ts index 1e01029b..a8ea772f 100644 --- a/front/src/Phaser/Components/TextInput.ts +++ b/front/src/Phaser/Components/TextInput.ts @@ -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(); + } } From e80735027919fa362418485fda1f61de7fa7c174 Mon Sep 17 00:00:00 2001 From: PizZaKatZe Date: Wed, 3 Feb 2021 23:22:23 +0100 Subject: [PATCH 034/184] Add touch input support to login scenes (thx @TabscoEye) --- front/src/Phaser/Login/EnableCameraScene.ts | 17 +++++++++- front/src/Phaser/Login/LoginScene.ts | 32 ++++++++++++++++--- .../src/Phaser/Login/SelectCharacterScene.ts | 17 ++++++++-- 3 files changed, 59 insertions(+), 7 deletions(-) diff --git a/front/src/Phaser/Login/EnableCameraScene.ts b/front/src/Phaser/Login/EnableCameraScene.ts index a3ca5cf1..c3aa5077 100644 --- a/front/src/Phaser/Login/EnableCameraScene.ts +++ b/front/src/Phaser/Login/EnableCameraScene.ts @@ -1,6 +1,7 @@ import {gameManager} from "../Game/GameManager"; import {TextField} from "../Components/TextField"; import Image = Phaser.GameObjects.Image; +import Rectangle = Phaser.GameObjects.Rectangle; import {mediaManager} from "../../WebRtc/MediaManager"; import {RESOLUTION} from "../../Enum/EnvironmentVariable"; import {SoundMeter} from "../Components/SoundMeter"; @@ -35,6 +36,7 @@ export class EnableCameraScene extends Phaser.Scene { private microphoneNameField!: TextField; private repositionCallback!: (this: Window, ev: UIEvent) => void; + private mobileTapRectangle!: Rectangle; constructor() { super({ key: EnableCameraSceneName @@ -54,7 +56,19 @@ export class EnableCameraScene extends Phaser.Scene { 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.pressReturnField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height - 30, 'Touch here\n\n or \n\nPress enter to start'); + // For mobile purposes - we need a big enough touchable area. + this.mobileTapRectangle = this.add + .rectangle( + this.game.renderer.width / 2, + this.game.renderer.height - 30, + 200, + 50, + ) + .setInteractive() + .on("pointerdown", () => { + this.login(); + }); this.cameraNameField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height - 60, ''); @@ -195,6 +209,7 @@ export class EnableCameraScene extends Phaser.Scene { } this.textField.x = this.game.renderer.width / 2; + this.mobileTapRectangle.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; diff --git a/front/src/Phaser/Login/LoginScene.ts b/front/src/Phaser/Login/LoginScene.ts index ffdf1367..4ff582b4 100644 --- a/front/src/Phaser/Login/LoginScene.ts +++ b/front/src/Phaser/Login/LoginScene.ts @@ -5,6 +5,8 @@ import Image = Phaser.GameObjects.Image; import {SelectCharacterSceneName} from "./SelectCharacterScene"; import {ResizableScene} from "./ResizableScene"; import {isUserNameValid, maxUserNameLength} from "../../Connexion/LocalUser"; +import { localUserStore } from "../../Connexion/LocalUserStore"; +import Rectangle = Phaser.GameObjects.Rectangle; //todo: put this constants in a dedicated file export const LoginSceneName = "LoginScene"; @@ -20,6 +22,7 @@ export class LoginScene extends ResizableScene { private pressReturnField!: TextField; private logo!: Image; private name: string = ''; + private mobileTapRectangle!: Rectangle; constructor() { super({ @@ -37,17 +40,36 @@ export class LoginScene extends ResizableScene { create() { - 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, maxUserNameLength, this.name,(text: string) => { this.name = text; - }); + localUserStore.setName(text); + }) + .setInteractive() + .on('pointerdown', () => { + this.nameInput.focus(); + }) - this.pressReturnField = new TextField(this, this.game.renderer.width / 2, 130, 'Press enter to start'); + this.textField = new TextField(this, this.game.renderer.width / 2, 50, 'Enter your name:') + .setInteractive() + .on('pointerdown', () => { + this.nameInput.focus(); + }) + // For mobile purposes - we need a big enough touchable area. + this.mobileTapRectangle = this.add.rectangle( + this.game.renderer.width / 2, + 130, + this.game.renderer.width / 2, + 60, + ).setInteractive() + .on('pointerdown', () => { + this.login(this.name) + }) + this.pressReturnField = new TextField(this, this.game.renderer.width / 2, 130, 'Touch here\n\n or \n\nPress 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"; + const infoText = "Commands: \n - Arrows or W, A, 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', () => { @@ -66,6 +88,7 @@ export class LoginScene extends ResizableScene { } private login(name: string): void { + if (this.name === '') return gameManager.setPlayerName(name); this.scene.stop(LoginSceneName) @@ -77,6 +100,7 @@ export class LoginScene extends ResizableScene { 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.mobileTapRectangle.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; diff --git a/front/src/Phaser/Login/SelectCharacterScene.ts b/front/src/Phaser/Login/SelectCharacterScene.ts index a1f3fd81..821fff20 100644 --- a/front/src/Phaser/Login/SelectCharacterScene.ts +++ b/front/src/Phaser/Login/SelectCharacterScene.ts @@ -36,6 +36,7 @@ export class SelectCharacterScene extends AbstractCharacterScene { 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 = new Array(); + private mobileTapRectangle!: Rectangle; private playerModels!: BodyResourceDescriptionInterface[]; constructor() { @@ -69,8 +70,20 @@ export class SelectCharacterScene extends AbstractCharacterScene { this.pressReturnField = new TextField( this, this.game.renderer.width / 2, - 90 + 32 * Math.ceil( this.playerModels.length / this.nbCharactersPerRow) + 40, - 'Press enter to start'); + 90 + 32 * Math.ceil( this.playerModels.length / this.nbCharactersPerRow) + 60, + 'Touch here\n\n or \n\nPress enter to start'); + // For mobile purposes - we need a big enough touchable area. + this.mobileTapRectangle = this.add + .rectangle( + this.game.renderer.width / 2, + 275, + this.game.renderer.width / 2, + 50, + ) + .setInteractive() + .on("pointerdown", () => { + this.nextScene(); + }); const rectangleXStart = this.game.renderer.width / 2 - (this.nbCharactersPerRow / 2) * 32 + 16; From aab65298aad3c92dda62f5a931c8f44f9178390f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C2=AF=5C=5F=28=E3=83=84=29=5F/=C2=AF?= Date: Sat, 20 Feb 2021 20:22:43 +0100 Subject: [PATCH 035/184] Add touch support to character customization screen --- front/src/Phaser/Login/CustomizeScene.ts | 89 ++++++++++++++++++- .../src/Phaser/Login/SelectCharacterScene.ts | 4 + 2 files changed, 91 insertions(+), 2 deletions(-) diff --git a/front/src/Phaser/Login/CustomizeScene.ts b/front/src/Phaser/Login/CustomizeScene.ts index 6cf13a86..3b27f01e 100644 --- a/front/src/Phaser/Login/CustomizeScene.ts +++ b/front/src/Phaser/Login/CustomizeScene.ts @@ -35,6 +35,13 @@ export class CustomizeScene extends AbstractCharacterScene { private Rectangle!: Rectangle; + private mobileTapUP!: Rectangle; + private mobileTapDOWN!: Rectangle; + private mobileTapLEFT!: Rectangle; + private mobileTapRIGHT!: Rectangle; + + private mobileTapENTER!: Rectangle; + private logo!: Image; private selectedLayers: number[] = [0]; @@ -70,7 +77,7 @@ export class CustomizeScene extends AbstractCharacterScene { create() { this.textField = new TextField(this, this.game.renderer.width / 2, 30, 'Customize your own Avatar!'); - this.enterField = new TextField(this, this.game.renderer.width / 2, 40, 'you can start the game by pressing SPACE..'); + this.enterField = new TextField(this, this.game.renderer.width / 2, 60, 'Start the game by pressing ENTER\n\n or touching the center rectangle'); this.logo = new Image(this, this.game.renderer.width - 30, this.game.renderer.height - 20, CustomizeTextures.icon); this.add.existing(this.logo); @@ -78,22 +85,88 @@ export class CustomizeScene extends AbstractCharacterScene { this.arrowRight = new Image(this, this.game.renderer.width*0.9, this.game.renderer.height/2, CustomizeTextures.arrowRight); this.add.existing(this.arrowRight); + this.mobileTapRIGHT = this.add + .rectangle( + this.game.renderer.width*0.9, + this.game.renderer.height/2, + 32, + 32, + ) + .setInteractive() + .on("pointerdown", () => { + this.moveCursorHorizontally(1); + }); 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.mobileTapLEFT = this.add + .rectangle( + this.game.renderer.width/9, + this.game.renderer.height/2, + 32, + 32, + ) + .setInteractive() + .on("pointerdown", () => { + this.moveCursorHorizontally(-1); + }); 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.setStrokeStyle(2, 0xFFFFFF); this.add.existing(this.Rectangle); + this.mobileTapENTER = this.add + .rectangle( + this.cameras.main.worldView.x + this.cameras.main.width / 2, + this.cameras.main.worldView.y + this.cameras.main.height / 2, + 32, + 32, + ) + .setInteractive() + .on("pointerdown", () => { + 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.arrowDown = new Image(this, this.game.renderer.width - 30, 100, CustomizeTextures.arrowUp); this.arrowDown.flipY = true; this.add.existing(this.arrowDown); + this.mobileTapDOWN = this.add + .rectangle( + this.game.renderer.width - 30, + 100, + 32, + 32, + ) + .setInteractive() + .on("pointerdown", () => { + this.moveCursorVertically(1); + }); this.arrowUp = new Image(this, this.game.renderer.width - 30, 60, CustomizeTextures.arrowUp); this.add.existing(this.arrowUp); + this.mobileTapUP = this.add + .rectangle( + this.game.renderer.width - 30, + 60, + 32, + 32, + ) + .setInteractive() + .on("pointerdown", () => { + this.moveCursorVertically(-1); + }); this.createCustomizeLayer(0, 0, 0); this.createCustomizeLayer(0, 0, 1); @@ -268,7 +341,9 @@ export class CustomizeScene extends AbstractCharacterScene { this.moveLayers(); this.Rectangle.x = this.cameras.main.worldView.x + this.cameras.main.width / 2; + this.mobileTapENTER.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.mobileTapENTER.y = this.cameras.main.worldView.y + this.cameras.main.height / 2; this.textField.x = this.game.renderer.width/2; @@ -276,15 +351,25 @@ export class CustomizeScene extends AbstractCharacterScene { this.logo.y = this.game.renderer.height - 20; this.arrowUp.x = this.game.renderer.width - 30; + this.mobileTapUP.x = this.game.renderer.width - 30; this.arrowUp.y = 60; + this.mobileTapUP.y = 60; this.arrowDown.x = this.game.renderer.width - 30; + this.mobileTapDOWN.x = this.game.renderer.width - 30; this.arrowDown.y = 100; + this.mobileTapDOWN.y = 100; this.arrowLeft.x = this.game.renderer.width/9; + this.mobileTapLEFT.x = this.game.renderer.width/9; this.arrowLeft.y = this.game.renderer.height/2; + this.mobileTapLEFT.y = this.game.renderer.height/2; this.arrowRight.x = this.game.renderer.width*0.9; + this.mobileTapRIGHT.x = this.game.renderer.width*0.9; this.arrowRight.y = this.game.renderer.height/2; + this.mobileTapRIGHT.y = this.game.renderer.height/2; + + } } diff --git a/front/src/Phaser/Login/SelectCharacterScene.ts b/front/src/Phaser/Login/SelectCharacterScene.ts index 821fff20..edd09a91 100644 --- a/front/src/Phaser/Login/SelectCharacterScene.ts +++ b/front/src/Phaser/Login/SelectCharacterScene.ts @@ -206,6 +206,10 @@ export class SelectCharacterScene extends AbstractCharacterScene { this.customizeButton.setInteractive().on("pointerdown", () => { this.selectedRectangleYPos = Math.ceil(this.playerModels.length / this.nbCharactersPerRow); this.updateSelectedPlayer(); + this.nextScene(); + }); + this.customizeButtonSelected.setInteractive().on("pointerdown", () => { + this.nextScene(); }); this.selectedPlayer = this.players[0]; From edd4e9e95d7fcf223c43fe616fb53fca33a304e5 Mon Sep 17 00:00:00 2001 From: PizZaKatZe Date: Tue, 9 Mar 2021 20:01:48 +0100 Subject: [PATCH 036/184] Add touch support for Jitsi and website triggers --- front/src/Phaser/Game/GameScene.ts | 4 ++-- front/src/WebRtc/LayoutManager.ts | 10 +++------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index a9ba86a6..24511582 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -668,7 +668,7 @@ export class GameScene extends ResizableScene implements CenterListener { if(openWebsiteTriggerValue && openWebsiteTriggerValue === ON_ACTION_TRIGGER_BUTTON) { let message = allProps.get(WEBSITE_MESSAGE_PROPERTIES); if(message === undefined){ - message = 'Press on SPACE to open the web site'; + message = 'Press SPACE or touch here to open web site'; } layoutManager.addActionButton('openWebsite', message.toString(), () => { openWebsiteFunction(); @@ -700,7 +700,7 @@ export class GameScene extends ResizableScene implements CenterListener { if(jitsiTriggerValue && jitsiTriggerValue === ON_ACTION_TRIGGER_BUTTON) { let message = allProps.get(JITSI_MESSAGE_PROPERTIES); if (message === undefined) { - message = 'Press on SPACE to enter in jitsi meet room'; + message = 'Press SPACE or touch here to enter Jitsi Meet room'; } layoutManager.addActionButton('jitsiRoom', message.toString(), () => { openJitsiRoomFunction(); diff --git a/front/src/WebRtc/LayoutManager.ts b/front/src/WebRtc/LayoutManager.ts index 233b5327..a0b805d4 100644 --- a/front/src/WebRtc/LayoutManager.ts +++ b/front/src/WebRtc/LayoutManager.ts @@ -340,14 +340,10 @@ class LayoutManager { const mainContainer = HtmlUtils.getElementByIdOrFail('main-container'); mainContainer.appendChild(div); - const callBackFunctionTrigger = (() => { - console.log('user click on space => ', id); - callBack(); - }); - //add trigger action - this.actionButtonTrigger.set(id, callBackFunctionTrigger); - userInputManager.addSpaceEventListner(callBackFunctionTrigger); + div.onpointerdown = () => callBack(); + this.actionButtonTrigger.set(id, callBack); + userInputManager.addSpaceEventListner(callBack); } public removeActionButton(id: string, userInputManager: UserInputManager){ From 5462af0dc786700a2a70e4ed199ca9905e0ad6ce Mon Sep 17 00:00:00 2001 From: PizZaKatZe Date: Tue, 13 Apr 2021 01:41:26 +0200 Subject: [PATCH 037/184] Fix first login with empty local storage During first login on a new domain the local storage is empty. This led to a TypeError and hence blank black screen. --- front/src/Connexion/LocalUser.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/front/src/Connexion/LocalUser.ts b/front/src/Connexion/LocalUser.ts index b8e0a204..56483976 100644 --- a/front/src/Connexion/LocalUser.ts +++ b/front/src/Connexion/LocalUser.ts @@ -14,8 +14,8 @@ export function isUserNameValid(value: string): boolean { return regexp.test(value); } -export function areCharacterLayersValid(value: string[]): boolean { - if (!value.length) return false; +export function areCharacterLayersValid(value: string[] | null): boolean { + if (!value || !value.length) return false; for (let i = 0; i < value.length; i++) { if (/^\w+$/.exec(value[i]) === null) { return false; From e75fb9a6a9abc72468dc2545432ffb32d7d9baca Mon Sep 17 00:00:00 2001 From: PizZaKatZe Date: Tue, 13 Apr 2021 00:44:51 +0200 Subject: [PATCH 038/184] Add touch support to companion selection scene --- front/src/Phaser/Login/SelectCompanionScene.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/front/src/Phaser/Login/SelectCompanionScene.ts b/front/src/Phaser/Login/SelectCompanionScene.ts index 9b5c38fb..c7ee56f1 100644 --- a/front/src/Phaser/Login/SelectCompanionScene.ts +++ b/front/src/Phaser/Login/SelectCompanionScene.ts @@ -29,6 +29,8 @@ export class SelectCompanionScene extends ResizableScene { private companions: Array = new Array(); private companionModels: Array = [null]; + private confirmTouchArea!: Rectangle; + constructor() { super({ key: SelectCompanionSceneName @@ -54,12 +56,17 @@ export class SelectCompanionScene extends ResizableScene { create() { this.textField = new TextField(this, this.game.renderer.width / 2, 50, 'Select your companion'); + const confirmTouchAreaY = 115 + 32 * Math.ceil(this.companionModels.length / this.nbCharactersPerRow); this.pressReturnField = new TextField( this, this.game.renderer.width / 2, - 90 + 32 * Math.ceil(this.companionModels.length / this.nbCharactersPerRow), - 'Press enter to start' + confirmTouchAreaY, + 'Touch here\n\n or \n\n press enter to start' ); + this.confirmTouchArea = this.add + .rectangle(this.game.renderer.width / 2, confirmTouchAreaY, 200, 50) + .setInteractive() + .on("pointerdown", this.nextScene.bind(this)); const rectangleXStart = this.game.renderer.width / 2 - (this.nbCharactersPerRow / 2) * 32 + 16; this.selectedRectangle = this.add.rectangle(rectangleXStart, 90, 32, 32).setStrokeStyle(2, 0xFFFFFF); From c3230bc2b39d77f649e6843200daf2f79295dbeb Mon Sep 17 00:00:00 2001 From: PizZaKatZe Date: Wed, 3 Feb 2021 23:28:46 +0100 Subject: [PATCH 039/184] Integrate virtual joystick --- front/dist/resources/html/gameMenu.html | 3 ++ front/dist/resources/style/style.css | 13 ++++++- front/package.json | 1 + front/src/Connexion/LocalUserStore.ts | 10 ++++- front/src/Phaser/Game/GameScene.ts | 27 ++++++++++++- front/src/Phaser/Menu/MenuScene.ts | 20 ++++++++++ .../src/Phaser/UserInput/UserInputManager.ts | 39 +++++++++++++++++-- front/src/types.ts | 22 +++++++++++ front/tsconfig.json | 21 +++++----- front/yarn.lock | 30 ++++++++++++++ 10 files changed, 170 insertions(+), 16 deletions(-) create mode 100644 front/src/types.ts diff --git a/front/dist/resources/html/gameMenu.html b/front/dist/resources/html/gameMenu.html index d5e9ad7e..fa46090a 100644 --- a/front/dist/resources/html/gameMenu.html +++ b/front/dist/resources/html/gameMenu.html @@ -36,6 +36,9 @@
+
+ +
diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index ba5b6c07..5544ca47 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -151,6 +151,7 @@ video#myCamVideo{ .btn-cam-action { + pointer-events: all; position: absolute; bottom: 0px; right: 0px; @@ -186,18 +187,26 @@ video#myCamVideo{ transition: 280ms; } .btn-micro{ + pointer-events: auto; transition: all .3s; right: 44px; } .btn-video{ + pointer-events: auto; transition: all .25s; right: 134px; } .btn-monitor{ + pointer-events: auto; transition: all .2s; right: 224px; } - +.btn-copy{ + pointer-events: auto; + transition: all .3s; + right: 44px; + opacity: 1; +} .btn-cam-action div img{ height: 22px; width: 30px; @@ -502,6 +511,7 @@ input[type=range]:focus::-ms-fill-upper { position: absolute; width: 100%; height: 100%; + pointer-events: none; /* TODO: DO WE NEED FLEX HERE???? WE WANT A SIDEBAR OF EXACTLY 25% (note: flex useful for direction!!!) */ } @@ -537,6 +547,7 @@ input[type=range]:focus::-ms-fill-upper { .sidebar { flex: 0 0 25%; display: flex; + pointer-events: none; } .sidebar > div { diff --git a/front/package.json b/front/package.json index 1ba7f3c2..6189ff4c 100644 --- a/front/package.json +++ b/front/package.json @@ -31,6 +31,7 @@ "generic-type-guard": "^3.2.0", "google-protobuf": "^3.13.0", "phaser": "3.24.1", + "phaser3-rex-plugins": "^1.1.42", "queue-typescript": "^1.0.1", "quill": "^1.3.7", "rxjs": "^6.6.3", diff --git a/front/src/Connexion/LocalUserStore.ts b/front/src/Connexion/LocalUserStore.ts index 4741fc8c..11977f7a 100644 --- a/front/src/Connexion/LocalUserStore.ts +++ b/front/src/Connexion/LocalUserStore.ts @@ -9,7 +9,8 @@ const gameQualityKey = 'gameQuality'; const videoQualityKey = 'videoQuality'; const audioPlayerVolumeKey = 'audioVolume'; const audioPlayerMuteKey = 'audioMute'; -const helpCameraSettingsShown = 'helpCameraSettingsShown'; +const helpCameraSettingsShown = 'helpCameraSettingsShown'; +const joystickKey = 'showJoystick'; class LocalUserStore { saveUser(localUser: LocalUser) { @@ -100,6 +101,13 @@ class LocalUserStore { getHelpCameraSettingsShown(): boolean { return localStorage.getItem(helpCameraSettingsShown) === '1'; } + + setJoystick(value: boolean): void { + localStorage.setItem(joystickKey, value.toString()); + } + getJoystick(): boolean { + return localStorage.getItem(joystickKey) === 'true'; + } } export const localUserStore = new LocalUserStore(); diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 24511582..5d7d173f 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -51,6 +51,10 @@ import {Room} from "../../Connexion/Room"; import {jitsiFactory} from "../../WebRtc/JitsiFactory"; import {urlManager} from "../../Url/UrlManager"; import {audioManager} from "../../WebRtc/AudioManager"; +import {IVirtualJoystick} from "../../types"; +const { + default: VirtualJoystick, +} = require("phaser3-rex-plugins/plugins/virtualjoystick.js"); import {PresentationModeIcon} from "../Components/PresentationModeIcon"; import {ChatModeIcon} from "../Components/ChatModeIcon"; import {OpenChatIcon, openChatIconName} from "../Components/OpenChatIcon"; @@ -164,6 +168,7 @@ export class GameScene extends ResizableScene implements CenterListener { private messageSubscription: Subscription|null = null; private popUpElements : Map = new Map(); private originalMapUrl: string|undefined; + public virtualJoystick!: IVirtualJoystick; constructor(private room: Room, MapUrlFile: string, customKey?: string|undefined) { super({ @@ -183,6 +188,16 @@ export class GameScene extends ResizableScene implements CenterListener { this.connectionAnswerPromise = new Promise((resolve, reject): void => { this.connectionAnswerPromiseResolve = resolve; }) + const joystickVisible = localUserStore.getJoystick(); + if (joystickVisible) { + const canvas = document.querySelector('canvas') + canvas?.addEventListener('click', () => { + const body = document.querySelector('body') + body?.requestFullscreen() + }, { + once: true + }) + } } //hook preload scene @@ -399,9 +414,19 @@ export class GameScene extends ResizableScene implements CenterListener { //initialise list of other player this.MapPlayers = this.physics.add.group({immovable: true}); + this.virtualJoystick = new VirtualJoystick(this, { + x: this.game.renderer.width / 2, + y: this.game.renderer.height / 2, + radius: 20, + base: this.add.circle(0, 0, 20, 0x888888), + thumb: this.add.circle(0, 0, 10, 0xcccccc), + enable: true, + dir: "8dir", + }); + this.virtualJoystick.visible = localUserStore.getJoystick() //create input to move - this.userInputManager = new UserInputManager(this); mediaManager.setUserInputManager(this.userInputManager); + this.userInputManager = new UserInputManager(this, this.virtualJoystick); //notify game manager can to create currentUser in map this.createCurrentPlayer(); diff --git a/front/src/Phaser/Menu/MenuScene.ts b/front/src/Phaser/Menu/MenuScene.ts index f29fd39d..cdbe13f5 100644 --- a/front/src/Phaser/Menu/MenuScene.ts +++ b/front/src/Phaser/Menu/MenuScene.ts @@ -291,6 +291,9 @@ export class MenuScene extends Phaser.Scene { case 'editGameSettingsButton': this.openGameSettingsMenu(); break; + case 'showJoystick': + this.showJoystick(); + break; case 'adminConsoleButton': gameManager.getCurrentGameScene(this).ConsoleGlobalMessageManager.activeMessageConsole(); break; @@ -328,4 +331,21 @@ export class MenuScene extends Phaser.Scene { this.closeGameShare(); this.gameReportElement.close(); } + + private showJoystick() { + const gameScene = gameManager.getCurrentGameScene(this) + if (gameScene?.virtualJoystick) { + const joystickVisible = !gameScene.virtualJoystick.visible + gameScene.virtualJoystick.visible = joystickVisible + localUserStore.setJoystick(joystickVisible) + } + const body = document.querySelector('body') + if (body) { + if (document.fullscreenElement ?? document.fullscreen) { + document.exitFullscreen() + } else { + body.requestFullscreen(); + } + } + } } diff --git a/front/src/Phaser/UserInput/UserInputManager.ts b/front/src/Phaser/UserInput/UserInputManager.ts index 2e686d8e..9f6c76f3 100644 --- a/front/src/Phaser/UserInput/UserInputManager.ts +++ b/front/src/Phaser/UserInput/UserInputManager.ts @@ -1,4 +1,8 @@ +import { Direction, IVirtualJoystick } from "../../types"; import {GameScene} from "../Game/GameScene"; +const { + default: VirtualJoystick, +} = require("phaser3-rex-plugins/plugins/virtualjoystick.js"); interface UserInputManagerDatum { keyInstance: Phaser.Input.Keyboard.Key; @@ -25,6 +29,9 @@ export class ActiveEventList { set(event: UserInputEvent, value: boolean): void { this.KeysCode.set(event, value); } + forEach(callback: (value: boolean, key: UserInputEvent) => void): void { + this.KeysCode.forEach(callback); + } } //this class is responsible for catching user inputs and listing all active user actions at every game tick events. @@ -32,10 +39,32 @@ export class UserInputManager { private KeysCode!: UserInputManagerDatum[]; private Scene: GameScene; private isInputDisabled : boolean; - constructor(Scene : GameScene) { + private joystickEvents = new ActiveEventList(); + + constructor(Scene: GameScene, virtualJoystick: IVirtualJoystick) { this.Scene = Scene; - this.initKeyBoardEvent(); this.isInputDisabled = false; + this.initKeyBoardEvent(); + virtualJoystick.on("update", () => { + const cursorKeys = virtualJoystick.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(){ @@ -78,11 +107,15 @@ export class UserInputManager { if (this.isInputDisabled) { return eventsMap; } + this.joystickEvents.forEach((value, key) => { + if (value) { + eventsMap.set(key, value); + } + }); this.KeysCode.forEach(d => { if (d. keyInstance.isDown) { eventsMap.set(d.event, true); } - }); return eventsMap; } diff --git a/front/src/types.ts b/front/src/types.ts new file mode 100644 index 00000000..d65b7f5a --- /dev/null +++ b/front/src/types.ts @@ -0,0 +1,22 @@ +import Phaser from "phaser"; + +export type CursorKey = { + isDown: boolean +} + +export type Direction = 'left' | 'right' | 'up' | 'down' + +export interface CursorKeys extends Record { + left: CursorKey; + right: CursorKey; + up: CursorKey; + down: CursorKey; +} + +export interface IVirtualJoystick extends Phaser.GameObjects.GameObject { + y: number; + x: number; + visible: boolean; + createCursorKeys: () => CursorKeys; +} + diff --git a/front/tsconfig.json b/front/tsconfig.json index b5c8c74d..fea8fd54 100644 --- a/front/tsconfig.json +++ b/front/tsconfig.json @@ -9,17 +9,18 @@ "downlevelIteration": true, "jsx": "react", "allowJs": true, + "esModuleInterop": true, - "strict": true, /* Enable all strict type-checking options. */ - "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ - "strictNullChecks": true, /* Enable strict null checks. */ - "strictFunctionTypes": true, /* Enable strict checking of function types. */ - "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ - "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ - "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ - "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + "strict": true, /* Enable all strict type-checking options. */ + "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + "strictNullChecks": true, /* Enable strict null checks. */ + "strictFunctionTypes": true, /* Enable strict checking of function types. */ + "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ - "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ - "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */ + "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */ } } diff --git a/front/yarn.lock b/front/yarn.lock index eac99e4e..26573239 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -1819,6 +1819,11 @@ eventemitter3@^2.0.3: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba" integrity sha1-teEHm1n7XhuidxwKmTvgYKWMmbo= +eventemitter3@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" + integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q== + eventemitter3@^4.0.0, eventemitter3@^4.0.4: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" @@ -3064,6 +3069,11 @@ loglevel@^1.6.8: resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.0.tgz#728166855a740d59d38db01cf46f042caa041bb0" integrity sha512-i2sY04nal5jDcagM3FMfG++T69GEEM8CYuOfeOIvmXzOIcwE9a/CJPR0MFM97pYMj/u10lzz7/zd7+qwhrBTqQ== +lokijs@^1.5.11: + version "1.5.11" + resolved "https://registry.yarnpkg.com/lokijs/-/lokijs-1.5.11.tgz#2b2ea82ec66050e4b112c6cfc588dac22d362b13" + integrity sha512-YYyuBPxMn/oS0tFznQDbIX5XL1ltMcwFqCboDr8voYE4VCDzR5vAsrvQDhlnua4lBeqMqHmLvUXRTmRUzUKH1Q== + lower-case@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.1.tgz#39eeb36e396115cc05e29422eaea9e692c9408c7" @@ -3625,6 +3635,11 @@ pako@~1.0.5: resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== +papaparse@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/papaparse/-/papaparse-5.3.0.tgz#ab1702feb96e79ab4309652f36db9536563ad05a" + integrity sha512-Lb7jN/4bTpiuGPrYy4tkKoUS8sTki8zacB5ke1p5zolhcSE4TlWgrlsxjrDTbG/dFVh07ck7X36hUf/b5V68pg== + parallel-transform@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.2.0.tgz#9049ca37d6cb2182c3b1d2c720be94d14a5814fc" @@ -3752,6 +3767,16 @@ pbkdf2@^3.0.3: safe-buffer "^5.0.1" sha.js "^2.4.8" +phaser3-rex-plugins@^1.1.42: + version "1.1.44" + resolved "https://registry.yarnpkg.com/phaser3-rex-plugins/-/phaser3-rex-plugins-1.1.44.tgz#83588ab2801c5b3a80a18be4f0ae37f1f32096b0" + integrity sha512-JPr3+UQv4+uv4etZr80aFhYxw2dk4UYG9/gQ4uMSSXQuc8gXQkXCr3J3zm8LJqH+1FXeK8nncpC7TKa4uKLgQw== + dependencies: + eventemitter3 "^3.1.2" + lokijs "^1.5.11" + papaparse "^5.3.0" + webfontloader "^1.6.28" + phaser@3.24.1: version "3.24.1" resolved "https://registry.yarnpkg.com/phaser/-/phaser-3.24.1.tgz#376e0c965d2a35af37c06ee78627dafbde5be017" @@ -5144,6 +5169,11 @@ wbuf@^1.1.0, wbuf@^1.7.3: dependencies: minimalistic-assert "^1.0.0" +webfontloader@^1.6.28: + version "1.6.28" + resolved "https://registry.yarnpkg.com/webfontloader/-/webfontloader-1.6.28.tgz#db786129253cb6e8eae54c2fb05f870af6675bae" + integrity sha1-23hhKSU8tujq5UwvsF+HCvZnW64= + webpack-cli@^3.3.11: version "3.3.12" resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.12.tgz#94e9ada081453cd0aa609c99e500012fd3ad2d4a" From e7131204342dde9421edfd5db46072a894d0a46b Mon Sep 17 00:00:00 2001 From: PizZaKatZe Date: Fri, 22 Jan 2021 23:25:39 +0100 Subject: [PATCH 040/184] Reposition joystick (thx @TabascoEye) --- front/src/Phaser/Game/GameScene.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 5d7d173f..22ce0f42 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -428,6 +428,12 @@ export class GameScene extends ResizableScene implements CenterListener { mediaManager.setUserInputManager(this.userInputManager); this.userInputManager = new UserInputManager(this, this.virtualJoystick); + // Listener event to reposition virtual joystick + // whatever place you click in game area + this.input.on('pointerdown', (pointer: { x: number; y: number; }) => { + this.virtualJoystick.x = pointer.x; + this.virtualJoystick.y = pointer.y; + }); //notify game manager can to create currentUser in map this.createCurrentPlayer(); this.removeAllRemotePlayers(); //cleanup the list of remote players in case the scene was rebooted From 9c9d262782dd8cce82c17aa1ad9120707d06b3aa Mon Sep 17 00:00:00 2001 From: PizZaKatZe Date: Sat, 23 Jan 2021 02:21:16 +0100 Subject: [PATCH 041/184] Make movement speed depend on joystick force --- front/src/Phaser/Player/Player.ts | 16 ++++--- .../src/Phaser/UserInput/UserInputManager.ts | 48 +++++++++++++++---- front/src/types.ts | 2 + 3 files changed, 50 insertions(+), 16 deletions(-) diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index bb961115..438f1228 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -47,7 +47,7 @@ export class Player extends Character implements CurrentGamerInterface { let x = 0; let y = 0; if (activeEvents.get(UserInputEvent.MoveUp)) { - y = - moveAmount; + y = -moveAmount; direction = PlayerAnimationDirections.Up; moving = true; } else if (activeEvents.get(UserInputEvent.MoveDown)) { @@ -64,16 +64,18 @@ export class Player extends Character implements CurrentGamerInterface { 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) { diff --git a/front/src/Phaser/UserInput/UserInputManager.ts b/front/src/Phaser/UserInput/UserInputManager.ts index 9f6c76f3..c3f2c0bb 100644 --- a/front/src/Phaser/UserInput/UserInputManager.ts +++ b/front/src/Phaser/UserInput/UserInputManager.ts @@ -17,20 +17,24 @@ 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 = new Map(); + private eventMap : Map = new Map(); 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.KeysCode.forEach(callback); + this.eventMap.forEach(callback); + } + any(): boolean { + return Array.from(this.eventMap.values()).reduce((accu, curr) => accu || curr, false); } } @@ -39,14 +43,22 @@ export class UserInputManager { private KeysCode!: UserInputManagerDatum[]; private Scene: GameScene; private isInputDisabled : boolean; + + private joystick : IVirtualJoystick; private joystickEvents = new ActiveEventList(); + private joystickForceThreshold = 60; + private joystickForceAccuX = 0; + private joystickForceAccuY = 0; constructor(Scene: GameScene, virtualJoystick: IVirtualJoystick) { this.Scene = Scene; this.isInputDisabled = false; this.initKeyBoardEvent(); - virtualJoystick.on("update", () => { - const cursorKeys = virtualJoystick.createCursorKeys(); + this.joystick = virtualJoystick; + 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) { @@ -109,11 +121,29 @@ export class UserInputManager { } this.joystickEvents.forEach((value, key) => { if (value) { - eventsMap.set(key, 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); } }); diff --git a/front/src/types.ts b/front/src/types.ts index d65b7f5a..6b99434d 100644 --- a/front/src/types.ts +++ b/front/src/types.ts @@ -16,6 +16,8 @@ export interface CursorKeys extends Record { export interface IVirtualJoystick extends Phaser.GameObjects.GameObject { y: number; x: number; + forceX: number; + forceY: number; visible: boolean; createCursorKeys: () => CursorKeys; } From 340caaff322430b281362932ad4bd7f2b460e089 Mon Sep 17 00:00:00 2001 From: PizZaKatZe Date: Wed, 7 Apr 2021 13:30:51 +0200 Subject: [PATCH 042/184] Make virtual joystick transparent --- front/src/Phaser/Game/GameScene.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 22ce0f42..428d9c0d 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -418,8 +418,8 @@ export class GameScene extends ResizableScene implements CenterListener { x: this.game.renderer.width / 2, y: this.game.renderer.height / 2, radius: 20, - base: this.add.circle(0, 0, 20, 0x888888), - thumb: this.add.circle(0, 0, 10, 0xcccccc), + base: this.add.circle(0, 0, 20), + thumb: this.add.circle(0, 0, 10), enable: true, dir: "8dir", }); From 633bdfcaffb6a6dc746a3105cc35c61015a8ac8c Mon Sep 17 00:00:00 2001 From: PizZaKatZe Date: Wed, 7 Apr 2021 12:42:56 +0200 Subject: [PATCH 043/184] Always enable virtual joystick Keeps 'toggle fullscreen' as a separate game setting. --- front/dist/resources/html/gameMenu.html | 2 +- front/src/Connexion/LocalUserStore.ts | 10 +++++----- front/src/Phaser/Game/GameScene.ts | 17 ++++++----------- front/src/Phaser/Menu/MenuScene.ts | 12 +++--------- 4 files changed, 15 insertions(+), 26 deletions(-) diff --git a/front/dist/resources/html/gameMenu.html b/front/dist/resources/html/gameMenu.html index fa46090a..ce740ec5 100644 --- a/front/dist/resources/html/gameMenu.html +++ b/front/dist/resources/html/gameMenu.html @@ -37,7 +37,7 @@
- +
diff --git a/front/src/Connexion/LocalUserStore.ts b/front/src/Connexion/LocalUserStore.ts index 11977f7a..ace7b17e 100644 --- a/front/src/Connexion/LocalUserStore.ts +++ b/front/src/Connexion/LocalUserStore.ts @@ -10,7 +10,7 @@ const videoQualityKey = 'videoQuality'; const audioPlayerVolumeKey = 'audioVolume'; const audioPlayerMuteKey = 'audioMute'; const helpCameraSettingsShown = 'helpCameraSettingsShown'; -const joystickKey = 'showJoystick'; +const fullscreenKey = 'fullscreen'; class LocalUserStore { saveUser(localUser: LocalUser) { @@ -102,11 +102,11 @@ class LocalUserStore { return localStorage.getItem(helpCameraSettingsShown) === '1'; } - setJoystick(value: boolean): void { - localStorage.setItem(joystickKey, value.toString()); + setFullscreen(value: boolean): void { + localStorage.setItem(fullscreenKey, value.toString()); } - getJoystick(): boolean { - return localStorage.getItem(joystickKey) === 'true'; + getFullscreen(): boolean { + return localStorage.getItem(fullscreenKey) === 'true'; } } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 428d9c0d..294174a2 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -188,16 +188,6 @@ export class GameScene extends ResizableScene implements CenterListener { this.connectionAnswerPromise = new Promise((resolve, reject): void => { this.connectionAnswerPromiseResolve = resolve; }) - const joystickVisible = localUserStore.getJoystick(); - if (joystickVisible) { - const canvas = document.querySelector('canvas') - canvas?.addEventListener('click', () => { - const body = document.querySelector('body') - body?.requestFullscreen() - }, { - once: true - }) - } } //hook preload scene @@ -423,7 +413,7 @@ export class GameScene extends ResizableScene implements CenterListener { enable: true, dir: "8dir", }); - this.virtualJoystick.visible = localUserStore.getJoystick() + this.virtualJoystick.visible = true; //create input to move mediaManager.setUserInputManager(this.userInputManager); this.userInputManager = new UserInputManager(this, this.virtualJoystick); @@ -434,6 +424,11 @@ export class GameScene extends ResizableScene implements CenterListener { this.virtualJoystick.x = pointer.x; this.virtualJoystick.y = pointer.y; }); + + if (localUserStore.getFullscreen()) { + document.querySelector('body')?.requestFullscreen(); + } + //notify game manager can to create currentUser in map this.createCurrentPlayer(); this.removeAllRemotePlayers(); //cleanup the list of remote players in case the scene was rebooted diff --git a/front/src/Phaser/Menu/MenuScene.ts b/front/src/Phaser/Menu/MenuScene.ts index cdbe13f5..b42b575f 100644 --- a/front/src/Phaser/Menu/MenuScene.ts +++ b/front/src/Phaser/Menu/MenuScene.ts @@ -291,8 +291,8 @@ export class MenuScene extends Phaser.Scene { case 'editGameSettingsButton': this.openGameSettingsMenu(); break; - case 'showJoystick': - this.showJoystick(); + case 'toggleFullscreen': + this.toggleFullscreen(); break; case 'adminConsoleButton': gameManager.getCurrentGameScene(this).ConsoleGlobalMessageManager.activeMessageConsole(); @@ -332,13 +332,7 @@ export class MenuScene extends Phaser.Scene { this.gameReportElement.close(); } - private showJoystick() { - const gameScene = gameManager.getCurrentGameScene(this) - if (gameScene?.virtualJoystick) { - const joystickVisible = !gameScene.virtualJoystick.visible - gameScene.virtualJoystick.visible = joystickVisible - localUserStore.setJoystick(joystickVisible) - } + private toggleFullscreen() { const body = document.querySelector('body') if (body) { if (document.fullscreenElement ?? document.fullscreen) { From f82551016a30e37402285b23087f20997454fda6 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Tue, 13 Apr 2021 11:24:57 +0200 Subject: [PATCH 044/184] FIX: in vertical mode, the coWebSite now open from the top --- front/dist/resources/style/cowebsite.scss | 13 +++++++++---- front/src/WebRtc/CoWebsiteManager.ts | 4 ++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/front/dist/resources/style/cowebsite.scss b/front/dist/resources/style/cowebsite.scss index 33a6b6bf..b752728d 100644 --- a/front/dist/resources/style/cowebsite.scss +++ b/front/dist/resources/style/cowebsite.scss @@ -88,21 +88,26 @@ } } @media (max-aspect-ratio: 1/1) { + + #main-container { + display: flex; + flex-direction: column-reverse; + } #cowebsite { left: 0; - bottom: 0; + top: 0; width: 100%; height: 50%; display: flex; - flex-direction: column; + flex-direction: column-reverse; &.loading { - transform: translateY(90%); + transform: translateY(-90%); } &.hidden { - transform: translateY(100%); + transform: translateY(-100%); } main { diff --git a/front/src/WebRtc/CoWebsiteManager.ts b/front/src/WebRtc/CoWebsiteManager.ts index e352d70c..95885a53 100644 --- a/front/src/WebRtc/CoWebsiteManager.ts +++ b/front/src/WebRtc/CoWebsiteManager.ts @@ -32,7 +32,7 @@ class CoWebsiteManager { private resizing: boolean = false; private cowebsiteMainDom: HTMLDivElement; private cowebsiteAsideDom: HTMLDivElement; - + get width(): number { return this.cowebsiteDiv.clientWidth; } @@ -74,7 +74,7 @@ class CoWebsiteManager { private initResizeListeners() { const movecallback = (event:MouseEvent) => { - this.verticalMode ? this.height -= event.movementY / this.getDevicePixelRatio() : this.width -= event.movementX / this.getDevicePixelRatio(); + this.verticalMode ? this.height += event.movementY / this.getDevicePixelRatio() : this.width -= event.movementX / this.getDevicePixelRatio(); this.fire(); } From 4119f69879166ba152f6daab39459bf953aa7d3b Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Tue, 13 Apr 2021 12:30:15 +0200 Subject: [PATCH 045/184] HotFix - Key up m when user write message - Size game share --- front/dist/resources/html/gameShare.html | 2 +- front/src/Phaser/Game/GameScene.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/front/dist/resources/html/gameShare.html b/front/dist/resources/html/gameShare.html index 85f63b80..60c2ee0f 100644 --- a/front/dist/resources/html/gameShare.html +++ b/front/dist/resources/html/gameShare.html @@ -5,7 +5,7 @@ border-radius: 6px; margin: 20px auto 0; width: 298px; - height: 150px; + height: 160px; } #gameShare h1 { background-image: linear-gradient(top, #f1f3f3, #d4dae0); diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 6c4c6e3e..29d9cd5b 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -440,9 +440,10 @@ export class GameScene extends ResizableScene implements CenterListener { this.openChatIcon = new OpenChatIcon(this, 2, this.game.renderer.height - 2) // FIXME: change this to use the UserInputManager class for input - this.input.keyboard.on('keyup-M', () => { + // FIXME: Comment this feature because when user write M key in report input, the layout change. + /*this.input.keyboard.on('keyup-M', () => { this.switchLayoutMode(); - }); + });*/ this.reposition(); From 5ebb0c94e6737e1b83a9b4a2f0e024b1620bb3ab Mon Sep 17 00:00:00 2001 From: kharhamel Date: Wed, 14 Apr 2021 11:56:54 +0200 Subject: [PATCH 046/184] FIX: calling emitPlayGlobalMessage in pusher without the admin tag will throw an error --- back/src/Services/SocketManager.ts | 13 ------------- pusher/src/Services/SocketManager.ts | 4 ++++ 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index 4a76f131..647afc95 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -510,19 +510,6 @@ export class SocketManager { return this.rooms; } - /** - * - * @param token - */ - /*searchClientByUuid(uuid: string): ExSocketInterface | null { - for(const socket of this.sockets.values()){ - if(socket.userUuid === uuid){ - return socket; - } - } - return null; - }*/ - public handleQueryJitsiJwtMessage(user: User, queryJitsiJwtMessage: QueryJitsiJwtMessage) { const room = queryJitsiJwtMessage.getJitsiroom(); diff --git a/pusher/src/Services/SocketManager.ts b/pusher/src/Services/SocketManager.ts index 6efd6f8d..726e11a1 100644 --- a/pusher/src/Services/SocketManager.ts +++ b/pusher/src/Services/SocketManager.ts @@ -364,6 +364,10 @@ export class SocketManager implements ZoneEventListener { } emitPlayGlobalMessage(client: ExSocketInterface, playglobalmessage: PlayGlobalMessage) { + if (!client.tags.includes('admin')) { + //In case of xss injection, we just kill the connection. + throw 'Client is not an admin!'; + } const pusherToBackMessage = new PusherToBackMessage(); pusherToBackMessage.setPlayglobalmessage(playglobalmessage); From 1a4f178728093754ef57ab4d5debf633b3bd7322 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Wed, 14 Apr 2021 15:57:57 +0200 Subject: [PATCH 047/184] Update front/src/Phaser/Menu/MenuScene.ts --- front/src/Phaser/Menu/MenuScene.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/src/Phaser/Menu/MenuScene.ts b/front/src/Phaser/Menu/MenuScene.ts index 9093c618..830f8e48 100644 --- a/front/src/Phaser/Menu/MenuScene.ts +++ b/front/src/Phaser/Menu/MenuScene.ts @@ -321,7 +321,7 @@ export class MenuScene extends Phaser.Scene { private gotToCreateMapPage() { //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/choose-map.html'; + const sparkHost = 'https://workadventu.re/getting-started'; window.open(sparkHost, '_blank'); } From ce2c5e0cb55af3d452b30b92120b84c37b70b3bf Mon Sep 17 00:00:00 2001 From: kharhamel Date: Wed, 14 Apr 2021 15:36:25 +0200 Subject: [PATCH 048/184] FEATURE: added the env variable MAX_PER_GROUP --- .env.template | 4 ++++ back/src/Enum/EnvironmentVariable.ts | 1 + back/src/Model/Group.ts | 4 ++-- docker-compose.single-domain.yaml | 1 + docker-compose.yaml | 3 +++ front/src/Enum/EnvironmentVariable.ts | 1 + front/src/Phaser/Game/GameScene.ts | 11 +++++++++-- 7 files changed, 21 insertions(+), 4 deletions(-) diff --git a/.env.template b/.env.template index a54df82e..a9ae63d7 100644 --- a/.env.template +++ b/.env.template @@ -13,3 +13,7 @@ TURN_STATIC_AUTH_SECRET= # The email address used by Let's encrypt to send renewal warnings (compulsory) ACME_EMAIL= + +MAX_PER_GROUP=4 +MAX_USERNAME_LENGTH=8 + diff --git a/back/src/Enum/EnvironmentVariable.ts b/back/src/Enum/EnvironmentVariable.ts index 8736a856..81693a98 100644 --- a/back/src/Enum/EnvironmentVariable.ts +++ b/back/src/Enum/EnvironmentVariable.ts @@ -11,6 +11,7 @@ const HTTP_PORT = parseInt(process.env.HTTP_PORT || '8080') || 8080; const GRPC_PORT = parseInt(process.env.GRPC_PORT || '50051') || 50051; export const SOCKET_IDLE_TIMER = parseInt(process.env.SOCKET_IDLE_TIMER as string) || 30; // maximum time (in second) without activity before a socket is closed export const TURN_STATIC_AUTH_SECRET = process.env.TURN_STATIC_AUTH_SECRET || ''; +export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || '4'); export { MINIMUM_DISTANCE, diff --git a/back/src/Model/Group.ts b/back/src/Model/Group.ts index f26a0e0d..ffe7a78a 100644 --- a/back/src/Model/Group.ts +++ b/back/src/Model/Group.ts @@ -4,9 +4,9 @@ import {PositionInterface} from "_Model/PositionInterface"; import {Movable} from "_Model/Movable"; import {PositionNotifier} from "_Model/PositionNotifier"; import {gaugeManager} from "../Services/GaugeManager"; +import {MAX_PER_GROUP} from "../Enum/EnvironmentVariable"; export class Group implements Movable { - static readonly MAX_PER_GROUP = 4; private static nextId: number = 1; @@ -88,7 +88,7 @@ export class Group implements Movable { } isFull(): boolean { - return this.users.size >= Group.MAX_PER_GROUP; + return this.users.size >= MAX_PER_GROUP; } isEmpty(): boolean { diff --git a/docker-compose.single-domain.yaml b/docker-compose.single-domain.yaml index 0bd1dcb6..1a390f4c 100644 --- a/docker-compose.single-domain.yaml +++ b/docker-compose.single-domain.yaml @@ -116,6 +116,7 @@ services: ADMIN_API_TOKEN: "$ADMIN_API_TOKEN" JITSI_URL: $JITSI_URL JITSI_ISS: $JITSI_ISS + MAX_PER_GROUP: "$MAX_PER_GROUP" volumes: - ./back:/usr/src/app labels: diff --git a/docker-compose.yaml b/docker-compose.yaml index 504c5b23..cea1bc03 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -38,6 +38,8 @@ services: TURN_USER: "" TURN_PASSWORD: "" START_ROOM_URL: "$START_ROOM_URL" + MAX_PER_GROUP: "$MAX_PER_GROUP" + MAX_USERNAME_LENGTH: "$MAX_USERNAME_LENGTH" command: yarn run start volumes: - ./front:/usr/src/app @@ -110,6 +112,7 @@ services: JITSI_URL: $JITSI_URL JITSI_ISS: $JITSI_ISS TURN_STATIC_AUTH_SECRET: SomeStaticAuthSecret + MAX_PER_GROUP: "MAX_PER_GROUP" volumes: - ./back:/usr/src/app labels: diff --git a/front/src/Enum/EnvironmentVariable.ts b/front/src/Enum/EnvironmentVariable.ts index 5040e59f..11479b0f 100644 --- a/front/src/Enum/EnvironmentVariable.ts +++ b/front/src/Enum/EnvironmentVariable.ts @@ -15,6 +15,7 @@ const ZOOM_LEVEL = 1/*3/4*/; const POSITION_DELAY = 200; // Wait 200ms between sending position events const MAX_EXTRAPOLATION_TIME = 100; // Extrapolate a maximum of 250ms if no new movement is sent by the player export const MAX_USERNAME_LENGTH = parseInt(process.env.MAX_USERNAME_LENGTH || '') || 8; +export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || '4'); export { DEBUG_MODE, diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 294174a2..e9028a00 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -10,7 +10,14 @@ import { RoomJoinedMessageInterface } from "../../Connexion/ConnexionModels"; import {CurrentGamerInterface, hasMovedEventName, Player} from "../Player/Player"; -import {DEBUG_MODE, JITSI_PRIVATE_MODE, POSITION_DELAY, RESOLUTION, ZOOM_LEVEL} from "../../Enum/EnvironmentVariable"; +import { + DEBUG_MODE, + JITSI_PRIVATE_MODE, + MAX_PER_GROUP, + POSITION_DELAY, + RESOLUTION, + ZOOM_LEVEL +} from "../../Enum/EnvironmentVariable"; import {ITiledMap, ITiledMapLayer, ITiledMapLayerProperty, ITiledMapObject, ITiledTileSet} from "../Map/ITiledMap"; import {AddPlayerInterface} from "./AddPlayerInterface"; import {PlayerAnimationDirections} from "../Player/Animation"; @@ -1344,7 +1351,7 @@ ${escapedMessage} this, Math.round(groupPositionMessage.position.x), Math.round(groupPositionMessage.position.y), - groupPositionMessage.groupSize === 4 ? 'circleSprite-red' : 'circleSprite-white' + groupPositionMessage.groupSize === MAX_PER_GROUP ? 'circleSprite-red' : 'circleSprite-white' ); sprite.setDisplayOrigin(48, 48); this.add.existing(sprite); From d7a74baa9d6344ce9e0a04df85f778f3196eef6f Mon Sep 17 00:00:00 2001 From: kharhamel Date: Wed, 14 Apr 2021 17:47:26 +0200 Subject: [PATCH 049/184] FEATURE: add the ability to zoom in and out using touch screen --- front/.eslintrc.json | 3 +-- front/src/Phaser/Game/GameManager.ts | 2 +- front/src/Phaser/Game/GameScene.ts | 10 +++++++--- front/src/Phaser/Login/EnableCameraScene.ts | 6 ++++++ front/src/Phaser/Login/LoginScene.ts | 5 +++++ front/src/Phaser/Login/SelectCharacterScene.ts | 8 ++++++-- front/src/Phaser/Login/SelectCompanionScene.ts | 6 ++++++ front/src/Phaser/UserInput/PinchManager.ts | 16 ++++++++++++++++ front/src/Touch/TouchScreenManager.ts | 16 ++++++++++++++++ front/src/index.ts | 3 +-- front/src/rex-plugins.d.ts | 12 ++++++++++++ 11 files changed, 77 insertions(+), 10 deletions(-) create mode 100644 front/src/Phaser/UserInput/PinchManager.ts create mode 100644 front/src/Touch/TouchScreenManager.ts create mode 100644 front/src/rex-plugins.d.ts diff --git a/front/.eslintrc.json b/front/.eslintrc.json index 3aab37d9..3ba19cb3 100644 --- a/front/.eslintrc.json +++ b/front/.eslintrc.json @@ -24,7 +24,6 @@ "@typescript-eslint" ], "rules": { - "no-unused-vars": "off", - "@typescript-eslint/no-explicit-any": "error" + "no-unused-vars": "off" } } diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts index c146c06d..da10a8ca 100644 --- a/front/src/Phaser/Game/GameManager.ts +++ b/front/src/Phaser/Game/GameManager.ts @@ -34,7 +34,7 @@ export class GameManager { public async init(scenePlugin: Phaser.Scenes.ScenePlugin): Promise { this.startRoom = await connectionManager.initGameConnexion(); await this.loadMap(this.startRoom, scenePlugin); - + if (!this.playerName) { return LoginSceneName; } else if (!this.characterLayers || !this.characterLayers.length) { diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 294174a2..f18a11d1 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -52,9 +52,7 @@ import {jitsiFactory} from "../../WebRtc/JitsiFactory"; import {urlManager} from "../../Url/UrlManager"; import {audioManager} from "../../WebRtc/AudioManager"; import {IVirtualJoystick} from "../../types"; -const { - default: VirtualJoystick, -} = require("phaser3-rex-plugins/plugins/virtualjoystick.js"); +import VirtualJoystick from 'phaser3-rex-plugins/plugins/virtualjoystick.js'; import {PresentationModeIcon} from "../Components/PresentationModeIcon"; import {ChatModeIcon} from "../Components/ChatModeIcon"; import {OpenChatIcon, openChatIconName} from "../Components/OpenChatIcon"; @@ -74,6 +72,8 @@ import DOMElement = Phaser.GameObjects.DOMElement; import {Subscription} from "rxjs"; import {worldFullMessageStream} from "../../Connexion/WorldFullMessageStream"; import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager"; +import {touchScreenManager} from "../../Touch/TouchScreenManager"; +import {PinchManager} from "../UserInput/PinchManager"; export interface GameSceneInitInterface { initPosition: PointInterface|null, @@ -351,6 +351,10 @@ export class GameScene extends ResizableScene implements CenterListener { urlManager.pushRoomIdToUrl(this.room); this.startLayerName = urlManager.getStartLayerNameFromUrl(); + if (touchScreenManager.supportTouchScreen) { + new PinchManager(this); + } + this.messageSubscription = worldFullMessageStream.stream.subscribe((message) => this.showWorldFullError()) const playerName = gameManager.getPlayerName(); diff --git a/front/src/Phaser/Login/EnableCameraScene.ts b/front/src/Phaser/Login/EnableCameraScene.ts index c3aa5077..6a91fc34 100644 --- a/front/src/Phaser/Login/EnableCameraScene.ts +++ b/front/src/Phaser/Login/EnableCameraScene.ts @@ -7,6 +7,8 @@ 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"; export const EnableCameraSceneName = "EnableCameraScene"; enum LoginTextures { @@ -54,6 +56,10 @@ export class EnableCameraScene extends Phaser.Scene { } create() { + if (touchScreenManager.supportTouchScreen) { + new PinchManager(this); + } + 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, 'Touch here\n\n or \n\nPress enter to start'); diff --git a/front/src/Phaser/Login/LoginScene.ts b/front/src/Phaser/Login/LoginScene.ts index 4ff582b4..057cb6ae 100644 --- a/front/src/Phaser/Login/LoginScene.ts +++ b/front/src/Phaser/Login/LoginScene.ts @@ -7,6 +7,8 @@ import {ResizableScene} from "./ResizableScene"; import {isUserNameValid, maxUserNameLength} from "../../Connexion/LocalUser"; import { localUserStore } from "../../Connexion/LocalUserStore"; import Rectangle = Phaser.GameObjects.Rectangle; +import {touchScreenManager} from "../../Touch/TouchScreenManager"; +import {PinchManager} from "../UserInput/PinchManager"; //todo: put this constants in a dedicated file export const LoginSceneName = "LoginScene"; @@ -39,6 +41,9 @@ export class LoginScene extends ResizableScene { } create() { + if (touchScreenManager.supportTouchScreen) { + new PinchManager(this); + } this.nameInput = new TextInput(this, this.game.renderer.width / 2, 70, maxUserNameLength, this.name,(text: string) => { this.name = text; diff --git a/front/src/Phaser/Login/SelectCharacterScene.ts b/front/src/Phaser/Login/SelectCharacterScene.ts index edd09a91..3c8d0281 100644 --- a/front/src/Phaser/Login/SelectCharacterScene.ts +++ b/front/src/Phaser/Login/SelectCharacterScene.ts @@ -4,13 +4,14 @@ import Image = Phaser.GameObjects.Image; import Rectangle = Phaser.GameObjects.Rectangle; import {EnableCameraSceneName} from "./EnableCameraScene"; import {CustomizeSceneName} from "./CustomizeScene"; -import {ResizableScene} from "./ResizableScene"; import {localUserStore} from "../../Connexion/LocalUserStore"; -import {loadAllDefaultModels, loadCustomTexture} from "../Entity/PlayerTexturesLoadingManager"; +import {loadAllDefaultModels} from "../Entity/PlayerTexturesLoadingManager"; import {addLoader} from "../Components/Loader"; import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures"; import {AbstractCharacterScene} from "./AbstractCharacterScene"; import {areCharacterLayersValid} from "../../Connexion/LocalUser"; +import {touchScreenManager} from "../../Touch/TouchScreenManager"; +import {PinchManager} from "../UserInput/PinchManager"; //todo: put this constants in a dedicated file @@ -66,6 +67,9 @@ export class SelectCharacterScene extends AbstractCharacterScene { } create() { + if (touchScreenManager.supportTouchScreen) { + new PinchManager(this); + } this.textField = new TextField(this, this.game.renderer.width / 2, 50, 'Select your character'); this.pressReturnField = new TextField( this, diff --git a/front/src/Phaser/Login/SelectCompanionScene.ts b/front/src/Phaser/Login/SelectCompanionScene.ts index c7ee56f1..aeacd0c2 100644 --- a/front/src/Phaser/Login/SelectCompanionScene.ts +++ b/front/src/Phaser/Login/SelectCompanionScene.ts @@ -8,6 +8,8 @@ import { EnableCameraSceneName } from "./EnableCameraScene"; import { localUserStore } from "../../Connexion/LocalUserStore"; import { CompanionResourceDescriptionInterface } from "../Companion/CompanionTextures"; import { getAllCompanionResources } from "../Companion/CompanionTexturesLoadingManager"; +import {touchScreenManager} from "../../Touch/TouchScreenManager"; +import {PinchManager} from "../UserInput/PinchManager"; export const SelectCompanionSceneName = "SelectCompanionScene"; @@ -54,6 +56,10 @@ export class SelectCompanionScene extends ResizableScene { } create() { + if (touchScreenManager.supportTouchScreen) { + new PinchManager(this); + } + this.textField = new TextField(this, this.game.renderer.width / 2, 50, 'Select your companion'); const confirmTouchAreaY = 115 + 32 * Math.ceil(this.companionModels.length / this.nbCharactersPerRow); diff --git a/front/src/Phaser/UserInput/PinchManager.ts b/front/src/Phaser/UserInput/PinchManager.ts new file mode 100644 index 00000000..f412b787 --- /dev/null +++ b/front/src/Phaser/UserInput/PinchManager.ts @@ -0,0 +1,16 @@ +import {Pinch} from "phaser3-rex-plugins/plugins/gestures.js"; + +export class PinchManager { + private scene: Phaser.Scene; + private pinch!: any; + + constructor(scene: Phaser.Scene) { + this.scene = scene; + this.pinch = new Pinch(scene); + + this.pinch.on('pinch', (pinch:any) => { + this.scene.cameras.main.zoom *= pinch.scaleFactor; + }); + } + +} \ No newline at end of file diff --git a/front/src/Touch/TouchScreenManager.ts b/front/src/Touch/TouchScreenManager.ts new file mode 100644 index 00000000..dcb56ded --- /dev/null +++ b/front/src/Touch/TouchScreenManager.ts @@ -0,0 +1,16 @@ + +class TouchScreenManager { + + readonly supportTouchScreen:boolean; + + constructor() { + this.supportTouchScreen = this.detectTouchscreen(); + } + + //found here: https://stackoverflow.com/questions/4817029/whats-the-best-way-to-detect-a-touch-screen-device-using-javascript#4819886 + detectTouchscreen(): boolean { + return (('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)); + } +} + +export const touchScreenManager = new TouchScreenManager(); \ No newline at end of file diff --git a/front/src/index.ts b/front/src/index.ts index aab45a9b..5a18951b 100644 --- a/front/src/index.ts +++ b/front/src/index.ts @@ -2,7 +2,7 @@ import 'phaser'; import GameConfig = Phaser.Types.Core.GameConfig; import "../dist/resources/style/index.scss"; -import {DEBUG_MODE, JITSI_URL, RESOLUTION} from "./Enum/EnvironmentVariable"; +import {DEBUG_MODE, RESOLUTION} from "./Enum/EnvironmentVariable"; import {LoginScene} from "./Phaser/Login/LoginScene"; import {ReconnectingScene} from "./Phaser/Reconnecting/ReconnectingScene"; import {SelectCharacterScene} from "./Phaser/Login/SelectCharacterScene"; @@ -17,7 +17,6 @@ import {HelpCameraSettingsScene} from "./Phaser/Menu/HelpCameraSettingsScene"; import {localUserStore} from "./Connexion/LocalUserStore"; import {ErrorScene} from "./Phaser/Reconnecting/ErrorScene"; import {iframeListener} from "./Api/IframeListener"; -import {discussionManager} from "./WebRtc/DiscussionManager"; const {width, height} = coWebsiteManager.getGameSize(); diff --git a/front/src/rex-plugins.d.ts b/front/src/rex-plugins.d.ts new file mode 100644 index 00000000..bb0816b6 --- /dev/null +++ b/front/src/rex-plugins.d.ts @@ -0,0 +1,12 @@ + +declare module 'phaser3-rex-plugins/plugins/virtualjoystick.js' { + const content: any; + export default content; +} +declare module 'phaser3-rex-plugins/plugins/gestures-plugin.js' { + const content: any; + export default content; +} +declare module 'phaser3-rex-plugins/plugins/gestures.js' { + export const Pinch: any; +} \ No newline at end of file From 16daf7332a869d4d2aea930c065f942f63a34e36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 16 Apr 2021 21:29:05 +0200 Subject: [PATCH 050/184] Adding the ability to display "Text" objects from Tiled I honestly don't believe text objects look good on map, and in real maps, I think text should be written on tiles. However, for a variety of use cases (like in test maps in the /maps/test directory, it can be useful to be able to display some text easily on a map. This PR adds the ability to display this text. Note: the "font" support cannot work correctly, as Tiled is listing fonts from the local system, and those fonts are not available in a browser. --- front/src/Phaser/Components/TextUtils.ts | 45 ++++++ front/src/Phaser/Game/GameScene.ts | 17 ++- front/src/Phaser/Map/ITiledMap.ts | 14 ++ maps/tests/text_object.json | 179 +++++++++++++++++++++++ 4 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 front/src/Phaser/Components/TextUtils.ts create mode 100644 maps/tests/text_object.json diff --git a/front/src/Phaser/Components/TextUtils.ts b/front/src/Phaser/Components/TextUtils.ts new file mode 100644 index 00000000..0161f52d --- /dev/null +++ b/front/src/Phaser/Components/TextUtils.ts @@ -0,0 +1,45 @@ +import {ITiledMapObject} from "../Map/ITiledMap"; +import Text = Phaser.GameObjects.Text; +import {GameScene} from "../Game/GameScene"; +import TextStyle = Phaser.GameObjects.TextStyle; + +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: {font?: string} = {}; + let font = ''; + if (object.text.italic) { + font += 'italic '; + } + // Note: there is no support for "strikeout" and "underline" + let fontSize: number = 16; + if (object.text.pixelsize) { + font += object.text.pixelsize+'px '; + fontSize = object.text.pixelsize; + } else { + font += '16px '; + } + if (object.text.fontfamily) { + font += '"'+object.text.fontfamily+'"'; + } + if (font !== '') { + options.font = font; + } + const textElem = scene.add.text(object.x, object.y, object.text.text, options); + textElem.setFontSize(fontSize); + let color = '#000000'; + if (object.text.color !== undefined) { + color = object.text.color; + } + textElem.setColor(color); + if (object.text.wrap) { + textElem.setWordWrapWidth(textElem.width); + } + textElem.setAngle(object.rotation); + if (object.text.halign !== undefined) { + textElem.setAlign(object.text.halign); + } + } +} diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 08afca58..d97c6dbc 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -18,7 +18,14 @@ import { RESOLUTION, ZOOM_LEVEL } from "../../Enum/EnvironmentVariable"; -import {ITiledMap, ITiledMapLayer, ITiledMapLayerProperty, ITiledMapObject, ITiledTileSet} from "../Map/ITiledMap"; +import { + ITiledMap, + ITiledMapLayer, + ITiledMapLayerProperty, + ITiledMapObject, + ITiledText, + ITiledTileSet +} from "../Map/ITiledMap"; import {AddPlayerInterface} from "./AddPlayerInterface"; import {PlayerAnimationDirections} from "../Player/Animation"; import {PlayerMovement} from "./PlayerMovement"; @@ -81,6 +88,7 @@ import DOMElement = Phaser.GameObjects.DOMElement; import {Subscription} from "rxjs"; import {worldFullMessageStream} from "../../Connexion/WorldFullMessageStream"; import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager"; +import {TextUtils} from "../Components/TextUtils"; export interface GameSceneInitInterface { initPosition: PointInterface|null, @@ -398,6 +406,13 @@ export class GameScene extends ResizableScene implements CenterListener { if (layer.type === 'objectgroup' && layer.name === 'floorLayer') { depth = 10000; } + if (layer.type === 'objectgroup') { + for (const object of layer.objects) { + if (object.text) { + TextUtils.createTextFromITiledMapObject(this, object); + } + } + } } if (depth === -2) { throw new Error('Your map MUST contain a layer of type "objectgroup" whose name is "floorLayer" that represents the layer characters are drawn at.'); diff --git a/front/src/Phaser/Map/ITiledMap.ts b/front/src/Phaser/Map/ITiledMap.ts index 39e0a1f5..c3f9d10b 100644 --- a/front/src/Phaser/Map/ITiledMap.ts +++ b/front/src/Phaser/Map/ITiledMap.ts @@ -94,6 +94,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 { diff --git a/maps/tests/text_object.json b/maps/tests/text_object.json new file mode 100644 index 00000000..2c3612f0 --- /dev/null +++ b/maps/tests/text_object.json @@ -0,0 +1,179 @@ +{ "compressionlevel":-1, + "height":10, + "infinite":false, + "layers":[ + { + "data":[1, 1, 1, 1, 1, 1, 23, 23, 23, 23, 1, 1, 1, 1, 1, 1, 23, 23, 23, 23, 1, 1, 1, 1, 1, 1, 23, 23, 23, 23, 1, 1, 1, 1, 1, 1, 23, 23, 23, 23, 1, 1, 1, 1, 1, 1, 23, 23, 23, 23, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + "height":10, + "id":1, + "name":"floor", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":10, + "id":2, + "name":"start", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "draworder":"topdown", + "id":3, + "name":"floorLayer", + "objects":[ + { + "height":154.708861498919, + "id":1, + "name":"", + "rotation":0, + "text": + { + "color":"#ffff00", + "fontfamily":"Sans Serif", + "text":"A yellow text that wraps automatically.\n\nIt is 16px high.", + "wrap":true + }, + "type":"", + "visible":true, + "width":127.701300455634, + "x":192.052147832817, + "y":5.45414451778726 + }, + { + "height":19, + "id":3, + "name":"", + "rotation":0, + "text": + { + "text":"Default text", + "wrap":true + }, + "type":"", + "visible":true, + "width":113.494863163736, + "x":1.76065884397454, + "y":8.44497342134471 + }, + { + "height":19, + "id":5, + "name":"", + "rotation":0, + "text": + { + "text":"A very long text with no world wrap so it keeps going" + }, + "type":"", + "visible":true, + "width":349.4375, + "x":1.01295161808517, + "y":168.828173374613 + }, + { + "height":19, + "id":6, + "name":"", + "rotation":45, + "text": + { + "text":"A rotated text" + }, + "type":"", + "visible":true, + "width":117.607252906128, + "x":62.3249441410129, + "y":41.3440913604766 + }, + { + "height":48.1605818096851, + "id":7, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "italic":true, + "pixelsize":27, + "strikeout":true, + "text":"Italic 27px", + "underline":true + }, + "type":"", + "visible":true, + "width":337.807030930545, + "x":6.6207558122554, + "y":209.952070798528 + }, + { + "height":40, + "id":9, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "halign":"center", + "pixelsize":15, + "text":"This text should appear below the plant and be centered", + "wrap":true + }, + "type":"", + "visible":true, + "width":317.4375, + "x":0.78125, + "y":269.5 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 78, 79, 80, 0, 0, 0, 0, 0, 0, 0, 89, 90, 91, 0, 0, 0, 0, 0, 0, 0, 100, 101, 102, 0, 0, 0, 0], + "height":10, + "id":8, + "name":"over", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }], + "nextlayerid":9, + "nextobjectid":10, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"2021.03.23", + "tileheight":32, + "tilesets":[ + { + "columns":11, + "firstgid":1, + "image":"tileset1.png", + "imageheight":352, + "imagewidth":352, + "margin":0, + "name":"tileset1", + "spacing":0, + "tilecount":121, + "tileheight":32, + "tilewidth":32 + }], + "tilewidth":32, + "type":"map", + "version":1.5, + "width":10 +} \ No newline at end of file From 110511461a2c4df4ef3a7383bc91c68fb1cff494 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Sat, 17 Apr 2021 18:55:39 +0200 Subject: [PATCH 051/184] Fixing WorkWrap --- front/src/Phaser/Components/TextUtils.ts | 42 ++++++++++++++---------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/front/src/Phaser/Components/TextUtils.ts b/front/src/Phaser/Components/TextUtils.ts index 0161f52d..beee840a 100644 --- a/front/src/Phaser/Components/TextUtils.ts +++ b/front/src/Phaser/Components/TextUtils.ts @@ -8,38 +8,46 @@ export class TextUtils { if (object.text === undefined) { throw new Error('This object has not textual representation.'); } - const options: {font?: string} = {}; - let font = ''; + const options: { + fontStyle?: string, + fontSize?: string, + fontFamily?: string, + color?: string, + align?: string, + wordWrap?: { + width: number, + useAdvancedWrap?: boolean + } + } = {}; if (object.text.italic) { - font += 'italic '; + options.fontStyle = 'italic'; } // Note: there is no support for "strikeout" and "underline" let fontSize: number = 16; if (object.text.pixelsize) { - font += object.text.pixelsize+'px '; fontSize = object.text.pixelsize; - } else { - font += '16px '; } + options.fontSize = fontSize + 'px'; if (object.text.fontfamily) { - font += '"'+object.text.fontfamily+'"'; + options.fontFamily = '"'+object.text.fontfamily+'"'; } - if (font !== '') { - options.font = font; - } - const textElem = scene.add.text(object.x, object.y, object.text.text, options); - textElem.setFontSize(fontSize); let color = '#000000'; if (object.text.color !== undefined) { color = object.text.color; } - textElem.setColor(color); - if (object.text.wrap) { - textElem.setWordWrapWidth(textElem.width); + options.color = color; + if (object.text.wrap === true) { + options.wordWrap = { + width: object.width, + //useAdvancedWrap: true + } } - textElem.setAngle(object.rotation); if (object.text.halign !== undefined) { - textElem.setAlign(object.text.halign); + 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); } } From 94317be1aedff4c4c3ffab705974cfffa42f210a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Sat, 17 Apr 2021 21:31:09 +0200 Subject: [PATCH 052/184] Improving test cases This PR improves test cases by adding a "index.html" webpage listing test maps and by adding text in the map, explaining what needs to be tested. --- maps/tests/iframe_api.json | 33 +++++++++++------ maps/tests/index.html | 63 ++++++++++++++++++++++++++++++++ maps/tests/jitsi_config.json | 33 +++++++++++------ maps/tests/jitsi_custom_url.json | 33 +++++++++++------ maps/tests/script.js | 4 +- maps/tests/script_api.json | 49 ++++++++++++++++++++----- 6 files changed, 170 insertions(+), 45 deletions(-) create mode 100644 maps/tests/index.html diff --git a/maps/tests/iframe_api.json b/maps/tests/iframe_api.json index 96d92423..fa138500 100644 --- a/maps/tests/iframe_api.json +++ b/maps/tests/iframe_api.json @@ -1,11 +1,4 @@ { "compressionlevel":-1, - "editorsettings": - { - "export": - { - "target":"." - } - }, "height":10, "infinite":false, "layers":[ @@ -60,7 +53,25 @@ "draworder":"topdown", "id":3, "name":"floorLayer", - "objects":[], + "objects":[ + { + "height":141, + "id":1, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":11, + "text":"Test:\nWalk on carpet, an iframe open, click the \"Send chat message\" button\nResult:\nThe chat opens and a \"Hello world!\" message is displayed.\nTest:\nEnter something in the chat\nResult:\nThe message is displayed in the iframe", + "wrap":true + }, + "type":"", + "visible":true, + "width":316.770833333333, + "x":0.28125, + "y":187.833333333333 + }], "opacity":1, "type":"objectgroup", "visible":true, @@ -68,10 +79,10 @@ "y":0 }], "nextlayerid":6, - "nextobjectid":1, + "nextobjectid":3, "orientation":"orthogonal", "renderorder":"right-down", - "tiledversion":"1.3.3", + "tiledversion":"2021.03.23", "tileheight":32, "tilesets":[ { @@ -89,6 +100,6 @@ }], "tilewidth":32, "type":"map", - "version":1.2, + "version":1.5, "width":10 } \ No newline at end of file diff --git a/maps/tests/index.html b/maps/tests/index.html new file mode 100644 index 00000000..f53bbae9 --- /dev/null +++ b/maps/tests/index.html @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ResultTest
+ Success Failure Pending + + Testing Jitsi special config parameters +
+ Success Failure Pending + + Testing jitsiUrl property +
+ Success Failure Pending + + Testing scripting API with an iFrame +
+ Success Failure Pending + + Testing scripting API with a script +
+ + + + + diff --git a/maps/tests/jitsi_config.json b/maps/tests/jitsi_config.json index d806b751..9a812cc8 100644 --- a/maps/tests/jitsi_config.json +++ b/maps/tests/jitsi_config.json @@ -1,11 +1,4 @@ { "compressionlevel":-1, - "editorsettings": - { - "export": - { - "target":"." - } - }, "height":10, "infinite":false, "layers":[ @@ -70,7 +63,25 @@ "draworder":"topdown", "id":3, "name":"floorLayer", - "objects":[], + "objects":[ + { + "height":83.6666666666666, + "id":1, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":13, + "text":"Test:\nWalk on the carpet and press space\nResult:\nJitsi opens, background in green and audio\/video is muted", + "wrap":true + }, + "type":"", + "visible":true, + "width":315.4375, + "x":2.28125, + "y":235.166666666667 + }], "opacity":1, "type":"objectgroup", "visible":true, @@ -78,10 +89,10 @@ "y":0 }], "nextlayerid":6, - "nextobjectid":1, + "nextobjectid":2, "orientation":"orthogonal", "renderorder":"right-down", - "tiledversion":"1.4.3", + "tiledversion":"2021.03.23", "tileheight":32, "tilesets":[ { @@ -99,6 +110,6 @@ }], "tilewidth":32, "type":"map", - "version":1.4, + "version":1.5, "width":10 } \ No newline at end of file diff --git a/maps/tests/jitsi_custom_url.json b/maps/tests/jitsi_custom_url.json index 65e3be9f..637796a1 100644 --- a/maps/tests/jitsi_custom_url.json +++ b/maps/tests/jitsi_custom_url.json @@ -1,11 +1,4 @@ { "compressionlevel":-1, - "editorsettings": - { - "export": - { - "target":"." - } - }, "height":10, "infinite":false, "layers":[ @@ -60,7 +53,25 @@ "draworder":"topdown", "id":3, "name":"floorLayer", - "objects":[], + "objects":[ + { + "height":94.6489098314831, + "id":1, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":11, + "text":"Test:\nWalk on the carpet and press space\nResult:\nJitsi opens on meet.jit.si (check this in the network tab). Note: this test only makes sense if the default configured Jitsi instance is NOT meet.jit.si (check your .env file)", + "wrap":true + }, + "type":"", + "visible":true, + "width":317.361946929159, + "x":2.32853056864467, + "y":224.602707451482 + }], "opacity":1, "type":"objectgroup", "visible":true, @@ -68,10 +79,10 @@ "y":0 }], "nextlayerid":6, - "nextobjectid":1, + "nextobjectid":2, "orientation":"orthogonal", "renderorder":"right-down", - "tiledversion":"1.3.3", + "tiledversion":"2021.03.23", "tileheight":32, "tilesets":[ { @@ -89,6 +100,6 @@ }], "tilewidth":32, "type":"map", - "version":1.2, + "version":1.5, "width":10 } \ No newline at end of file diff --git a/maps/tests/script.js b/maps/tests/script.js index 5c129f1e..25e47249 100644 --- a/maps/tests/script.js +++ b/maps/tests/script.js @@ -3,8 +3,8 @@ console.log('SCRIPT LAUNCHED'); var isFirstTimeTuto = false; var textFirstPopup = 'Hey ! This is how to open start a discussion with someone ! You can be 4 max in a booble'; var textSecondPopup = 'You can also use the chat to communicate ! '; -var targetObjectTutoBubble ='tutoBobble'; -var targetObjectTutoChat ='tutoChat'; +var targetObjectTutoBubble ='myPopup1'; +var targetObjectTutoChat ='myPopup2'; var popUpExplanation = undefined; function launchTuto (){ WA.openPopup(targetObjectTutoBubble, textFirstPopup, [ diff --git a/maps/tests/script_api.json b/maps/tests/script_api.json index 54d93819..73ef9d01 100644 --- a/maps/tests/script_api.json +++ b/maps/tests/script_api.json @@ -1,11 +1,4 @@ { "compressionlevel":-1, - "editorsettings": - { - "export": - { - "target":"." - } - }, "height":10, "infinite":false, "layers":[ @@ -95,6 +88,42 @@ "width":125.735549178518, "x":13.649632619596, "y":50.8502491249093 + }, + { + "height":67, + "id":3, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":11, + "text":"Test:\nWalk on top carpet\nResult:\nA message \"Don't step on my carpet\" is displayed", + "wrap":true + }, + "type":"", + "visible":true, + "width":252.4375, + "x":2.78125, + "y":2.5 + }, + { + "height":67, + "id":4, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":11, + "text":"Test:\nWalk on bottom carpet\nResult:\nA series of 2 popups open. A bubble opens. The player cannot move until popup closes.\nWhen the player leaves the zone, the bubble closes.", + "wrap":true + }, + "type":"", + "visible":true, + "width":252.438, + "x":-1.71899999999999, + "y":163.5 }], "opacity":1, "type":"objectgroup", @@ -103,7 +132,7 @@ "y":0 }], "nextlayerid":8, - "nextobjectid":3, + "nextobjectid":5, "orientation":"orthogonal", "properties":[ { @@ -112,7 +141,7 @@ "value":"script.js" }], "renderorder":"right-down", - "tiledversion":"1.4.3", + "tiledversion":"2021.03.23", "tileheight":32, "tilesets":[ { @@ -130,6 +159,6 @@ }], "tilewidth":32, "type":"map", - "version":1.4, + "version":1.5, "width":10 } \ No newline at end of file From dab74460bb857af0f378d0f69647f0928608c7d8 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Mon, 19 Apr 2021 15:45:46 +0200 Subject: [PATCH 053/184] Hotfix redirect map tuto --- maps/Tuto/tutoV3.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maps/Tuto/tutoV3.json b/maps/Tuto/tutoV3.json index 0362ae1c..9953f5a9 100644 --- a/maps/Tuto/tutoV3.json +++ b/maps/Tuto/tutoV3.json @@ -24,7 +24,7 @@ { "name":"exitUrl", "type":"string", - "value":"@\/tcm\/workadventure\/floor0" + "value":"\/@\/tcm\/workadventure\/floor0" }], "type":"tilelayer", "visible":true, From 56287a29583e46572a8aabbda24b6dca2bc1f537 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Fri, 16 Apr 2021 18:49:04 +0200 Subject: [PATCH 054/184] put the virtual joystick into the userInputManager and restricted it to touchscreens --- front/.eslintrc.json | 3 +- front/src/Phaser/Game/GameScene.ts | 20 +------ front/src/Phaser/UserInput/PinchManager.ts | 12 +++-- .../src/Phaser/UserInput/UserInputManager.ts | 53 +++++++++++++------ front/src/rex-plugins.d.ts | 6 +-- 5 files changed, 54 insertions(+), 40 deletions(-) diff --git a/front/.eslintrc.json b/front/.eslintrc.json index 3ba19cb3..3aab37d9 100644 --- a/front/.eslintrc.json +++ b/front/.eslintrc.json @@ -24,6 +24,7 @@ "@typescript-eslint" ], "rules": { - "no-unused-vars": "off" + "no-unused-vars": "off", + "@typescript-eslint/no-explicit-any": "error" } } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index f18a11d1..cfefd548 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -408,26 +408,10 @@ export class GameScene extends ResizableScene implements CenterListener { //initialise list of other player this.MapPlayers = this.physics.add.group({immovable: true}); - this.virtualJoystick = new VirtualJoystick(this, { - x: this.game.renderer.width / 2, - y: this.game.renderer.height / 2, - radius: 20, - base: this.add.circle(0, 0, 20), - thumb: this.add.circle(0, 0, 10), - enable: true, - dir: "8dir", - }); - this.virtualJoystick.visible = true; + //create input to move mediaManager.setUserInputManager(this.userInputManager); - this.userInputManager = new UserInputManager(this, this.virtualJoystick); - - // Listener event to reposition virtual joystick - // whatever place you click in game area - this.input.on('pointerdown', (pointer: { x: number; y: number; }) => { - this.virtualJoystick.x = pointer.x; - this.virtualJoystick.y = pointer.y; - }); + this.userInputManager = new UserInputManager(this); if (localUserStore.getFullscreen()) { document.querySelector('body')?.requestFullscreen(); diff --git a/front/src/Phaser/UserInput/PinchManager.ts b/front/src/Phaser/UserInput/PinchManager.ts index f412b787..f7c445fa 100644 --- a/front/src/Phaser/UserInput/PinchManager.ts +++ b/front/src/Phaser/UserInput/PinchManager.ts @@ -2,14 +2,20 @@ import {Pinch} from "phaser3-rex-plugins/plugins/gestures.js"; export class PinchManager { private scene: Phaser.Scene; - private pinch!: any; + private pinch!: any; // eslint-disable-line constructor(scene: Phaser.Scene) { this.scene = scene; this.pinch = new Pinch(scene); - this.pinch.on('pinch', (pinch:any) => { - this.scene.cameras.main.zoom *= pinch.scaleFactor; + this.pinch.on('pinch', (pinch:any) => { // eslint-disable-line + let newZoom = this.scene.cameras.main.zoom * pinch.scaleFactor; + if (newZoom < 0.25) { + newZoom = 0.25; + } else if(newZoom > 2) { + newZoom = 2; + } + this.scene.cameras.main.setZoom(newZoom); }); } diff --git a/front/src/Phaser/UserInput/UserInputManager.ts b/front/src/Phaser/UserInput/UserInputManager.ts index c3f2c0bb..ddc3ffa8 100644 --- a/front/src/Phaser/UserInput/UserInputManager.ts +++ b/front/src/Phaser/UserInput/UserInputManager.ts @@ -1,5 +1,6 @@ import { Direction, IVirtualJoystick } from "../../types"; import {GameScene} from "../Game/GameScene"; +import {touchScreenManager} from "../../Touch/TouchScreenManager"; const { default: VirtualJoystick, } = require("phaser3-rex-plugins/plugins/virtualjoystick.js"); @@ -44,17 +45,39 @@ export class UserInputManager { private Scene: GameScene; private isInputDisabled : boolean; - private joystick : IVirtualJoystick; + private joystick!: IVirtualJoystick; private joystickEvents = new ActiveEventList(); private joystickForceThreshold = 60; private joystickForceAccuX = 0; private joystickForceAccuY = 0; - constructor(Scene: GameScene, virtualJoystick: IVirtualJoystick) { + constructor(Scene: GameScene) { this.Scene = Scene; this.isInputDisabled = false; this.initKeyBoardEvent(); - this.joystick = virtualJoystick; + if (touchScreenManager.supportTouchScreen) { + this.initVirtualJoystick(); + } + } + + initVirtualJoystick() { + this.joystick = new VirtualJoystick(this, { + x: this.Scene.game.renderer.width / 2, + y: this.Scene.game.renderer.height / 2, + radius: 20, + base: this.Scene.add.circle(0, 0, 20), + thumb: this.Scene.add.circle(0, 0, 10), + enable: true, + dir: "8dir", + }); + this.joystick.visible = true; + + // Listener event to reposition virtual joystick + // whatever place you click in game area + this.Scene.input.on('pointerdown', (pointer: { x: number; y: number; }) => { + this.joystick.x = pointer.x; + this.joystick.y = pointer.y; + }); this.joystick.on("update", () => { this.joystickForceAccuX = this.joystick.forceX ? this.joystickForceAccuX : 0; this.joystickForceAccuY = this.joystick.forceY ? this.joystickForceAccuY : 0; @@ -62,18 +85,18 @@ export class UserInputManager { 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; + 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; } } }); diff --git a/front/src/rex-plugins.d.ts b/front/src/rex-plugins.d.ts index bb0816b6..7ba8f65b 100644 --- a/front/src/rex-plugins.d.ts +++ b/front/src/rex-plugins.d.ts @@ -1,12 +1,12 @@ declare module 'phaser3-rex-plugins/plugins/virtualjoystick.js' { - const content: any; + const content: any; // eslint-disable-line export default content; } declare module 'phaser3-rex-plugins/plugins/gestures-plugin.js' { - const content: any; + const content: any; // eslint-disable-line export default content; } declare module 'phaser3-rex-plugins/plugins/gestures.js' { - export const Pinch: any; + export const Pinch: any; // eslint-disable-line } \ No newline at end of file From e7b5bb261d075bd27794b178f5ac944731a18c30 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Mon, 19 Apr 2021 20:19:40 +0200 Subject: [PATCH 055/184] HotFix performance - Change forceSetTimeOut to false and permit to freeze the scene. - Fix style sending audio message - Fix banned message from admin console --- front/dist/resources/style/style.css | 3 ++- front/src/Connexion/AdminMessagesService.ts | 1 + front/src/Connexion/RoomConnection.ts | 2 +- front/src/index.ts | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index 010da692..5680da4a 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -795,7 +795,7 @@ input[type=range]:focus::-ms-fill-upper { right: 0; } .main-container .audio-playing img{ - width: 30px; + /*width: 30px;*/ border-radius: 50%; background-color: #ffda01; padding: 10px; @@ -803,6 +803,7 @@ input[type=range]:focus::-ms-fill-upper { .main-container .audio-playing p{ color: white; margin-left: 10px; + margin-top: 14px; } /* VIDEO QUALITY */ diff --git a/front/src/Connexion/AdminMessagesService.ts b/front/src/Connexion/AdminMessagesService.ts index 7f96a673..1750dd28 100644 --- a/front/src/Connexion/AdminMessagesService.ts +++ b/front/src/Connexion/AdminMessagesService.ts @@ -5,6 +5,7 @@ export enum AdminMessageEventTypes { admin = 'message', audio = 'audio', ban = 'ban', + banned = 'banned', } interface AdminMessageEvent { diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index 0220cb52..cc3b71de 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -184,7 +184,7 @@ export class RoomConnection implements RoomConnection { } else if (message.hasSendusermessage()) { adminMessagesService.onSendusermessage(message.getSendusermessage() as SendUserMessage); } else if (message.hasBanusermessage()) { - adminMessagesService.onSendusermessage(message.getSendusermessage() as BanUserMessage); + adminMessagesService.onSendusermessage(message.getBanusermessage() as BanUserMessage); } else if (message.hasWorldfullwarningmessage()) { worldFullWarningStream.onMessage(); } else if (message.hasRefreshroommessage()) { diff --git a/front/src/index.ts b/front/src/index.ts index c0663acd..f8756d57 100644 --- a/front/src/index.ts +++ b/front/src/index.ts @@ -33,7 +33,7 @@ const fps : Phaser.Types.Core.FPSConfig = { /** * Use setTimeout instead of requestAnimationFrame to run the game loop. */ - forceSetTimeOut: true, + forceSetTimeOut: false, /** * Calculate the average frame delta from this many consecutive frame intervals. */ From 415d8f9466699d15d3a9e9de8afaff1c91cdef3d Mon Sep 17 00:00:00 2001 From: kharhamel Date: Tue, 20 Apr 2021 10:52:06 +0200 Subject: [PATCH 056/184] the joystick is now visible only when pointer is down --- front/src/Phaser/Game/GameScene.ts | 3 --- .../src/Phaser/UserInput/UserInputManager.ts | 23 +++++++++++-------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index cfefd548..0f31a519 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -51,8 +51,6 @@ import {Room} from "../../Connexion/Room"; import {jitsiFactory} from "../../WebRtc/JitsiFactory"; import {urlManager} from "../../Url/UrlManager"; import {audioManager} from "../../WebRtc/AudioManager"; -import {IVirtualJoystick} from "../../types"; -import VirtualJoystick from 'phaser3-rex-plugins/plugins/virtualjoystick.js'; import {PresentationModeIcon} from "../Components/PresentationModeIcon"; import {ChatModeIcon} from "../Components/ChatModeIcon"; import {OpenChatIcon, openChatIconName} from "../Components/OpenChatIcon"; @@ -168,7 +166,6 @@ export class GameScene extends ResizableScene implements CenterListener { private messageSubscription: Subscription|null = null; private popUpElements : Map = new Map(); private originalMapUrl: string|undefined; - public virtualJoystick!: IVirtualJoystick; constructor(private room: Room, MapUrlFile: string, customKey?: string|undefined) { super({ diff --git a/front/src/Phaser/UserInput/UserInputManager.ts b/front/src/Phaser/UserInput/UserInputManager.ts index ddc3ffa8..602afee7 100644 --- a/front/src/Phaser/UserInput/UserInputManager.ts +++ b/front/src/Phaser/UserInput/UserInputManager.ts @@ -1,9 +1,7 @@ import { Direction, IVirtualJoystick } from "../../types"; import {GameScene} from "../Game/GameScene"; import {touchScreenManager} from "../../Touch/TouchScreenManager"; -const { - default: VirtualJoystick, -} = require("phaser3-rex-plugins/plugins/virtualjoystick.js"); +import VirtualJoystick from 'phaser3-rex-plugins/plugins/virtualjoystick.js'; interface UserInputManagerDatum { keyInstance: Phaser.Input.Keyboard.Key; @@ -21,6 +19,9 @@ export enum UserInputEvent { JoystickMove, } +const outOfScreenX = -1000; +const outOfScreenY = -1000; + //we cannot use a map structure so we have to create a replacment export class ActiveEventList { private eventMap : Map = new Map(); @@ -62,22 +63,23 @@ export class UserInputManager { initVirtualJoystick() { this.joystick = new VirtualJoystick(this, { - x: this.Scene.game.renderer.width / 2, - y: this.Scene.game.renderer.height / 2, + x: outOfScreenX, + y: outOfScreenY, radius: 20, - base: this.Scene.add.circle(0, 0, 20), - thumb: this.Scene.add.circle(0, 0, 10), + base: this.Scene.add.circle(0, 0, 20, 0xdddddd), + thumb: this.Scene.add.circle(0, 0, 10, 0x000000), enable: true, dir: "8dir", }); - this.joystick.visible = true; - // Listener event to reposition virtual joystick - // whatever place you click in game area this.Scene.input.on('pointerdown', (pointer: { x: number; y: number; }) => { this.joystick.x = pointer.x; this.joystick.y = pointer.y; }); + this.Scene.input.on('pointerup', (pointer: { x: number; y: number; }) => { + this.joystick.x = outOfScreenX; + this.joystick.y = outOfScreenY; + }); this.joystick.on("update", () => { this.joystickForceAccuX = this.joystick.forceX ? this.joystickForceAccuX : 0; this.joystickForceAccuY = this.joystick.forceY ? this.joystickForceAccuY : 0; @@ -128,6 +130,7 @@ export class UserInputManager { this.Scene.input.keyboard.removeAllListeners(); } + //todo: should we also disable the joystick? disableControls(){ this.Scene.input.keyboard.removeAllKeys(); this.isInputDisabled = true; From 341b048d6c85a83b0acb8ec6702009e51555b969 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Tue, 20 Apr 2021 11:40:39 +0200 Subject: [PATCH 057/184] improved the visuals of the joystick --- .../resources/objects/joystickSplitted.png | Bin 0 -> 12833 bytes .../objects/smallHandleFilledGrey.png | Bin 0 -> 3554 bytes front/src/Phaser/Components/MobileJoystick.ts | 35 ++++++++++++++++++ front/src/Phaser/Game/GameScene.ts | 8 +++- .../src/Phaser/UserInput/UserInputManager.ts | 27 ++------------ 5 files changed, 45 insertions(+), 25 deletions(-) create mode 100644 front/dist/resources/objects/joystickSplitted.png create mode 100644 front/dist/resources/objects/smallHandleFilledGrey.png create mode 100644 front/src/Phaser/Components/MobileJoystick.ts diff --git a/front/dist/resources/objects/joystickSplitted.png b/front/dist/resources/objects/joystickSplitted.png new file mode 100644 index 0000000000000000000000000000000000000000..04ae67a3bef04ab710b4793e0f9990609d5a4ed4 GIT binary patch literal 12833 zcmb_@i91y9|NdbL*(cjrLQ^O^MaY(pWQj1xzGO*dNs&ZkDU6+DSKgHE$llnu5~ghL zWb9iZ8CfC;;rDodzJJ2+Tvu1uHJ-CQ&+~e{?$>?aC-SPH4hth6BLqP#x|d-S2%>R5 z_{Ts8e&ON2APoLHE1QI_*ON(JFG;VzP7%vFhJBu1%%)p?hVJpUdLkRkDJd!R zW~DY;^kZsAJt9L42A!)$ie_eJcJfV%d}$xtP!6~eNshDT-JX|aZb|H$mwEFkVzFKV z`ZZigN=I3v$!gHDDP@XC_N1eyU$JcR-}tA;udq%op~$fy`_XpH@87@u&~0_BGOo2D zcfgdg{#f{r<1@a}#TU>uk7s;IIcR(o`8nzzE%%MPZ(Xp6GCuGCM?UMYaL>OJJe!{b zPEZn0UrZg-eoxIlj;Nhr2yNEuY$$CIY&GPz&UoF~^(DZ&h)^$*-M25&VOQ8Io3Owo zWl=uO;^E;DQAB+^$F*uld0~p^FD)&dNs+St{nxqErD`OQuu((#UTlJH>pi-^)@D zpEF?nID$VzS%dxkw4HaCv56!|dOdL8NUlcbNSmgpvwV6Dk*tgNQVrhyx=i2tsZcn9 zrm^k*iRwu59`u`jH9Kx*95bzlG^u9yX$W|`J%gBMMd#|5mr?&pViv=-soI^9clVQ- zk0y$_pEJ3V*=7m%-W8ayfd<1J9$4mFeQFMdqEWpZIjK>=LP}-*qGa5zoQ{2z1sBEvG_2qxB z72PhR%3WZ$vHf5%#myx&x815f4S5Sw{{PG!bcKm1y6 z?ZZedBq&ucPrcl0?qjD1<(|L|Z14;`t7)x}bQ#IUCNf!DS^3vJH=e?geLmjJdJNC< zL*F_k^f}RO+2EDfxVX~#=};y`f>T_3G>_*Qb#-+e_`X!K-uXzf0d~X^@ue%TBK|Oo z^>aEJ6C3-y%9Vn)bWSi_5yaH>zWThd8!I;-Nv5A!zFzrpo?Sy2ze)3;kRU#RoZ?)L zY>>J3S@{^4s`K&j@qK4I8aZ062^%HQ?-#ANd?IsE86tTNgu+}`s!WjF@}rwy!878Y z8@5c~yi17zXP51&^Z%as6$lBw%ZHlnk(KqpAmQan@e1b;Pdy?cA~xkZe&nGKolGyc z(6@HiK76P!w=dL8-PuEHALc|l2#bxs3uBrZ=s(lZIm&2d_^Y`NyRVbp6s*3fyIIiaLeTHE z{Sfq(Y0V##hsKZN&lOR77Kh4O9xjcN@@jYV>cg0LnkhJM^0x%K)%x0p;tdyJf7QN* zGs~C0XUipUoP2J$lBSpEPm_i7l|fbLrd6<(C-D1=ev?ORjHR3?43naJyq<3gOY-zn z4TS^+)s10wP{2w8K9XRidc|VQPjqx`}Fv@i42H#$Gq(QBvq!-pm zHgqRM?weL=_|u{Qlc`%!KlHnB<1bpT$lP{@G3?zL(u;->xdgYH2el;MD0|NXtE?Qp zu)?1B_t(PbvhF#r8M$twB(b=efJ4FjZ5MQdFbO{c_bd7O#s+X?g~~f$-qlpUd1G^A z+Jlr~is-y10E3Nj<9h6PhE^ztg_oDjGBPz!1|F*>@vqWY##5u7?*zuZX^ zz~4H+cSV_X`aI;-ys}TaG=BkF21?-xffa$f~w1Y2cTB8+xo_)njf1TSBZv0g~^?nsmFTQ4p9rrHx8;;!r0@+Zo5 z>Q(_NgL7*B>SrgYs+VYYVXNScCp27TYzsm2oZH59f z(Y@CnH!IaxTIck)2}e5bx)E+3bvYq+>)uL9OzqDKZaK0=9MX%yls|PiP;YvLq2`< zWo6!C-{ySKl!S6`2IOn#fp6#bX}Wb8&N)p#EMrZp?db@9YG5*4=NLVe~tS z?uv$?th8xDhu)654v6^|t!?4lpdZ#@YsZ6=0;XDW8A<%y=46g%JuU^bwHIL4vVKlB zoD=I>;C`GqMQmHlchYbEjE=zF_u3aPUL1=iKa@=6L6$Pk+~~Cj_gNv?G&tN1Q>7Vk zzwe3*ABraLpvLyd&O6y3r#TrX>0Gzuqw zUs(yFwqn@+N(W=0#t$waBx|dx);HQ6e_Cbw@UvjHxCnmN0~WS77BvEXw)1$t*7C@W z;)-t1IT5GymA?yo;hyBpd2s*-mZsb|29Yvbz865(Q zaN#?0F-3OIgZ8%gBf`U3X;+!76}s#h_R40I=!d~*9Qk_bq8&r)Zyh1?FbAcYtEW_* z>ZFaYl-M=-m%n^Da|ar%9*3Qyh&EH7KUbxeWNgG)b3+21mM8JmLh@NNJA0NXZb#eE z;$jH}?{D4vba!G(GSGTEc(RATpO<{ccJngQWCp+9wR__5z+XMP0b|YwFI2nA#}uV=Mz_12KY#vk>+ux5Ud}{cA-~zg)CvE^k}G~X_NH`e zPet^rS=v@pq1S;`ec+-5Np8>Q=Y5x<;1lix9Eob69M{NjM_<`$e)RdUmvpL|-gw%pWHLEk zazOV@IIA<#WCG_~7M>9Z|B~3$)U@dFOM5_aZR_t9{Mu>#IdxEtQl%(*uflg_-}_A1 zK{}SFxT45%TLF`!mgR8ID+|fVsgaR7Ot7-C8|WIm=X;y#EhN=%<&fUrB6C$P*6YwJ zwOx`zc2&!4@lp9NU%u2qPE3{CsSoeb+uGW))TO!CYTDJ`TeyepHXpp=k}yN_^_Sen zX8?+r0X6I^w6*J~@ z5y}U_L7KQfHX)2JBVD~s}9h$I3_S>|IGyoD_tGbyh zzOK4r3F%;P@fNPsKYt#y31|+mhcHh&VsFKpXd<12jkM`C zX8q_apI!a0$q(wDBva>IL+(URN^k&p{p~a1a>lB9dHn&9FRQKH zeR#-mGXlc+#9n1HiWq1;df!kuup<@z8&o-`?jmD0;iHLF4Gj&}9vg@R7>xJ2J-~-% z*fs}jMJsL}$2Wk*=g=IualXMYQ&Gy}WwsSsta)Ex9)g637T*Q~)b}s)@3I^FfJ`VF ze3>T$rKZ+5G@P*iVy5rIMTYP#{f;qPS+SRhh5q32y-~ge%S7T2S&z{-GNaTYZmK9) zaWw+W8fEdVGFifW@2%cNn1DFoz9Ab$#OwZ`A^2^^hlbQ2afSnqT|)HBR~6W?luJ2R zaoEH%ShDI0F8qe|twK8LSKB7g@u@o*3|MEkfE0eqj1Qk{h3&j6809rshsMRk#Vy}^ zdzi<)e8fUwpqB0U|}BrLe|Jb=c08 zj1SInq<&p45w(QFt)WB(Gs;d51@7tH+&xd&xMA!{y!=&zQ}>>5L+QgtVxziNB#(}c z&WY@Y7vP_!6d9OT2ETp)vPvGI$3KNI3-`CFLQ`mjSTX#@2SC_I(9or#9F$joiJ zQ3hC)W;#KyvlxwwkJo9Qj3hq22)l1${T;PJ$etOIb9CX^RW{AJ}DL3}FGANC%t|JUq9wTOHIvT+hy z^yir<68LU&_wvIl@H2ysG|yt{wY(Ya*&cxKZdHj{9|il;Fh<@2R<(|0H?Dl};@qL> zT;g^7?jbv=NT%7tOx%r^5DE45+P@Rqu9<3B<4T-UD6u9g!1S~|**>n)xuN7c8at7dHs5JVjqpOZiXN3EPlm1s)OXP%y`7^z|Oc1 zw-J8uA&9Fb={33|_N6>$7-I=fMlkGj0+L6Jd+d0w3MiA}c3F{^-N3#r=y@cb_(U_# z%X2bG!tC{*svZzh>LoUHYv-Un?i~2X7N3dC@&23pAIK_|M|Uvi0(CWcZg%?vE3g;lMBN!p>W}xjc1b znk9&nJoB7DPWmrLxoE%xIM5OW@m_C~bJ4JFGVoun6{TaJ3;LxtSTCF#Ug4V!zT8~VhRE(Dj=aw8z zoI=2KJB{eXi5zTlO*2YVP*P-J5uwoo8}1;vL#{YfiC#J~t1Bbf;4+UZc1!3?kpTywV0 zP#H$J+CRN8gC5{G<7$cAzjPeN97jT@Pa}b){Xe65WF|kkXmA*>YbtiYTjmjCT<}@m zMTuidii(PDE+-EDU`8pBI3|Ht^-3a&8o=U{Uzyv=e`4wg;@U@M7u|nr!Le76b7m@v z$$AQpgn1al065rV3}?NPt-kBB$bvO)i>lP3D{lgmd@61;0fGCI1YgykOzjGz+$0kD z1~v0A2y8M+Y>NlUx;R#Q?fm`ri!S2ZL+GW452e$zsC}(_RqNFgIV2T4X)~! zh>e@DAKE`W-2#ebfk#!3VpV5nr~N5T#>V&0i0*|C*p%a>qK7elRg_|LHrhYh%VR5UTe4#j(;@;|EX4#CMB%kb5#i@uCe-TZjiF>mVGiRqE0QZ^ z%6N-ciEZ@9bv)SE1OF0879W(b4#EHeoWxHZk}WC=KsM5=tE;<;2l^%SDE?ZYXo_=3 z2QB^haTa1t|BiF&z6&DyL1C0fljOl|8vhUENkX1uqh$z15Oi}T7p(VlznMQ{$F{fM z&6?4E1zjsh-6|8Jzw>%!r=|3+^?s|SXm0*Y{qiGdlIEH(#76Ugyt$jDtc$mTrcqk)-Df><9d1eKyS{7$_;RadqjsGPu~B^HX z|2Wcpg~;O%eeU1CZ7#`O7kB|?kx1dHl*@n0r2nvT#J#6%P85$OXV6#9D3$RfVk86} z)lyuD#G|jig-GIMHa~oQQb^r7*m2KJDzMGUGW*8K1{Bjj2GFD!oFwN`+h2ihyJR-8 zV?}fYBxZH+%u`Lp#nC$9M~yE=&9$))`A9UUHW0*@ECCI7P*fDE|BuJBW1hYYHAItH zp?}a+%%2OO1Aaex%~G_dy-aM<=nyI zlzap^q$5oNdUItKP!p0!k9l2*IoA-Q^2f(&&9^SV)(Wz+cjSXVls+)}=u&m2_){PJ z{wUc2XV$3ot=JPjd%^IqlNG_IIN}AOFBvl4-+%OaSYreU0KelKzkdBVe62;V1Esom zEg4uyhW!0A6R&}yTus}ec+-z9BQPxE77*|klwdYXH|M90M`lTGLn-iVUveB<+nuchW_c(}nZFtgl!2${Fc9BWKS_@wQWPSb5-_-D1`(WKruQXyI1mNkPX>PfW{}zH};31e2e!FvwI7t?%F@8(6L?& zB~q=x&Jb!+h%P5V?f-COw)jMxMz0<~0IBa}|5`lxB13n9eNpUlrU*Xa zMt9m>j^J3V-kq_~Lfr#hO#zBS?=_;6E)~x&AH|aEY@`X4Y(&KlVe4|K;@Yp(f;Wmu zqzwz~t&)3s57>{DPEB#bxL#$__Lm46L79$EM(@^bqa7a(u%q_^M4>lz3HZN{yu zMVSkmnB?*MBK)D|+S|~fLMlB#ag@zn8aEd0XK9+!oQ!=mwF+91FZ+~cVvf0QfPq7U zgZCYuEipo=)@~i4?+eU3KBXQFl=kKI}CxC4gP1rZ54# z2-plP8?Hf4cZ^H(9qq{GZVT$#USo`T;G~#Y^5=~h9u-3UGl;M7F-8OT4=0psN?UVW zzk>_65#dl!r|G0sef=!ABl)0_(xoJf5P;QJlC0ezQ zzmj64a8f0K=2YKQk4FOq!z#50CiIf>T_1QA^}XCS5>hAv4QlfgU2 zME;VIz_yE}cJX2@xhuLUFXl_C`mb_{s3(}0SZ(+sO|p*ZGaxWfqr<(+Y!HO)KJWqn zvN`*3`NQ|}A+dI-)XxxR$jQlR{`j`I@{I}+JQ{LOWr9_h>ithdz*M}~mUhp8w(#c0 zh8piiEE9GCB-NE_(3&IJavg_P>rW|w0Ljn0kCsZ)@!K}Ja~DDqz?g;W=1eU4MeTF|pX$soPIKRr>kuBJg~n_f(orKw^&RKF5w7i`TpK$^2w#cIZ2nbp zz(PQ>Dk}cfDC?!7<9K?dxVB7f;!)=V{s^0RS?F#NRYt9Er=sMAYmqdju2C=UZ%zbJ zat5QG>kPPA52*Pl$q*OLB=v?Px}Mn?N|SR6r=PAMURMc^h%~=Zv#hdl8aH7R^|5j>H0}b&j+HD@Z#H1rwx5twCIB4;^Nk^yEH=ZtotP43V}av<_C-L znt2uKp9J)25}?2UtLC9LjrsSfUS8?<<>ePHIo#N$&;xN9Gi`;9-1hYV`fy3Dhq}ZYujH37n8niw!&}K#C(~jQ}NR@?fm&{}w&llrk^*Y&&K~ zNyid!Yu03EdHX}F4orE=zf{YlOIvpY16WXuN)K=GrVTBvo#c#%lXglv+!=5inP>JAKi42meFDGwX~)aZ1*&th-vB(iqaS-1?)|5uR) z=n417Cf~Mj79PL_4!i+6*BUg*1$anTkbwMv3M|6pA!0ZYgj>@Cs?89D;uVtBoLch=RUqm|J$*Q7P zPYwU+5dOrco$6d|Ly@5IMa$YdUoxR__b*j9phNTdm?J;7|^$Nq(1aZ*xJHIZc26g|8213L~u;%A0vLMo5} zRfE}3Djoj@>^wL;JRA!m9*y;fH*UWTJzS=nXHo+VQ!c3?yU&SPt}o4+Zf0oGK~tOi zuTY&LYFey6zMnVzOJD!Oy@rF6P{2Nd+is{pE9J&Uh`nq_!0>wGasCaew&`JcXm)+e zK8jr6LE%Vz4AQ#-U{*@1U5`?dmY0`rnHCr}8N#e@5L3ixO;u)%cYY(^L4(;(0q9%J zfc|zrfnOmJyyAhUVWeTHdTfqO{m(yEsP3}|2U$-Vufda8$K6%R9C|Ie-QVZsf*CpU z@P%?1DXT(^VD+;={E_%Ll+K~h2vXzB>gpY1`1M5POLXYoy?X<@6AzMIsjD|VJk;zE zp*I$5!z}!YuP@95(db^01||UzZp)mBHKS+|+9JTmry`x&HKO!5Us@sX4roDaW8_-6 z6OT!yG@(F|dT%3r&b30+7_SspRYM%7y(SiYT?lvV*wT;1#fNVld+I;`0?{_&{(Hg! zHz|!*oJ#rA0`!0QOn@!i)8;d=3yssZepCJu4@j9_t(?`E_6(n*q8dp>K%U>mZk-Cg z8-0DA&uO{aS-!#YwJv9-fvC4|@R-B7m~81}J^C{-=NyDnx0?4&vC0>alIK<8_=nNt ziG%z-it2LGcix?p!JosFZ~0Ky^Gt5p9ppUlP#v0&jsW>tz9WnIJpC93f}J}}ihgy0 zYp0cDz@*vwWk*PZRF_wPwn6F^r#Ri!31tt*FFxY^Pv(4>(tGDHROqUU?5kz>t^dZN zOzZP~+46zgzx3hRlbJqU8exLGhu*R%E-HrR|CM!iI}mGu!Zq###Z@Dwa12r4^T^0) zu+(POK9T70qz^w7eW(Ltu=9(a`uY#20lDQ0G|t23zyFgzDXGzvF*vv}ZHjN)Pxd9oudzVG?@&1%Pr zKd*X5VPl|p9mZ~zmQ=;qJKDhN+=t%Ir7S9PmL`a``b?D3{9zmOL406j!bN2z`RT3y z^VUfpzLT{yimK$JI`ivW*(0SBH*sVRFiYoXBuh=UB!H_;g9Lc_w?Itx*ZyBQF}k+! zy<3WzM(N|`Zf5b-hA(%z97Chpzp?=B?l@G;ru=bo64+_J0s-�ub=(88;&BQWopi zwP(dGR0xllWKe3(1@>{fuu+ysiCJAQfq7?lf05p84VO9JGdy$`Rz1c@k#?@1a<$8V z$7l+3$2mD42i-|K=-W+p!{2L`YKkT)cjMMIpz4x-&LL249#xl>#k2h-*8{#?4!b~l zuri@xOlgTGw}2gVRe^dA7v>^7Vg3e1PVI5%MsL{_eF~=y?T6sT{xvu63Iu84J-mbH}`YDnl_d%d?1sf5( z1(7C~n>W9oY(CA&f*A?5kshjaXbV@s_ai-igbfEMn<4a{mW=hQyMRh_0kxrkMo9;@ zvoG-@AlkFV%_~79EAO)$bd}m*>w>KR`kTO9pOLvyy8QKpt@-ShiVSb#Krpw$*oBKe zHU0mfeY{TfKsjm7SU)%m$kC4kT5GOtjt>XrpnZ^%40BtvR*tMmPhFw=&41PyAmcU@Kwzfqx~Bv7li$>ht1 zNFRIRple^}?pLaVu(__v^V{b-4&m48_^xab(HF-Ky}dr|=5^(GY8Q!_nK_G9nEu)h zp8;>me#~?0$8{M;F?An-fz1wXju!WHXnY@tZYK*fJeYKUEWYm)#24I-U(2`Z{;>OX z^qTOHrc{lrqABGOvg-wu?;0>dUJ*;lu z<%euYLro4iyWWVPja#5bc;0k#vpzKKT{t_UK`N!V^XvBzMc{~oMU&D!V(M@T0UR@Ph17qDe_rgPmyrKhFC|MP}85U(L=IX5<|e%;E4+_ zp#7s1oPKxnB`O0cc>-xb;(#dlQoECL1xG$YXuh{*4XLyH%48N%iNtZ5X<((h3!&0yI>I3mBHa7Mc>-1G9Vvvf7JHmi$d<9b@BW2&5wRujI8Ke9bOq>bQB8}haoapMp zU8Pt<ns+osNIPU!3HQ--CB_n$Z@8NrJ5t79|E3)jc<(R5*kNBq7gey!oiAAl1q zEBMO%=<+e&Gi6`zB2Cy<;B%S0dW!Q!WJ8XtW%;$YZh&RKQA~A5p0iw23~qu?CI)4L zcy;RL0nsJCZ16flk_XsqJ^VQ&3wb+t)+Ylq?TVPVuu=tZw zzZT11h4@qxyXj=Dk*TKxs}t{_U~iCgnF1c>st>dYiI0ZPDDh&q; ziS&dGW2f%G7xNR`zV@vez5FBEh%cs`DcEujnmqGuP@JR(_pYg~jMVbyL^=r%6&=8m z&-quZq3JG26gG1{FWf7Tb1_0DwBh@Q%k{S&XH6KlM{Bt0A`#lN zUxE8$FN#ZIPrz5>tRM5NN)1>853u2Gw8`E(`s-v+Kfz}hw|=^B{gHEVK7iX!pF>pX zBgt;=lq>(-KokI6kg~b+_Avj~+u`D!nzx049>+9&@8ox%x(_Cl_6zKMbqdq$aoC{&&^O3DqXL+!ru3TVOMQ+gy`2^8UUyfE2 z>1jf@v%ytj8?cqpA>*wdJsGm-gjue|*Y2dBLTs^z-hS2Vs4sme5WHVKZesHHEOVRZ zxAiUSueYC|k3+@EH$RA zu(7c0J`?gfD@NzVAz^P-N81|Zr4(cX8)|lcd5s3WzBB%2`%m!lz9}UeWAS-%ve{*% z@_JwA1Y3J_hyk>R?Uk`r_N7t6wq{uciAv(##CdPToJK`=qzw_^s5=MM<`Befr^q)R z{X0t;@(Oi5K!&IA5xh>Urj$0H`6WO(QQicL;W?MF?QVf%lkcxPnmpWh=LX}gGzJ>-cg-z`N=1P3nCfgW6-<0G5z7L2z=u{5MZy!N?!^K_2p+pT{AT;>M8&{uSs<$4pD%*XWSv xbO&mYK&vV6&@cY?IYON_{{QEZTfe>!Z9h)k@Md^+1AOxV>1u;ERO?#k{{vv2v@ZYv literal 0 HcmV?d00001 diff --git a/front/dist/resources/objects/smallHandleFilledGrey.png b/front/dist/resources/objects/smallHandleFilledGrey.png new file mode 100644 index 0000000000000000000000000000000000000000..40e968fb816c3f3400ae00a0af985c3fdf42c75c GIT binary patch literal 3554 zcmV<84IT1{P)e{5UVb;rNwK8mC$S-&9b7wpQ8t=Nm{T1FdNk{!88-9NhJ zk0ODwCEGf0EsR1CMGEeqY-=$M8xpt+LYgWPR6wzGC^B>cS|Ck}22H!BtZm7bQrU_e zCvvLTv8W$*Oevy7(iY#@AC?$LmZEzfA0=Ak2Oz{B_ndq6&b#;BpXU(LoVK>M!dNWk z1h9jdYXCR_Yy_|YKt6yCL{tm_%p3wRO+?eo90qWmhz4|B9|G_$Gr!%@(ed#b{VP(? zS{LAMjvYJZa5xxk204EGc=g=e+_wOH zortQ|svL_LVdft(^AG%f|JAi9z^WU><#O%SH0>E?ev*i^)y`j%IRITm^qkl09aznb zSIr=9xBGD-`Zj<=tCgoo{8rcX|MdI)zgmsVQf`pu=H{K6ru`QY)ulSGRKm>NLWsZh zdcBuZnNP|L(%#-~4TVDgLPXC1u%;@X)r6U20G^FRBF}YpcHT}!9w{+MYisKR%={{V zU8&HY2An6NC%j(opUhS)%vR`jyN~F)K7jRW3;@`p>-vD(?LK0*V$*8K(W6K0g@uLR zC!%kdrE>wB&4!|)BIM@g!e+B2{@U&K#0vnT(P-ioi^UL$L=t}|Cnph$#mp`m0CY}H zPP$)y`Q@k?%1kjxYinzXuIq0Q(Oxt30YGtaF)Ax7QCV4uqM{;GbcRA9jE#*U7z|=^ za?%u?0Km+BcDwz{Cr+H0N>*3W25D+)s<2wE0RY>R(POb#P+nf1FoM;Z+I~PR7E2gs zVqyYwb8|`QW9ExQblB(fjV7ZfDT5q8e!My!j|Tv3Hmbv7v0%rJ9jK|Pfu^m^I58fN zV`yjymo8mGJZ^mEgqg1r(IKDD_kmFzh7Ho%+PX{E_5UNHjYf5B-MSUKcI|@AW;3e7 zRAR9h1_uW*JUpC?nh7F$%Rkmly*trm-=8$g*6`Kqca?B2Z_`T6-q#8XZ< z9LD+c=P@=mW=Niy$2CoR_=Oi<_)t|=*&r^LYon%VeK62W6%`d>@7}#ADJfAET@!BH zxPkuueoRfNKdYJfeIdj?uh)B1MNY{eO-)VtR;#re2F4f^CqG&VM3<3_`su(V^th7G8wsKB*r*P!cCH#`7< zh`v}?S2xhz-TlsrX;(_%@p!fXxPUvG3Z!s29B6E8L|$H=tjK*uI2=YG5WwuL{8BtK zf2?VmbETe_O#OQW3dXQlEO_LRN7k(|03bg<9|sQ}gvBDi{7*y$@p$}4E2aC)Cd=dT z90kxUohyKY2M?mSxL8HxzQks;p`@e)SFg%1y%W*ay1Kf{-QC^GE(UGhZQf2%0?bxzqi;75TEEfCTaz8FvLtHLboe<*t(m9ltqQ1T!L~FjyYfY%Eti;5` z1R{}$tjHJk?c4WSPfyQ1Gs8>P5KYsh`^|ZId1z>8$XH_lcLt9Q4GnNO9MUpE2=9{L zFaE;qb~gg}qO9Wm`}ZR!Cr7#<>lLfjiiaP5SViQK*4Ea0uJckg ztFd8&^ms92C@d_*=FM^|6GTMJ{HbwX!XWMK?ShE@N>UMa?OM;fe3HZIbgFc6{^srv z34=^cO&x~h3Sdo54RUjHr3;v8I2;Z<@W2C7X^E)3rKRQ3h0h6t=(_&bG72mf%YD1H zv6?*e&_mLjHiQsQC*Ci-A)?1+gg0;AobBdHV6|FRrjedlcq0J7?RI}2fYd^_O1Cc4 zQMgtm9*^hGfKO`3mt-Z&$_#BZ$w5c4C&?DHcl4U4GMMWwb z$7=)tDT8Du5sjm;Q?o`u@)*Iwp_nY{$ZjSVLde&UblIVPKO%cGGxK&qL{d+k?A(=c zWDP<@6@rxqqHGOGF$&Fno`B@ut8_i>bU&bQ zGv^6rmU0o1>?zeuMt1d(nJt2dW@Hkl=+J#e5RFRv6bK6dQU*~_nq?8P*N%wf4U(-P z#!9EF>CTmX>rke00>#*w|5iRiK*qW8^In&n;; znymK)GfUNwk3RY+sX}Ekp-@OF9W%?q(4h9Y~-m^tkC`!5Rs3?LvQG&-7;Z?23)_L>n<001v+1A0?N=;qCv*@wFVu~WUN5 z>oUT@U{J}&N2W46JF9Z@{$e3a#@$ES{#!}8wcm@Xt!OXJB6W2arkfNfZ zSLZ_pt;h!-e1PfcX|)n&5YyAsDn3vqi;IhYx$xocGlp{jej=;o?YG~SE?}lHFd!dt zgqeRlf9(M*I)$s^P9qo$;^s{y|Be|!C=`-@b}%ywAx_@?-KPvP?`!rKvYBUV$-SIE ze_lE*5xu%N9?3nP9CTg(S6PMEuV2UY>(`|Vu-+LP8&jHujmP8vw)ney4C43u`Ryd-hi0-Ia#eUGc!1S`m{KBrQ)6mM6~2JUh*JyPfyQKU0vOm07!Y}ibNuaMx&^#tduU$dg9!Nk(vtVydk-IesN~}Pmc?TE`f@)m>uR98yL+;} zzMcR)x?GyYjE|3_w6t_xdnCP^@$qr=_V%iZeb?*tzP8+tpXnxcWMt%7W`0*Cw=-wX zARLxOB7}HyrC7TwdPg~X_UzdH{rksceW3ur?c29;<;oQl7ZzVDR(tJJw1JOb#)IxAxwp?>$rOLYQo|0dSGa12UVAI9VHhK`PoYqA2$&UQT>&lYArYa|$$ z&1Qqs=|pvPb=v#+Wo8Ty598v+i$*tgi~?v-jo4;1DA-Pm#c~RgK2jD_P*8w9d-kNQ ztEaKCFTN7#tkLl`B`0nk1c%%y!7@ z^^O?Tk#s}ua=A7NAzn{54z3UarKP2)s;WX&RaMG8M?@kK1cN~YgF)Q5al?#WV1FzY zd+eo`UXt4xw+yB{S&tn%=E%*>eQm)jni&)n6rifAD&fvN*-ij6BNPfD7z|=`bkyvP z<8KntSG``Z{QUbenA+8GyWP(d(Qz|%E|8a(2ZzIf+}vC^9FD}xZnq;pKOfAD+1Xh{ zA`#5a&L&<9pJ!&IPVO1udp@79%`9c6_dVTi_g4V?goxI=gRL9P{Pf9_Cx30G(v%Da znwpv_a&mHhMnrX~(7)C&^B;0@a-Mwt`R9#y$Ce>wqqFw*b`c7Nz6Ibv)=O<+=8u^9 zJAS|a2e@;L!*o(UTyAY`Ety}V|0WdX&e9B>nLF)v`!gp_oKTNctiY^4ABG3{6Cte{jX{qbhAi{Dc6>kmd_E*Grs}g4I=uD&*z&oS$|Tb zok14U*49=Si^ZG(b}YP@xdL|rCg%g#a96kbCNqbLXnOwd7&BjD=68h<@5STsfsT%j cv_5SA|HL;Y|6+VldjJ3c07*qoM6N<$f~1?!_5c6? literal 0 HcmV?d00001 diff --git a/front/src/Phaser/Components/MobileJoystick.ts b/front/src/Phaser/Components/MobileJoystick.ts new file mode 100644 index 00000000..1ace529c --- /dev/null +++ b/front/src/Phaser/Components/MobileJoystick.ts @@ -0,0 +1,35 @@ +import VirtualJoystick from 'phaser3-rex-plugins/plugins/virtualjoystick.js'; + +const outOfScreenX = -1000; +const outOfScreenY = -1000; + + +//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'; + +export class MobileJoystick extends VirtualJoystick { + + constructor(scene: Phaser.Scene) { + super(scene, { + x: outOfScreenX, + y: outOfScreenY, + radius: 20, + base: scene.add.image(0, 0, joystickBaseKey).setDisplaySize(60, 60).setDepth(99999), + thumb: scene.add.image(0, 0, joystickThumbKey).setDisplaySize(30, 30).setDepth(99999), + enable: true, + dir: "8dir", + }); + + this.scene.input.on('pointerdown', (pointer: { x: number; y: number; }) => { + this.x = pointer.x; + this.y = pointer.y; + }); + this.scene.input.on('pointerup', () => { + this.x = outOfScreenX; + this.y = outOfScreenY; + }); + } +} \ No newline at end of file diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 0f31a519..9c261cf2 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -72,6 +72,7 @@ import {worldFullMessageStream} from "../../Connexion/WorldFullMessageStream"; import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager"; import {touchScreenManager} from "../../Touch/TouchScreenManager"; import {PinchManager} from "../UserInput/PinchManager"; +import {joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey} from "../Components/MobileJoystick"; export interface GameSceneInitInterface { initPosition: PointInterface|null, @@ -189,6 +190,7 @@ export class GameScene extends ResizableScene implements CenterListener { //hook preload scene preload(): void { + addLoader(this); const localUser = localUserStore.getLocalUser(); const textures = localUser?.textures; if (textures) { @@ -198,6 +200,10 @@ export class GameScene extends ResizableScene implements CenterListener { } this.load.image(openChatIconName, 'resources/objects/talk.png'); + if (touchScreenManager.supportTouchScreen) { + this.load.image(joystickBaseKey, joystickBaseImg); + this.load.image(joystickThumbKey, joystickThumbImg); + } this.load.on(FILE_LOAD_ERROR, (file: {src: string}) => { // If we happen to be in HTTP and we are trying to load a URL in HTTPS only... (this happens only in dev environments) if (window.location.protocol === 'http:' && file.src === this.MapUrlFile && file.src.startsWith('http:') && this.originalMapUrl === undefined) { @@ -244,8 +250,6 @@ export class GameScene extends ResizableScene implements CenterListener { this.load.spritesheet('layout_modes', 'resources/objects/layout_modes.png', {frameWidth: 32, frameHeight: 32}); this.load.bitmapFont('main_font', 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml'); - - addLoader(this); } // FIXME: we need to put a "unknown" instead of a "any" and validate the structure of the JSON we are receiving. diff --git a/front/src/Phaser/UserInput/UserInputManager.ts b/front/src/Phaser/UserInput/UserInputManager.ts index 602afee7..2f14672b 100644 --- a/front/src/Phaser/UserInput/UserInputManager.ts +++ b/front/src/Phaser/UserInput/UserInputManager.ts @@ -1,7 +1,7 @@ -import { Direction, IVirtualJoystick } from "../../types"; +import { Direction } from "../../types"; import {GameScene} from "../Game/GameScene"; import {touchScreenManager} from "../../Touch/TouchScreenManager"; -import VirtualJoystick from 'phaser3-rex-plugins/plugins/virtualjoystick.js'; +import {MobileJoystick} from "../Components/MobileJoystick"; interface UserInputManagerDatum { keyInstance: Phaser.Input.Keyboard.Key; @@ -19,8 +19,6 @@ export enum UserInputEvent { JoystickMove, } -const outOfScreenX = -1000; -const outOfScreenY = -1000; //we cannot use a map structure so we have to create a replacment export class ActiveEventList { @@ -46,7 +44,7 @@ export class UserInputManager { private Scene: GameScene; private isInputDisabled : boolean; - private joystick!: IVirtualJoystick; + private joystick!: MobileJoystick; private joystickEvents = new ActiveEventList(); private joystickForceThreshold = 60; private joystickForceAccuX = 0; @@ -62,24 +60,7 @@ export class UserInputManager { } initVirtualJoystick() { - this.joystick = new VirtualJoystick(this, { - x: outOfScreenX, - y: outOfScreenY, - radius: 20, - base: this.Scene.add.circle(0, 0, 20, 0xdddddd), - thumb: this.Scene.add.circle(0, 0, 10, 0x000000), - enable: true, - dir: "8dir", - }); - - this.Scene.input.on('pointerdown', (pointer: { x: number; y: number; }) => { - this.joystick.x = pointer.x; - this.joystick.y = pointer.y; - }); - this.Scene.input.on('pointerup', (pointer: { x: number; y: number; }) => { - this.joystick.x = outOfScreenX; - this.joystick.y = outOfScreenY; - }); + 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; From 2c862fe7e7ced311b198a19fceaf10e662ea7170 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Tue, 20 Apr 2021 18:49:42 +0200 Subject: [PATCH 058/184] Refactor Login Scene --- front/dist/resources/html/loginScene.html | 101 ++++++++++++++ front/dist/resources/objects/play_button.png | Bin 969 -> 0 bytes front/dist/resources/style/mobile-style.scss | 17 +++ front/src/Phaser/Login/LoginScene.ts | 135 ++++++++----------- 4 files changed, 176 insertions(+), 77 deletions(-) create mode 100644 front/dist/resources/html/loginScene.html delete mode 100644 front/dist/resources/objects/play_button.png create mode 100644 front/dist/resources/style/mobile-style.scss diff --git a/front/dist/resources/html/loginScene.html b/front/dist/resources/html/loginScene.html new file mode 100644 index 00000000..b237b3e2 --- /dev/null +++ b/front/dist/resources/html/loginScene.html @@ -0,0 +1,101 @@ + + + diff --git a/front/dist/resources/objects/play_button.png b/front/dist/resources/objects/play_button.png deleted file mode 100644 index 36aa309b6b26ddb120f8562bcb8c5090c8e8f38f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 969 zcmV;)12+7LP)jd6qRl{>LPf>_yuuR;D=wb2R1}h;Etm#RLFm zW z<#^dSuC!0nuyEc2=$TnB*O^|w`M&^eCNTb=0PFyGmYVr(JVp6lo_wU@n9iQMygZE! zGP)y75q)Qy6!v?%{wPS*YB}>&hILUeBzpk`vqo~8f{4qv@Y@`jnc_YlT?0AB-X_aqwF32)^Q7uS-xZh1*^ z&2g8NB)2^Eaf0MgpQSL#agwL$ktXJ^w5Msas>=QDHvqS5{KOfOTO_a54COa~2cEYZ zb^!bW@DjlH*s7#j%Too9Gx~7rKUYcalwD$mT;CjW?FxWP2d*6fa2CKE$*BST$;Y|` zV0ws9XMB4NGus8QO7c}MZeL>Yeh6*8RXZqHy^`|ZHP8?_LQzz&H*^zM%#MBGF`3%_|Qhf$~0+ana=>G+G<~- ziAxM+Hx4gi?|kYYKLLD{XbvBB4k>?xdQC>6=?1s3_h7z^uqefgewNt@)u1v>;+WYz rje-zD2qA div { + max-height: 240px; + } + + .sidebar > div { + max-height: 240px; + } + + .sidebar{ + max-width: 200px; + } + + .btn-cam-action div{ + bottom: 50px; + } +} \ No newline at end of file diff --git a/front/src/Phaser/Login/LoginScene.ts b/front/src/Phaser/Login/LoginScene.ts index 057cb6ae..ecffa6e5 100644 --- a/front/src/Phaser/Login/LoginScene.ts +++ b/front/src/Phaser/Login/LoginScene.ts @@ -1,30 +1,17 @@ import {gameManager} from "../Game/GameManager"; -import {TextField} from "../Components/TextField"; -import {TextInput} from "../Components/TextInput"; -import Image = Phaser.GameObjects.Image; import {SelectCharacterSceneName} from "./SelectCharacterScene"; import {ResizableScene} from "./ResizableScene"; -import {isUserNameValid, maxUserNameLength} from "../../Connexion/LocalUser"; import { localUserStore } from "../../Connexion/LocalUserStore"; -import Rectangle = Phaser.GameObjects.Rectangle; -import {touchScreenManager} from "../../Touch/TouchScreenManager"; -import {PinchManager} from "../UserInput/PinchManager"; +import {MenuScene} from "../Menu/MenuScene"; -//todo: put this constants in a dedicated file export const LoginSceneName = "LoginScene"; -enum LoginTextures { - icon = "icon", - mainFont = "main_font" -} + +const loginSceneKey = 'loginScene'; export class LoginScene extends ResizableScene { - private nameInput!: TextInput; - private textField!: TextField; - private infoTextField!: TextField; - private pressReturnField!: TextField; - private logo!: Image; + + private loginSceneElement!: Phaser.GameObjects.DOMElement; private name: string = ''; - private mobileTapRectangle!: Rectangle; constructor() { super({ @@ -34,81 +21,75 @@ export class LoginScene extends ResizableScene { } 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'); + this.load.html(loginSceneKey, 'resources/html/loginScene.html'); } create() { - if (touchScreenManager.supportTouchScreen) { - new PinchManager(this); - } + const middleX = this.getMiddleX(); + this.loginSceneElement = this.add.dom((middleX/2), 0).createFromCache(loginSceneKey); + MenuScene.revealMenusAfterInit(this.loginSceneElement, loginSceneKey); - this.nameInput = new TextInput(this, this.game.renderer.width / 2, 70, maxUserNameLength, this.name,(text: string) => { - this.name = text; - localUserStore.setName(text); - }) - .setInteractive() - .on('pointerdown', () => { - this.nameInput.focus(); - }) - - this.textField = new TextField(this, this.game.renderer.width / 2, 50, 'Enter your name:') - .setInteractive() - .on('pointerdown', () => { - this.nameInput.focus(); - }) - // For mobile purposes - we need a big enough touchable area. - this.mobileTapRectangle = this.add.rectangle( - this.game.renderer.width / 2, - 130, - this.game.renderer.width / 2, - 60, - ).setInteractive() - .on('pointerdown', () => { - this.login(this.name) - }) - this.pressReturnField = new TextField(this, this.game.renderer.width / 2, 130, 'Touch here\n\n or \n\nPress 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 W, A, 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 (isUserNameValid(this.name)) { - this.login(this.name); + const pErrorElement = this.loginSceneElement.getChildByID('errorLoginScene') as HTMLInputElement; + const inputElement = this.loginSceneElement.getChildByID('loginSceneName') as HTMLInputElement; + inputElement.value = localUserStore.getName() ?? ''; + inputElement.focus(); + inputElement.addEventListener('keypress', (event: KeyboardEvent) => { + if(inputElement.value.length > 8){ + event.preventDefault(); + return; } + pErrorElement.innerHTML = ''; + if (event.key === 'Enter') { + event.preventDefault(); + this.login(inputElement); + return; + } + }); + + const continuingButton = this.loginSceneElement.getChildByID('loginSceneFormSubmit') as HTMLButtonElement; + continuingButton.addEventListener('click', (e) => { + e.preventDefault(); + this.login(inputElement); }); } - update(time: number, delta: number): void { - if (this.name == '') { - this.pressReturnField?.setVisible(false); - } else { - this.pressReturnField?.setVisible(!!(Math.floor(time / 500) % 2)); + private login(inputElement: HTMLInputElement): void { + const pErrorElement = this.loginSceneElement.getChildByID('errorLoginScene') as HTMLInputElement; + this.name = inputElement.value; + if (this.name === '') { + pErrorElement.innerHTML = 'The name is empty'; + return } - } - - private login(name: string): void { if (this.name === '') return - gameManager.setPlayerName(name); + gameManager.setPlayerName(this.name); this.scene.stop(LoginSceneName) gameManager.tryResumingGame(this, SelectCharacterSceneName); this.scene.remove(LoginSceneName) } - 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.mobileTapRectangle.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 { + const middleX = this.getMiddleX(); + this.tweens.add({ + targets: this.loginSceneElement, + x: (middleX/2), + duration: 1000, + ease: 'Power3' + }); } + public onResize(ev: UIEvent): void { + const middleX = this.getMiddleX(); + this.tweens.add({ + targets: this.loginSceneElement, + x: (middleX/2), + duration: 1000, + ease: 'Power3' + }); + } + + private getMiddleX() : number{ + const middleX = ((window.innerWidth) - ((this.loginSceneElement && this.loginSceneElement.width > 0 ? this.loginSceneElement.width : 200 /*FIXME to use a const will be injected in HTMLElement*/)*2)) / 2; + return (middleX > 0 ? middleX : 0); + } } From 09d5e0b689da3a707cbc327e5190871cbf4fedf5 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Tue, 20 Apr 2021 18:58:19 +0200 Subject: [PATCH 059/184] Fix center select name modal --- front/dist/resources/html/loginScene.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/front/dist/resources/html/loginScene.html b/front/dist/resources/html/loginScene.html index b237b3e2..7011227d 100644 --- a/front/dist/resources/html/loginScene.html +++ b/front/dist/resources/html/loginScene.html @@ -4,7 +4,8 @@ border: 1px solid #42464b; border-radius: 6px; margin: 20px auto 0; - width: 200px; + width: 90%; + max-width: 200px; height: 200px; } #loginScene h1 { From 30c1825abd23e3d6a0050b5087dfa620348a4ebf Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Tue, 20 Apr 2021 19:54:37 +0200 Subject: [PATCH 060/184] Update syle mobile - update my video side - update user video side - update button video cam and microphone side --- front/dist/resources/style/index.scss | 3 +- front/dist/resources/style/mobile-style.scss | 53 ++++++++++++++++---- front/dist/resources/style/style.css | 18 ------- 3 files changed, 45 insertions(+), 29 deletions(-) diff --git a/front/dist/resources/style/index.scss b/front/dist/resources/style/index.scss index 4d0355b7..7454b991 100644 --- a/front/dist/resources/style/index.scss +++ b/front/dist/resources/style/index.scss @@ -1,2 +1,3 @@ @import "cowebsite.scss"; -@import "style.css"; \ No newline at end of file +@import "style.css"; +@import "mobile-style.scss"; \ No newline at end of file diff --git a/front/dist/resources/style/mobile-style.scss b/front/dist/resources/style/mobile-style.scss index 286735b4..329dc78d 100644 --- a/front/dist/resources/style/mobile-style.scss +++ b/front/dist/resources/style/mobile-style.scss @@ -1,17 +1,50 @@ -@media only screen and (max-width: 600px) { - .sidebar > div { - max-height: 240px; +@media only screen and (max-width: 700px) { + video#myCamVideo { + width: 200px; } - .sidebar > div { - max-height: 240px; + .sidebar { + width: 20%; + min-width: 200px; + position: absolute; + display: block; + right: 0; + height: 80%; + + &> div { + max-height: 120px; + min-width: 200px; + } + + .video-container{ + min-width: 200px; + } } - .sidebar{ - max-width: 200px; + .btn-cam-action div { + bottom: 30px; + + &.btn-micro { + right: 15px; + } + + &.btn-monitor { + right: 171px; + } + + &.btn-video { + right: 95px; + } } - .btn-cam-action div{ - bottom: 50px; + .main-section { + position: absolute; + width: 100%; + min-width: 400px; + + & > div { + z-index: 2; + } } -} \ No newline at end of file +} + diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index ff79245c..492f5fa3 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -338,24 +338,6 @@ body { } -@media (max-aspect-ratio: 1/1) { - .game-overlay { - flex-direction: column; - } - - .sidebar { - flex-direction: row; - align-items: flex-end; - } - - .sidebar > div { - max-width: 21%; - } - - .sidebar > div:hover { - max-width: 25%; - } -} #game { width: 100%; From 20e0a2553e9319877ecd9ea9b5adecb219b0141b Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Tue, 20 Apr 2021 21:11:05 +0200 Subject: [PATCH 061/184] Update cowebsite mobile style --- front/dist/resources/style/cowebsite.scss | 48 ------------ front/dist/resources/style/mobile-style.scss | 80 +++++++++++++++++--- 2 files changed, 69 insertions(+), 59 deletions(-) diff --git a/front/dist/resources/style/cowebsite.scss b/front/dist/resources/style/cowebsite.scss index b752728d..515dc0df 100644 --- a/front/dist/resources/style/cowebsite.scss +++ b/front/dist/resources/style/cowebsite.scss @@ -87,51 +87,3 @@ } } } -@media (max-aspect-ratio: 1/1) { - - #main-container { - display: flex; - flex-direction: column-reverse; - } - - - #cowebsite { - left: 0; - top: 0; - width: 100%; - height: 50%; - display: flex; - flex-direction: column-reverse; - - &.loading { - transform: translateY(-90%); - } - &.hidden { - transform: translateY(-100%); - } - - main { - height: 100%; - } - - - aside { - height: 30px; - cursor: ns-resize; - flex-direction: column; - - img { - cursor: ns-resize; - } - } - - .top-right-btn { - &#cowebsite-close { - right: 0px; - } - &#cowebsite-fullscreen { - right: 25px; - } - } - } -} \ No newline at end of file diff --git a/front/dist/resources/style/mobile-style.scss b/front/dist/resources/style/mobile-style.scss index 329dc78d..452e1604 100644 --- a/front/dist/resources/style/mobile-style.scss +++ b/front/dist/resources/style/mobile-style.scss @@ -1,6 +1,6 @@ @media only screen and (max-width: 700px) { video#myCamVideo { - width: 200px; + width: 150px; } .sidebar { @@ -21,19 +21,28 @@ } } - .btn-cam-action div { - bottom: 30px; - - &.btn-micro { - right: 15px; + .btn-cam-action { + &:hover{ + transform: translateY(20px); } + div { + &:hover { + background-color: #666; + } - &.btn-monitor { - right: 171px; - } + bottom: 30px; - &.btn-video { - right: 95px; + &.btn-micro { + right: 0; + } + + &.btn-monitor { + right: 130px; + } + + &.btn-video { + right: 65px; + } } } @@ -48,3 +57,52 @@ } } +@media (max-aspect-ratio: 1/1) { + + #main-container { + display: flex; + flex-direction: column-reverse; + } + + + #cowebsite { + left: 0; + top: 0; + width: 100%; + height: 50%; + display: flex; + flex-direction: column-reverse; + + &.loading { + transform: translateY(-90%); + } + &.hidden { + transform: translateY(-100%); + } + + main { + height: 100%; + } + + + aside { + height: 30px; + cursor: ns-resize; + flex-direction: column; + + img { + cursor: ns-resize; + } + } + + .top-right-btn { + &#cowebsite-close { + right: 0; + } + &#cowebsite-fullscreen { + left: 0; + } + } + } +} + From 78d888ffaf9ba95d905a23776d202088db403c6e Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Wed, 21 Apr 2021 00:53:45 +0200 Subject: [PATCH 062/184] Update menu mobile - Add mobile style share modal - Add mobile style settings modal - Add menu mobile style --- front/dist/resources/html/gameMenu.html | 18 ++++++ front/dist/resources/html/gameMenuIcon.html | 8 ++- .../dist/resources/html/gameQualityMenu.html | 8 +-- front/dist/resources/html/gameShare.html | 8 +-- .../resources/html/helpCameraSettings.html | 20 ++----- front/dist/resources/html/loginScene.html | 23 +++++--- .../Phaser/Menu/HelpCameraSettingsScene.ts | 57 ++++++++++++++++--- front/src/Phaser/Menu/MenuScene.ts | 2 +- 8 files changed, 102 insertions(+), 42 deletions(-) diff --git a/front/dist/resources/html/gameMenu.html b/front/dist/resources/html/gameMenu.html index ce740ec5..399cf349 100644 --- a/front/dist/resources/html/gameMenu.html +++ b/front/dist/resources/html/gameMenu.html @@ -1,4 +1,7 @@