From 5d463d097a45766735d7c1dd99209d7e56fc35e4 Mon Sep 17 00:00:00 2001 From: gparant Date: Tue, 7 Apr 2020 19:23:21 +0200 Subject: [PATCH 01/73] Refactor Class - Add MapManager permit to create map, camera and player. - Add CameraManager permit to move and update camera. - Add player Call extended of Phaser.GameObjects.Sprite. Permit to manager player data and moving in the map. - Add Animation class permit to manage the player animations. --- front/src/GameScene.ts | 185 --------------------------- front/src/Phaser/CameraManager.ts | 95 ++++++++++++++ front/src/Phaser/GameScene.ts | 35 +++++ front/src/Phaser/MapManager.ts | 85 ++++++++++++ front/src/Phaser/Player.ts | 90 +++++++++++++ front/src/Phaser/Player/Animation.ts | 60 +++++++++ front/src/index.ts | 2 +- 7 files changed, 366 insertions(+), 186 deletions(-) delete mode 100644 front/src/GameScene.ts create mode 100644 front/src/Phaser/CameraManager.ts create mode 100644 front/src/Phaser/GameScene.ts create mode 100644 front/src/Phaser/MapManager.ts create mode 100644 front/src/Phaser/Player.ts create mode 100644 front/src/Phaser/Player/Animation.ts diff --git a/front/src/GameScene.ts b/front/src/GameScene.ts deleted file mode 100644 index 2cb36131..00000000 --- a/front/src/GameScene.ts +++ /dev/null @@ -1,185 +0,0 @@ -import {RESOLUTION} from "./Enum/EnvironmentVariable"; - -export class GameScene extends Phaser.Scene { - private player: Phaser.GameObjects.Sprite; - - private keyZ: Phaser.Input.Keyboard.Key; - private keyQ: Phaser.Input.Keyboard.Key; - private keyS: Phaser.Input.Keyboard.Key; - private keyD: Phaser.Input.Keyboard.Key; - private keyRight: Phaser.Input.Keyboard.Key; - private keyLeft: Phaser.Input.Keyboard.Key; - private keyUp: Phaser.Input.Keyboard.Key; - private keyDown: Phaser.Input.Keyboard.Key; - private keyShift: Phaser.Input.Keyboard.Key; - - private Mappy : Phaser.Tilemaps.Tilemap; - - private startX = ((window.innerWidth / 2) / RESOLUTION); - private startY = ((window.innerHeight / 2) / RESOLUTION); - - constructor() { - super({ - key: "GameScene" - }); - } - - preload(): void { - this.load.image('tiles', 'maps/tiles.png'); - this.load.tilemapTiledJSON('map', 'maps/map2.json'); - this.load.spritesheet('player', - 'resources/characters/pipoya/Male 01-1.png', - { frameWidth: 32, frameHeight: 32 } - ); - } - - init(): void { - this.keyShift = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SHIFT); - - this.keyZ = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Z); - this.keyQ = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Q); - this.keyS = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.S); - this.keyD = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.D); - - this.keyUp = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.UP); - this.keyLeft = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.LEFT); - this.keyDown = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.DOWN); - this.keyRight = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.RIGHT); - } - - private moveCamera(x:number, y:number, speedMultiplier: number): void { - this.cameras.main.scrollX += speedMultiplier * 2 * x; - this.cameras.main.scrollY += speedMultiplier * 2 * y; - } - - create(): void { - this.Mappy = this.add.tilemap("map"); - let terrain = this.Mappy.addTilesetImage("tiles", "tiles"); - - let bottomLayer = this.Mappy.createStaticLayer("Calque 1", [terrain], 0, 0); - let topLayer = this.Mappy.createStaticLayer("Calque 2", [terrain], 0, 0); - - // Let's manage animations of the player - this.anims.create({ - key: 'down', - frames: this.anims.generateFrameNumbers('player', { start: 0, end: 2 }), - frameRate: 10, - repeat: -1 - }); - - this.anims.create({ - key: 'left', - frames: this.anims.generateFrameNumbers('player', { start: 3, end: 5 }), - frameRate: 10, - repeat: -1 - }); - - this.anims.create({ - key: 'right', - frames: this.anims.generateFrameNumbers('player', { start: 6, end: 8 }), - frameRate: 10, - repeat: -1 - }); - - this.anims.create({ - key: 'up', - frames: this.anims.generateFrameNumbers('player', { start: 9, end: 11 }), - frameRate: 10, - repeat: -1 - }); - - //let player = this.add.sprite(450, 450, 'player'); - //player.anims.play('down'); - //player.setBounce(0.2); - //player.setCollideWorldBounds(true); - this.player = this.add.sprite(this.startX, this.startY, 'player'); - } - - private angle: number = 0; - - update(dt: number): void { - let xCameraPosition = this.cameras.main.scrollX; - let yCameraPosition = this.cameras.main.scrollY; - - let speedMultiplier = this.keyShift.isDown ? 5 : 1; - - if (this.keyZ.isDown || this.keyUp.isDown) { - this.managePlayerAnimation('up'); - if (this.player.y > 0) { - this.player.setY(this.player.y - 2); - } else { - this.player.setY(0); - } - - if (yCameraPosition > 0) { - if (this.player.y < (this.Mappy.widthInPixels - this.startY)) { - this.moveCamera(0, -1, speedMultiplier); - } - } else { - this.cameras.main.scrollY = 0; - } - } else if (this.keyQ.isDown || this.keyLeft.isDown) { - - this.managePlayerAnimation('left'); - if (this.player.x > 0) { - this.player.setX(this.player.x - 2); - } else { - this.player.setX(0); - } - - if (xCameraPosition > 0) { - if (this.player.x < (this.Mappy.heightInPixels - this.startX)) { - this.moveCamera(-1, 0, speedMultiplier); - } - } else { - this.cameras.main.scrollX = 0; - } - } else if (this.keyS.isDown || this.keyDown.isDown) { - - this.managePlayerAnimation('down'); - if (this.Mappy.heightInPixels > this.player.y) { - this.player.setY(this.player.y + 2); - } else { - this.player.setY(this.Mappy.heightInPixels); - } - - if (this.Mappy.heightInPixels > (yCameraPosition + (window.innerHeight / RESOLUTION))) { - if (this.player.y > this.startY) { - this.moveCamera(0, 1, speedMultiplier); - } - } else { - this.cameras.main.scrollY = (this.Mappy.heightInPixels - (window.innerHeight / RESOLUTION)); - } - } else if (this.keyD.isDown || this.keyRight.isDown) { - - this.managePlayerAnimation('right'); - if (this.Mappy.widthInPixels > this.player.x) { - this.player.setX(this.player.x + 2) - } else { - this.player.setX(this.Mappy.widthInPixels) - } - - if (this.Mappy.widthInPixels > (xCameraPosition + (window.innerWidth / RESOLUTION))) { - if (this.player.x > this.startX) { - this.moveCamera(1, 0, speedMultiplier); - } - } else { - this.cameras.main.scrollX = (this.Mappy.widthInPixels - (window.innerWidth / RESOLUTION)); - } - } else { - this.managePlayerAnimation('none'); - } - /*this.cameras.main.scrollX = Math.floor(300 + 300 * Math.cos(this.angle)); - this.cameras.main.scrollY = Math.floor(300 + 300 * Math.sin(this.angle)); - - this.angle = dt * 0.0001;*/ - } - - managePlayerAnimation(direction: string) { - if (!this.player.anims.currentAnim || this.player.anims.currentAnim.key !== direction) { - this.player.anims.play(direction); - } else if (direction === 'none' && this.player.anims.currentAnim) { - this.player.anims.currentAnim.destroy(); - } - } -} diff --git a/front/src/Phaser/CameraManager.ts b/front/src/Phaser/CameraManager.ts new file mode 100644 index 00000000..528e06ca --- /dev/null +++ b/front/src/Phaser/CameraManager.ts @@ -0,0 +1,95 @@ +import {RESOLUTION} from "../Enum/EnvironmentVariable"; +import {Player} from "./Player"; +import {MapManagerInterface} from "./MapManager"; + +export interface CameraManagerInterface { + CurrentPlayer : Player; + MapManager : MapManagerInterface; + moveCamera() : void; +} + +export class CameraManager implements CameraManagerInterface{ + Scene : Phaser.Scene; + Camera : Phaser.Cameras.Scene2D.Camera; + CurrentPlayer : Player; + MapManager : MapManagerInterface; + + constructor( + Scene: Phaser.Scene, + Camera : Phaser.Cameras.Scene2D.Camera, + MapManager: MapManagerInterface, + CurrentPlayer: Player + ) { + this.Scene = Scene; + this.MapManager = MapManager; + this.Camera = Camera; + this.CurrentPlayer = CurrentPlayer; + } + /** + * + * @param x + * @param y + * @param speedMultiplier + */ + private moveCameraPosition(x:number, y:number, speedMultiplier: number): void { + this.Camera.scrollX += speedMultiplier * 2 * x; + this.Camera.scrollY += speedMultiplier * 2 * y; + } + + /** + * + */ + moveCamera(): void { + //center of camera + let startX = ((window.innerWidth / 2) / RESOLUTION); + let startY = ((window.innerHeight / 2) / RESOLUTION); + + //if user client on shift, camera and player speed + let speedMultiplier = this.MapManager.keyShift.isDown ? 5 : 1; + + if (this.MapManager.keyZ.isDown || this.MapManager.keyUp.isDown) { + if (!this.CanToMoveUp()) { + this.Camera.scrollY = 0; + }else if (this.CurrentPlayer.y < (this.MapManager.Map.widthInPixels - startY)) { + this.moveCameraPosition(0, -1, speedMultiplier); + } + } + if (this.MapManager.keyQ.isDown || this.MapManager.keyLeft.isDown) { + if (!this.CanToMoveLeft()) { + this.Camera.scrollX = 0; + }else if (this.CurrentPlayer.x < (this.MapManager.Map.heightInPixels - startX)) { + this.moveCameraPosition(-1, 0, speedMultiplier); + } + } + if (this.MapManager.keyS.isDown || this.MapManager.keyDown.isDown) { + if (!this.CanToMoveDown()) { + this.Camera.scrollY = (this.MapManager.Map.heightInPixels - (window.innerHeight / RESOLUTION)); + } else if (this.CurrentPlayer.y > startY) { + this.moveCameraPosition(0, 1, speedMultiplier); + } + } + if (this.MapManager.keyD.isDown || this.MapManager.keyRight.isDown) { + if (!this.CanToMoveRight()) { + this.Camera.scrollX = (this.MapManager.Map.widthInPixels - (window.innerWidth / RESOLUTION)); + } else if (this.CurrentPlayer.x > startX) { + this.moveCameraPosition(1, 0, speedMultiplier); + } + } + } + + private CanToMoveUp(){ + return this.Camera.scrollY > 0; + } + + private CanToMoveLeft(){ + return this.Camera.scrollX > 0; + } + + private CanToMoveDown(){ + return this.MapManager.Map.heightInPixels > (this.Camera.scrollY + (window.innerHeight / RESOLUTION)) + } + + private CanToMoveRight(){ + return this.MapManager.Map.widthInPixels > (this.Camera.scrollX + (window.innerWidth / RESOLUTION)) + } +} \ No newline at end of file diff --git a/front/src/Phaser/GameScene.ts b/front/src/Phaser/GameScene.ts new file mode 100644 index 00000000..4f2eb9cc --- /dev/null +++ b/front/src/Phaser/GameScene.ts @@ -0,0 +1,35 @@ +import {MapManagerInterface, MapManager} from "./MapManager"; + +export class GameScene extends Phaser.Scene { + private MapManager : MapManagerInterface; + + constructor() { + super({ + key: "GameScene" + }); + } + + //hook preload scene + preload(): void { + this.load.image('tiles', 'maps/tiles.png'); + this.load.tilemapTiledJSON('map', 'maps/map2.json'); + this.load.spritesheet('player', + 'resources/characters/pipoya/Male 01-1.png', + { frameWidth: 32, frameHeight: 32 } + ); + } + + //hook initialisation + init(){}; + + //hook create scene + create(): void { + //create map manager + this.MapManager = new MapManager(this); + } + + //hook update + update(dt: number): void { + this.MapManager.update(); + } +} diff --git a/front/src/Phaser/MapManager.ts b/front/src/Phaser/MapManager.ts new file mode 100644 index 00000000..ecd86210 --- /dev/null +++ b/front/src/Phaser/MapManager.ts @@ -0,0 +1,85 @@ +import {CameraManager, CameraManagerInterface} from "./CameraManager"; +import {RESOLUTION} from "../Enum/EnvironmentVariable"; +import {Player} from "./Player"; + +export interface MapManagerInterface { + keyZ: Phaser.Input.Keyboard.Key; + keyQ: Phaser.Input.Keyboard.Key; + keyS: Phaser.Input.Keyboard.Key; + keyD: Phaser.Input.Keyboard.Key; + keyRight: Phaser.Input.Keyboard.Key; + keyLeft: Phaser.Input.Keyboard.Key; + keyUp: Phaser.Input.Keyboard.Key; + keyDown: Phaser.Input.Keyboard.Key; + keyShift: Phaser.Input.Keyboard.Key; + + Map: Phaser.Tilemaps.Tilemap; + Terrain: Phaser.Tilemaps.Tileset; + Camera: CameraManagerInterface; + update(): void; +} +export class MapManager implements MapManagerInterface{ + keyZ: Phaser.Input.Keyboard.Key; + keyQ: Phaser.Input.Keyboard.Key; + keyS: Phaser.Input.Keyboard.Key; + keyD: Phaser.Input.Keyboard.Key; + keyRight: Phaser.Input.Keyboard.Key; + keyLeft: Phaser.Input.Keyboard.Key; + keyUp: Phaser.Input.Keyboard.Key; + keyDown: Phaser.Input.Keyboard.Key; + keyShift: Phaser.Input.Keyboard.Key; + + Terrain : Phaser.Tilemaps.Tileset; + Camera: CameraManagerInterface; + CurrentPlayer: Player; + Scene: Phaser.Scene; + Map: Phaser.Tilemaps.Tilemap; + startX = (window.innerWidth / 2) / RESOLUTION; + startY = (window.innerHeight / 2) / RESOLUTION; + + constructor(scene: Phaser.Scene){ + this.Scene = scene; + + //initalise map + this.Map = this.Scene.add.tilemap("map"); + this.Terrain = this.Map.addTilesetImage("tiles", "tiles"); + this.Map.createStaticLayer("tiles", "tiles"); + this.Map.createStaticLayer("Calque 1", [this.Terrain], 0, 0); + this.Map.createStaticLayer("Calque 2", [this.Terrain], 0, 0); + + //initialise keyboard + this.initKeyBoard(); + + //initialise player + this.CurrentPlayer = new Player( + this.Scene, + this.startX, + this.startY, + this + ); + this.CurrentPlayer.initAnimation(); + + //initialise camera + this.Camera = new CameraManager(this.Scene, this.Scene.cameras.main, this, this.CurrentPlayer); + } + + + initKeyBoard() { + this.keyShift = this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SHIFT); + + this.keyZ = this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Z); + this.keyQ = this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Q); + this.keyS = this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.S); + this.keyD = this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.D); + + this.keyUp = this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.UP); + this.keyLeft = this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.LEFT); + this.keyDown = this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.DOWN); + this.keyRight = this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.RIGHT); + } + + update() : void { + this.CurrentPlayer.move(); + this.Camera.moveCamera() + } +} \ No newline at end of file diff --git a/front/src/Phaser/Player.ts b/front/src/Phaser/Player.ts new file mode 100644 index 00000000..f76778a4 --- /dev/null +++ b/front/src/Phaser/Player.ts @@ -0,0 +1,90 @@ +import {MapManagerInterface} from "./MapManager"; +import {getPlayerAnimations, playAnimation, PlayerAnimationNames} from "./Player/Animation"; + +export class Player extends Phaser.GameObjects.Sprite{ + MapManager : MapManagerInterface; + PlayerValue : string; + + constructor( + Scene : Phaser.Scene, + x : number, + y : number, + MapManager: MapManagerInterface, + PlayerValue : string = "player" + ) { + super(Scene, x, y, PlayerValue); + this.PlayerValue = PlayerValue; + Scene.add.existing(this); + this.MapManager = MapManager; + } + + + initAnimation(){ + getPlayerAnimations(this.PlayerValue).forEach(d => { + this.scene.anims.create({ + key: d.key, + frames: this.scene.anims.generateFrameNumbers(d.frameModel, { start: d.frameStart, end: d.frameEnd }), + frameRate: d.frameRate, + repeat: d.repeat + }); + }) + } + + move(){ + //if user client on shift, camera and player speed + let speedMultiplier = this.MapManager.keyShift.isDown ? 5 : 1; + let haveMove = false; + + if((this.MapManager.keyZ.isDown || this.MapManager.keyUp.isDown)){ + if(!this.CanToMoveUp()){ + return; + } + playAnimation(this, PlayerAnimationNames.WalkUp); + this.setY(this.y - (2 * speedMultiplier)); + haveMove = true; + } + if((this.MapManager.keyQ.isDown || this.MapManager.keyLeft.isDown)){ + if(!this.CanToMoveLeft()){ + return; + } + playAnimation(this, PlayerAnimationNames.WalkLeft); + this.setX(this.x - (2 * speedMultiplier)); + haveMove = true; + } + if((this.MapManager.keyS.isDown || this.MapManager.keyDown.isDown)){ + if(!this.CanToMoveDown()){ + return; + } + playAnimation(this, PlayerAnimationNames.WalkDown); + this.setY(this.y + (2 * speedMultiplier)); + haveMove = true; + } + if((this.MapManager.keyD.isDown || this.MapManager.keyRight.isDown)){ + if(!this.CanToMoveRight()){ + return; + } + playAnimation(this, PlayerAnimationNames.WalkRight); + this.setX(this.x + (2 * speedMultiplier)); + haveMove = true; + } + if(!haveMove){ + playAnimation(this, PlayerAnimationNames.None); + } + } + + private CanToMoveUp(){ + return this.y > 0; + } + + private CanToMoveLeft(){ + return this.x > 0; + } + + private CanToMoveDown(){ + return this.MapManager.Map.heightInPixels > this.y; + } + + private CanToMoveRight(){ + return this.MapManager.Map.widthInPixels > this.x; + } +} \ No newline at end of file diff --git a/front/src/Phaser/Player/Animation.ts b/front/src/Phaser/Player/Animation.ts new file mode 100644 index 00000000..3652a8d1 --- /dev/null +++ b/front/src/Phaser/Player/Animation.ts @@ -0,0 +1,60 @@ +interface AnimationData { + key: string; + frameRate: number; + repeat: number; + frameModel: string; //todo use an enum + frameStart: number; + frameEnd: number; +} + +export enum PlayerAnimationNames { + WalkDown = 'down', + WalkLeft = 'left', + WalkUp = 'up', + WalkRight = 'right', + None = 'none', +}; + +export const getPlayerAnimations = (PlayerValue : string): AnimationData[] => { + return [{ + key: PlayerAnimationNames.WalkDown, + frameModel: PlayerValue, + frameStart: 0, + frameEnd: 2, + frameRate: 10, + repeat: -1 + }, { + key: PlayerAnimationNames.WalkLeft, + frameModel: PlayerValue, + frameStart: 3, + frameEnd: 5, + frameRate: 10, + repeat: -1 + }, { + key: PlayerAnimationNames.WalkRight, + frameModel: PlayerValue, + frameStart: 6, + frameEnd: 8, + frameRate: 10, + repeat: -1 + }, { + key: PlayerAnimationNames.WalkUp, + frameModel: PlayerValue, + frameStart: 9, + frameEnd: 11, + frameRate: 10, + repeat: -1 + }]; +}; + +export const playAnimation = (Player : Phaser.GameObjects.Sprite, direction : string) => { + if (!Player.anims.currentAnim || Player.anims.currentAnim.key !== direction) { + if (direction !== PlayerAnimationNames.None) { + Player.anims.play(direction); + } else if (Player.anims.isPlaying) { + Player.anims.stop(); + } + } else if (direction === PlayerAnimationNames.None && Player.anims.currentAnim) { + Player.anims.currentAnim.destroy(); + } +}; diff --git a/front/src/index.ts b/front/src/index.ts index cf100627..f0f5107d 100644 --- a/front/src/index.ts +++ b/front/src/index.ts @@ -1,6 +1,6 @@ import 'phaser'; import GameConfig = Phaser.Types.Core.GameConfig; -import {GameScene} from "./GameScene"; +import {GameScene} from "./Phaser/GameScene"; import {Connexion} from "./Connexion"; import {RESOLUTION} from "./Enum/EnvironmentVariable"; From bac1e804adc9f10e79ff4cbbbff4aed92f84634a Mon Sep 17 00:00:00 2001 From: gparant Date: Tue, 7 Apr 2020 20:41:35 +0200 Subject: [PATCH 02/73] Refactor to include connexion --- front/src/Connexion.ts | 20 ++++++++----- front/src/Enum/EnvironmentVariable.ts | 4 ++- front/src/Phaser/{ => Game}/CameraManager.ts | 4 +-- front/src/Phaser/Game/GameManager.ts | 31 ++++++++++++++++++++ front/src/Phaser/{ => Game}/GameScene.ts | 16 ++++++++-- front/src/Phaser/{ => Game}/MapManager.ts | 10 ++++--- front/src/Phaser/{ => Player}/Player.ts | 18 ++++++++++-- front/src/index.ts | 9 +++--- 8 files changed, 88 insertions(+), 24 deletions(-) rename front/src/Phaser/{ => Game}/CameraManager.ts (96%) create mode 100644 front/src/Phaser/Game/GameManager.ts rename front/src/Phaser/{ => Game}/GameScene.ts (59%) rename front/src/Phaser/{ => Game}/MapManager.ts (91%) rename front/src/Phaser/{ => Player}/Player.ts (83%) diff --git a/front/src/Connexion.ts b/front/src/Connexion.ts index 4f094152..b27be366 100644 --- a/front/src/Connexion.ts +++ b/front/src/Connexion.ts @@ -1,3 +1,5 @@ +import {GameManagerInterface} from "./Phaser/Game/GameManager"; + const SocketIo = require('socket.io-client'); import Axios from "axios"; import {API_URL} from "./Enum/EnvironmentVariable"; @@ -70,8 +72,11 @@ export class Connexion { email : string; startedRoom : string; - constructor(email : string) { + GameManager: GameManagerInterface; + + constructor(email : string, GameManager: GameManagerInterface) { this.email = email; + this.GameManager = GameManager; Axios.post(`${API_URL}/login`, {email: email}) .then((res) => { this.token = res.data.token; @@ -87,9 +92,8 @@ export class Connexion { this.joinARoom(this.startedRoom); //share your first position - this.sharePosition(0, 0); + this.sharePosition(this.startedRoom, 0, 0); - //create listen event to get all data user shared by the back this.positionOfAllUser(); this.errorMessage(); @@ -114,8 +118,8 @@ export class Connexion { * @param x * @param y */ - sharePosition(x : number, y : number){ - let messageUserPosition = new MessageUserPosition(this.email, this.startedRoom, new Point(x, y)); + sharePosition(roomId : string, x : number, y : number){ + let messageUserPosition = new MessageUserPosition(this.email, roomId, new Point(x, y)); this.socket.emit('user-position', messageUserPosition.toString()); } @@ -135,8 +139,10 @@ export class Connexion { **/ positionOfAllUser(){ this.socket.on("user-position", (message : string) => { - //TODO show all user in map - console.info("user-position", message); + let data = JSON.parse(message); + data.forEach((UserPositions : any) => { + this.GameManager.sharedUserPosition(UserPositions); + }); }); } diff --git a/front/src/Enum/EnvironmentVariable.ts b/front/src/Enum/EnvironmentVariable.ts index 71934e6c..3cf52b85 100644 --- a/front/src/Enum/EnvironmentVariable.ts +++ b/front/src/Enum/EnvironmentVariable.ts @@ -1,7 +1,9 @@ const API_URL = process.env.API_URL || "http://api.workadventure.localhost"; +const ROOM = [process.env.ROOM || "THECODINGMACHINE"]; const RESOLUTION = 2; export { API_URL, - RESOLUTION + RESOLUTION, + ROOM } \ No newline at end of file diff --git a/front/src/Phaser/CameraManager.ts b/front/src/Phaser/Game/CameraManager.ts similarity index 96% rename from front/src/Phaser/CameraManager.ts rename to front/src/Phaser/Game/CameraManager.ts index 528e06ca..829bedf4 100644 --- a/front/src/Phaser/CameraManager.ts +++ b/front/src/Phaser/Game/CameraManager.ts @@ -1,5 +1,5 @@ -import {RESOLUTION} from "../Enum/EnvironmentVariable"; -import {Player} from "./Player"; +import {RESOLUTION} from "../../Enum/EnvironmentVariable"; +import {Player} from "../Player/Player"; import {MapManagerInterface} from "./MapManager"; export interface CameraManagerInterface { diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts new file mode 100644 index 00000000..ff9f5b5f --- /dev/null +++ b/front/src/Phaser/Game/GameManager.ts @@ -0,0 +1,31 @@ +import {GameSceneInterface, GameScene} from "./GameScene"; +import {ROOM} from "../../Enum/EnvironmentVariable" +import {Connexion} from "../../Connexion"; + +export let ConnexionInstance : Connexion; + +export interface GameManagerInterface { + GameScenes: Array; + + sharedUserPosition(UserPositions: any): void; +} +export class GameManager implements GameManagerInterface { + GameScenes: Array = []; + + constructor() { + this.configureGame(); + ConnexionInstance = new Connexion("test@gmail.com", this); + } + + configureGame() { + ROOM.forEach((roomId) => { + let newGame = new GameScene(roomId, this); + this.GameScenes.push(newGame); + }); + } + + sharedUserPosition(UserPositions: any) { + let Game: GameSceneInterface = this.GameScenes.find((Game: GameSceneInterface) => Game.RoomId === UserPositions.roomId); + Game.sharedUserPosition(UserPositions) + } +} \ No newline at end of file diff --git a/front/src/Phaser/GameScene.ts b/front/src/Phaser/Game/GameScene.ts similarity index 59% rename from front/src/Phaser/GameScene.ts rename to front/src/Phaser/Game/GameScene.ts index 4f2eb9cc..947da7bd 100644 --- a/front/src/Phaser/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1,12 +1,19 @@ import {MapManagerInterface, MapManager} from "./MapManager"; +import {GameManagerInterface} from "./GameManager"; -export class GameScene extends Phaser.Scene { +export interface GameSceneInterface extends Phaser.Scene { + RoomId : string; + sharedUserPosition(data : []): void; +} +export class GameScene extends Phaser.Scene implements GameSceneInterface{ private MapManager : MapManagerInterface; + RoomId : string; - constructor() { + constructor(RoomId : string, GameManager : GameManagerInterface) { super({ key: "GameScene" }); + this.RoomId = RoomId; } //hook preload scene @@ -32,4 +39,9 @@ export class GameScene extends Phaser.Scene { update(dt: number): void { this.MapManager.update(); } + + sharedUserPosition(data: []): void { + //TODO share position of all user + //console.log("sharedUserPosition", data); + } } diff --git a/front/src/Phaser/MapManager.ts b/front/src/Phaser/Game/MapManager.ts similarity index 91% rename from front/src/Phaser/MapManager.ts rename to front/src/Phaser/Game/MapManager.ts index ecd86210..f12a32d1 100644 --- a/front/src/Phaser/MapManager.ts +++ b/front/src/Phaser/Game/MapManager.ts @@ -1,6 +1,7 @@ import {CameraManager, CameraManagerInterface} from "./CameraManager"; -import {RESOLUTION} from "../Enum/EnvironmentVariable"; -import {Player} from "./Player"; +import {RESOLUTION} from "../../Enum/EnvironmentVariable"; +import {Player} from "../Player/Player"; +import {GameScene, GameSceneInterface} from "./GameScene"; export interface MapManagerInterface { keyZ: Phaser.Input.Keyboard.Key; @@ -16,6 +17,7 @@ export interface MapManagerInterface { Map: Phaser.Tilemaps.Tilemap; Terrain: Phaser.Tilemaps.Tileset; Camera: CameraManagerInterface; + Scene: GameSceneInterface; update(): void; } export class MapManager implements MapManagerInterface{ @@ -32,12 +34,12 @@ export class MapManager implements MapManagerInterface{ Terrain : Phaser.Tilemaps.Tileset; Camera: CameraManagerInterface; CurrentPlayer: Player; - Scene: Phaser.Scene; + Scene: GameSceneInterface; Map: Phaser.Tilemaps.Tilemap; startX = (window.innerWidth / 2) / RESOLUTION; startY = (window.innerHeight / 2) / RESOLUTION; - constructor(scene: Phaser.Scene){ + constructor(scene: GameSceneInterface){ this.Scene = scene; //initalise map diff --git a/front/src/Phaser/Player.ts b/front/src/Phaser/Player/Player.ts similarity index 83% rename from front/src/Phaser/Player.ts rename to front/src/Phaser/Player/Player.ts index f76778a4..ebfc19b0 100644 --- a/front/src/Phaser/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -1,12 +1,16 @@ -import {MapManagerInterface} from "./MapManager"; -import {getPlayerAnimations, playAnimation, PlayerAnimationNames} from "./Player/Animation"; +import {MapManagerInterface} from "../Game/MapManager"; +import {getPlayerAnimations, playAnimation, PlayerAnimationNames} from "./Animation"; +import {Connexion} from "../../Connexion"; +import {GameSceneInterface} from "../Game/GameScene"; +import {ConnexionInstance} from "../Game/GameManager"; export class Player extends Phaser.GameObjects.Sprite{ MapManager : MapManagerInterface; PlayerValue : string; + Connexion: Connexion; constructor( - Scene : Phaser.Scene, + Scene : GameSceneInterface, x : number, y : number, MapManager: MapManagerInterface, @@ -69,6 +73,14 @@ export class Player extends Phaser.GameObjects.Sprite{ } if(!haveMove){ playAnimation(this, PlayerAnimationNames.None); + }else{ + this.sharePosition(); + } + } + + private sharePosition(){ + if(ConnexionInstance) { + ConnexionInstance.sharePosition((this.scene as GameSceneInterface).RoomId, this.x, this.y); } } diff --git a/front/src/index.ts b/front/src/index.ts index f0f5107d..650fd52c 100644 --- a/front/src/index.ts +++ b/front/src/index.ts @@ -1,15 +1,16 @@ import 'phaser'; import GameConfig = Phaser.Types.Core.GameConfig; -import {GameScene} from "./Phaser/GameScene"; -import {Connexion} from "./Connexion"; +import {GameManager} from "./Phaser/Game/GameManager"; import {RESOLUTION} from "./Enum/EnvironmentVariable"; +let gameManager = new GameManager(); + const config: GameConfig = { title: "Office game", width: window.innerWidth / RESOLUTION, height: window.innerHeight / RESOLUTION, parent: "game", - scene: [GameScene], + scene: gameManager.GameScenes, zoom: RESOLUTION, }; @@ -18,5 +19,3 @@ let game = new Phaser.Game(config); window.addEventListener('resize', function (event) { game.scale.resize(window.innerWidth / RESOLUTION, window.innerHeight / RESOLUTION); }); - -const connexion = new Connexion("test@gmail.com"); From aba332218881a86eac79250a6669e912d3e0db3f Mon Sep 17 00:00:00 2001 From: gparant Date: Tue, 7 Apr 2020 20:46:30 +0200 Subject: [PATCH 03/73] Fix CI --- front/src/Connexion.ts | 3 +++ front/src/Phaser/Game/GameScene.ts | 2 +- front/src/Phaser/Player/Animation.ts | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/front/src/Connexion.ts b/front/src/Connexion.ts index b27be366..18bcd6dd 100644 --- a/front/src/Connexion.ts +++ b/front/src/Connexion.ts @@ -119,6 +119,9 @@ export class Connexion { * @param y */ sharePosition(roomId : string, x : number, y : number){ + if(!this.socket){ + return; + } let messageUserPosition = new MessageUserPosition(this.email, roomId, new Point(x, y)); this.socket.emit('user-position', messageUserPosition.toString()); } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 947da7bd..d3abfd15 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -27,7 +27,7 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ } //hook initialisation - init(){}; + init(){} //hook create scene create(): void { diff --git a/front/src/Phaser/Player/Animation.ts b/front/src/Phaser/Player/Animation.ts index 3652a8d1..38f2afd3 100644 --- a/front/src/Phaser/Player/Animation.ts +++ b/front/src/Phaser/Player/Animation.ts @@ -13,7 +13,7 @@ export enum PlayerAnimationNames { WalkUp = 'up', WalkRight = 'right', None = 'none', -}; +} export const getPlayerAnimations = (PlayerValue : string): AnimationData[] => { return [{ From 67c3eaa7f48533e32c5d46e348a66a4aaa08eb09 Mon Sep 17 00:00:00 2001 From: gparant Date: Tue, 7 Apr 2020 21:02:23 +0200 Subject: [PATCH 04/73] Fix Message send to add direction --- back/src/Model/Websocket/MessageUserPosition.ts | 9 ++++++--- back/src/Model/Websocket/PointInterface.ts | 1 + front/src/Connexion.ts | 15 ++++++++++----- front/src/Phaser/Player/Player.ts | 11 ++++++++--- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/back/src/Model/Websocket/MessageUserPosition.ts b/back/src/Model/Websocket/MessageUserPosition.ts index 493f1457..117c99cb 100644 --- a/back/src/Model/Websocket/MessageUserPosition.ts +++ b/back/src/Model/Websocket/MessageUserPosition.ts @@ -4,19 +4,22 @@ import {PointInterface} from "./PointInterface"; export class Point implements PointInterface{ x: number; y: number; + direction: string; - constructor(x : number, y : number) { + constructor(x : number, y : number, direction : string = "none") { if(x === null || y === null){ throw Error("position x and y cannot be null"); } this.x = x; this.y = y; + this.direction = direction; } toJson(){ return { x : this.x, - y: this.y + y: this.y, + direction: this.direction } } } @@ -27,7 +30,7 @@ export class MessageUserPosition extends Message{ constructor(message: string) { super(message); let data = JSON.parse(message); - this.position = new Point(data.position.x, data.position.y); + this.position = new Point(data.position.x, data.position.y, data.position.direction); } toString() { diff --git a/back/src/Model/Websocket/PointInterface.ts b/back/src/Model/Websocket/PointInterface.ts index 7f2ab39a..7b464a5d 100644 --- a/back/src/Model/Websocket/PointInterface.ts +++ b/back/src/Model/Websocket/PointInterface.ts @@ -1,5 +1,6 @@ export interface PointInterface { x: number; y: number; + direction: string; toJson() : object; } \ No newline at end of file diff --git a/front/src/Connexion.ts b/front/src/Connexion.ts index 18bcd6dd..9c6bde05 100644 --- a/front/src/Connexion.ts +++ b/front/src/Connexion.ts @@ -30,19 +30,22 @@ export class Message { export class Point implements PointInterface{ x: number; y: number; + direction : string; - constructor(x : number, y : number) { + constructor(x : number, y : number, direction : string = "none") { if(x === null || y === null){ throw Error("position x and y cannot be null"); } this.x = x; this.y = y; + this.direction = direction; } toJson(){ return { x : this.x, - y: this.y + y: this.y, + direction: this.direction } } } @@ -114,15 +117,17 @@ export class Connexion { } /** - * Permit to share your position in map + * + * @param roomId * @param x * @param y + * @param direction */ - sharePosition(roomId : string, x : number, y : number){ + sharePosition(roomId : string, x : number, y : number, direction : string = "none"){ if(!this.socket){ return; } - let messageUserPosition = new MessageUserPosition(this.email, roomId, new Point(x, y)); + let messageUserPosition = new MessageUserPosition(this.email, roomId, new Point(x, y, direction)); this.socket.emit('user-position', messageUserPosition.toString()); } diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index ebfc19b0..f3911041 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -38,6 +38,7 @@ export class Player extends Phaser.GameObjects.Sprite{ //if user client on shift, camera and player speed let speedMultiplier = this.MapManager.keyShift.isDown ? 5 : 1; let haveMove = false; + let direction = null; if((this.MapManager.keyZ.isDown || this.MapManager.keyUp.isDown)){ if(!this.CanToMoveUp()){ @@ -46,6 +47,7 @@ export class Player extends Phaser.GameObjects.Sprite{ playAnimation(this, PlayerAnimationNames.WalkUp); this.setY(this.y - (2 * speedMultiplier)); haveMove = true; + direction = PlayerAnimationNames.WalkUp; } if((this.MapManager.keyQ.isDown || this.MapManager.keyLeft.isDown)){ if(!this.CanToMoveLeft()){ @@ -54,6 +56,7 @@ export class Player extends Phaser.GameObjects.Sprite{ playAnimation(this, PlayerAnimationNames.WalkLeft); this.setX(this.x - (2 * speedMultiplier)); haveMove = true; + direction = PlayerAnimationNames.WalkLeft; } if((this.MapManager.keyS.isDown || this.MapManager.keyDown.isDown)){ if(!this.CanToMoveDown()){ @@ -62,6 +65,7 @@ export class Player extends Phaser.GameObjects.Sprite{ playAnimation(this, PlayerAnimationNames.WalkDown); this.setY(this.y + (2 * speedMultiplier)); haveMove = true; + direction = PlayerAnimationNames.WalkDown; } if((this.MapManager.keyD.isDown || this.MapManager.keyRight.isDown)){ if(!this.CanToMoveRight()){ @@ -70,17 +74,18 @@ export class Player extends Phaser.GameObjects.Sprite{ playAnimation(this, PlayerAnimationNames.WalkRight); this.setX(this.x + (2 * speedMultiplier)); haveMove = true; + direction = PlayerAnimationNames.WalkRight; } if(!haveMove){ playAnimation(this, PlayerAnimationNames.None); }else{ - this.sharePosition(); + this.sharePosition(direction); } } - private sharePosition(){ + private sharePosition(direction : string){ if(ConnexionInstance) { - ConnexionInstance.sharePosition((this.scene as GameSceneInterface).RoomId, this.x, this.y); + ConnexionInstance.sharePosition((this.scene as GameSceneInterface).RoomId, this.x, this.y, direction); } } From 77780bd27be0c6891fe47b707f79d5c4e3926f4e Mon Sep 17 00:00:00 2001 From: gparant Date: Tue, 7 Apr 2020 21:03:33 +0200 Subject: [PATCH 05/73] Change comment with new message strategy --- back/src/Controller/IoSocketController.ts | 3 ++- front/src/Connexion.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/back/src/Controller/IoSocketController.ts b/back/src/Controller/IoSocketController.ts index aa5dfdc9..24392f09 100644 --- a/back/src/Controller/IoSocketController.ts +++ b/back/src/Controller/IoSocketController.ts @@ -103,7 +103,8 @@ export class IoSocketController{ roomId: , position: { x : , - y : + y : , + direction: } }, ... diff --git a/front/src/Connexion.ts b/front/src/Connexion.ts index 9c6bde05..d6f09110 100644 --- a/front/src/Connexion.ts +++ b/front/src/Connexion.ts @@ -139,7 +139,8 @@ export class Connexion { * roomId: , * position: { * x : , - * y : + * y : , + * direction: * } * }, * ... From 25895e51f72b73c89135768adf705eb5dfa56abb Mon Sep 17 00:00:00 2001 From: gparant Date: Tue, 7 Apr 2020 22:38:53 +0200 Subject: [PATCH 06/73] Fix and refactor with comments of @moumoug --- front/src/Phaser/Game/CameraManager.ts | 85 +++++++------------------- front/src/Phaser/Game/MapManager.ts | 7 +-- front/src/Phaser/Player/Player.ts | 24 +++++--- 3 files changed, 39 insertions(+), 77 deletions(-) diff --git a/front/src/Phaser/Game/CameraManager.ts b/front/src/Phaser/Game/CameraManager.ts index 829bedf4..b1585542 100644 --- a/front/src/Phaser/Game/CameraManager.ts +++ b/front/src/Phaser/Game/CameraManager.ts @@ -1,95 +1,54 @@ import {RESOLUTION} from "../../Enum/EnvironmentVariable"; import {Player} from "../Player/Player"; import {MapManagerInterface} from "./MapManager"; +import {PlayerAnimationNames} from "../Player/Animation"; export interface CameraManagerInterface { - CurrentPlayer : Player; MapManager : MapManagerInterface; - moveCamera() : void; + moveCamera(CurrentPlayer : Player) : void; } export class CameraManager implements CameraManagerInterface{ Scene : Phaser.Scene; Camera : Phaser.Cameras.Scene2D.Camera; - CurrentPlayer : Player; MapManager : MapManagerInterface; constructor( Scene: Phaser.Scene, Camera : Phaser.Cameras.Scene2D.Camera, MapManager: MapManagerInterface, - CurrentPlayer: Player ) { this.Scene = Scene; this.MapManager = MapManager; this.Camera = Camera; - this.CurrentPlayer = CurrentPlayer; - } - /** - * - * @param x - * @param y - * @param speedMultiplier - */ - private moveCameraPosition(x:number, y:number, speedMultiplier: number): void { - this.Camera.scrollX += speedMultiplier * 2 * x; - this.Camera.scrollY += speedMultiplier * 2 * y; } - /** - * - */ - moveCamera(): void { + moveCamera(CurrentPlayer : Player): void { //center of camera let startX = ((window.innerWidth / 2) / RESOLUTION); let startY = ((window.innerHeight / 2) / RESOLUTION); - //if user client on shift, camera and player speed - let speedMultiplier = this.MapManager.keyShift.isDown ? 5 : 1; + let limit = { + top: startY, + left: startX, + bottom : this.MapManager.Map.heightInPixels - startY, + right: this.MapManager.Map.widthInPixels - startX, + }; - if (this.MapManager.keyZ.isDown || this.MapManager.keyUp.isDown) { - if (!this.CanToMoveUp()) { - this.Camera.scrollY = 0; - }else if (this.CurrentPlayer.y < (this.MapManager.Map.widthInPixels - startY)) { - this.moveCameraPosition(0, -1, speedMultiplier); - } + if(CurrentPlayer.x < limit.left){ + this.Camera.scrollX = 0; + }else if(CurrentPlayer.x > limit.right){ + this.Camera.scrollX = (limit.right - startX); + }else { + this.Camera.scrollX = (CurrentPlayer.x - startX); } - if (this.MapManager.keyQ.isDown || this.MapManager.keyLeft.isDown) { - if (!this.CanToMoveLeft()) { - this.Camera.scrollX = 0; - }else if (this.CurrentPlayer.x < (this.MapManager.Map.heightInPixels - startX)) { - this.moveCameraPosition(-1, 0, speedMultiplier); - } + + if(CurrentPlayer.y < limit.top){ + this.Camera.scrollY = 0; + }else if(CurrentPlayer.y > limit.bottom){ + this.Camera.scrollY = (limit.bottom - startY); + }else { + this.Camera.scrollY = (CurrentPlayer.y - startY); } - if (this.MapManager.keyS.isDown || this.MapManager.keyDown.isDown) { - if (!this.CanToMoveDown()) { - this.Camera.scrollY = (this.MapManager.Map.heightInPixels - (window.innerHeight / RESOLUTION)); - } else if (this.CurrentPlayer.y > startY) { - this.moveCameraPosition(0, 1, speedMultiplier); - } - } - if (this.MapManager.keyD.isDown || this.MapManager.keyRight.isDown) { - if (!this.CanToMoveRight()) { - this.Camera.scrollX = (this.MapManager.Map.widthInPixels - (window.innerWidth / RESOLUTION)); - } else if (this.CurrentPlayer.x > startX) { - this.moveCameraPosition(1, 0, speedMultiplier); - } - } - } - - private CanToMoveUp(){ - return this.Camera.scrollY > 0; - } - - private CanToMoveLeft(){ - return this.Camera.scrollX > 0; - } - - private CanToMoveDown(){ - return this.MapManager.Map.heightInPixels > (this.Camera.scrollY + (window.innerHeight / RESOLUTION)) - } - - private CanToMoveRight(){ - return this.MapManager.Map.widthInPixels > (this.Camera.scrollX + (window.innerWidth / RESOLUTION)) } } \ No newline at end of file diff --git a/front/src/Phaser/Game/MapManager.ts b/front/src/Phaser/Game/MapManager.ts index f12a32d1..8b3d9231 100644 --- a/front/src/Phaser/Game/MapManager.ts +++ b/front/src/Phaser/Game/MapManager.ts @@ -52,17 +52,17 @@ export class MapManager implements MapManagerInterface{ //initialise keyboard this.initKeyBoard(); + //initialise camera + this.Camera = new CameraManager(this.Scene, this.Scene.cameras.main, this); //initialise player this.CurrentPlayer = new Player( this.Scene, this.startX, this.startY, + this.Camera, this ); this.CurrentPlayer.initAnimation(); - - //initialise camera - this.Camera = new CameraManager(this.Scene, this.Scene.cameras.main, this, this.CurrentPlayer); } @@ -82,6 +82,5 @@ export class MapManager implements MapManagerInterface{ update() : void { this.CurrentPlayer.move(); - this.Camera.moveCamera() } } \ No newline at end of file diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index f3911041..563843fd 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -1,18 +1,19 @@ import {MapManagerInterface} from "../Game/MapManager"; import {getPlayerAnimations, playAnimation, PlayerAnimationNames} from "./Animation"; -import {Connexion} from "../../Connexion"; import {GameSceneInterface} from "../Game/GameScene"; import {ConnexionInstance} from "../Game/GameManager"; +import {CameraManagerInterface} from "../Game/CameraManager"; export class Player extends Phaser.GameObjects.Sprite{ MapManager : MapManagerInterface; PlayerValue : string; - Connexion: Connexion; + CameraManager: CameraManagerInterface; constructor( Scene : GameSceneInterface, x : number, y : number, + CameraManager: CameraManagerInterface, MapManager: MapManagerInterface, PlayerValue : string = "player" ) { @@ -20,6 +21,7 @@ export class Player extends Phaser.GameObjects.Sprite{ this.PlayerValue = PlayerValue; Scene.add.existing(this); this.MapManager = MapManager; + this.CameraManager = CameraManager; } @@ -41,7 +43,7 @@ export class Player extends Phaser.GameObjects.Sprite{ let direction = null; if((this.MapManager.keyZ.isDown || this.MapManager.keyUp.isDown)){ - if(!this.CanToMoveUp()){ + if(!this.CanMoveUp()){ return; } playAnimation(this, PlayerAnimationNames.WalkUp); @@ -50,7 +52,7 @@ export class Player extends Phaser.GameObjects.Sprite{ direction = PlayerAnimationNames.WalkUp; } if((this.MapManager.keyQ.isDown || this.MapManager.keyLeft.isDown)){ - if(!this.CanToMoveLeft()){ + if(!this.CanMoveLeft()){ return; } playAnimation(this, PlayerAnimationNames.WalkLeft); @@ -59,7 +61,7 @@ export class Player extends Phaser.GameObjects.Sprite{ direction = PlayerAnimationNames.WalkLeft; } if((this.MapManager.keyS.isDown || this.MapManager.keyDown.isDown)){ - if(!this.CanToMoveDown()){ + if(!this.CanMoveDown()){ return; } playAnimation(this, PlayerAnimationNames.WalkDown); @@ -68,7 +70,7 @@ export class Player extends Phaser.GameObjects.Sprite{ direction = PlayerAnimationNames.WalkDown; } if((this.MapManager.keyD.isDown || this.MapManager.keyRight.isDown)){ - if(!this.CanToMoveRight()){ + if(!this.CanMoveRight()){ return; } playAnimation(this, PlayerAnimationNames.WalkRight); @@ -81,6 +83,8 @@ export class Player extends Phaser.GameObjects.Sprite{ }else{ this.sharePosition(direction); } + + this.CameraManager.moveCamera(this); } private sharePosition(direction : string){ @@ -89,19 +93,19 @@ export class Player extends Phaser.GameObjects.Sprite{ } } - private CanToMoveUp(){ + private CanMoveUp(){ return this.y > 0; } - private CanToMoveLeft(){ + private CanMoveLeft(){ return this.x > 0; } - private CanToMoveDown(){ + private CanMoveDown(){ return this.MapManager.Map.heightInPixels > this.y; } - private CanToMoveRight(){ + private CanMoveRight(){ return this.MapManager.Map.widthInPixels > this.x; } } \ No newline at end of file From 9d83ba22d5feb17a888ab6f201eeae5ffeed3333 Mon Sep 17 00:00:00 2001 From: gparant Date: Tue, 7 Apr 2020 23:56:16 +0200 Subject: [PATCH 07/73] Fix play anim --- front/src/Phaser/Player/Animation.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/front/src/Phaser/Player/Animation.ts b/front/src/Phaser/Player/Animation.ts index 38f2afd3..eb8298f4 100644 --- a/front/src/Phaser/Player/Animation.ts +++ b/front/src/Phaser/Player/Animation.ts @@ -49,11 +49,7 @@ export const getPlayerAnimations = (PlayerValue : string): AnimationData[] => { export const playAnimation = (Player : Phaser.GameObjects.Sprite, direction : string) => { if (!Player.anims.currentAnim || Player.anims.currentAnim.key !== direction) { - if (direction !== PlayerAnimationNames.None) { - Player.anims.play(direction); - } else if (Player.anims.isPlaying) { - Player.anims.stop(); - } + Player.anims.play(direction); } else if (direction === PlayerAnimationNames.None && Player.anims.currentAnim) { Player.anims.currentAnim.destroy(); } From bc7b5fc6c99888429e0e0b179482ae9f6a9abbf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 9 Apr 2020 11:00:30 +0200 Subject: [PATCH 08/73] Setting up continuous deployment --- .github/workflows/build-and-deploy.yml | 65 ++++++++++++++++++++++++++ back/.dockerignore | 4 ++ back/Dockerfile | 8 ++++ deeployer.json | 19 ++++++++ front/.dockerignore | 3 ++ front/Dockerfile | 9 ++++ 6 files changed, 108 insertions(+) create mode 100644 .github/workflows/build-and-deploy.yml create mode 100644 back/.dockerignore create mode 100644 back/Dockerfile create mode 100644 deeployer.json create mode 100644 front/.dockerignore create mode 100644 front/Dockerfile diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml new file mode 100644 index 00000000..ec7d3c69 --- /dev/null +++ b/.github/workflows/build-and-deploy.yml @@ -0,0 +1,65 @@ +name: Build, push and deploy Docker image + +on: + push: + branches: + - master + - cd +# tags: +# - '*' + +# Enables BuildKit +env: + DOCKER_BUILDKIT: 1 + +jobs: + + build-front: + + runs-on: ubuntu-latest + + steps: + + - name: Checkout + uses: actions/checkout@v2 + + - name: "Build and push front image" + uses: docker/build-push-action@v1 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + repository: thecodingmachine/workadventure-front + tag_with_ref: true + add_git_labels: true + working-directory: "front" + + build-back: + + runs-on: ubuntu-latest + + steps: + + - name: Checkout + uses: actions/checkout@v2 + + - name: "Build and push back image" + uses: docker/build-push-action@v1 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + repository: thecodingmachine/workadventure-back + tag_with_ref: true + add_git_labels: true + working-directory: "back" + + deeploy: + needs: + - build-front + - build-back + runs-on: ubuntu-latest + + - name: Checkout + uses: actions/checkout@v2 + + - name: "Deploy" + uses: thecodingmachine/deeployer@master diff --git a/back/.dockerignore b/back/.dockerignore new file mode 100644 index 00000000..ca0a17d7 --- /dev/null +++ b/back/.dockerignore @@ -0,0 +1,4 @@ +/dist/ +/node_modules/ +/dist/bundle.js +/yarn-error.log diff --git a/back/Dockerfile b/back/Dockerfile new file mode 100644 index 00000000..edaeee6a --- /dev/null +++ b/back/Dockerfile @@ -0,0 +1,8 @@ +FROM thecodingmachine/nodejs:12 + +COPY . . +RUN yarn install + +ENV NODE_ENV=production + +CMD ['yarn', 'run', 'prod'] diff --git a/deeployer.json b/deeployer.json new file mode 100644 index 00000000..2eaeeba0 --- /dev/null +++ b/deeployer.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://raw.githubusercontent.com/thecodingmachine/deeployer/master/deeployer.schema.json", + "containers": { + "back": { + "image": "", + "host": "http://api.workadventure.test.thecodingmachine.com", + "env": { + "SECRET_KEY": "tempSecretKeyNeedsToChange" + } + }, + "front": { + "image": "", + "host": "http://workadventure.test.thecodingmachine.com", + "env": { + "API_URL": "http://api.workadventure.test.thecodingmachine.com" + } + } + } +} diff --git a/front/.dockerignore b/front/.dockerignore new file mode 100644 index 00000000..048e02ca --- /dev/null +++ b/front/.dockerignore @@ -0,0 +1,3 @@ +/node_modules/ +/dist/bundle.js +/yarn-error.log diff --git a/front/Dockerfile b/front/Dockerfile new file mode 100644 index 00000000..4ab4f273 --- /dev/null +++ b/front/Dockerfile @@ -0,0 +1,9 @@ +# we are rebuilding on each deploy to cope with the API_URL environment URL +FROM thecodingmachine/nodejs:12-apache + +COPY . . +RUN yarn install + +ENV NODE_ENV=production +ENV STARTUP_COMMAND_1="yarn run build" +ENV APACHE_DOCUMENT_ROOT=dist/ From c6770042b3bcc3faf80f01c0893dcab03c768121 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 9 Apr 2020 11:03:24 +0200 Subject: [PATCH 09/73] Fixing action file --- .github/workflows/build-and-deploy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index ec7d3c69..9318049d 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -58,6 +58,7 @@ jobs: - build-back runs-on: ubuntu-latest + steps: - name: Checkout uses: actions/checkout@v2 From 3b3e615e37a2fcc474208ad8f5b86985db1e0f83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 9 Apr 2020 11:56:29 +0200 Subject: [PATCH 10/73] Fixing push action --- .github/workflows/build-and-deploy.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 9318049d..a2e21d3f 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -26,6 +26,7 @@ jobs: - name: "Build and push front image" uses: docker/build-push-action@v1 with: + dockerfile: front/Dockerfile username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} repository: thecodingmachine/workadventure-front @@ -45,12 +46,12 @@ jobs: - name: "Build and push back image" uses: docker/build-push-action@v1 with: + dockerfile: back/Dockerfile username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} repository: thecodingmachine/workadventure-back tag_with_ref: true add_git_labels: true - working-directory: "back" deeploy: needs: From 1376d5b24cb2a9dda4440cfac9d8be9d337f845b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 9 Apr 2020 11:57:19 +0200 Subject: [PATCH 11/73] Fixing push action --- .github/workflows/build-and-deploy.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index a2e21d3f..54c143fb 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -32,7 +32,6 @@ jobs: repository: thecodingmachine/workadventure-front tag_with_ref: true add_git_labels: true - working-directory: "front" build-back: From 8eba5276dde8c0c816ec37137c4f0db9e75bfde2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 9 Apr 2020 12:14:06 +0200 Subject: [PATCH 12/73] Adding namespace to deeployer --- .github/workflows/build-and-deploy.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 54c143fb..c44607c4 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -62,5 +62,7 @@ jobs: - name: Checkout uses: actions/checkout@v2 - - name: "Deploy" + - name: Deploy uses: thecodingmachine/deeployer@master + with: + namespace: workadventure-${GITHUB_REF#refs/heads/} From 3038141070a0fa99c7a6cfcfbf627a538c06f5cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 9 Apr 2020 14:21:43 +0200 Subject: [PATCH 13/73] Setting Kube config in deeployer --- .github/workflows/build-and-deploy.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index c44607c4..5f6cd582 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -64,5 +64,8 @@ jobs: - name: Deploy uses: thecodingmachine/deeployer@master + env: + KUBE_CONFIG_FILE: ${{ secrets.KUBE_CONFIG_FILE }} + AUTOCONNECT: 1 with: namespace: workadventure-${GITHUB_REF#refs/heads/} From d68e39f2e3a5326eeed7146ef503f63727d43175 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 9 Apr 2020 14:50:43 +0200 Subject: [PATCH 14/73] Testing --- .github/workflows/build-and-deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 5f6cd582..58566734 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -68,4 +68,4 @@ jobs: KUBE_CONFIG_FILE: ${{ secrets.KUBE_CONFIG_FILE }} AUTOCONNECT: 1 with: - namespace: workadventure-${GITHUB_REF#refs/heads/} + namespace: workadventure-master From fd4d8dc651b750013d89bc5d516a04d25a199019 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 9 Apr 2020 15:10:44 +0200 Subject: [PATCH 15/73] Fixing deeployer file --- deeployer.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/deeployer.json b/deeployer.json index 2eaeeba0..7e0e4984 100644 --- a/deeployer.json +++ b/deeployer.json @@ -2,15 +2,17 @@ "$schema": "https://raw.githubusercontent.com/thecodingmachine/deeployer/master/deeployer.schema.json", "containers": { "back": { - "image": "", + "image": "thecodingmachine/workadventure-back:cd", "host": "http://api.workadventure.test.thecodingmachine.com", + "ports": [8080], "env": { "SECRET_KEY": "tempSecretKeyNeedsToChange" } }, "front": { - "image": "", + "image": "thecodingmachine/workadventure-front:cd", "host": "http://workadventure.test.thecodingmachine.com", + "ports": [80], "env": { "API_URL": "http://api.workadventure.test.thecodingmachine.com" } From d257b2b9447b072d22171dfc216cd6a04be4b316 Mon Sep 17 00:00:00 2001 From: gparant Date: Fri, 10 Apr 2020 12:54:05 +0200 Subject: [PATCH 16/73] Multi players on the map - Fix share user position - Fix initialise map - Create function to add user on the map with back end data --- back/package.json | 4 +- back/src/Controller/AuthenticateController.ts | 10 ++- back/src/Controller/IoSocketController.ts | 3 +- back/yarn.lock | 19 +++++ front/src/Connexion.ts | 76 ++++++++++++++----- front/src/Phaser/Game/GameManager.ts | 67 +++++++++++++--- front/src/Phaser/Game/GameScene.ts | 30 ++++++-- front/src/Phaser/Game/MapManager.ts | 68 ++++++++++++++++- front/src/Phaser/Player/Player.ts | 16 +++- front/src/index.ts | 8 +- 10 files changed, 253 insertions(+), 48 deletions(-) diff --git a/back/package.json b/back/package.json index 4fe7681d..d4bf6349 100644 --- a/back/package.json +++ b/back/package.json @@ -25,13 +25,15 @@ "@types/http-status-codes": "^1.2.0", "@types/jsonwebtoken": "^8.3.8", "@types/socket.io": "^2.1.4", + "@types/uuidv4": "^5.0.0", "body-parser": "^1.19.0", "express": "^4.17.1", "http-status-codes": "^1.4.0", "jsonwebtoken": "^8.5.1", "socket.io": "^2.3.0", "ts-node-dev": "^1.0.0-pre.44", - "typescript": "^3.8.3" + "typescript": "^3.8.3", + "uuidv4": "^6.0.7" }, "devDependencies": { "@types/jasmine": "^3.5.10", diff --git a/back/src/Controller/AuthenticateController.ts b/back/src/Controller/AuthenticateController.ts index 7555c67a..f9eed470 100644 --- a/back/src/Controller/AuthenticateController.ts +++ b/back/src/Controller/AuthenticateController.ts @@ -2,6 +2,7 @@ import {Application, Request, Response} from "express"; import Jwt from "jsonwebtoken"; import {BAD_REQUEST, OK} from "http-status-codes"; import {SECRET_KEY, ROOM} from "../Enum/EnvironmentVariable"; //TODO fix import by "_Enum/..." +import { uuid } from 'uuidv4'; export class AuthenticateController{ App : Application; @@ -21,8 +22,13 @@ export class AuthenticateController{ }); } //TODO check user email for The Coding Machine game - let token = Jwt.sign({email: param.email, roomId: ROOM}, SECRET_KEY, {expiresIn: '24h'}); - return res.status(OK).send({token: token, roomId: ROOM}); + let userId = uuid(); + let token = Jwt.sign({email: param.email, roomId: ROOM, userId: userId}, SECRET_KEY, {expiresIn: '24h'}); + return res.status(OK).send({ + token: token, + roomId: ROOM, + userId: userId + }); }); } } \ No newline at end of file diff --git a/back/src/Controller/IoSocketController.ts b/back/src/Controller/IoSocketController.ts index 24392f09..1fc114a4 100644 --- a/back/src/Controller/IoSocketController.ts +++ b/back/src/Controller/IoSocketController.ts @@ -125,8 +125,7 @@ export class IoSocketController{ } arrayMap.forEach((value : any) => { let roomId = value[0]; - let data = value[1]; - this.Io.in(roomId).emit('user-position', JSON.stringify(data)); + this.Io.in(roomId).emit('user-position', JSON.stringify(arrayMap)); }); this.seTimeOutInProgress = setTimeout(() => { this.shareUsersPosition(); diff --git a/back/yarn.lock b/back/yarn.lock index 27f4503e..4b4f1fe2 100644 --- a/back/yarn.lock +++ b/back/yarn.lock @@ -135,6 +135,13 @@ resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1" integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ== +"@types/uuidv4@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@types/uuidv4/-/uuidv4-5.0.0.tgz#2c94e67b0c06d5adb28fb7ced1a1b5f0866ecd50" + integrity sha512-xUrhYSJnkTq9CP79cU3svoKTLPCIbMMnu9Twf/tMpHATYSHCAAeDNeb2a/29YORhk5p4atHhCTMsIBU/tvdh6A== + dependencies: + uuidv4 "*" + "@typescript-eslint/eslint-plugin@^2.26.0": version "2.26.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.26.0.tgz#04c96560c8981421e5a9caad8394192363cc423f" @@ -2094,6 +2101,18 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= +uuid@7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b" + integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg== + +uuidv4@*, uuidv4@^6.0.7: + version "6.0.7" + resolved "https://registry.yarnpkg.com/uuidv4/-/uuidv4-6.0.7.tgz#15e920848e1afbbd97b4919bc50f4f2f2278f880" + integrity sha512-4mpYRFNqO22EckzxPSJ/+xjn9GgO6SAqEJ33yt23Y+HZZoZOt/6l4U4iIjc86ZfxSN2fSCGGmHNb3kiACFNd1g== + dependencies: + uuid "7.0.3" + v8-compile-cache@^2.0.3: version "2.1.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e" diff --git a/front/src/Connexion.ts b/front/src/Connexion.ts index d6f09110..707d31c4 100644 --- a/front/src/Connexion.ts +++ b/front/src/Connexion.ts @@ -4,13 +4,7 @@ const SocketIo = require('socket.io-client'); import Axios from "axios"; import {API_URL} from "./Enum/EnvironmentVariable"; -export interface PointInterface { - x: number; - y: number; - toJson() : object; -} - -export class Message { +class Message { userId: string; roomId: string; @@ -27,7 +21,14 @@ export class Message { } } -export class Point implements PointInterface{ +export interface PointInterface { + x: number; + y: number; + direction : string; + toJson() : object; +} + +class Point implements PointInterface{ x: number; y: number; direction : string; @@ -50,7 +51,12 @@ export class Point implements PointInterface{ } } -export class MessageUserPosition extends Message{ +export interface MessageUserPositionInterface { + userId: string; + roomId: string; + position: PointInterface; +} +class MessageUserPosition extends Message implements MessageUserPositionInterface{ position: PointInterface; constructor(userId : string, roomId : string, point : Point) { @@ -69,10 +75,36 @@ export class MessageUserPosition extends Message{ } } +export interface ListMessageUserPositionInterface { + roomId : string; + listUsersPosition: Array; +} +class ListMessageUserPosition{ + roomId : string; + listUsersPosition: Array; + + constructor(roomId : string, data : any) { + this.roomId = roomId; + this.listUsersPosition = new Array(); + data.forEach((userPosition: any) => { + this.listUsersPosition.push(new MessageUserPosition( + userPosition.userId, + userPosition.roomId, + new Point( + userPosition.position.x, + userPosition.position.y, + userPosition.position.direction + ) + )); + }); + } +} + export class Connexion { socket : any; token : string; email : string; + userId: string; startedRoom : string; GameManager: GameManagerInterface; @@ -80,10 +112,14 @@ export class Connexion { constructor(email : string, GameManager: GameManagerInterface) { this.email = email; this.GameManager = GameManager; - Axios.post(`${API_URL}/login`, {email: email}) + } + + createConnexion(){ + return Axios.post(`${API_URL}/login`, {email: this.email}) .then((res) => { this.token = res.data.token; this.startedRoom = res.data.roomId; + this.userId = res.data.userId; this.socket = SocketIo(`${API_URL}`, { query: { @@ -100,6 +136,11 @@ export class Connexion { this.positionOfAllUser(); this.errorMessage(); + + return{ + userId: this.userId, + roomId: this.startedRoom + } }) .catch((err) => { console.error(err); @@ -112,7 +153,7 @@ export class Connexion { * @param roomId */ joinARoom(roomId : string){ - let messageUserPosition = new MessageUserPosition(this.email, this.startedRoom, new Point(0, 0)); + let messageUserPosition = new MessageUserPosition(this.userId, this.startedRoom, new Point(0, 0)); this.socket.emit('join-room', messageUserPosition.toString()); } @@ -127,7 +168,7 @@ export class Connexion { if(!this.socket){ return; } - let messageUserPosition = new MessageUserPosition(this.email, roomId, new Point(x, y, direction)); + let messageUserPosition = new MessageUserPosition(this.userId, roomId, new Point(x, y, direction)); this.socket.emit('user-position', messageUserPosition.toString()); } @@ -146,11 +187,12 @@ export class Connexion { * ... * ] **/ - positionOfAllUser(){ - this.socket.on("user-position", (message : string) => { - let data = JSON.parse(message); - data.forEach((UserPositions : any) => { - this.GameManager.sharedUserPosition(UserPositions); + positionOfAllUser() { + this.socket.on("user-position", (message: string) => { + let dataList = JSON.parse(message); + dataList.forEach((UserPositions: any) => { + let listMessageUserPosition = new ListMessageUserPosition(UserPositions[0], UserPositions[1]); + this.GameManager.shareUserPosition(listMessageUserPosition); }); }); } diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts index ff9f5b5f..2ab1d0d7 100644 --- a/front/src/Phaser/Game/GameManager.ts +++ b/front/src/Phaser/Game/GameManager.ts @@ -1,31 +1,76 @@ import {GameSceneInterface, GameScene} from "./GameScene"; import {ROOM} from "../../Enum/EnvironmentVariable" -import {Connexion} from "../../Connexion"; +import {Connexion, ListMessageUserPositionInterface} from "../../Connexion"; + +export enum StatusGameManagerEnum { + IN_PROGRESS = 1, + CURRENT_USER_CREATED = 2 +} export let ConnexionInstance : Connexion; export interface GameManagerInterface { GameScenes: Array; - - sharedUserPosition(UserPositions: any): void; + status : number; + createCurrentPlayer() : void; + shareUserPosition(ListMessageUserPosition : ListMessageUserPositionInterface): void; } export class GameManager implements GameManagerInterface { GameScenes: Array = []; + status: number; constructor() { - this.configureGame(); + this.status = StatusGameManagerEnum.IN_PROGRESS; ConnexionInstance = new Connexion("test@gmail.com", this); } - configureGame() { - ROOM.forEach((roomId) => { - let newGame = new GameScene(roomId, this); - this.GameScenes.push(newGame); + createGame(){ + return ConnexionInstance.createConnexion().then((data: any) => { + this.configureGame(); + /** TODO add loader in the page **/ + }).catch((err) => { + console.error(err); + throw err; }); } - sharedUserPosition(UserPositions: any) { - let Game: GameSceneInterface = this.GameScenes.find((Game: GameSceneInterface) => Game.RoomId === UserPositions.roomId); - Game.sharedUserPosition(UserPositions) + /** + * permit to config rooms + */ + configureGame() { + ROOM.forEach((roomId) => { + let newGame = new GameScene(roomId, this); + this.GameScenes.push((newGame as GameSceneInterface)); + }); + } + + /** + * Permit to create player in started room + * @param RoomId + * @param UserId + */ + createCurrentPlayer(): void { + let game: GameSceneInterface = this.GameScenes.find((Game: GameSceneInterface) => Game.RoomId === ConnexionInstance.startedRoom); + game.createCurrentPlayer(ConnexionInstance.userId); + this.status = StatusGameManagerEnum.CURRENT_USER_CREATED; + } + + /** + * Share position in game + * @param ListMessageUserPosition + */ + shareUserPosition(ListMessageUserPosition: ListMessageUserPositionInterface): void { + if (this.status === StatusGameManagerEnum.IN_PROGRESS) { + return; + } + try { + let Game: GameSceneInterface = this.GameScenes.find((Game: GameSceneInterface) => Game.RoomId === ListMessageUserPosition.roomId); + if (!Game) { + return; + } + Game.shareUserPosition(ListMessageUserPosition.listUsersPosition) + } catch (e) { + console.error(e); + } } } \ No newline at end of file diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index d3abfd15..61afdf68 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1,12 +1,15 @@ import {MapManagerInterface, MapManager} from "./MapManager"; -import {GameManagerInterface} from "./GameManager"; +import {GameManagerInterface, StatusGameManagerEnum} from "./GameManager"; +import {MessageUserPositionInterface} from "../../Connexion"; export interface GameSceneInterface extends Phaser.Scene { RoomId : string; - sharedUserPosition(data : []): void; + createCurrentPlayer(UserId : string) : void; + shareUserPosition(UsersPosition : Array): void; } export class GameScene extends Phaser.Scene implements GameSceneInterface{ private MapManager : MapManagerInterface; + GameManager : GameManagerInterface; RoomId : string; constructor(RoomId : string, GameManager : GameManagerInterface) { @@ -14,6 +17,7 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ key: "GameScene" }); this.RoomId = RoomId; + this.GameManager = GameManager; } //hook preload scene @@ -33,15 +37,31 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ create(): void { //create map manager this.MapManager = new MapManager(this); + //notify game manager can to create currentUser in map + this.GameManager.createCurrentPlayer(); + } + + /** + * Create current player + * @param UserId + */ + createCurrentPlayer(UserId : string): void { + this.MapManager.createCurrentPlayer(UserId) } //hook update update(dt: number): void { + if(this.GameManager.status === StatusGameManagerEnum.IN_PROGRESS){ + return; + } this.MapManager.update(); } - sharedUserPosition(data: []): void { - //TODO share position of all user - //console.log("sharedUserPosition", data); + /** + * Share position in scene + * @param UsersPosition + */ + shareUserPosition(UsersPosition : Array): void { + this.MapManager.updateOrCreateMapPlayer(UsersPosition); } } diff --git a/front/src/Phaser/Game/MapManager.ts b/front/src/Phaser/Game/MapManager.ts index 8b3d9231..99ce2863 100644 --- a/front/src/Phaser/Game/MapManager.ts +++ b/front/src/Phaser/Game/MapManager.ts @@ -1,7 +1,8 @@ import {CameraManager, CameraManagerInterface} from "./CameraManager"; import {RESOLUTION} from "../../Enum/EnvironmentVariable"; import {Player} from "../Player/Player"; -import {GameScene, GameSceneInterface} from "./GameScene"; +import {GameSceneInterface} from "./GameScene"; +import {MessageUserPositionInterface} from "../../Connexion"; export interface MapManagerInterface { keyZ: Phaser.Input.Keyboard.Key; @@ -18,7 +19,10 @@ export interface MapManagerInterface { Terrain: Phaser.Tilemaps.Tileset; Camera: CameraManagerInterface; Scene: GameSceneInterface; + + createCurrentPlayer(UserId : string): void; update(): void; + updateOrCreateMapPlayer(UsersPosition : Array): void; } export class MapManager implements MapManagerInterface{ keyZ: Phaser.Input.Keyboard.Key; @@ -34,6 +38,7 @@ export class MapManager implements MapManagerInterface{ Terrain : Phaser.Tilemaps.Tileset; Camera: CameraManagerInterface; CurrentPlayer: Player; + MapPlayers : Player[]; Scene: GameSceneInterface; Map: Phaser.Tilemaps.Tilemap; startX = (window.innerWidth / 2) / RESOLUTION; @@ -51,11 +56,16 @@ export class MapManager implements MapManagerInterface{ //initialise keyboard this.initKeyBoard(); - //initialise camera this.Camera = new CameraManager(this.Scene, this.Scene.cameras.main, this); + //initialise list of other player + this.MapPlayers = new Array(); + } + + createCurrentPlayer(UserId : string){ //initialise player this.CurrentPlayer = new Player( + UserId, this.Scene, this.startX, this.startY, @@ -65,7 +75,6 @@ export class MapManager implements MapManagerInterface{ this.CurrentPlayer.initAnimation(); } - initKeyBoard() { this.keyShift = this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SHIFT); @@ -83,4 +92,57 @@ export class MapManager implements MapManagerInterface{ update() : void { this.CurrentPlayer.move(); } + + /** + * Create new player and clean the player on the map + * @param UsersPosition + */ + updateOrCreateMapPlayer(UsersPosition : Array){ + if(!this.CurrentPlayer){ + return; + } + + //add or create new user + UsersPosition.forEach((userPosition : MessageUserPositionInterface) => { + if(userPosition.userId === this.CurrentPlayer.userId){ + return; + } + let player = this.MapPlayers.find((player: Player) => userPosition.userId === player.userId); + if(!player){ + this.addPlayer(userPosition); + }else{ + player.updatePosition(userPosition); + } + }); + + //clean map + let mapPlayers = new Array(); + this.MapPlayers.forEach((player: Player) => { + if(UsersPosition.find((message : MessageUserPositionInterface) => message.userId === player.userId)){ + mapPlayers.push(player); + return; + } + player.destroy(); + }); + this.MapPlayers = mapPlayers; + } + + /** + * Create new player + * @param MessageUserPosition + */ + addPlayer(MessageUserPosition : MessageUserPositionInterface){ + //initialise player + let player = new Player( + MessageUserPosition.userId, + this.Scene, + MessageUserPosition.position.x, + MessageUserPosition.position.y, + this.Camera, + this + ); + player.initAnimation(); + this.MapPlayers.push(player); + player.updatePosition(MessageUserPosition) + } } \ No newline at end of file diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index 563843fd..74db636d 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -3,13 +3,16 @@ import {getPlayerAnimations, playAnimation, PlayerAnimationNames} from "./Animat import {GameSceneInterface} from "../Game/GameScene"; import {ConnexionInstance} from "../Game/GameManager"; import {CameraManagerInterface} from "../Game/CameraManager"; +import {MessageUserPositionInterface} from "../../Connexion"; export class Player extends Phaser.GameObjects.Sprite{ + userId : string; MapManager : MapManagerInterface; PlayerValue : string; CameraManager: CameraManagerInterface; constructor( + userId: string, Scene : GameSceneInterface, x : number, y : number, @@ -18,13 +21,13 @@ export class Player extends Phaser.GameObjects.Sprite{ PlayerValue : string = "player" ) { super(Scene, x, y, PlayerValue); + this.userId = userId; this.PlayerValue = PlayerValue; Scene.add.existing(this); this.MapManager = MapManager; this.CameraManager = CameraManager; } - initAnimation(){ getPlayerAnimations(this.PlayerValue).forEach(d => { this.scene.anims.create({ @@ -80,10 +83,9 @@ export class Player extends Phaser.GameObjects.Sprite{ } if(!haveMove){ playAnimation(this, PlayerAnimationNames.None); - }else{ - this.sharePosition(direction); + direction = PlayerAnimationNames.None; } - + this.sharePosition(direction); this.CameraManager.moveCamera(this); } @@ -108,4 +110,10 @@ export class Player extends Phaser.GameObjects.Sprite{ private CanMoveRight(){ return this.MapManager.Map.widthInPixels > this.x; } + + updatePosition(MessageUserPosition : MessageUserPositionInterface){ + playAnimation(this, MessageUserPosition.position.direction); + this.setX(MessageUserPosition.position.x); + this.setY(MessageUserPosition.position.y); + } } \ No newline at end of file diff --git a/front/src/index.ts b/front/src/index.ts index 650fd52c..2a18110d 100644 --- a/front/src/index.ts +++ b/front/src/index.ts @@ -14,8 +14,10 @@ const config: GameConfig = { zoom: RESOLUTION, }; -let game = new Phaser.Game(config); +gameManager.createGame().then(() => { + let game = new Phaser.Game(config); -window.addEventListener('resize', function (event) { - game.scale.resize(window.innerWidth / RESOLUTION, window.innerHeight / RESOLUTION); + window.addEventListener('resize', function (event) { + game.scale.resize(window.innerWidth / RESOLUTION, window.innerHeight / RESOLUTION); + }); }); From 7b51f734c57c91cbd018880ed32e03550958723b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Sat, 11 Apr 2020 12:27:03 +0200 Subject: [PATCH 17/73] Fixing hosts in deeployer config --- deeployer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deeployer.json b/deeployer.json index 7e0e4984..930e6f37 100644 --- a/deeployer.json +++ b/deeployer.json @@ -3,7 +3,7 @@ "containers": { "back": { "image": "thecodingmachine/workadventure-back:cd", - "host": "http://api.workadventure.test.thecodingmachine.com", + "host": "api.workadventure.test.thecodingmachine.com", "ports": [8080], "env": { "SECRET_KEY": "tempSecretKeyNeedsToChange" @@ -11,7 +11,7 @@ }, "front": { "image": "thecodingmachine/workadventure-front:cd", - "host": "http://workadventure.test.thecodingmachine.com", + "host": "workadventure.test.thecodingmachine.com", "ports": [80], "env": { "API_URL": "http://api.workadventure.test.thecodingmachine.com" From d979636a19aa8fd8e011eb6d5bd7548f3e5b1e12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Sat, 11 Apr 2020 15:11:22 +0200 Subject: [PATCH 18/73] Fixing Dockerfiles --- back/.dockerignore | 1 + back/Dockerfile | 4 ++-- front/.dockerignore | 1 + front/Dockerfile | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/back/.dockerignore b/back/.dockerignore index ca0a17d7..576c21a2 100644 --- a/back/.dockerignore +++ b/back/.dockerignore @@ -2,3 +2,4 @@ /node_modules/ /dist/bundle.js /yarn-error.log +/Dockerfile diff --git a/back/Dockerfile b/back/Dockerfile index edaeee6a..316f505b 100644 --- a/back/Dockerfile +++ b/back/Dockerfile @@ -1,8 +1,8 @@ FROM thecodingmachine/nodejs:12 -COPY . . +COPY --chown=docker:docker . . RUN yarn install ENV NODE_ENV=production -CMD ['yarn', 'run', 'prod'] +CMD ["yarn", "run", "prod"] diff --git a/front/.dockerignore b/front/.dockerignore index 048e02ca..64d1025d 100644 --- a/front/.dockerignore +++ b/front/.dockerignore @@ -1,3 +1,4 @@ /node_modules/ /dist/bundle.js /yarn-error.log +/Dockerfile diff --git a/front/Dockerfile b/front/Dockerfile index 4ab4f273..ee15270e 100644 --- a/front/Dockerfile +++ b/front/Dockerfile @@ -1,7 +1,7 @@ # we are rebuilding on each deploy to cope with the API_URL environment URL FROM thecodingmachine/nodejs:12-apache -COPY . . +COPY --chown=docker:docker . . RUN yarn install ENV NODE_ENV=production From 793e5318f7fe2d097433725d1d56fac4156cb9fc Mon Sep 17 00:00:00 2001 From: kharhamel Date: Sat, 11 Apr 2020 16:46:28 +0200 Subject: [PATCH 19/73] created a class to centralize all user inputs catching and expose user events --- front/src/Phaser/Game/MapManager.ts | 46 +++----------- front/src/Phaser/Player/Player.ts | 13 ++-- .../src/Phaser/UserInput/UserInputManager.ts | 63 +++++++++++++++++++ 3 files changed, 77 insertions(+), 45 deletions(-) create mode 100644 front/src/Phaser/UserInput/UserInputManager.ts diff --git a/front/src/Phaser/Game/MapManager.ts b/front/src/Phaser/Game/MapManager.ts index 8b3d9231..3f8521d4 100644 --- a/front/src/Phaser/Game/MapManager.ts +++ b/front/src/Phaser/Game/MapManager.ts @@ -2,35 +2,17 @@ import {CameraManager, CameraManagerInterface} from "./CameraManager"; import {RESOLUTION} from "../../Enum/EnvironmentVariable"; import {Player} from "../Player/Player"; import {GameScene, GameSceneInterface} from "./GameScene"; +import {UserInputManager} from "../UserInput/UserInputManager"; export interface MapManagerInterface { - keyZ: Phaser.Input.Keyboard.Key; - keyQ: Phaser.Input.Keyboard.Key; - keyS: Phaser.Input.Keyboard.Key; - keyD: Phaser.Input.Keyboard.Key; - keyRight: Phaser.Input.Keyboard.Key; - keyLeft: Phaser.Input.Keyboard.Key; - keyUp: Phaser.Input.Keyboard.Key; - keyDown: Phaser.Input.Keyboard.Key; - keyShift: Phaser.Input.Keyboard.Key; - Map: Phaser.Tilemaps.Tilemap; Terrain: Phaser.Tilemaps.Tileset; Camera: CameraManagerInterface; Scene: GameSceneInterface; + userInputManager: UserInputManager; update(): void; } export class MapManager implements MapManagerInterface{ - keyZ: Phaser.Input.Keyboard.Key; - keyQ: Phaser.Input.Keyboard.Key; - keyS: Phaser.Input.Keyboard.Key; - keyD: Phaser.Input.Keyboard.Key; - keyRight: Phaser.Input.Keyboard.Key; - keyLeft: Phaser.Input.Keyboard.Key; - keyUp: Phaser.Input.Keyboard.Key; - keyDown: Phaser.Input.Keyboard.Key; - keyShift: Phaser.Input.Keyboard.Key; - Terrain : Phaser.Tilemaps.Tileset; Camera: CameraManagerInterface; CurrentPlayer: Player; @@ -38,6 +20,7 @@ export class MapManager implements MapManagerInterface{ Map: Phaser.Tilemaps.Tilemap; startX = (window.innerWidth / 2) / RESOLUTION; startY = (window.innerHeight / 2) / RESOLUTION; + userInputManager: UserInputManager; constructor(scene: GameSceneInterface){ this.Scene = scene; @@ -49,11 +32,9 @@ export class MapManager implements MapManagerInterface{ this.Map.createStaticLayer("Calque 1", [this.Terrain], 0, 0); this.Map.createStaticLayer("Calque 2", [this.Terrain], 0, 0); - //initialise keyboard - this.initKeyBoard(); - //initialise camera this.Camera = new CameraManager(this.Scene, this.Scene.cameras.main, this); + this.userInputManager = new UserInputManager(this.Scene); //initialise player this.CurrentPlayer = new Player( this.Scene, @@ -65,22 +46,9 @@ export class MapManager implements MapManagerInterface{ this.CurrentPlayer.initAnimation(); } - - initKeyBoard() { - this.keyShift = this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SHIFT); - - this.keyZ = this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Z); - this.keyQ = this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Q); - this.keyS = this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.S); - this.keyD = this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.D); - - this.keyUp = this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.UP); - this.keyLeft = this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.LEFT); - this.keyDown = this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.DOWN); - this.keyRight = this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.RIGHT); - } - update() : void { - this.CurrentPlayer.move(); + let activeEvents = this.userInputManager.getEventListForGameTick(); + + this.CurrentPlayer.move(activeEvents); } } \ No newline at end of file diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index 563843fd..11b3d4f6 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -3,6 +3,7 @@ import {getPlayerAnimations, playAnimation, PlayerAnimationNames} from "./Animat import {GameSceneInterface} from "../Game/GameScene"; import {ConnexionInstance} from "../Game/GameManager"; import {CameraManagerInterface} from "../Game/CameraManager"; +import {ActiveEventList, UserInputEvent} from "../UserInput/UserInputManager"; export class Player extends Phaser.GameObjects.Sprite{ MapManager : MapManagerInterface; @@ -36,13 +37,13 @@ export class Player extends Phaser.GameObjects.Sprite{ }) } - move(){ + move(activeEvents: ActiveEventList){ //if user client on shift, camera and player speed - let speedMultiplier = this.MapManager.keyShift.isDown ? 5 : 1; + let speedMultiplier = activeEvents.get(UserInputEvent.SpeedUp) ? 5 : 1; let haveMove = false; let direction = null; - if((this.MapManager.keyZ.isDown || this.MapManager.keyUp.isDown)){ + if(activeEvents.get(UserInputEvent.MoveUp)){ if(!this.CanMoveUp()){ return; } @@ -51,7 +52,7 @@ export class Player extends Phaser.GameObjects.Sprite{ haveMove = true; direction = PlayerAnimationNames.WalkUp; } - if((this.MapManager.keyQ.isDown || this.MapManager.keyLeft.isDown)){ + if(activeEvents.get(UserInputEvent.MoveLeft)){ if(!this.CanMoveLeft()){ return; } @@ -60,7 +61,7 @@ export class Player extends Phaser.GameObjects.Sprite{ haveMove = true; direction = PlayerAnimationNames.WalkLeft; } - if((this.MapManager.keyS.isDown || this.MapManager.keyDown.isDown)){ + if(activeEvents.get(UserInputEvent.MoveDown)){ if(!this.CanMoveDown()){ return; } @@ -69,7 +70,7 @@ export class Player extends Phaser.GameObjects.Sprite{ haveMove = true; direction = PlayerAnimationNames.WalkDown; } - if((this.MapManager.keyD.isDown || this.MapManager.keyRight.isDown)){ + if(activeEvents.get(UserInputEvent.MoveRight)){ if(!this.CanMoveRight()){ return; } diff --git a/front/src/Phaser/UserInput/UserInputManager.ts b/front/src/Phaser/UserInput/UserInputManager.ts new file mode 100644 index 00000000..c362bd95 --- /dev/null +++ b/front/src/Phaser/UserInput/UserInputManager.ts @@ -0,0 +1,63 @@ +import Map = Phaser.Structs.Map; +import {GameSceneInterface} from "../Game/GameScene"; + +interface UserInputManagerDatum { + keyCode: number; + keyInstance: Phaser.Input.Keyboard.Key; + event: UserInputEvent +} + +export enum UserInputEvent { + MoveLeft = 1, + MoveUp, + MoveRight, + MoveDown, + SpeedUp, +} + +//we cannot the map structure so we have to create a replacment +export class ActiveEventList { + private data:any; + constructor() { + this.data = {}; + } + get(event: UserInputEvent): boolean { + return this.data[event] || false; + } + set(event: UserInputEvent, value: boolean): boolean { + return this.data[event] = true; + } +} + +//this class is responsible for catching user inputs and listing all active user actions at every game tick events. +export class UserInputManager { + private data: UserInputManagerDatum[] = [ + {keyCode: Phaser.Input.Keyboard.KeyCodes.Z, event: UserInputEvent.MoveUp, keyInstance: null}, + {keyCode: Phaser.Input.Keyboard.KeyCodes.Q, event: UserInputEvent.MoveLeft, keyInstance: null}, + {keyCode: Phaser.Input.Keyboard.KeyCodes.S, event: UserInputEvent.MoveDown, keyInstance: null}, + {keyCode: Phaser.Input.Keyboard.KeyCodes.D, event: UserInputEvent.MoveRight, keyInstance: null}, + + {keyCode: Phaser.Input.Keyboard.KeyCodes.UP, event: UserInputEvent.MoveUp, keyInstance: null}, + {keyCode: Phaser.Input.Keyboard.KeyCodes.LEFT, event: UserInputEvent.MoveLeft, keyInstance: null}, + {keyCode: Phaser.Input.Keyboard.KeyCodes.DOWN, event: UserInputEvent.MoveDown, keyInstance: null}, + {keyCode: Phaser.Input.Keyboard.KeyCodes.RIGHT, event: UserInputEvent.MoveRight, keyInstance: null}, + + {keyCode: Phaser.Input.Keyboard.KeyCodes.SHIFT, event: UserInputEvent.SpeedUp, keyInstance: null}, + ]; + + constructor(Scene : GameSceneInterface) { + this.data.forEach(d => { + d.keyInstance = Scene.input.keyboard.addKey(d.keyCode); + }); + } + + getEventListForGameTick(): ActiveEventList { + let eventsMap = new ActiveEventList(); + this.data.forEach(d => { + if (d. keyInstance.isDown) { + eventsMap.set(d.event, true); + } + }); + return eventsMap; + } +} \ No newline at end of file From 241cbd720ab5e3062381b16d7d5724cef8406c20 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Sat, 11 Apr 2020 18:17:36 +0200 Subject: [PATCH 20/73] added a rock --- front/dist/resources/objects/rockSprite.png | Bin 0 -> 3659 bytes front/src/Phaser/Game/GameScene.ts | 5 ++++ front/src/Phaser/Game/MapManager.ts | 16 ++++++++-- front/src/Phaser/Rock/Rock.ts | 28 ++++++++++++++++++ .../src/Phaser/UserInput/UserInputManager.ts | 3 ++ front/src/index.ts | 3 ++ 6 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 front/dist/resources/objects/rockSprite.png create mode 100644 front/src/Phaser/Rock/Rock.ts diff --git a/front/dist/resources/objects/rockSprite.png b/front/dist/resources/objects/rockSprite.png new file mode 100644 index 0000000000000000000000000000000000000000..40820abd0ee4564498e779462128d879c48dae1e GIT binary patch literal 3659 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 YM$joE+UD)Q$@RdIgSOVi2i$4@0|kzR_y7O^ literal 0 HcmV?d00001 diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index d3abfd15..faf19616 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1,6 +1,10 @@ import {MapManagerInterface, MapManager} from "./MapManager"; import {GameManagerInterface} from "./GameManager"; +export enum Textures { + Rock = 'rock', +} + export interface GameSceneInterface extends Phaser.Scene { RoomId : string; sharedUserPosition(data : []): void; @@ -18,6 +22,7 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ //hook preload scene preload(): void { + this.load.image(Textures.Rock, 'resources/objects/rockSprite.png'); this.load.image('tiles', 'maps/tiles.png'); this.load.tilemapTiledJSON('map', 'maps/map2.json'); this.load.spritesheet('player', diff --git a/front/src/Phaser/Game/MapManager.ts b/front/src/Phaser/Game/MapManager.ts index 3f8521d4..e6a82dc0 100644 --- a/front/src/Phaser/Game/MapManager.ts +++ b/front/src/Phaser/Game/MapManager.ts @@ -1,8 +1,9 @@ import {CameraManager, CameraManagerInterface} from "./CameraManager"; import {RESOLUTION} from "../../Enum/EnvironmentVariable"; import {Player} from "../Player/Player"; -import {GameScene, GameSceneInterface} from "./GameScene"; -import {UserInputManager} from "../UserInput/UserInputManager"; +import {Rock} from "../Rock/Rock"; +import {GameSceneInterface} from "./GameScene"; +import {UserInputEvent, UserInputManager} from "../UserInput/UserInputManager"; export interface MapManagerInterface { Map: Phaser.Tilemaps.Tilemap; @@ -21,6 +22,7 @@ export class MapManager implements MapManagerInterface{ startX = (window.innerWidth / 2) / RESOLUTION; startY = (window.innerHeight / 2) / RESOLUTION; userInputManager: UserInputManager; + private rock: Rock; constructor(scene: GameSceneInterface){ this.Scene = scene; @@ -44,11 +46,21 @@ export class MapManager implements MapManagerInterface{ this ); this.CurrentPlayer.initAnimation(); + this.rock = new Rock( + this.Scene, + 100, + 300, + ); + //this.rock.set() } update() : void { let activeEvents = this.userInputManager.getEventListForGameTick(); this.CurrentPlayer.move(activeEvents); + + /*if (activeEvents.get(UserInputEvent.Interact)) { + + }*/ } } \ No newline at end of file diff --git a/front/src/Phaser/Rock/Rock.ts b/front/src/Phaser/Rock/Rock.ts new file mode 100644 index 00000000..8892f10d --- /dev/null +++ b/front/src/Phaser/Rock/Rock.ts @@ -0,0 +1,28 @@ +import {GameSceneInterface, Textures} from "../Game/GameScene"; +import {CameraManagerInterface} from "../Game/CameraManager"; +import {MapManagerInterface} from "../Game/MapManager"; + +export class Rock extends Phaser.GameObjects.Image { + private isMoving: boolean; + + constructor( + Scene : GameSceneInterface, + x : number, + y : number, + ) { + super(Scene, x, y, Textures.Rock); + Scene.add.existing(this); + this.isMoving = false; + } + + push() { + console.log("the rock is pushed!") + } + + move() { + if(!this.isMoving) { + return; + } + } + +} \ No newline at end of file diff --git a/front/src/Phaser/UserInput/UserInputManager.ts b/front/src/Phaser/UserInput/UserInputManager.ts index c362bd95..e05eccb2 100644 --- a/front/src/Phaser/UserInput/UserInputManager.ts +++ b/front/src/Phaser/UserInput/UserInputManager.ts @@ -13,6 +13,7 @@ export enum UserInputEvent { MoveRight, MoveDown, SpeedUp, + Interact, } //we cannot the map structure so we have to create a replacment @@ -43,6 +44,8 @@ export class UserInputManager { {keyCode: Phaser.Input.Keyboard.KeyCodes.RIGHT, event: UserInputEvent.MoveRight, keyInstance: null}, {keyCode: Phaser.Input.Keyboard.KeyCodes.SHIFT, event: UserInputEvent.SpeedUp, keyInstance: null}, + + {keyCode: Phaser.Input.Keyboard.KeyCodes.E, event: UserInputEvent.Interact, keyInstance: null}, ]; constructor(Scene : GameSceneInterface) { diff --git a/front/src/index.ts b/front/src/index.ts index 650fd52c..f74c9fa5 100644 --- a/front/src/index.ts +++ b/front/src/index.ts @@ -12,6 +12,9 @@ const config: GameConfig = { parent: "game", scene: gameManager.GameScenes, zoom: RESOLUTION, + physics: { + default: 'impact' + }, }; let game = new Phaser.Game(config); From bbc3935d60e0cbb82e3fefff8b16d4b86ab9ff20 Mon Sep 17 00:00:00 2001 From: gparant Date: Sun, 12 Apr 2020 13:57:00 +0200 Subject: [PATCH 21/73] Fix feedback @kharhamel --- front/src/Connexion.ts | 29 ++++++++++++++++---------- front/src/Phaser/Game/CameraManager.ts | 1 - front/src/Phaser/Game/GameManager.ts | 6 +++--- front/src/Phaser/Game/MapManager.ts | 8 +++---- front/src/Phaser/Player/Player.ts | 24 ++++++++++++++++++--- 5 files changed, 46 insertions(+), 22 deletions(-) diff --git a/front/src/Connexion.ts b/front/src/Connexion.ts index 707d31c4..0d9f998a 100644 --- a/front/src/Connexion.ts +++ b/front/src/Connexion.ts @@ -99,8 +99,18 @@ class ListMessageUserPosition{ }); } } - -export class Connexion { +export interface ConnexionInterface { + socket : any; + token : string; + email : string; + userId: string; + startedRoom : string; + createConnexion() : Promise; + joinARoom(roomId : string) : void; + sharePosition(roomId : string, x : number, y : number, direction : string) : void; + positionOfAllUser() : void; +} +export class Connexion implements ConnexionInterface{ socket : any; token : string; email : string; @@ -114,7 +124,7 @@ export class Connexion { this.GameManager = GameManager; } - createConnexion(){ + createConnexion() : Promise{ return Axios.post(`${API_URL}/login`, {email: this.email}) .then((res) => { this.token = res.data.token; @@ -137,10 +147,7 @@ export class Connexion { this.errorMessage(); - return{ - userId: this.userId, - roomId: this.startedRoom - } + return this; }) .catch((err) => { console.error(err); @@ -152,7 +159,7 @@ export class Connexion { * Permit to join a room * @param roomId */ - joinARoom(roomId : string){ + joinARoom(roomId : string) : void { let messageUserPosition = new MessageUserPosition(this.userId, this.startedRoom, new Point(0, 0)); this.socket.emit('join-room', messageUserPosition.toString()); } @@ -164,7 +171,7 @@ export class Connexion { * @param y * @param direction */ - sharePosition(roomId : string, x : number, y : number, direction : string = "none"){ + sharePosition(roomId : string, x : number, y : number, direction : string = "none") : void{ if(!this.socket){ return; } @@ -187,7 +194,7 @@ export class Connexion { * ... * ] **/ - positionOfAllUser() { + positionOfAllUser() : void { this.socket.on("user-position", (message: string) => { let dataList = JSON.parse(message); dataList.forEach((UserPositions: any) => { @@ -197,7 +204,7 @@ export class Connexion { }); } - errorMessage(){ + errorMessage() : void { this.socket.on('message-error', (message : string) => { console.error("message-error", message); }) diff --git a/front/src/Phaser/Game/CameraManager.ts b/front/src/Phaser/Game/CameraManager.ts index b1585542..a52554e5 100644 --- a/front/src/Phaser/Game/CameraManager.ts +++ b/front/src/Phaser/Game/CameraManager.ts @@ -1,7 +1,6 @@ import {RESOLUTION} from "../../Enum/EnvironmentVariable"; import {Player} from "../Player/Player"; import {MapManagerInterface} from "./MapManager"; -import {PlayerAnimationNames} from "../Player/Animation"; export interface CameraManagerInterface { MapManager : MapManagerInterface; diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts index 2ab1d0d7..71a3cbe3 100644 --- a/front/src/Phaser/Game/GameManager.ts +++ b/front/src/Phaser/Game/GameManager.ts @@ -1,13 +1,13 @@ import {GameSceneInterface, GameScene} from "./GameScene"; import {ROOM} from "../../Enum/EnvironmentVariable" -import {Connexion, ListMessageUserPositionInterface} from "../../Connexion"; +import {Connexion, ConnexionInterface, ListMessageUserPositionInterface} from "../../Connexion"; export enum StatusGameManagerEnum { IN_PROGRESS = 1, CURRENT_USER_CREATED = 2 } -export let ConnexionInstance : Connexion; +export let ConnexionInstance : ConnexionInterface; export interface GameManagerInterface { GameScenes: Array; @@ -25,7 +25,7 @@ export class GameManager implements GameManagerInterface { } createGame(){ - return ConnexionInstance.createConnexion().then((data: any) => { + return ConnexionInstance.createConnexion().then(() => { this.configureGame(); /** TODO add loader in the page **/ }).catch((err) => { diff --git a/front/src/Phaser/Game/MapManager.ts b/front/src/Phaser/Game/MapManager.ts index 99ce2863..a66e0d37 100644 --- a/front/src/Phaser/Game/MapManager.ts +++ b/front/src/Phaser/Game/MapManager.ts @@ -1,6 +1,6 @@ import {CameraManager, CameraManagerInterface} from "./CameraManager"; import {RESOLUTION} from "../../Enum/EnvironmentVariable"; -import {Player} from "../Player/Player"; +import {CurrentGamerInterface, GamerInterface, Player} from "../Player/Player"; import {GameSceneInterface} from "./GameScene"; import {MessageUserPositionInterface} from "../../Connexion"; @@ -37,8 +37,8 @@ export class MapManager implements MapManagerInterface{ Terrain : Phaser.Tilemaps.Tileset; Camera: CameraManagerInterface; - CurrentPlayer: Player; - MapPlayers : Player[]; + CurrentPlayer: CurrentGamerInterface; + MapPlayers : GamerInterface[]; Scene: GameSceneInterface; Map: Phaser.Tilemaps.Tilemap; startX = (window.innerWidth / 2) / RESOLUTION; @@ -59,7 +59,7 @@ export class MapManager implements MapManagerInterface{ //initialise camera this.Camera = new CameraManager(this.Scene, this.Scene.cameras.main, this); //initialise list of other player - this.MapPlayers = new Array(); + this.MapPlayers = new Array(); } createCurrentPlayer(UserId : string){ diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index 74db636d..f256bbf4 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -5,7 +5,25 @@ import {ConnexionInstance} from "../Game/GameManager"; import {CameraManagerInterface} from "../Game/CameraManager"; import {MessageUserPositionInterface} from "../../Connexion"; -export class Player extends Phaser.GameObjects.Sprite{ +export interface CurrentGamerInterface{ + userId : string; + MapManager : MapManagerInterface; + PlayerValue : string; + CameraManager: CameraManagerInterface; + initAnimation() : void; + move() : void; +} + +export interface GamerInterface{ + userId : string; + MapManager : MapManagerInterface; + PlayerValue : string; + CameraManager: CameraManagerInterface; + initAnimation() : void; + updatePosition(MessageUserPosition : MessageUserPositionInterface) : void; +} + +export class Player extends Phaser.GameObjects.Sprite implements CurrentGamerInterface, GamerInterface{ userId : string; MapManager : MapManagerInterface; PlayerValue : string; @@ -28,7 +46,7 @@ export class Player extends Phaser.GameObjects.Sprite{ this.CameraManager = CameraManager; } - initAnimation(){ + initAnimation() : void{ getPlayerAnimations(this.PlayerValue).forEach(d => { this.scene.anims.create({ key: d.key, @@ -39,7 +57,7 @@ export class Player extends Phaser.GameObjects.Sprite{ }) } - move(){ + move() : void{ //if user client on shift, camera and player speed let speedMultiplier = this.MapManager.keyShift.isDown ? 5 : 1; let haveMove = false; From 62dd34b5912d1006fcf36ffd069de30262dd7c70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Sun, 12 Apr 2020 15:50:14 +0200 Subject: [PATCH 22/73] Triggerring CI rerun --- back/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/back/Dockerfile b/back/Dockerfile index 316f505b..8b1b8f61 100644 --- a/back/Dockerfile +++ b/back/Dockerfile @@ -6,3 +6,4 @@ RUN yarn install ENV NODE_ENV=production CMD ["yarn", "run", "prod"] + From 6e27377b07f31c0e62f3b69f129bcba99f5b51e6 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Sun, 12 Apr 2020 16:12:08 +0200 Subject: [PATCH 23/73] rewrote the app code to more easily allow for collisions --- front/dist/maps/map2.json | 6 ++ front/src/Phaser/Entity/PlayableCaracter.ts | 16 +++ front/src/Phaser/Entity/Sprite.ts | 8 ++ front/src/Phaser/Game/CameraManager.ts | 54 ---------- front/src/Phaser/Game/GameScene.ts | 76 +++++++++++-- front/src/Phaser/Game/MapManager.ts | 66 ------------ front/src/Phaser/Player/Animation.ts | 32 +++--- front/src/Phaser/Player/Player.ts | 114 ++++---------------- front/src/Phaser/Rock/Rock.ts | 28 ----- front/src/index.ts | 7 +- 10 files changed, 139 insertions(+), 268 deletions(-) create mode 100644 front/src/Phaser/Entity/PlayableCaracter.ts create mode 100644 front/src/Phaser/Entity/Sprite.ts delete mode 100644 front/src/Phaser/Game/CameraManager.ts delete mode 100644 front/src/Phaser/Game/MapManager.ts delete mode 100644 front/src/Phaser/Rock/Rock.ts diff --git a/front/dist/maps/map2.json b/front/dist/maps/map2.json index 9a10e634..d68f4df9 100644 --- a/front/dist/maps/map2.json +++ b/front/dist/maps/map2.json @@ -155,6 +155,12 @@ }, { "id":77, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }], "terrain":[2, 2, 2, 2] }, { diff --git a/front/src/Phaser/Entity/PlayableCaracter.ts b/front/src/Phaser/Entity/PlayableCaracter.ts new file mode 100644 index 00000000..8456ed44 --- /dev/null +++ b/front/src/Phaser/Entity/PlayableCaracter.ts @@ -0,0 +1,16 @@ +import {getPlayerAnimations, playAnimation, PlayerAnimationNames} from "../Player/Animation"; +import {ActiveEventList, UserInputEvent} from "../UserInput/UserInputManager"; + +export class PlayableCaracter extends Phaser.Physics.Arcade.Sprite { + + constructor(scene: Phaser.Scene, x: number, y: number, texture: string, frame?: string | number) { + super(scene, x, y, texture, frame); + + scene.sys.updateList.add(this); + scene.sys.displayList.add(this); + this.setScale(2); + scene.physics.world.enableBody(this); + this.setImmovable(true); + this.setCollideWorldBounds(true) + } +} \ No newline at end of file diff --git a/front/src/Phaser/Entity/Sprite.ts b/front/src/Phaser/Entity/Sprite.ts new file mode 100644 index 00000000..f2abad52 --- /dev/null +++ b/front/src/Phaser/Entity/Sprite.ts @@ -0,0 +1,8 @@ +export class Sprite extends Phaser.GameObjects.Sprite { + + constructor(scene: Phaser.Scene, x: number, y: number, texture: string, frame?: number | string) { + super(scene, x, y, texture, frame); + scene.sys.updateList.add(this); + scene.sys.displayList.add(this); + } +} \ No newline at end of file diff --git a/front/src/Phaser/Game/CameraManager.ts b/front/src/Phaser/Game/CameraManager.ts deleted file mode 100644 index b1585542..00000000 --- a/front/src/Phaser/Game/CameraManager.ts +++ /dev/null @@ -1,54 +0,0 @@ -import {RESOLUTION} from "../../Enum/EnvironmentVariable"; -import {Player} from "../Player/Player"; -import {MapManagerInterface} from "./MapManager"; -import {PlayerAnimationNames} from "../Player/Animation"; - -export interface CameraManagerInterface { - MapManager : MapManagerInterface; - moveCamera(CurrentPlayer : Player) : void; -} - -export class CameraManager implements CameraManagerInterface{ - Scene : Phaser.Scene; - Camera : Phaser.Cameras.Scene2D.Camera; - MapManager : MapManagerInterface; - - constructor( - Scene: Phaser.Scene, - Camera : Phaser.Cameras.Scene2D.Camera, - MapManager: MapManagerInterface, - ) { - this.Scene = Scene; - this.MapManager = MapManager; - this.Camera = Camera; - } - - moveCamera(CurrentPlayer : Player): void { - //center of camera - let startX = ((window.innerWidth / 2) / RESOLUTION); - let startY = ((window.innerHeight / 2) / RESOLUTION); - - let limit = { - top: startY, - left: startX, - bottom : this.MapManager.Map.heightInPixels - startY, - right: this.MapManager.Map.widthInPixels - startX, - }; - - if(CurrentPlayer.x < limit.left){ - this.Camera.scrollX = 0; - }else if(CurrentPlayer.x > limit.right){ - this.Camera.scrollX = (limit.right - startX); - }else { - this.Camera.scrollX = (CurrentPlayer.x - startX); - } - - if(CurrentPlayer.y < limit.top){ - this.Camera.scrollY = 0; - }else if(CurrentPlayer.y > limit.bottom){ - this.Camera.scrollY = (limit.bottom - startY); - }else { - this.Camera.scrollY = (CurrentPlayer.y - startY); - } - } -} \ No newline at end of file diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index faf19616..3d02cf72 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1,8 +1,13 @@ -import {MapManagerInterface, MapManager} from "./MapManager"; import {GameManagerInterface} from "./GameManager"; +import {UserInputManager} from "../UserInput/UserInputManager"; +import {getPlayerAnimations, PlayerAnimationNames} from "../Player/Animation"; +import {Player} from "../Player/Player"; export enum Textures { Rock = 'rock', + Player = 'player', + Map = 'map', + Tiles = 'tiles' } export interface GameSceneInterface extends Phaser.Scene { @@ -10,8 +15,11 @@ export interface GameSceneInterface extends Phaser.Scene { sharedUserPosition(data : []): void; } export class GameScene extends Phaser.Scene implements GameSceneInterface{ - private MapManager : MapManagerInterface; + //private MapManager : MapManagerInterface; RoomId : string; + private player: Player; + private rock: Phaser.Physics.Arcade.Sprite; + private userInputManager: UserInputManager; constructor(RoomId : string, GameManager : GameManagerInterface) { super({ @@ -23,26 +31,72 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ //hook preload scene preload(): void { this.load.image(Textures.Rock, 'resources/objects/rockSprite.png'); - this.load.image('tiles', 'maps/tiles.png'); - this.load.tilemapTiledJSON('map', 'maps/map2.json'); - this.load.spritesheet('player', + this.load.image(Textures.Tiles, 'maps/tiles.png'); + this.load.tilemapTiledJSON(Textures.Map, 'maps/map2.json'); + this.load.spritesheet(Textures.Player, 'resources/characters/pipoya/Male 01-1.png', { frameWidth: 32, frameHeight: 32 } ); - } - //hook initialisation - init(){} + getPlayerAnimations().forEach(d => { + this.anims.create({ + key: d.key, + frames: this.anims.generateFrameNumbers(d.frameModel, { start: d.frameStart, end: d.frameEnd }), + frameRate: d.frameRate, + //repeat: d.repeat + }); + }); + } //hook create scene create(): void { - //create map manager - this.MapManager = new MapManager(this); + this.userInputManager = new UserInputManager(this); + + //create entities + this.player = new Player(this, 400, 400); + this.rock = this.physics.add.sprite(200, 400, Textures.Rock, 26).setImmovable(true); + this.physics.world.addCollider(this.player, this.rock, (player: Player, rock) => { + rock.destroy(); + }); + + //create map + let currentMap = this.add.tilemap(Textures.Map); + let terrain = currentMap.addTilesetImage(Textures.Tiles, "tiles"); + let bottomLayer = currentMap.createStaticLayer("Calque 1", [terrain], 0, 0).setDepth(-1); + let topLayer = currentMap.createStaticLayer("Calque 2", [terrain], 0, 0); + this.physics.world.setBounds(0,0, currentMap.widthInPixels, currentMap.heightInPixels); + + this.physics.add.collider(this.player, topLayer); + topLayer.setCollisionByProperty({collides:true}); + + + this.cameras.main.startFollow(this.player); + + + + //debug code + //debug code to see the collision hitbox of the object in the top layer + topLayer.renderDebug(this.add.graphics(),{ + tileColor: null, //non-colliding tiles + collidingTileColor: new Phaser.Display.Color(243, 134, 48, 200), // Colliding tiles, + faceColor: new Phaser.Display.Color(40, 39, 37, 255) // Colliding face edges + }) + + // debug code to get a tile properties by clicking on it + this.input.on("pointerdown", (pointer: Phaser.Input.Pointer)=>{ + //pixel position to tile position + let tile = currentMap.getTileAt(currentMap.worldToTileX(pointer.worldX), currentMap.worldToTileY(pointer.worldY)); + if(tile){ + console.log(tile); + } + }); } //hook update update(dt: number): void { - this.MapManager.update(); + let eventList = this.userInputManager.getEventListForGameTick(); + + this.player.move(eventList); } sharedUserPosition(data: []): void { diff --git a/front/src/Phaser/Game/MapManager.ts b/front/src/Phaser/Game/MapManager.ts deleted file mode 100644 index e6a82dc0..00000000 --- a/front/src/Phaser/Game/MapManager.ts +++ /dev/null @@ -1,66 +0,0 @@ -import {CameraManager, CameraManagerInterface} from "./CameraManager"; -import {RESOLUTION} from "../../Enum/EnvironmentVariable"; -import {Player} from "../Player/Player"; -import {Rock} from "../Rock/Rock"; -import {GameSceneInterface} from "./GameScene"; -import {UserInputEvent, UserInputManager} from "../UserInput/UserInputManager"; - -export interface MapManagerInterface { - Map: Phaser.Tilemaps.Tilemap; - Terrain: Phaser.Tilemaps.Tileset; - Camera: CameraManagerInterface; - Scene: GameSceneInterface; - userInputManager: UserInputManager; - update(): void; -} -export class MapManager implements MapManagerInterface{ - Terrain : Phaser.Tilemaps.Tileset; - Camera: CameraManagerInterface; - CurrentPlayer: Player; - Scene: GameSceneInterface; - Map: Phaser.Tilemaps.Tilemap; - startX = (window.innerWidth / 2) / RESOLUTION; - startY = (window.innerHeight / 2) / RESOLUTION; - userInputManager: UserInputManager; - private rock: Rock; - - constructor(scene: GameSceneInterface){ - this.Scene = scene; - - //initalise map - this.Map = this.Scene.add.tilemap("map"); - this.Terrain = this.Map.addTilesetImage("tiles", "tiles"); - this.Map.createStaticLayer("tiles", "tiles"); - this.Map.createStaticLayer("Calque 1", [this.Terrain], 0, 0); - this.Map.createStaticLayer("Calque 2", [this.Terrain], 0, 0); - - //initialise camera - this.Camera = new CameraManager(this.Scene, this.Scene.cameras.main, this); - this.userInputManager = new UserInputManager(this.Scene); - //initialise player - this.CurrentPlayer = new Player( - this.Scene, - this.startX, - this.startY, - this.Camera, - this - ); - this.CurrentPlayer.initAnimation(); - this.rock = new Rock( - this.Scene, - 100, - 300, - ); - //this.rock.set() - } - - update() : void { - let activeEvents = this.userInputManager.getEventListForGameTick(); - - this.CurrentPlayer.move(activeEvents); - - /*if (activeEvents.get(UserInputEvent.Interact)) { - - }*/ - } -} \ No newline at end of file diff --git a/front/src/Phaser/Player/Animation.ts b/front/src/Phaser/Player/Animation.ts index eb8298f4..48dc3ac9 100644 --- a/front/src/Phaser/Player/Animation.ts +++ b/front/src/Phaser/Player/Animation.ts @@ -1,7 +1,10 @@ +import {Textures} from "../Game/GameScene"; +import {PlayableCaracter} from "../Entity/PlayableCaracter"; + interface AnimationData { key: string; frameRate: number; - repeat: number; + //repeat: number; frameModel: string; //todo use an enum frameStart: number; frameEnd: number; @@ -15,42 +18,39 @@ export enum PlayerAnimationNames { None = 'none', } -export const getPlayerAnimations = (PlayerValue : string): AnimationData[] => { +export const getPlayerAnimations = (): AnimationData[] => { return [{ key: PlayerAnimationNames.WalkDown, - frameModel: PlayerValue, + frameModel: Textures.Player, frameStart: 0, frameEnd: 2, frameRate: 10, - repeat: -1 + //repeat: -1 }, { key: PlayerAnimationNames.WalkLeft, - frameModel: PlayerValue, + frameModel: Textures.Player, frameStart: 3, frameEnd: 5, frameRate: 10, - repeat: -1 + //repeat: -1 }, { key: PlayerAnimationNames.WalkRight, - frameModel: PlayerValue, + frameModel: Textures.Player, frameStart: 6, frameEnd: 8, frameRate: 10, - repeat: -1 + //repeat: -1 }, { key: PlayerAnimationNames.WalkUp, - frameModel: PlayerValue, + frameModel: Textures.Player, frameStart: 9, frameEnd: 11, frameRate: 10, - repeat: -1 + //repeat: -1 }]; }; -export const playAnimation = (Player : Phaser.GameObjects.Sprite, direction : string) => { - if (!Player.anims.currentAnim || Player.anims.currentAnim.key !== direction) { - Player.anims.play(direction); - } else if (direction === PlayerAnimationNames.None && Player.anims.currentAnim) { - Player.anims.currentAnim.destroy(); - } +export const playAnimation = (Player : PlayableCaracter, direction : string) => { + //if (direction === 'none') return; + //Player.play(direction, true); }; diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index 11b3d4f6..ed85f5b4 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -1,112 +1,44 @@ -import {MapManagerInterface} from "../Game/MapManager"; import {getPlayerAnimations, playAnimation, PlayerAnimationNames} from "./Animation"; -import {GameSceneInterface} from "../Game/GameScene"; +import {GameSceneInterface, Textures} from "../Game/GameScene"; import {ConnexionInstance} from "../Game/GameManager"; -import {CameraManagerInterface} from "../Game/CameraManager"; import {ActiveEventList, UserInputEvent} from "../UserInput/UserInputManager"; +import {PlayableCaracter} from "../Entity/PlayableCaracter"; -export class Player extends Phaser.GameObjects.Sprite{ - MapManager : MapManagerInterface; - PlayerValue : string; - CameraManager: CameraManagerInterface; - - constructor( - Scene : GameSceneInterface, - x : number, - y : number, - CameraManager: CameraManagerInterface, - MapManager: MapManagerInterface, - PlayerValue : string = "player" - ) { - super(Scene, x, y, PlayerValue); - this.PlayerValue = PlayerValue; - Scene.add.existing(this); - this.MapManager = MapManager; - this.CameraManager = CameraManager; - } - - - initAnimation(){ - getPlayerAnimations(this.PlayerValue).forEach(d => { - this.scene.anims.create({ - key: d.key, - frames: this.scene.anims.generateFrameNumbers(d.frameModel, { start: d.frameStart, end: d.frameEnd }), - frameRate: d.frameRate, - repeat: d.repeat - }); - }) +export class Player extends PlayableCaracter{ + + constructor(scene: Phaser.Scene, x: number, y: number) { + super(scene, x, y, Textures.Player, 26); + this.setSize(32, 32); //edit the hitbox to better match the caracter model } move(activeEvents: ActiveEventList){ - //if user client on shift, camera and player speed - let speedMultiplier = activeEvents.get(UserInputEvent.SpeedUp) ? 5 : 1; + let speed = activeEvents.get(UserInputEvent.SpeedUp) ? 500 : 100; let haveMove = false; let direction = null; if(activeEvents.get(UserInputEvent.MoveUp)){ - if(!this.CanMoveUp()){ - return; - } + this.setVelocity(0, -speed) playAnimation(this, PlayerAnimationNames.WalkUp); - this.setY(this.y - (2 * speedMultiplier)); - haveMove = true; - direction = PlayerAnimationNames.WalkUp; - } - if(activeEvents.get(UserInputEvent.MoveLeft)){ - if(!this.CanMoveLeft()){ - return; - } - playAnimation(this, PlayerAnimationNames.WalkLeft); - this.setX(this.x - (2 * speedMultiplier)); - haveMove = true; - direction = PlayerAnimationNames.WalkLeft; - } - if(activeEvents.get(UserInputEvent.MoveDown)){ - if(!this.CanMoveDown()){ - return; - } + } else if(activeEvents.get(UserInputEvent.MoveLeft)){ + this.setVelocity(-speed, 0) + } else if(activeEvents.get(UserInputEvent.MoveDown)){ playAnimation(this, PlayerAnimationNames.WalkDown); - this.setY(this.y + (2 * speedMultiplier)); - haveMove = true; - direction = PlayerAnimationNames.WalkDown; - } - if(activeEvents.get(UserInputEvent.MoveRight)){ - if(!this.CanMoveRight()){ - return; - } - playAnimation(this, PlayerAnimationNames.WalkRight); - this.setX(this.x + (2 * speedMultiplier)); - haveMove = true; - direction = PlayerAnimationNames.WalkRight; - } - if(!haveMove){ + this.setVelocity(0, speed) + } else if(activeEvents.get(UserInputEvent.MoveRight)){ + this.setVelocity(speed, 0) + } else { + this.setVelocity(0, 0) playAnimation(this, PlayerAnimationNames.None); - }else{ - this.sharePosition(direction); } - - this.CameraManager.moveCamera(this); } - private sharePosition(direction : string){ + stop() { + this.setVelocity(0, 0) + } + + /*private sharePosition(direction : string){ if(ConnexionInstance) { ConnexionInstance.sharePosition((this.scene as GameSceneInterface).RoomId, this.x, this.y, direction); } - } - - private CanMoveUp(){ - return this.y > 0; - } - - private CanMoveLeft(){ - return this.x > 0; - } - - private CanMoveDown(){ - return this.MapManager.Map.heightInPixels > this.y; - } - - private CanMoveRight(){ - return this.MapManager.Map.widthInPixels > this.x; - } + }*/ } \ No newline at end of file diff --git a/front/src/Phaser/Rock/Rock.ts b/front/src/Phaser/Rock/Rock.ts deleted file mode 100644 index 8892f10d..00000000 --- a/front/src/Phaser/Rock/Rock.ts +++ /dev/null @@ -1,28 +0,0 @@ -import {GameSceneInterface, Textures} from "../Game/GameScene"; -import {CameraManagerInterface} from "../Game/CameraManager"; -import {MapManagerInterface} from "../Game/MapManager"; - -export class Rock extends Phaser.GameObjects.Image { - private isMoving: boolean; - - constructor( - Scene : GameSceneInterface, - x : number, - y : number, - ) { - super(Scene, x, y, Textures.Rock); - Scene.add.existing(this); - this.isMoving = false; - } - - push() { - console.log("the rock is pushed!") - } - - move() { - if(!this.isMoving) { - return; - } - } - -} \ No newline at end of file diff --git a/front/src/index.ts b/front/src/index.ts index f74c9fa5..a366078a 100644 --- a/front/src/index.ts +++ b/front/src/index.ts @@ -13,8 +13,11 @@ const config: GameConfig = { scene: gameManager.GameScenes, zoom: RESOLUTION, physics: { - default: 'impact' - }, + default: "arcade", + arcade: { + debug: true + } + } }; let game = new Phaser.Game(config); From 2b2b615e7bb2094d7a25f563228da0bb18d9457e Mon Sep 17 00:00:00 2001 From: kharhamel Date: Sun, 12 Apr 2020 17:08:28 +0200 Subject: [PATCH 24/73] added other players models and fixed collision with other entities --- front/src/Phaser/Game/GameScene.ts | 12 +++++++++--- front/src/Phaser/NonPlayer/NonPlayer.ts | 10 ++++++++++ front/src/Phaser/Player/Player.ts | 1 + 3 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 front/src/Phaser/NonPlayer/NonPlayer.ts diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 3d02cf72..545146b1 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -2,6 +2,7 @@ import {GameManagerInterface} from "./GameManager"; import {UserInputManager} from "../UserInput/UserInputManager"; import {getPlayerAnimations, PlayerAnimationNames} from "../Player/Animation"; import {Player} from "../Player/Player"; +import {NonPlayer} from "../NonPlayer/NonPlayer"; export enum Textures { Rock = 'rock', @@ -20,6 +21,7 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ private player: Player; private rock: Phaser.Physics.Arcade.Sprite; private userInputManager: UserInputManager; + private otherPlayers: Phaser.Physics.Arcade.Group; constructor(RoomId : string, GameManager : GameManagerInterface) { super({ @@ -55,9 +57,13 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ //create entities this.player = new Player(this, 400, 400); this.rock = this.physics.add.sprite(200, 400, Textures.Rock, 26).setImmovable(true); - this.physics.world.addCollider(this.player, this.rock, (player: Player, rock) => { - rock.destroy(); - }); + this.physics.add.collider(this.player, this.rock); + + this.otherPlayers = this.physics.add.group({ immovable: true }); + this.otherPlayers.add(new NonPlayer(this, 200, 600)); + this.otherPlayers.add(new NonPlayer(this, 400, 600)); + + this.physics.add.collider(this.player, this.otherPlayers); //create map let currentMap = this.add.tilemap(Textures.Map); diff --git a/front/src/Phaser/NonPlayer/NonPlayer.ts b/front/src/Phaser/NonPlayer/NonPlayer.ts new file mode 100644 index 00000000..d9b22001 --- /dev/null +++ b/front/src/Phaser/NonPlayer/NonPlayer.ts @@ -0,0 +1,10 @@ +import {PlayableCaracter} from "../Entity/PlayableCaracter"; +import {Textures} from "../Game/GameScene"; + +export class NonPlayer extends PlayableCaracter { + + constructor(scene: Phaser.Scene, x: number, y: number) { + super(scene, x, y, Textures.Player, 26); + this.setSize(32, 32); //edit the hitbox to better match the caracter model + } +} \ No newline at end of file diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index ed85f5b4..f6079ac8 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -8,6 +8,7 @@ export class Player extends PlayableCaracter{ constructor(scene: Phaser.Scene, x: number, y: number) { super(scene, x, y, Textures.Player, 26); + this.setImmovable(false); this.setSize(32, 32); //edit the hitbox to better match the caracter model } From d1106d757daba6b0e85a1ac3c22e97bacb6f1d7c Mon Sep 17 00:00:00 2001 From: kharhamel Date: Sun, 12 Apr 2020 17:13:33 +0200 Subject: [PATCH 25/73] made the player pushable by other models --- front/src/Phaser/Game/GameScene.ts | 4 ++++ front/src/Phaser/Player/Player.ts | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 545146b1..dac1f2fc 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -103,6 +103,10 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ let eventList = this.userInputManager.getEventListForGameTick(); this.player.move(eventList); + + this.otherPlayers.getChildren().forEach((otherPlayer: NonPlayer) => { + otherPlayer.setVelocity(20, 5); + }) } sharedUserPosition(data: []): void { diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index f6079ac8..e4395b3d 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -8,7 +8,7 @@ export class Player extends PlayableCaracter{ constructor(scene: Phaser.Scene, x: number, y: number) { super(scene, x, y, Textures.Player, 26); - this.setImmovable(false); + this.setImmovable(false); //the current player model should be push away by other players to prevent conflict this.setSize(32, 32); //edit the hitbox to better match the caracter model } From 97a55ab66cd61a3b875fa4ef24bdb98d8d9fd677 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Sun, 12 Apr 2020 18:28:05 +0200 Subject: [PATCH 26/73] fixed the player animations --- front/src/Phaser/Game/GameScene.ts | 16 +++++++++------- front/src/Phaser/NonPlayer/NonPlayer.ts | 2 +- front/src/Phaser/Player/Player.ts | 21 +++++++++++---------- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index dac1f2fc..648e6579 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -6,7 +6,7 @@ import {NonPlayer} from "../NonPlayer/NonPlayer"; export enum Textures { Rock = 'rock', - Player = 'player', + Player = 'playerModel', Map = 'map', Tiles = 'tiles' } @@ -39,7 +39,11 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ 'resources/characters/pipoya/Male 01-1.png', { frameWidth: 32, frameHeight: 32 } ); + } + //hook create scene + create(): void { + //anims cannot be in preload because it needs to wait to the sprit to be loaded getPlayerAnimations().forEach(d => { this.anims.create({ key: d.key, @@ -48,10 +52,7 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ //repeat: d.repeat }); }); - } - - //hook create scene - create(): void { + this.userInputManager = new UserInputManager(this); //create entities @@ -68,8 +69,8 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ //create map let currentMap = this.add.tilemap(Textures.Map); let terrain = currentMap.addTilesetImage(Textures.Tiles, "tiles"); - let bottomLayer = currentMap.createStaticLayer("Calque 1", [terrain], 0, 0).setDepth(-1); - let topLayer = currentMap.createStaticLayer("Calque 2", [terrain], 0, 0); + let bottomLayer = currentMap.createStaticLayer("Calque 1", [terrain], 0, 0).setDepth(-2); + let topLayer = currentMap.createStaticLayer("Calque 2", [terrain], 0, 0).setDepth(-1); this.physics.world.setBounds(0,0, currentMap.widthInPixels, currentMap.heightInPixels); this.physics.add.collider(this.player, topLayer); @@ -105,6 +106,7 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ this.player.move(eventList); this.otherPlayers.getChildren().forEach((otherPlayer: NonPlayer) => { + //this.physics.accelerateToObject(otherPlayer, this.player); //this line make the models chase the player otherPlayer.setVelocity(20, 5); }) } diff --git a/front/src/Phaser/NonPlayer/NonPlayer.ts b/front/src/Phaser/NonPlayer/NonPlayer.ts index d9b22001..46fa6450 100644 --- a/front/src/Phaser/NonPlayer/NonPlayer.ts +++ b/front/src/Phaser/NonPlayer/NonPlayer.ts @@ -4,7 +4,7 @@ import {Textures} from "../Game/GameScene"; export class NonPlayer extends PlayableCaracter { constructor(scene: Phaser.Scene, x: number, y: number) { - super(scene, x, y, Textures.Player, 26); + super(scene, x, y, Textures.Player, 1); this.setSize(32, 32); //edit the hitbox to better match the caracter model } } \ No newline at end of file diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index e4395b3d..ff2278f5 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -7,7 +7,7 @@ import {PlayableCaracter} from "../Entity/PlayableCaracter"; export class Player extends PlayableCaracter{ constructor(scene: Phaser.Scene, x: number, y: number) { - super(scene, x, y, Textures.Player, 26); + super(scene, x, y, Textures.Player, 1); this.setImmovable(false); //the current player model should be push away by other players to prevent conflict this.setSize(32, 32); //edit the hitbox to better match the caracter model } @@ -19,27 +19,28 @@ export class Player extends PlayableCaracter{ if(activeEvents.get(UserInputEvent.MoveUp)){ this.setVelocity(0, -speed) - playAnimation(this, PlayerAnimationNames.WalkUp); } else if(activeEvents.get(UserInputEvent.MoveLeft)){ this.setVelocity(-speed, 0) } else if(activeEvents.get(UserInputEvent.MoveDown)){ - playAnimation(this, PlayerAnimationNames.WalkDown); this.setVelocity(0, speed) } else if(activeEvents.get(UserInputEvent.MoveRight)){ this.setVelocity(speed, 0) } else { this.setVelocity(0, 0) - playAnimation(this, PlayerAnimationNames.None); + } + + if (this.body.velocity.x > 0) { //moving right + this.play("right", true); + } else if (this.body.velocity.x < 0) { //moving left + this.anims.playReverse("left", true); + } else if (this.body.velocity.y < 0) { //moving up + this.play("up", true); + } else if (this.body.velocity.y > 0) { //moving down + this.play("down", true); } } stop() { this.setVelocity(0, 0) } - - /*private sharePosition(direction : string){ - if(ConnexionInstance) { - ConnexionInstance.sharePosition((this.scene as GameSceneInterface).RoomId, this.x, this.y, direction); - } - }*/ } \ No newline at end of file From 05379c800190adabddda3003ba65630a8c21cdf3 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Sun, 12 Apr 2020 19:06:31 +0200 Subject: [PATCH 27/73] the other playes now run away from the player on contact --- front/src/Phaser/Entity/PlayableCaracter.ts | 16 +++++++++++ front/src/Phaser/Game/GameScene.ts | 31 +++++++++++++++++---- front/src/Phaser/NonPlayer/NonPlayer.ts | 20 +++++++++++++ front/src/Phaser/Player/Player.ts | 28 ------------------- 4 files changed, 62 insertions(+), 33 deletions(-) diff --git a/front/src/Phaser/Entity/PlayableCaracter.ts b/front/src/Phaser/Entity/PlayableCaracter.ts index 8456ed44..789b2475 100644 --- a/front/src/Phaser/Entity/PlayableCaracter.ts +++ b/front/src/Phaser/Entity/PlayableCaracter.ts @@ -13,4 +13,20 @@ export class PlayableCaracter extends Phaser.Physics.Arcade.Sprite { this.setImmovable(true); this.setCollideWorldBounds(true) } + + move(x: number, y: number){ + + this.setVelocity(x, y); + + //todo improve animations to better account for diagonal movement + if (this.body.velocity.x > 0) { //moving right + this.play(PlayerAnimationNames.WalkRight, true); + } else if (this.body.velocity.x < 0) { //moving left + this.anims.playReverse(PlayerAnimationNames.WalkLeft, true); + } else if (this.body.velocity.y < 0) { //moving up + this.play(PlayerAnimationNames.WalkUp, true); + } else if (this.body.velocity.y > 0) { //moving down + this.play(PlayerAnimationNames.WalkDown, true); + } + } } \ No newline at end of file diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 648e6579..112e452c 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1,5 +1,5 @@ import {GameManagerInterface} from "./GameManager"; -import {UserInputManager} from "../UserInput/UserInputManager"; +import {UserInputEvent, UserInputManager} from "../UserInput/UserInputManager"; import {getPlayerAnimations, PlayerAnimationNames} from "../Player/Animation"; import {Player} from "../Player/Player"; import {NonPlayer} from "../NonPlayer/NonPlayer"; @@ -64,7 +64,10 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ this.otherPlayers.add(new NonPlayer(this, 200, 600)); this.otherPlayers.add(new NonPlayer(this, 400, 600)); - this.physics.add.collider(this.player, this.otherPlayers); + this.physics.add.collider(this.player, this.otherPlayers, (player: Player, otherPlayer: NonPlayer) => { + console.log("Don't touch me!"); + otherPlayer.fleeFrom(player) + }); //create map let currentMap = this.add.tilemap(Textures.Map); @@ -101,13 +104,31 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ //hook update update(dt: number): void { - let eventList = this.userInputManager.getEventListForGameTick(); + //user inputs + let activeEvents = this.userInputManager.getEventListForGameTick(); + let speed = activeEvents.get(UserInputEvent.SpeedUp) ? 500 : 100; + + if(activeEvents.get(UserInputEvent.MoveUp)){ + this.player.move(0, -speed) + } else if(activeEvents.get(UserInputEvent.MoveLeft)){ + this.player.move(-speed, 0) + } else if(activeEvents.get(UserInputEvent.MoveDown)){ + this.player.move(0, speed) + } else if(activeEvents.get(UserInputEvent.MoveRight)){ + this.player.move(speed, 0) + } else { + this.player.move(0, 0) + } - this.player.move(eventList); + //updates other players this.otherPlayers.getChildren().forEach((otherPlayer: NonPlayer) => { //this.physics.accelerateToObject(otherPlayer, this.player); //this line make the models chase the player - otherPlayer.setVelocity(20, 5); + if (otherPlayer.isFleeing) { + otherPlayer.move(otherPlayer.fleeingDirection.x, otherPlayer.fleeingDirection.y); + } else { + otherPlayer.move(0, 0); + } }) } diff --git a/front/src/Phaser/NonPlayer/NonPlayer.ts b/front/src/Phaser/NonPlayer/NonPlayer.ts index 46fa6450..91b83ba3 100644 --- a/front/src/Phaser/NonPlayer/NonPlayer.ts +++ b/front/src/Phaser/NonPlayer/NonPlayer.ts @@ -1,10 +1,30 @@ import {PlayableCaracter} from "../Entity/PlayableCaracter"; import {Textures} from "../Game/GameScene"; +import {UserInputEvent} from "../UserInput/UserInputManager"; +import {Player} from "../Player/Player"; export class NonPlayer extends PlayableCaracter { + isFleeing: boolean = false; + fleeingDirection:any = null //todo create a vector class + constructor(scene: Phaser.Scene, x: number, y: number) { super(scene, x, y, Textures.Player, 1); this.setSize(32, 32); //edit the hitbox to better match the caracter model } + + fleeFrom(player:Player) { + if (this.isFleeing) return; + this.isFleeing = true; + + setTimeout(() => { + console.log("I escaped"); + this.isFleeing = false + this.fleeingDirection = null + }, 3000); + + let vectorX = this.x - player.x; + let vectorY = this.y - player.y; + this.fleeingDirection = {x: vectorX, y: vectorY} + } } \ No newline at end of file diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index ff2278f5..8c9b462b 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -12,34 +12,6 @@ export class Player extends PlayableCaracter{ this.setSize(32, 32); //edit the hitbox to better match the caracter model } - move(activeEvents: ActiveEventList){ - let speed = activeEvents.get(UserInputEvent.SpeedUp) ? 500 : 100; - let haveMove = false; - let direction = null; - - if(activeEvents.get(UserInputEvent.MoveUp)){ - this.setVelocity(0, -speed) - } else if(activeEvents.get(UserInputEvent.MoveLeft)){ - this.setVelocity(-speed, 0) - } else if(activeEvents.get(UserInputEvent.MoveDown)){ - this.setVelocity(0, speed) - } else if(activeEvents.get(UserInputEvent.MoveRight)){ - this.setVelocity(speed, 0) - } else { - this.setVelocity(0, 0) - } - - if (this.body.velocity.x > 0) { //moving right - this.play("right", true); - } else if (this.body.velocity.x < 0) { //moving left - this.anims.playReverse("left", true); - } else if (this.body.velocity.y < 0) { //moving up - this.play("up", true); - } else if (this.body.velocity.y > 0) { //moving down - this.play("down", true); - } - } - stop() { this.setVelocity(0, 0) } From c51f5f4aa9977e542f6e5c571709555978d0df35 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Sun, 12 Apr 2020 19:35:51 +0200 Subject: [PATCH 28/73] added som ebasic speech bubbles --- front/src/Phaser/Entity/PlayableCaracter.ts | 12 ++++ front/src/Phaser/Entity/SpeechBubble.ts | 62 +++++++++++++++++++ front/src/Phaser/Game/GameScene.ts | 7 ++- front/src/Phaser/NonPlayer/NonPlayer.ts | 3 +- .../src/Phaser/UserInput/UserInputManager.ts | 2 + 5 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 front/src/Phaser/Entity/SpeechBubble.ts diff --git a/front/src/Phaser/Entity/PlayableCaracter.ts b/front/src/Phaser/Entity/PlayableCaracter.ts index 789b2475..0c9db376 100644 --- a/front/src/Phaser/Entity/PlayableCaracter.ts +++ b/front/src/Phaser/Entity/PlayableCaracter.ts @@ -1,7 +1,9 @@ import {getPlayerAnimations, playAnimation, PlayerAnimationNames} from "../Player/Animation"; import {ActiveEventList, UserInputEvent} from "../UserInput/UserInputManager"; +import {SpeechBubble} from "./SpeechBubble"; export class PlayableCaracter extends Phaser.Physics.Arcade.Sprite { + private bubble: SpeechBubble; constructor(scene: Phaser.Scene, x: number, y: number, texture: string, frame?: string | number) { super(scene, x, y, texture, frame); @@ -29,4 +31,14 @@ export class PlayableCaracter extends Phaser.Physics.Arcade.Sprite { this.play(PlayerAnimationNames.WalkDown, true); } } + + say(text: string) { + if (this.bubble) return; + this.bubble = new SpeechBubble(this.scene, this, text) + //todo make the buble destroy on player movement? + setTimeout(() => { + this.bubble.destroy(); + this.bubble = null; + }, 3000) + } } \ No newline at end of file diff --git a/front/src/Phaser/Entity/SpeechBubble.ts b/front/src/Phaser/Entity/SpeechBubble.ts new file mode 100644 index 00000000..e3a055b2 --- /dev/null +++ b/front/src/Phaser/Entity/SpeechBubble.ts @@ -0,0 +1,62 @@ +import Scene = Phaser.Scene; +import {PlayableCaracter} from "./PlayableCaracter"; + +export class SpeechBubble { + private bubble: Phaser.GameObjects.Graphics; + private content: Phaser.GameObjects.Text; + + constructor(scene: Scene, player: PlayableCaracter, text: string) { + + let bubbleHeight = 50; + let bubblePadding = 10; + let bubbleWidth = bubblePadding * 2 + text.length * 10; + let arrowHeight = bubbleHeight / 4; + + this.bubble = scene.add.graphics({ x: player.x + 16, y: player.y - 80 }); + + // Bubble shadow + this.bubble.fillStyle(0x222222, 0.5); + this.bubble.fillRoundedRect(6, 6, bubbleWidth, bubbleHeight, 16); + + // this.bubble color + this.bubble.fillStyle(0xffffff, 1); + + // this.bubble outline line style + this.bubble.lineStyle(4, 0x565656, 1); + + // this.bubble shape and outline + this.bubble.strokeRoundedRect(0, 0, bubbleWidth, bubbleHeight, 16); + this.bubble.fillRoundedRect(0, 0, bubbleWidth, bubbleHeight, 16); + + // Calculate arrow coordinates + let point1X = Math.floor(bubbleWidth / 7); + let point1Y = bubbleHeight; + let point2X = Math.floor((bubbleWidth / 7) * 2); + let point2Y = bubbleHeight; + let point3X = Math.floor(bubbleWidth / 7); + let point3Y = Math.floor(bubbleHeight + arrowHeight); + + // bubble arrow shadow + this.bubble.lineStyle(4, 0x222222, 0.5); + this.bubble.lineBetween(point2X - 1, point2Y + 6, point3X + 2, point3Y); + + // bubble arrow fill + this.bubble.fillTriangle(point1X, point1Y, point2X, point2Y, point3X, point3Y); + this.bubble.lineStyle(2, 0x565656, 1); + this.bubble.lineBetween(point2X, point2Y, point3X, point3Y); + this.bubble.lineBetween(point1X, point1Y, point3X, point3Y); + + this.content = scene.add.text(0, 0, text, { fontFamily: 'Arial', fontSize: 20, color: '#000000', align: 'center', wordWrap: { width: bubbleWidth - (bubblePadding * 2) } }); + + let bounds = this.content.getBounds(); + + this.content.setPosition(this.bubble.x + (bubbleWidth / 2) - (bounds.width / 2), this.bubble.y + (bubbleHeight / 2) - (bounds.height / 2)); + + } + + destroy(): void { + this.bubble.setVisible(false) //todo find a better way + this.content.destroy() + } + +} \ No newline at end of file diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 112e452c..eab3df65 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1,6 +1,6 @@ import {GameManagerInterface} from "./GameManager"; import {UserInputEvent, UserInputManager} from "../UserInput/UserInputManager"; -import {getPlayerAnimations, PlayerAnimationNames} from "../Player/Animation"; +import {getPlayerAnimations} from "../Player/Animation"; import {Player} from "../Player/Player"; import {NonPlayer} from "../NonPlayer/NonPlayer"; @@ -65,7 +65,6 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ this.otherPlayers.add(new NonPlayer(this, 400, 600)); this.physics.add.collider(this.player, this.otherPlayers, (player: Player, otherPlayer: NonPlayer) => { - console.log("Don't touch me!"); otherPlayer.fleeFrom(player) }); @@ -120,6 +119,10 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ this.player.move(0, 0) } + if(activeEvents.get(UserInputEvent.Shout)) { + this.player.say('HEHO!'); + } + //updates other players this.otherPlayers.getChildren().forEach((otherPlayer: NonPlayer) => { diff --git a/front/src/Phaser/NonPlayer/NonPlayer.ts b/front/src/Phaser/NonPlayer/NonPlayer.ts index 91b83ba3..f2d748ab 100644 --- a/front/src/Phaser/NonPlayer/NonPlayer.ts +++ b/front/src/Phaser/NonPlayer/NonPlayer.ts @@ -15,10 +15,11 @@ export class NonPlayer extends PlayableCaracter { fleeFrom(player:Player) { if (this.isFleeing) return; + this.say("Don't touch me!"); this.isFleeing = true; setTimeout(() => { - console.log("I escaped"); + this.say("Feww, I escaped."); this.isFleeing = false this.fleeingDirection = null }, 3000); diff --git a/front/src/Phaser/UserInput/UserInputManager.ts b/front/src/Phaser/UserInput/UserInputManager.ts index e05eccb2..1e5e6e13 100644 --- a/front/src/Phaser/UserInput/UserInputManager.ts +++ b/front/src/Phaser/UserInput/UserInputManager.ts @@ -14,6 +14,7 @@ export enum UserInputEvent { MoveDown, SpeedUp, Interact, + Shout, } //we cannot the map structure so we have to create a replacment @@ -46,6 +47,7 @@ export class UserInputManager { {keyCode: Phaser.Input.Keyboard.KeyCodes.SHIFT, event: UserInputEvent.SpeedUp, keyInstance: null}, {keyCode: Phaser.Input.Keyboard.KeyCodes.E, event: UserInputEvent.Interact, keyInstance: null}, + {keyCode: Phaser.Input.Keyboard.KeyCodes.F, event: UserInputEvent.Shout, keyInstance: null}, ]; constructor(Scene : GameSceneInterface) { From 877a47e2dd133f2b2cce587b29f86e2eebd12bad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 13 Apr 2020 14:46:45 +0200 Subject: [PATCH 29/73] Fixing Dockerfile build path --- .github/workflows/build-and-deploy.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 58566734..607e6ae7 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -27,6 +27,7 @@ jobs: uses: docker/build-push-action@v1 with: dockerfile: front/Dockerfile + path: front/ username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} repository: thecodingmachine/workadventure-front @@ -46,6 +47,7 @@ jobs: uses: docker/build-push-action@v1 with: dockerfile: back/Dockerfile + path: back/ username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} repository: thecodingmachine/workadventure-back From 48fe86634fd2499f8ab3fd711d453a10ba2d8c11 Mon Sep 17 00:00:00 2001 From: gparant Date: Mon, 13 Apr 2020 15:15:20 +0200 Subject: [PATCH 30/73] Add feature to move bubble --- front/src/Phaser/Entity/PlayableCaracter.ts | 4 ++ front/src/Phaser/Entity/SpeechBubble.ts | 34 +++++++++-- front/src/Phaser/Game/MapManager.ts | 63 ++++++++++++++++----- 3 files changed, 82 insertions(+), 19 deletions(-) diff --git a/front/src/Phaser/Entity/PlayableCaracter.ts b/front/src/Phaser/Entity/PlayableCaracter.ts index 69bcfd55..d2a72b14 100644 --- a/front/src/Phaser/Entity/PlayableCaracter.ts +++ b/front/src/Phaser/Entity/PlayableCaracter.ts @@ -31,6 +31,10 @@ export class PlayableCaracter extends Phaser.Physics.Arcade.Sprite { } else if (this.body.velocity.y > 0) { //moving down this.play(PlayerAnimationNames.WalkDown, true); } + + if(this.bubble) { + this.bubble.moveBubble(this.x, this.y); + } } say(text: string) { diff --git a/front/src/Phaser/Entity/SpeechBubble.ts b/front/src/Phaser/Entity/SpeechBubble.ts index e3a055b2..51aaa169 100644 --- a/front/src/Phaser/Entity/SpeechBubble.ts +++ b/front/src/Phaser/Entity/SpeechBubble.ts @@ -5,7 +5,13 @@ export class SpeechBubble { private bubble: Phaser.GameObjects.Graphics; private content: Phaser.GameObjects.Text; - constructor(scene: Scene, player: PlayableCaracter, text: string) { + /** + * + * @param scene + * @param player + * @param text + */ + constructor(scene: Scene, player: PlayableCaracter, text: string = "") { let bubbleHeight = 50; let bubblePadding = 10; @@ -49,14 +55,34 @@ export class SpeechBubble { this.content = scene.add.text(0, 0, text, { fontFamily: 'Arial', fontSize: 20, color: '#000000', align: 'center', wordWrap: { width: bubbleWidth - (bubblePadding * 2) } }); let bounds = this.content.getBounds(); - this.content.setPosition(this.bubble.x + (bubbleWidth / 2) - (bounds.width / 2), this.bubble.y + (bubbleHeight / 2) - (bounds.height / 2)); - + } + + /** + * + * @param x + * @param y + */ + moveBubble(x : number, y : number) { + if (this.bubble) { + this.bubble.setPosition((x + 16), (y - 80)); + } + if (this.content) { + let bubbleHeight = 50; + let bubblePadding = 10; + let bubbleWidth = bubblePadding * 2 + this.content.text.length * 10; + let bounds = this.content.getBounds(); + //this.content.setPosition(x, y); + this.content.setPosition(this.bubble.x + (bubbleWidth / 2) - (bounds.width / 2), this.bubble.y + (bubbleHeight / 2) - (bounds.height / 2)); + } } destroy(): void { this.bubble.setVisible(false) //todo find a better way - this.content.destroy() + this.bubble.destroy(); + this.bubble = null; + this.content.destroy(); + this.content = null; } } \ No newline at end of file diff --git a/front/src/Phaser/Game/MapManager.ts b/front/src/Phaser/Game/MapManager.ts index 3a91954c..1ac8d57e 100644 --- a/front/src/Phaser/Game/MapManager.ts +++ b/front/src/Phaser/Game/MapManager.ts @@ -4,6 +4,8 @@ import {CurrentGamerInterface, GamerInterface, Player} from "../Player/Player"; import {GameSceneInterface, Textures} from "./GameScene"; import {MessageUserPositionInterface} from "../../Connexion"; import {NonPlayer} from "../NonPlayer/NonPlayer"; +import GameObject = Phaser.GameObjects.GameObject; +import Tile = Phaser.Tilemaps.Tile; export interface MapManagerInterface { Map: Phaser.Tilemaps.Tilemap; @@ -22,14 +24,11 @@ export class MapManager implements MapManagerInterface{ MapPlayers : Phaser.Physics.Arcade.Group; Scene: GameSceneInterface; Map: Phaser.Tilemaps.Tilemap; - BottomLayer: Phaser.Tilemaps.StaticTilemapLayer; - TopLayer: Phaser.Tilemaps.StaticTilemapLayer; + Layers : Array; + Objects : Array; startX = (window.innerWidth / 2) / RESOLUTION; startY = (window.innerHeight / 2) / RESOLUTION; - //entities - private rock: Phaser.Physics.Arcade.Sprite; - constructor(scene: GameSceneInterface){ this.Scene = scene; @@ -37,20 +36,26 @@ export class MapManager implements MapManagerInterface{ this.Map = this.Scene.add.tilemap("map"); this.Terrain = this.Map.addTilesetImage("tiles", "tiles"); this.Map.createStaticLayer("tiles", "tiles"); - this.BottomLayer = this.Map.createStaticLayer("Calque 1", [this.Terrain], 0, 0).setDepth(-2); - this.TopLayer = this.Map.createStaticLayer("Calque 2", [this.Terrain], 0, 0).setDepth(-1); + + //permit to set bound collision this.Scene.physics.world.setBounds(0,0, this.Map.widthInPixels, this.Map.heightInPixels); - //add entitites - this.rock = this.Scene.physics.add.sprite(200, 400, Textures.Rock, 26).setImmovable(true); + //add layer on map + this.Layers = new Array(); + this.addLayer( this.Map.createStaticLayer("Calque 1", [this.Terrain], 0, 0).setDepth(-2) ); + this.addLayer( this.Map.createStaticLayer("Calque 2", [this.Terrain], 0, 0).setDepth(-1) ); + + //add entities + this.Objects = new Array(); + this.addSpite(this.Scene.physics.add.sprite(200, 400, Textures.Rock, 26)); //debug code //debug code to see the collision hitbox of the object in the top layer - this.TopLayer.renderDebug(this.Scene.add.graphics(),{ + /*this.TopLayer.renderDebug(this.Scene.add.graphics(),{ tileColor: null, //non-colliding tiles collidingTileColor: new Phaser.Display.Color(243, 134, 48, 200), // Colliding tiles, faceColor: new Phaser.Display.Color(40, 39, 37, 255) // Colliding face edges - }); + });*/ //init event click this.EventToClickOnTile(); @@ -62,6 +67,36 @@ export class MapManager implements MapManagerInterface{ this.MapPlayers = this.Scene.physics.add.group({ immovable: true }); } + addLayer(Layer : Phaser.Tilemaps.StaticTilemapLayer){ + this.Layers.push(Layer); + } + + createCollisionWithPlayer() { + //add collision layer + this.Layers.forEach((Layer: Phaser.Tilemaps.StaticTilemapLayer) => { + this.Scene.physics.add.collider(this.CurrentPlayer, Layer); + Layer.setCollisionByProperty({collides: true}); + //debug code + //debug code to see the collision hitbox of the object in the top layer + Layer.renderDebug(this.Scene.add.graphics(), { + tileColor: null, //non-colliding tiles + collidingTileColor: new Phaser.Display.Color(243, 134, 48, 200), // Colliding tiles, + faceColor: new Phaser.Display.Color(40, 39, 37, 255) // Colliding face edges + }); + }); + } + + addSpite(Object : Phaser.Physics.Arcade.Sprite){ + Object.setImmovable(true); + this.Objects.push(Object); + } + + createCollisionObject(){ + this.Objects.forEach((Object : Phaser.Physics.Arcade.Sprite) => { + this.Scene.physics.add.collider(this.CurrentPlayer, Object); + }) + } + createCurrentPlayer(UserId : string){ //initialise player this.CurrentPlayer = new Player( @@ -75,10 +110,8 @@ export class MapManager implements MapManagerInterface{ this.CurrentPlayer.initAnimation(); //create collision - this.Scene.physics.add.collider(this.CurrentPlayer, this.rock); - //add collision layer - this.Scene.physics.add.collider(this.CurrentPlayer, this.TopLayer); - this.TopLayer.setCollisionByProperty({collides:true}); + this.createCollisionWithPlayer(); + this.createCollisionObject(); } EventToClickOnTile(){ From 2afe6b4b6e78d87fc91b644d26acf4f95c67b6aa Mon Sep 17 00:00:00 2001 From: gparant Date: Mon, 13 Apr 2020 15:34:09 +0200 Subject: [PATCH 31/73] Fix feadback @Kharhamel --- front/src/Phaser/Game/CameraManager.ts | 14 +- front/src/Phaser/Game/GameScene.ts | 183 ++++++++++++++++++++--- front/src/Phaser/Game/MapManager.ts | 196 ------------------------- front/src/Phaser/Player/Player.ts | 63 ++------ 4 files changed, 183 insertions(+), 273 deletions(-) delete mode 100644 front/src/Phaser/Game/MapManager.ts diff --git a/front/src/Phaser/Game/CameraManager.ts b/front/src/Phaser/Game/CameraManager.ts index a52554e5..3b2dc06b 100644 --- a/front/src/Phaser/Game/CameraManager.ts +++ b/front/src/Phaser/Game/CameraManager.ts @@ -1,24 +1,20 @@ import {RESOLUTION} from "../../Enum/EnvironmentVariable"; import {Player} from "../Player/Player"; -import {MapManagerInterface} from "./MapManager"; +import {GameSceneInterface} from "./GameScene"; export interface CameraManagerInterface { - MapManager : MapManagerInterface; moveCamera(CurrentPlayer : Player) : void; } export class CameraManager implements CameraManagerInterface{ - Scene : Phaser.Scene; + Scene : GameSceneInterface; Camera : Phaser.Cameras.Scene2D.Camera; - MapManager : MapManagerInterface; constructor( - Scene: Phaser.Scene, + Scene: GameSceneInterface, Camera : Phaser.Cameras.Scene2D.Camera, - MapManager: MapManagerInterface, ) { this.Scene = Scene; - this.MapManager = MapManager; this.Camera = Camera; } @@ -30,8 +26,8 @@ export class CameraManager implements CameraManagerInterface{ let limit = { top: startY, left: startX, - bottom : this.MapManager.Map.heightInPixels - startY, - right: this.MapManager.Map.widthInPixels - startX, + bottom : this.Scene.Map.heightInPixels - startY, + right: this.Scene.Map.widthInPixels - startX, }; if(CurrentPlayer.x < limit.left){ diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index a9a08fc6..69b5a712 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1,6 +1,9 @@ -import {MapManagerInterface, MapManager} from "./MapManager"; import {GameManagerInterface, StatusGameManagerEnum} from "./GameManager"; import {MessageUserPositionInterface} from "../../Connexion"; +import {CameraManager, CameraManagerInterface} from "./CameraManager"; +import {CurrentGamerInterface, GamerInterface, Player} from "../Player/Player"; +import {RESOLUTION} from "../../Enum/EnvironmentVariable"; +import Tile = Phaser.Tilemaps.Tile; export enum Textures { Rock = 'rock', @@ -11,13 +14,22 @@ export enum Textures { export interface GameSceneInterface extends Phaser.Scene { RoomId : string; + Map: Phaser.Tilemaps.Tilemap; createCurrentPlayer(UserId : string) : void; shareUserPosition(UsersPosition : Array): void; } export class GameScene extends Phaser.Scene implements GameSceneInterface{ - private MapManager : MapManagerInterface; GameManager : GameManagerInterface; RoomId : string; + Terrain : Phaser.Tilemaps.Tileset; + Camera: CameraManagerInterface; + CurrentPlayer: CurrentGamerInterface; + MapPlayers : Phaser.Physics.Arcade.Group; + Map: Phaser.Tilemaps.Tilemap; + Layers : Array; + Objects : Array; + startX = (window.innerWidth / 2) / RESOLUTION; + startY = (window.innerHeight / 2) / RESOLUTION; constructor(RoomId : string, GameManager : GameManagerInterface) { super({ @@ -43,26 +55,100 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ //hook create scene create(): void { - //create map manager - this.MapManager = new MapManager(this); + + //initalise map + this.Map = this.add.tilemap("map"); + this.Terrain = this.Map.addTilesetImage("tiles", "tiles"); + this.Map.createStaticLayer("tiles", "tiles"); + + //permit to set bound collision + this.physics.world.setBounds(0,0, this.Map.widthInPixels, this.Map.heightInPixels); + + //add layer on map + this.Layers = new Array(); + this.addLayer( this.Map.createStaticLayer("Calque 1", [this.Terrain], 0, 0).setDepth(-2) ); + this.addLayer( this.Map.createStaticLayer("Calque 2", [this.Terrain], 0, 0).setDepth(-1) ); + + //add entities + this.Objects = new Array(); + this.addSpite(this.physics.add.sprite(200, 400, Textures.Rock, 26)); + + //init event click + this.EventToClickOnTile(); + + //initialise camera + this.Camera = new CameraManager(this, this.cameras.main); + + //initialise list of other player + this.MapPlayers = this.physics.add.group({ immovable: true }); + //notify game manager can to create currentUser in map this.GameManager.createCurrentPlayer(); } - /** - * Create current player - * @param UserId - */ - createCurrentPlayer(UserId : string): void { - this.MapManager.createCurrentPlayer(UserId) + addLayer(Layer : Phaser.Tilemaps.StaticTilemapLayer){ + this.Layers.push(Layer); } - //hook update - update(dt: number): void { - if(this.GameManager.status === StatusGameManagerEnum.IN_PROGRESS){ - return; - } - this.MapManager.update(); + createCollisionWithPlayer() { + //add collision layer + this.Layers.forEach((Layer: Phaser.Tilemaps.StaticTilemapLayer) => { + this.physics.add.collider(this.CurrentPlayer, Layer, (object1: any, object2: any) => { + this.CurrentPlayer.say("Collision with layer : "+ (object2 as Tile).layer.name) + }); + Layer.setCollisionByProperty({collides: true}); + //debug code + //debug code to see the collision hitbox of the object in the top layer + Layer.renderDebug(this.add.graphics(), { + tileColor: null, //non-colliding tiles + collidingTileColor: new Phaser.Display.Color(243, 134, 48, 200), // Colliding tiles, + faceColor: new Phaser.Display.Color(40, 39, 37, 255) // Colliding face edges + }); + }); + } + + addSpite(Object : Phaser.Physics.Arcade.Sprite){ + Object.setImmovable(true); + this.Objects.push(Object); + } + + createCollisionObject(){ + this.Objects.forEach((Object : Phaser.Physics.Arcade.Sprite) => { + this.physics.add.collider(this.CurrentPlayer, Object, (object1: any, object2: any) => { + this.CurrentPlayer.say("Collision with object : " + (object2 as Phaser.Physics.Arcade.Sprite).texture.key) + }); + }) + } + + createCurrentPlayer(UserId : string){ + //initialise player + this.CurrentPlayer = new Player( + UserId, + this, + this.startX, + this.startY, + this.Camera, + ); + this.CurrentPlayer.initAnimation(); + + //create collision + this.createCollisionWithPlayer(); + this.createCollisionObject(); + } + + EventToClickOnTile(){ + // debug code to get a tile properties by clicking on it + this.input.on("pointerdown", (pointer: Phaser.Input.Pointer)=>{ + //pixel position toz tile position + let tile = this.Map.getTileAt(this.Map.worldToTileX(pointer.worldX), this.Map.worldToTileY(pointer.worldY)); + if(tile){ + this.CurrentPlayer.say("Your touch " + tile.layer.name); + } + }); + } + + update() : void { + this.CurrentPlayer.moveUser(); } /** @@ -70,6 +156,69 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ * @param UsersPosition */ shareUserPosition(UsersPosition : Array): void { - this.MapManager.updateOrCreateMapPlayer(UsersPosition); + this.updateOrCreateMapPlayer(UsersPosition); + } + + /** + * Create new player and clean the player on the map + * @param UsersPosition + */ + updateOrCreateMapPlayer(UsersPosition : Array){ + if(!this.CurrentPlayer){ + return; + } + + //add or create new user + UsersPosition.forEach((userPosition : MessageUserPositionInterface) => { + if(userPosition.userId === this.CurrentPlayer.userId){ + return; + } + let player = this.findPlayerInMap(userPosition.userId); + if(!player){ + this.addPlayer(userPosition); + }else{ + player.updatePosition(userPosition); + } + }); + + //clean map + this.MapPlayers.getChildren().forEach((player: GamerInterface) => { + if(UsersPosition.find((message : MessageUserPositionInterface) => message.userId === player.userId)){ + return; + } + player.destroy(); + this.MapPlayers.remove(player); + }); + } + + private findPlayerInMap(UserId : string) : GamerInterface | null{ + let player = this.MapPlayers.getChildren().find((player: Player) => UserId === player.userId); + if(!player){ + return null; + } + return (player as GamerInterface); + } + + /** + * Create new player + * @param MessageUserPosition + */ + addPlayer(MessageUserPosition : MessageUserPositionInterface){ + //initialise player + let player = new Player( + MessageUserPosition.userId, + this, + MessageUserPosition.position.x, + MessageUserPosition.position.y, + this.Camera, + ); + player.initAnimation(); + this.MapPlayers.add(player); + player.updatePosition(MessageUserPosition); + + //init colision + this.physics.add.collider(this.CurrentPlayer, player, (CurrentPlayer: CurrentGamerInterface, MapPlayer: GamerInterface) => { + MapPlayer.say("Hello, how are you ? "); + }); } } diff --git a/front/src/Phaser/Game/MapManager.ts b/front/src/Phaser/Game/MapManager.ts deleted file mode 100644 index 1ac8d57e..00000000 --- a/front/src/Phaser/Game/MapManager.ts +++ /dev/null @@ -1,196 +0,0 @@ -import {CameraManager, CameraManagerInterface} from "./CameraManager"; -import {RESOLUTION} from "../../Enum/EnvironmentVariable"; -import {CurrentGamerInterface, GamerInterface, Player} from "../Player/Player"; -import {GameSceneInterface, Textures} from "./GameScene"; -import {MessageUserPositionInterface} from "../../Connexion"; -import {NonPlayer} from "../NonPlayer/NonPlayer"; -import GameObject = Phaser.GameObjects.GameObject; -import Tile = Phaser.Tilemaps.Tile; - -export interface MapManagerInterface { - Map: Phaser.Tilemaps.Tilemap; - Terrain: Phaser.Tilemaps.Tileset; - Camera: CameraManagerInterface; - Scene: GameSceneInterface; - - createCurrentPlayer(UserId : string): void; - update(): void; - updateOrCreateMapPlayer(UsersPosition : Array): void; -} -export class MapManager implements MapManagerInterface{ - Terrain : Phaser.Tilemaps.Tileset; - Camera: CameraManagerInterface; - CurrentPlayer: CurrentGamerInterface; - MapPlayers : Phaser.Physics.Arcade.Group; - Scene: GameSceneInterface; - Map: Phaser.Tilemaps.Tilemap; - Layers : Array; - Objects : Array; - startX = (window.innerWidth / 2) / RESOLUTION; - startY = (window.innerHeight / 2) / RESOLUTION; - - constructor(scene: GameSceneInterface){ - this.Scene = scene; - - //initalise map - this.Map = this.Scene.add.tilemap("map"); - this.Terrain = this.Map.addTilesetImage("tiles", "tiles"); - this.Map.createStaticLayer("tiles", "tiles"); - - //permit to set bound collision - this.Scene.physics.world.setBounds(0,0, this.Map.widthInPixels, this.Map.heightInPixels); - - //add layer on map - this.Layers = new Array(); - this.addLayer( this.Map.createStaticLayer("Calque 1", [this.Terrain], 0, 0).setDepth(-2) ); - this.addLayer( this.Map.createStaticLayer("Calque 2", [this.Terrain], 0, 0).setDepth(-1) ); - - //add entities - this.Objects = new Array(); - this.addSpite(this.Scene.physics.add.sprite(200, 400, Textures.Rock, 26)); - - //debug code - //debug code to see the collision hitbox of the object in the top layer - /*this.TopLayer.renderDebug(this.Scene.add.graphics(),{ - tileColor: null, //non-colliding tiles - collidingTileColor: new Phaser.Display.Color(243, 134, 48, 200), // Colliding tiles, - faceColor: new Phaser.Display.Color(40, 39, 37, 255) // Colliding face edges - });*/ - - //init event click - this.EventToClickOnTile(); - - //initialise camera - this.Camera = new CameraManager(this.Scene, this.Scene.cameras.main, this); - - //initialise list of other player - this.MapPlayers = this.Scene.physics.add.group({ immovable: true }); - } - - addLayer(Layer : Phaser.Tilemaps.StaticTilemapLayer){ - this.Layers.push(Layer); - } - - createCollisionWithPlayer() { - //add collision layer - this.Layers.forEach((Layer: Phaser.Tilemaps.StaticTilemapLayer) => { - this.Scene.physics.add.collider(this.CurrentPlayer, Layer); - Layer.setCollisionByProperty({collides: true}); - //debug code - //debug code to see the collision hitbox of the object in the top layer - Layer.renderDebug(this.Scene.add.graphics(), { - tileColor: null, //non-colliding tiles - collidingTileColor: new Phaser.Display.Color(243, 134, 48, 200), // Colliding tiles, - faceColor: new Phaser.Display.Color(40, 39, 37, 255) // Colliding face edges - }); - }); - } - - addSpite(Object : Phaser.Physics.Arcade.Sprite){ - Object.setImmovable(true); - this.Objects.push(Object); - } - - createCollisionObject(){ - this.Objects.forEach((Object : Phaser.Physics.Arcade.Sprite) => { - this.Scene.physics.add.collider(this.CurrentPlayer, Object); - }) - } - - createCurrentPlayer(UserId : string){ - //initialise player - this.CurrentPlayer = new Player( - UserId, - this.Scene, - this.startX, - this.startY, - this.Camera, - this - ); - this.CurrentPlayer.initAnimation(); - - //create collision - this.createCollisionWithPlayer(); - this.createCollisionObject(); - } - - EventToClickOnTile(){ - // debug code to get a tile properties by clicking on it - this.Scene.input.on("pointerdown", (pointer: Phaser.Input.Pointer)=>{ - //pixel position toz tile position - let tile = this.Map.getTileAt(this.Map.worldToTileX(pointer.worldX), this.Map.worldToTileY(pointer.worldY)); - if(tile){ - //console.log("MapManager => tile => pointerdown", tile); - this.CurrentPlayer.say("Your touch " + tile.layer.name); - } - }); - } - - update() : void { - this.CurrentPlayer.moveUser(); - } - - /** - * Create new player and clean the player on the map - * @param UsersPosition - */ - updateOrCreateMapPlayer(UsersPosition : Array){ - if(!this.CurrentPlayer){ - return; - } - - //add or create new user - UsersPosition.forEach((userPosition : MessageUserPositionInterface) => { - if(userPosition.userId === this.CurrentPlayer.userId){ - return; - } - let player = this.findPlayerInMap(userPosition.userId); - if(!player){ - this.addPlayer(userPosition); - }else{ - player.updatePosition(userPosition); - } - }); - - //clean map - this.MapPlayers.getChildren().forEach((player: GamerInterface) => { - if(UsersPosition.find((message : MessageUserPositionInterface) => message.userId === player.userId)){ - return; - } - player.destroy(); - this.MapPlayers.remove(player); - }); - } - - private findPlayerInMap(UserId : string) : GamerInterface | null{ - let player = this.MapPlayers.getChildren().find((player: Player) => UserId === player.userId); - if(!player){ - return null; - } - return (player as GamerInterface); - } - - /** - * Create new player - * @param MessageUserPosition - */ - addPlayer(MessageUserPosition : MessageUserPositionInterface){ - //initialise player - let player = new Player( - MessageUserPosition.userId, - this.Scene, - MessageUserPosition.position.x, - MessageUserPosition.position.y, - this.Camera, - this - ); - player.initAnimation(); - this.MapPlayers.add(player); - player.updatePosition(MessageUserPosition); - - //init colision - this.Scene.physics.add.collider(this.CurrentPlayer, player, (CurrentPlayer: CurrentGamerInterface, MapPlayer: GamerInterface) => { - MapPlayer.say("Hello, how are you ? "); - }); - } -} \ No newline at end of file diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index 78aa5801..d426b3d2 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -5,11 +5,9 @@ import {CameraManagerInterface} from "../Game/CameraManager"; import {MessageUserPositionInterface} from "../../Connexion"; import {ActiveEventList, UserInputEvent, UserInputManager} from "../UserInput/UserInputManager"; import {PlayableCaracter} from "../Entity/PlayableCaracter"; -import {MapManagerInterface} from "../Game/MapManager"; export interface CurrentGamerInterface extends PlayableCaracter{ userId : string; - MapManager : MapManagerInterface; PlayerValue : string; CameraManager: CameraManagerInterface; initAnimation() : void; @@ -19,7 +17,6 @@ export interface CurrentGamerInterface extends PlayableCaracter{ export interface GamerInterface extends PlayableCaracter{ userId : string; - MapManager : MapManagerInterface; PlayerValue : string; CameraManager: CameraManagerInterface; initAnimation() : void; @@ -27,21 +24,19 @@ export interface GamerInterface extends PlayableCaracter{ say(text : string) : void; } -export class Player extends PlayableCaracter implements CurrentGamerInterface, GamerInterface{ - userId : string; - MapManager : MapManagerInterface; - PlayerValue : string; +export class Player extends PlayableCaracter implements CurrentGamerInterface, GamerInterface { + userId: string; + PlayerValue: string; CameraManager: CameraManagerInterface; userInputManager: UserInputManager; constructor( userId: string, - Scene : GameSceneInterface, - x : number, - y : number, + Scene: GameSceneInterface, + x: number, + y: number, CameraManager: CameraManagerInterface, - MapManager: MapManagerInterface, - PlayerValue : string = Textures.Player + PlayerValue: string = Textures.Player ) { super(Scene, x, y, PlayerValue, 1); @@ -51,7 +46,6 @@ export class Player extends PlayableCaracter implements CurrentGamerInterface, G //set data this.userId = userId; this.PlayerValue = PlayerValue; - this.MapManager = MapManager; this.CameraManager = CameraManager; //the current player model should be push away by other players to prevent conflict @@ -60,7 +54,7 @@ export class Player extends PlayableCaracter implements CurrentGamerInterface, G this.setSize(32, 32); } - initAnimation() : void { + initAnimation(): void { getPlayerAnimations().forEach(d => { this.scene.anims.create({ key: d.key, @@ -71,9 +65,8 @@ export class Player extends PlayableCaracter implements CurrentGamerInterface, G }) } - moveUser() : void { + moveUser(): void { //if user client on shift, camera and player speed - //let speedMultiplier = this.MapManager.keyShift.isDown ? 5 : 1; let haveMove = false; let direction = null; @@ -81,33 +74,21 @@ export class Player extends PlayableCaracter implements CurrentGamerInterface, G let speedMultiplier = activeEvents.get(UserInputEvent.SpeedUp) ? 500 : 100; if (activeEvents.get(UserInputEvent.MoveUp)) { - if (!this.CanMoveUp()) { - return; - } this.move(0, -speedMultiplier); haveMove = true; direction = PlayerAnimationNames.WalkUp; } if (activeEvents.get(UserInputEvent.MoveLeft)) { - if (!this.CanMoveLeft()) { - return; - } this.move(-speedMultiplier, 0); haveMove = true; direction = PlayerAnimationNames.WalkLeft; } if (activeEvents.get(UserInputEvent.MoveDown)) { - if (!this.CanMoveDown()) { - return; - } this.move(0, speedMultiplier); haveMove = true; direction = PlayerAnimationNames.WalkDown; } if (activeEvents.get(UserInputEvent.MoveRight)) { - if (!this.CanMoveRight()) { - return; - } this.move(speedMultiplier, 0); haveMove = true; direction = PlayerAnimationNames.WalkRight; @@ -120,33 +101,13 @@ export class Player extends PlayableCaracter implements CurrentGamerInterface, G this.CameraManager.moveCamera(this); } - private sharePosition(direction : string){ - if(ConnexionInstance) { + private sharePosition(direction: string) { + if (ConnexionInstance) { ConnexionInstance.sharePosition((this.scene as GameSceneInterface).RoomId, this.x, this.y, direction); } } - private CanMoveUp(){ - return this.y > 0; - } - - private CanMoveLeft(){ - return this.x > 0; - } - - private CanMoveDown(){ - return this.MapManager.Map.heightInPixels > this.y; - } - - private CanMoveRight() { - return this.MapManager.Map.widthInPixels > this.x; - } - - stop() { - this.setVelocity(0, 0) - } - - updatePosition(MessageUserPosition : MessageUserPositionInterface){ + updatePosition(MessageUserPosition: MessageUserPositionInterface) { playAnimation(this, MessageUserPosition.position.direction); this.setX(MessageUserPosition.position.x); this.setY(MessageUserPosition.position.y); From ab70b28bb3145c7f027e75fbf8fddd218db43de8 Mon Sep 17 00:00:00 2001 From: gparant Date: Mon, 13 Apr 2020 15:35:38 +0200 Subject: [PATCH 32/73] Fix, current player say --- front/src/Phaser/Game/GameScene.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 69b5a712..8665fd2f 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -218,7 +218,7 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ //init colision this.physics.add.collider(this.CurrentPlayer, player, (CurrentPlayer: CurrentGamerInterface, MapPlayer: GamerInterface) => { - MapPlayer.say("Hello, how are you ? "); + CurrentPlayer.say("Hello, how are you ? "); }); } } From b391ee271ac515aa9e4d585b0e13b20edbc5aade Mon Sep 17 00:00:00 2001 From: gparant Date: Mon, 13 Apr 2020 15:41:11 +0200 Subject: [PATCH 33/73] Fix move & stop player --- front/src/Phaser/Entity/PlayableCaracter.ts | 14 +++++++++++--- front/src/Phaser/Player/Player.ts | 2 +- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/front/src/Phaser/Entity/PlayableCaracter.ts b/front/src/Phaser/Entity/PlayableCaracter.ts index d2a72b14..66ddd579 100644 --- a/front/src/Phaser/Entity/PlayableCaracter.ts +++ b/front/src/Phaser/Entity/PlayableCaracter.ts @@ -24,11 +24,14 @@ export class PlayableCaracter extends Phaser.Physics.Arcade.Sprite { //todo improve animations to better account for diagonal movement if (this.body.velocity.x > 0) { //moving right this.play(PlayerAnimationNames.WalkRight, true); - } else if (this.body.velocity.x < 0) { //moving left + } + if (this.body.velocity.x < 0) { //moving left this.anims.playReverse(PlayerAnimationNames.WalkLeft, true); - } else if (this.body.velocity.y < 0) { //moving up + } + if (this.body.velocity.y < 0) { //moving up this.play(PlayerAnimationNames.WalkUp, true); - } else if (this.body.velocity.y > 0) { //moving down + } + if (this.body.velocity.y > 0) { //moving down this.play(PlayerAnimationNames.WalkDown, true); } @@ -36,6 +39,11 @@ export class PlayableCaracter extends Phaser.Physics.Arcade.Sprite { this.bubble.moveBubble(this.x, this.y); } } + + stop(){ + this.setVelocity(0, 0); + this.play(PlayerAnimationNames.None, true); + } say(text: string) { if (this.bubble) return; diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index d426b3d2..d0169297 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -95,7 +95,7 @@ export class Player extends PlayableCaracter implements CurrentGamerInterface, G } if (!haveMove) { direction = PlayerAnimationNames.None; - this.move(0, 0) + this.stop(); } this.sharePosition(direction); this.CameraManager.moveCamera(this); From ba3f0e07f83b1ccb94e36532c4557743a25f209d Mon Sep 17 00:00:00 2001 From: gparant Date: Mon, 13 Apr 2020 16:53:19 +0200 Subject: [PATCH 34/73] Refactor sizing hitbox charactere --- front/src/Phaser/Entity/PlayableCaracter.ts | 3 ++- front/src/Phaser/Player/Player.ts | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/front/src/Phaser/Entity/PlayableCaracter.ts b/front/src/Phaser/Entity/PlayableCaracter.ts index 66ddd579..3a18ea73 100644 --- a/front/src/Phaser/Entity/PlayableCaracter.ts +++ b/front/src/Phaser/Entity/PlayableCaracter.ts @@ -14,7 +14,8 @@ export class PlayableCaracter extends Phaser.Physics.Arcade.Sprite { this.scene.physics.world.enableBody(this); this.setImmovable(true); this.setCollideWorldBounds(true); - this.setSize(32, 32); //edit the hitbox to better match the caracter model + this.setSize(16, 16); //edit the hitbox to better match the caracter model + this.setOffset(8, 16); } move(x: number, y: number){ diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index d0169297..e2d7d692 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -50,8 +50,6 @@ export class Player extends PlayableCaracter implements CurrentGamerInterface, G //the current player model should be push away by other players to prevent conflict this.setImmovable(false); - //edit the hitbox to better match the caracter model - this.setSize(32, 32); } initAnimation(): void { From d6653c61ccf74d6a6c65168a2249cbaded7b37b5 Mon Sep 17 00:00:00 2001 From: gparant Date: Mon, 13 Apr 2020 16:56:06 +0200 Subject: [PATCH 35/73] Add comment talk when user have a collision & comment collision shwon --- front/src/Phaser/Game/GameScene.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 8665fd2f..48acf4a2 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -94,16 +94,16 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ //add collision layer this.Layers.forEach((Layer: Phaser.Tilemaps.StaticTilemapLayer) => { this.physics.add.collider(this.CurrentPlayer, Layer, (object1: any, object2: any) => { - this.CurrentPlayer.say("Collision with layer : "+ (object2 as Tile).layer.name) + //this.CurrentPlayer.say("Collision with layer : "+ (object2 as Tile).layer.name) }); Layer.setCollisionByProperty({collides: true}); //debug code //debug code to see the collision hitbox of the object in the top layer - Layer.renderDebug(this.add.graphics(), { + /*Layer.renderDebug(this.add.graphics(), { tileColor: null, //non-colliding tiles collidingTileColor: new Phaser.Display.Color(243, 134, 48, 200), // Colliding tiles, faceColor: new Phaser.Display.Color(40, 39, 37, 255) // Colliding face edges - }); + });*/ }); } @@ -115,7 +115,7 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ createCollisionObject(){ this.Objects.forEach((Object : Phaser.Physics.Arcade.Sprite) => { this.physics.add.collider(this.CurrentPlayer, Object, (object1: any, object2: any) => { - this.CurrentPlayer.say("Collision with object : " + (object2 as Phaser.Physics.Arcade.Sprite).texture.key) + //this.CurrentPlayer.say("Collision with object : " + (object2 as Phaser.Physics.Arcade.Sprite).texture.key) }); }) } From b5901b253acd9541d8ef0decbeb7e6778e9d366e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 13 Apr 2020 15:07:04 +0200 Subject: [PATCH 36/73] Deploying one environment per branch --- deeployer.json | 21 --------------------- deeployer.libsonnet | 24 ++++++++++++++++++++++++ 2 files changed, 24 insertions(+), 21 deletions(-) delete mode 100644 deeployer.json create mode 100644 deeployer.libsonnet diff --git a/deeployer.json b/deeployer.json deleted file mode 100644 index 930e6f37..00000000 --- a/deeployer.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/thecodingmachine/deeployer/master/deeployer.schema.json", - "containers": { - "back": { - "image": "thecodingmachine/workadventure-back:cd", - "host": "api.workadventure.test.thecodingmachine.com", - "ports": [8080], - "env": { - "SECRET_KEY": "tempSecretKeyNeedsToChange" - } - }, - "front": { - "image": "thecodingmachine/workadventure-front:cd", - "host": "workadventure.test.thecodingmachine.com", - "ports": [80], - "env": { - "API_URL": "http://api.workadventure.test.thecodingmachine.com" - } - } - } -} diff --git a/deeployer.libsonnet b/deeployer.libsonnet new file mode 100644 index 00000000..60c5d470 --- /dev/null +++ b/deeployer.libsonnet @@ -0,0 +1,24 @@ +{ + local env = std.extVar("env"), + # FIXME: namespace does not work if the branch contains a "/" + local namespace = std.split(env.GITHUB_REF, "/")[2] + "$schema": "https://raw.githubusercontent.com/thecodingmachine/deeployer/master/deeployer.schema.json", + "containers": { + "back": { + "image": "thecodingmachine/workadventure-back:"+namespace, + "host": "api."+namespace+".workadventure.test.thecodingmachine.com", + "ports": [8080], + "env": { + "SECRET_KEY": "tempSecretKeyNeedsToChange" + } + }, + "front": { + "image": "thecodingmachine/workadventure-front:"+namespace, + "host": namespace+".workadventure.test.thecodingmachine.com", + "ports": [80], + "env": { + "API_URL": "http://api."+namespace+".workadventure.test.thecodingmachine.com" + } + } + } +} From b0c347b41b6904d35bebfd9dd3283eadd424522d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 13 Apr 2020 15:08:36 +0200 Subject: [PATCH 37/73] Triggering build and deploy on every branch --- .github/workflows/build-and-deploy.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 607e6ae7..7425cb48 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -1,12 +1,7 @@ name: Build, push and deploy Docker image on: - push: - branches: - - master - - cd -# tags: -# - '*' + - push # Enables BuildKit env: From d5195385316031f647c04676987b2b5b2a72462e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 13 Apr 2020 15:14:16 +0200 Subject: [PATCH 38/73] Fixing libsonnet and changing deployment namespace --- .github/workflows/build-and-deploy.yml | 3 +-- deeployer.libsonnet | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 7425cb48..1dd48da8 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -63,6 +63,5 @@ jobs: uses: thecodingmachine/deeployer@master env: KUBE_CONFIG_FILE: ${{ secrets.KUBE_CONFIG_FILE }} - AUTOCONNECT: 1 with: - namespace: workadventure-master + namespace: workadventure-${GITHUB_REF##*/} diff --git a/deeployer.libsonnet b/deeployer.libsonnet index 60c5d470..51c55580 100644 --- a/deeployer.libsonnet +++ b/deeployer.libsonnet @@ -1,11 +1,11 @@ { local env = std.extVar("env"), # FIXME: namespace does not work if the branch contains a "/" - local namespace = std.split(env.GITHUB_REF, "/")[2] + local namespace = std.split(env.GITHUB_REF, "/")[2], "$schema": "https://raw.githubusercontent.com/thecodingmachine/deeployer/master/deeployer.schema.json", "containers": { "back": { - "image": "thecodingmachine/workadventure-back:"+namespace, + "image": "thecodingmachine/workadventure-back:"+(if namespace == "master" then "latest" else namespace), "host": "api."+namespace+".workadventure.test.thecodingmachine.com", "ports": [8080], "env": { @@ -13,7 +13,7 @@ } }, "front": { - "image": "thecodingmachine/workadventure-front:"+namespace, + "image": "thecodingmachine/workadventure-front:"+(if namespace == "master" then "latest" else namespace), "host": namespace+".workadventure.test.thecodingmachine.com", "ports": [80], "env": { From 0ba6941cd201ce617d63d1cee194d4c4105c5f18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 13 Apr 2020 15:44:33 +0200 Subject: [PATCH 39/73] Fixing ref slug --- .github/workflows/build-and-deploy.yml | 5 ++++- deeployer.libsonnet | 7 ++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 1dd48da8..7c5ca664 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -59,9 +59,12 @@ jobs: - name: Checkout uses: actions/checkout@v2 + # Create a slugified value of the branch + - uses: rlespinasse/github-slug-action@master + - name: Deploy uses: thecodingmachine/deeployer@master env: KUBE_CONFIG_FILE: ${{ secrets.KUBE_CONFIG_FILE }} with: - namespace: workadventure-${GITHUB_REF##*/} + namespace: workadventure-${{GITHUB_REF_SLUG}} diff --git a/deeployer.libsonnet b/deeployer.libsonnet index 51c55580..599012c4 100644 --- a/deeployer.libsonnet +++ b/deeployer.libsonnet @@ -1,11 +1,12 @@ { local env = std.extVar("env"), # FIXME: namespace does not work if the branch contains a "/" - local namespace = std.split(env.GITHUB_REF, "/")[2], + local namespace = env.GITHUB_REF_SLUG, + local tag = if namespace == "master" then "latest" else namespace, "$schema": "https://raw.githubusercontent.com/thecodingmachine/deeployer/master/deeployer.schema.json", "containers": { "back": { - "image": "thecodingmachine/workadventure-back:"+(if namespace == "master" then "latest" else namespace), + "image": "thecodingmachine/workadventure-back:"+tag, "host": "api."+namespace+".workadventure.test.thecodingmachine.com", "ports": [8080], "env": { @@ -13,7 +14,7 @@ } }, "front": { - "image": "thecodingmachine/workadventure-front:"+(if namespace == "master" then "latest" else namespace), + "image": "thecodingmachine/workadventure-front:"+tag, "host": namespace+".workadventure.test.thecodingmachine.com", "ports": [80], "env": { From efccdcbb06001b59286976ef4df5931ffc1dbb65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 13 Apr 2020 15:45:35 +0200 Subject: [PATCH 40/73] Fixing action --- .github/workflows/build-and-deploy.yml | 2 +- deeployer.libsonnet | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 7c5ca664..297c8405 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -67,4 +67,4 @@ jobs: env: KUBE_CONFIG_FILE: ${{ secrets.KUBE_CONFIG_FILE }} with: - namespace: workadventure-${{GITHUB_REF_SLUG}} + namespace: workadventure-${{ env.GITHUB_REF_SLUG }} diff --git a/deeployer.libsonnet b/deeployer.libsonnet index 599012c4..4e44db34 100644 --- a/deeployer.libsonnet +++ b/deeployer.libsonnet @@ -1,6 +1,5 @@ { local env = std.extVar("env"), - # FIXME: namespace does not work if the branch contains a "/" local namespace = env.GITHUB_REF_SLUG, local tag = if namespace == "master" then "latest" else namespace, "$schema": "https://raw.githubusercontent.com/thecodingmachine/deeployer/master/deeployer.schema.json", From 62e28bed1097d352a62d3fdbe196a1534c3f6eb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 13 Apr 2020 18:15:27 +0200 Subject: [PATCH 41/73] Fixing slugify issue --- .github/workflows/build-and-deploy.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 297c8405..f2608367 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -18,6 +18,10 @@ jobs: - name: Checkout uses: actions/checkout@v2 + + # Create a slugified value of the branch + - uses: rlespinasse/github-slug-action@master + - name: "Build and push front image" uses: docker/build-push-action@v1 with: @@ -26,7 +30,7 @@ jobs: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} repository: thecodingmachine/workadventure-front - tag_with_ref: true + tags: ${{ env.GITHUB_REF_SLUG }} add_git_labels: true build-back: @@ -38,6 +42,9 @@ jobs: - name: Checkout uses: actions/checkout@v2 + # Create a slugified value of the branch + - uses: rlespinasse/github-slug-action@master + - name: "Build and push back image" uses: docker/build-push-action@v1 with: @@ -46,7 +53,7 @@ jobs: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} repository: thecodingmachine/workadventure-back - tag_with_ref: true + tags: ${{ env.GITHUB_REF_SLUG }} add_git_labels: true deeploy: From 226fbb1e9e482eb3216a8e930d3ebc003a7b62a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 13 Apr 2020 18:30:22 +0200 Subject: [PATCH 42/73] Adding a message with deployment location at the end of deployment --- .github/workflows/build-and-deploy.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index f2608367..ca8e5c02 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -75,3 +75,11 @@ jobs: KUBE_CONFIG_FILE: ${{ secrets.KUBE_CONFIG_FILE }} with: namespace: workadventure-${{ env.GITHUB_REF_SLUG }} + + - name: Add a comment in PR + uses: unsplash/comment-on-pr@1.2.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + msg: Environment deployed at http://${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com + check_for_duplicate_msg: true From 3a788b7c15c7d2087540ecbc122d421c3783c6ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 13 Apr 2020 18:33:27 +0200 Subject: [PATCH 43/73] Fixing version --- .github/workflows/build-and-deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index ca8e5c02..ae11e07f 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -77,7 +77,7 @@ jobs: namespace: workadventure-${{ env.GITHUB_REF_SLUG }} - name: Add a comment in PR - uses: unsplash/comment-on-pr@1.2.0 + uses: unsplash/comment-on-pr@v1.2.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: From 6bf7a3eb437cb5e350b857288f5f8db042e0bb2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 13 Apr 2020 18:34:13 +0200 Subject: [PATCH 44/73] Using tagged version for rlespinasse/github-slug-action --- .github/workflows/build-and-deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index ae11e07f..2b003230 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -67,7 +67,7 @@ jobs: uses: actions/checkout@v2 # Create a slugified value of the branch - - uses: rlespinasse/github-slug-action@master + - uses: rlespinasse/github-slug-action@1.1.0 - name: Deploy uses: thecodingmachine/deeployer@master From 7f2f977a8109cd20e99c1bff50d6ce7b05b8772b Mon Sep 17 00:00:00 2001 From: NIP Date: Mon, 13 Apr 2020 19:38:28 +0200 Subject: [PATCH 45/73] My first map representing TCM office grand floor without KEN office --- front/dist/maps/map.json | 125 +++++++++++++++++++++------------------ 1 file changed, 66 insertions(+), 59 deletions(-) diff --git a/front/dist/maps/map.json b/front/dist/maps/map.json index d116dc3e..a490c3b2 100644 --- a/front/dist/maps/map.json +++ b/front/dist/maps/map.json @@ -1,60 +1,67 @@ -{ "compressionlevel":-1, - "editorsettings": - { - "export": - { - "target":"." - } - }, - "height":20, - "infinite":false, - "layers":[ - { - "data":[1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 1, 2, 2, 2, 2, 2, 2, 3, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 17, 18, 18, 18, 18, 18, 18, 19, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 17, 18, 18, 18, 18, 18, 18, 19, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 17, 18, 18, 18, 18, 18, 18, 19, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 33, 34, 34, 34, 34, 34, 34, 35, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178], - "height":20, - "id":1, - "name":"Calque 1", - "opacity":1, - "type":"tilelayer", - "visible":true, - "width":20, - "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, 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, 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, 61, 62, 62, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 93, 94, 94, 95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 109, 110, 110, 111, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 194, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 209, 177, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 164, 193, 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, 195, 164, 209, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 211], - "height":20, - "id":3, - "name":"Calque 2", - "opacity":1, - "type":"tilelayer", - "visible":true, - "width":20, - "x":0, - "y":0 - }], - "nextlayerid":4, - "nextobjectid":1, - "orientation":"orthogonal", - "renderorder":"right-down", - "tiledversion":"1.3.3", - "tileheight":32, - "tilesets":[ - { - "columns":16, - "firstgid":1, - "image":"tiles.png", - "imageheight":512, - "imagewidth":512, - "margin":0, - "name":"tiles", - "spacing":0, - "tilecount":256, - "tileheight":32, - "tilewidth":32 - }], - "tilewidth":32, - "type":"map", - "version":1.2, - "width":20 +{ "compressionlevel":-1, + "height":18, + "infinite":false, + "layers":[ + { + "data":[294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294], + "height":18, + "id":2, + "name":"bottom", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":46, + "x":0, + "y":0 + }, + { + "data":[0, 0, 115, 51, 52, 116, 115, 51, 52, 116, 115, 51, 52, 116, 0, 0, 57, 58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 200, 245, 246, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 68, 0, 0, 67, 68, 0, 0, 67, 68, 0, 0, 0, 73, 74, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 216, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 232, 245, 246, 247, 0, 0, 153, 155, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 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, 153, 155, 0, 0, 0, 217, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 30, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 153, 155, 0, 0, 0, 199, 0, 0, 0, 0, 0, 0, 0, 0, 115, 67, 68, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 30, 0, 0, 29, 30, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 169, 171, 0, 0, 0, 215, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 98, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 29, 30, 29, 30, 0, 0, 0, 0, 185, 187, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 116, 0, 0, 51, 52, 116, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 67, 68, 0, 0, 115, 67, 68, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 116, 0, 0, 51, 52, 116, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 98, 0, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 67, 68, 0, 0, 115, 67, 68, 0, 0, 0, 0, 13, 14, 13, 14, 13, 14, 13, 14, 13, 14, 13, 14, 13, 14, 13, 14, 165, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 116, 0, 0, 51, 52, 116, 0, 0, 0, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 181, 0, 0, 0, 0, 98, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 67, 68, 0, 0, 115, 67, 68, 0, 0, 0, 0, 45, 46, 45, 46, 45, 46, 45, 46, 45, 46, 45, 46, 45, 46, 45, 46, 197, 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, 113, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 113, 0, 0, 0, 0, 0, 0, 0, 113, 0, 113, 0, 113, 0, 0, 51, 52, 0, 0, 0, 113, 0, 113, 0, 113, 0, 0, 51, 49, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49, 50, 0, 0, 0, 0, 0, 51, 49, 50, 49, 50, 49, 50, 0, 115, 67, 68, 116, 0, 49, 50, 49, 50, 49, 50, 0, 115, 67, 65, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 0, 0, 0, 0, 115, 67, 65, 66, 65, 66, 65, 66, 0, 0, 51, 52, 0, 0, 65, 66, 65, 66, 65, 66, 0, 0, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 67, 68, 116, 0, 0, 0, 0, 0, 114, 0, 114, 0, 114, 0, 0, 115, 67, 68, 116, 0, 114, 0, 114, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":18, + "id":1, + "name":"top", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":46, + "x":0, + "y":0 + }], + "nextlayerid":3, + "nextobjectid":1, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"1.3.3", + "tileheight":32, + "tilesets":[ + { + "firstgid":1, + "source":"office_1.tsx" + }, + { + "columns":8, + "firstgid":257, + "image":"floortileset.png", + "imageheight":256, + "imagewidth":256, + "margin":0, + "name":"floortileset", + "spacing":0, + "tilecount":64, + "tileheight":32, + "tiles":[ + { + "id":37, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }], + "tilewidth":32 + }], + "tilewidth":32, + "type":"map", + "version":1.2, + "width":46 } \ No newline at end of file From 33c58874e03fbdfc0e8d6b9d544db9cae788abaf Mon Sep 17 00:00:00 2001 From: kharhamel Date: Mon, 13 Apr 2020 19:40:10 +0200 Subject: [PATCH 46/73] create an env variable for debug mode --- .env.template | 1 + .gitignore | 1 + docker-compose.yaml | 1 + front/src/Enum/EnvironmentVariable.ts | 2 ++ front/src/Phaser/Game/GameScene.ts | 18 ++++++++++-------- front/src/index.ts | 4 ++-- front/webpack.config.js | 2 +- 7 files changed, 18 insertions(+), 11 deletions(-) create mode 100644 .env.template diff --git a/.env.template b/.env.template new file mode 100644 index 00000000..81044e99 --- /dev/null +++ b/.env.template @@ -0,0 +1 @@ +DEBUG_MODE=false \ No newline at end of file diff --git a/.gitignore b/.gitignore index a806c47e..bc00a82a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.env .idea .vagrant Vagrantfile \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index a23a5204..03842238 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -13,6 +13,7 @@ services: front: image: thecodingmachine/nodejs:12 environment: + DEBUG_MODE: "$DEBUG_MODE" HOST: "0.0.0.0" NODE_ENV: development API_URL: http://api.workadventure.localhost diff --git a/front/src/Enum/EnvironmentVariable.ts b/front/src/Enum/EnvironmentVariable.ts index 3cf52b85..b4dd4f26 100644 --- a/front/src/Enum/EnvironmentVariable.ts +++ b/front/src/Enum/EnvironmentVariable.ts @@ -1,8 +1,10 @@ +const DEBUG_MODE: boolean = !!process.env.DEBUG_MODE || false; const API_URL = process.env.API_URL || "http://api.workadventure.localhost"; const ROOM = [process.env.ROOM || "THECODINGMACHINE"]; const RESOLUTION = 2; export { + DEBUG_MODE, API_URL, RESOLUTION, ROOM diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 48acf4a2..88514a6e 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -2,7 +2,7 @@ import {GameManagerInterface, StatusGameManagerEnum} from "./GameManager"; import {MessageUserPositionInterface} from "../../Connexion"; import {CameraManager, CameraManagerInterface} from "./CameraManager"; import {CurrentGamerInterface, GamerInterface, Player} from "../Player/Player"; -import {RESOLUTION} from "../../Enum/EnvironmentVariable"; +import {DEBUG_MODE, RESOLUTION} from "../../Enum/EnvironmentVariable"; import Tile = Phaser.Tilemaps.Tile; export enum Textures { @@ -97,13 +97,14 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ //this.CurrentPlayer.say("Collision with layer : "+ (object2 as Tile).layer.name) }); Layer.setCollisionByProperty({collides: true}); - //debug code - //debug code to see the collision hitbox of the object in the top layer - /*Layer.renderDebug(this.add.graphics(), { - tileColor: null, //non-colliding tiles - collidingTileColor: new Phaser.Display.Color(243, 134, 48, 200), // Colliding tiles, - faceColor: new Phaser.Display.Color(40, 39, 37, 255) // Colliding face edges - });*/ + if (DEBUG_MODE) { + //debug code to see the collision hitbox of the object in the top layer + Layer.renderDebug(this.add.graphics(), { + tileColor: null, //non-colliding tiles + collidingTileColor: new Phaser.Display.Color(243, 134, 48, 200), // Colliding tiles, + faceColor: new Phaser.Display.Color(40, 39, 37, 255) // Colliding face edges + }); + } }); } @@ -142,6 +143,7 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ //pixel position toz tile position let tile = this.Map.getTileAt(this.Map.worldToTileX(pointer.worldX), this.Map.worldToTileY(pointer.worldY)); if(tile){ + console.log(tile) this.CurrentPlayer.say("Your touch " + tile.layer.name); } }); diff --git a/front/src/index.ts b/front/src/index.ts index 5d90a19e..f3dfb22f 100644 --- a/front/src/index.ts +++ b/front/src/index.ts @@ -1,7 +1,7 @@ import 'phaser'; import GameConfig = Phaser.Types.Core.GameConfig; import {GameManager} from "./Phaser/Game/GameManager"; -import {RESOLUTION} from "./Enum/EnvironmentVariable"; +import {DEBUG_MODE, RESOLUTION} from "./Enum/EnvironmentVariable"; let gameManager = new GameManager(); @@ -15,7 +15,7 @@ const config: GameConfig = { physics: { default: "arcade", arcade: { - debug: true + debug: DEBUG_MODE } } }; diff --git a/front/webpack.config.js b/front/webpack.config.js index 21431e4c..ff164804 100644 --- a/front/webpack.config.js +++ b/front/webpack.config.js @@ -29,6 +29,6 @@ module.exports = { new webpack.ProvidePlugin({ Phaser: 'phaser' }), - new webpack.EnvironmentPlugin(['API_URL']) + new webpack.EnvironmentPlugin(['API_URL', 'DEBUG_MODE']) ] }; From 7b41a9ee2fefa3506a72e5ef1de83f31254357ab Mon Sep 17 00:00:00 2001 From: NIP Date: Mon, 13 Apr 2020 19:52:25 +0200 Subject: [PATCH 47/73] Add tilesets --- front/dist/maps/floortileset.png | Bin 0 -> 87093 bytes front/dist/maps/office_1.tsx | 254 +++++++++++++++++++++++++++++++ 2 files changed, 254 insertions(+) create mode 100644 front/dist/maps/floortileset.png create mode 100644 front/dist/maps/office_1.tsx diff --git a/front/dist/maps/floortileset.png b/front/dist/maps/floortileset.png new file mode 100644 index 0000000000000000000000000000000000000000..c672dcbbfd97cd2fc89df49f4e37d52533b7ea73 GIT binary patch literal 87093 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

R;XhMK`9&U#i3b`hdM=%=1O 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 literal 0 HcmV?d00001 diff --git a/front/dist/maps/office_1.tsx b/front/dist/maps/office_1.tsx new file mode 100644 index 00000000..6a0d5cdb --- /dev/null +++ b/front/dist/maps/office_1.tsx @@ -0,0 +1,254 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 1774594e76ac779b52d6d2d117d2f3db699f579f Mon Sep 17 00:00:00 2001 From: kharhamel Date: Mon, 13 Apr 2020 19:56:41 +0200 Subject: [PATCH 48/73] deleted cameraManager, use camera follow code instead --- front/src/Enum/EnvironmentVariable.ts | 2 ++ front/src/Phaser/Game/CameraManager.ts | 49 -------------------------- front/src/Phaser/Game/GameScene.ts | 20 ++++++----- front/src/Phaser/Player/Player.ts | 7 ---- 4 files changed, 14 insertions(+), 64 deletions(-) delete mode 100644 front/src/Phaser/Game/CameraManager.ts diff --git a/front/src/Enum/EnvironmentVariable.ts b/front/src/Enum/EnvironmentVariable.ts index b4dd4f26..db5b912c 100644 --- a/front/src/Enum/EnvironmentVariable.ts +++ b/front/src/Enum/EnvironmentVariable.ts @@ -2,10 +2,12 @@ const DEBUG_MODE: boolean = !!process.env.DEBUG_MODE || false; const API_URL = process.env.API_URL || "http://api.workadventure.localhost"; const ROOM = [process.env.ROOM || "THECODINGMACHINE"]; const RESOLUTION = 2; +const ZOOM_LEVEL = 3/4; export { DEBUG_MODE, API_URL, RESOLUTION, + ZOOM_LEVEL, ROOM } \ No newline at end of file diff --git a/front/src/Phaser/Game/CameraManager.ts b/front/src/Phaser/Game/CameraManager.ts deleted file mode 100644 index 3b2dc06b..00000000 --- a/front/src/Phaser/Game/CameraManager.ts +++ /dev/null @@ -1,49 +0,0 @@ -import {RESOLUTION} from "../../Enum/EnvironmentVariable"; -import {Player} from "../Player/Player"; -import {GameSceneInterface} from "./GameScene"; - -export interface CameraManagerInterface { - moveCamera(CurrentPlayer : Player) : void; -} - -export class CameraManager implements CameraManagerInterface{ - Scene : GameSceneInterface; - Camera : Phaser.Cameras.Scene2D.Camera; - - constructor( - Scene: GameSceneInterface, - Camera : Phaser.Cameras.Scene2D.Camera, - ) { - this.Scene = Scene; - this.Camera = Camera; - } - - moveCamera(CurrentPlayer : Player): void { - //center of camera - let startX = ((window.innerWidth / 2) / RESOLUTION); - let startY = ((window.innerHeight / 2) / RESOLUTION); - - let limit = { - top: startY, - left: startX, - bottom : this.Scene.Map.heightInPixels - startY, - right: this.Scene.Map.widthInPixels - startX, - }; - - if(CurrentPlayer.x < limit.left){ - this.Camera.scrollX = 0; - }else if(CurrentPlayer.x > limit.right){ - this.Camera.scrollX = (limit.right - startX); - }else { - this.Camera.scrollX = (CurrentPlayer.x - startX); - } - - if(CurrentPlayer.y < limit.top){ - this.Camera.scrollY = 0; - }else if(CurrentPlayer.y > limit.bottom){ - this.Camera.scrollY = (limit.bottom - startY); - }else { - this.Camera.scrollY = (CurrentPlayer.y - startY); - } - } -} \ No newline at end of file diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 88514a6e..582312dc 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1,8 +1,7 @@ import {GameManagerInterface, StatusGameManagerEnum} from "./GameManager"; import {MessageUserPositionInterface} from "../../Connexion"; -import {CameraManager, CameraManagerInterface} from "./CameraManager"; import {CurrentGamerInterface, GamerInterface, Player} from "../Player/Player"; -import {DEBUG_MODE, RESOLUTION} from "../../Enum/EnvironmentVariable"; +import {DEBUG_MODE, RESOLUTION, ZOOM_LEVEL} from "../../Enum/EnvironmentVariable"; import Tile = Phaser.Tilemaps.Tile; export enum Textures { @@ -22,7 +21,6 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ GameManager : GameManagerInterface; RoomId : string; Terrain : Phaser.Tilemaps.Tileset; - Camera: CameraManagerInterface; CurrentPlayer: CurrentGamerInterface; MapPlayers : Phaser.Physics.Arcade.Group; Map: Phaser.Tilemaps.Tilemap; @@ -76,14 +74,22 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ //init event click this.EventToClickOnTile(); - //initialise camera - this.Camera = new CameraManager(this, this.cameras.main); - //initialise list of other player this.MapPlayers = this.physics.add.group({ immovable: true }); //notify game manager can to create currentUser in map this.GameManager.createCurrentPlayer(); + + + //initialise camera + this.initCamera(); + } + + //todo: in a dedicated class/function? + initCamera() { + this.cameras.main.setBounds(0,0, this.Map.widthInPixels, this.Map.heightInPixels); + this.cameras.main.startFollow(this.CurrentPlayer); + this.cameras.main.setZoom(ZOOM_LEVEL); } addLayer(Layer : Phaser.Tilemaps.StaticTilemapLayer){ @@ -128,7 +134,6 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ this, this.startX, this.startY, - this.Camera, ); this.CurrentPlayer.initAnimation(); @@ -212,7 +217,6 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ this, MessageUserPosition.position.x, MessageUserPosition.position.y, - this.Camera, ); player.initAnimation(); this.MapPlayers.add(player); diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index e2d7d692..4cd1a6aa 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -1,7 +1,6 @@ import {getPlayerAnimations, playAnimation, PlayerAnimationNames} from "./Animation"; import {GameSceneInterface, Textures} from "../Game/GameScene"; import {ConnexionInstance} from "../Game/GameManager"; -import {CameraManagerInterface} from "../Game/CameraManager"; import {MessageUserPositionInterface} from "../../Connexion"; import {ActiveEventList, UserInputEvent, UserInputManager} from "../UserInput/UserInputManager"; import {PlayableCaracter} from "../Entity/PlayableCaracter"; @@ -9,7 +8,6 @@ import {PlayableCaracter} from "../Entity/PlayableCaracter"; export interface CurrentGamerInterface extends PlayableCaracter{ userId : string; PlayerValue : string; - CameraManager: CameraManagerInterface; initAnimation() : void; moveUser() : void; say(text : string) : void; @@ -18,7 +16,6 @@ export interface CurrentGamerInterface extends PlayableCaracter{ export interface GamerInterface extends PlayableCaracter{ userId : string; PlayerValue : string; - CameraManager: CameraManagerInterface; initAnimation() : void; updatePosition(MessageUserPosition : MessageUserPositionInterface) : void; say(text : string) : void; @@ -27,7 +24,6 @@ export interface GamerInterface extends PlayableCaracter{ export class Player extends PlayableCaracter implements CurrentGamerInterface, GamerInterface { userId: string; PlayerValue: string; - CameraManager: CameraManagerInterface; userInputManager: UserInputManager; constructor( @@ -35,7 +31,6 @@ export class Player extends PlayableCaracter implements CurrentGamerInterface, G Scene: GameSceneInterface, x: number, y: number, - CameraManager: CameraManagerInterface, PlayerValue: string = Textures.Player ) { super(Scene, x, y, PlayerValue, 1); @@ -46,7 +41,6 @@ export class Player extends PlayableCaracter implements CurrentGamerInterface, G //set data this.userId = userId; this.PlayerValue = PlayerValue; - this.CameraManager = CameraManager; //the current player model should be push away by other players to prevent conflict this.setImmovable(false); @@ -96,7 +90,6 @@ export class Player extends PlayableCaracter implements CurrentGamerInterface, G this.stop(); } this.sharePosition(direction); - this.CameraManager.moveCamera(this); } private sharePosition(direction: string) { From f00d10d56ac4824a2beb3e58175c2e9d7a4d78c7 Mon Sep 17 00:00:00 2001 From: NIP Date: Mon, 13 Apr 2020 20:05:13 +0200 Subject: [PATCH 49/73] Change calque names into game scene TS file --- 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 48acf4a2..12acded0 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -66,8 +66,8 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ //add layer on map this.Layers = new Array(); - this.addLayer( this.Map.createStaticLayer("Calque 1", [this.Terrain], 0, 0).setDepth(-2) ); - this.addLayer( this.Map.createStaticLayer("Calque 2", [this.Terrain], 0, 0).setDepth(-1) ); + this.addLayer( this.Map.createStaticLayer("bottom", [this.Terrain], 0, 0).setDepth(-2) ); + this.addLayer( this.Map.createStaticLayer("top", [this.Terrain], 0, 0).setDepth(-1) ); //add entities this.Objects = new Array(); From 84f04206940fcf1c4985306d7cb6961d01c665d7 Mon Sep 17 00:00:00 2001 From: NIP Date: Mon, 13 Apr 2020 20:34:19 +0200 Subject: [PATCH 50/73] Change GameScene to import new png, change map name and add missing png files --- front/dist/maps/tilesets_deviant_milkian_1.png | Bin 0 -> 33809 bytes front/src/Phaser/Game/GameScene.ts | 5 +++-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 front/dist/maps/tilesets_deviant_milkian_1.png diff --git a/front/dist/maps/tilesets_deviant_milkian_1.png b/front/dist/maps/tilesets_deviant_milkian_1.png new file mode 100644 index 0000000000000000000000000000000000000000..47400131336dd700c36323efafadeb61005e0c77 GIT binary patch literal 33809 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

2L{|`B8wo&sk~P?wgEkES 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 z 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 U>aL9^1peK literal 0 HcmV?d00001 diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 12acded0..ab4703e1 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -41,8 +41,9 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ //hook preload scene preload(): void { - this.load.image(Textures.Tiles, 'maps/tiles.png'); - this.load.tilemapTiledJSON(Textures.Map, 'maps/map2.json'); + this.load.image(Textures.Tiles, 'maps/floortileset.png'); + this.load.image(Textures.Tiles, 'maps/tilesets_deviant_milkian_1.png'); + this.load.tilemapTiledJSON(Textures.Map, 'maps/map.json'); this.load.image(Textures.Rock, 'resources/objects/rockSprite.png'); this.load.spritesheet(Textures.Player, 'resources/characters/pipoya/Male 01-1.png', From c42dbc3f954806cd662cc0715faeb068ea05dc33 Mon Sep 17 00:00:00 2001 From: NIP Date: Mon, 13 Apr 2020 21:24:48 +0200 Subject: [PATCH 51/73] Fix JS issue regarding multiple tilesets --- front/src/Phaser/Game/GameScene.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index ab4703e1..f202c7c8 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -9,7 +9,8 @@ export enum Textures { Rock = 'rock', Player = 'playerModel', Map = 'map', - Tiles = 'tiles' + Tiles = 'tiles', + Tiles2 = 'tiles2' } export interface GameSceneInterface extends Phaser.Scene { @@ -42,7 +43,7 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ //hook preload scene preload(): void { this.load.image(Textures.Tiles, 'maps/floortileset.png'); - this.load.image(Textures.Tiles, 'maps/tilesets_deviant_milkian_1.png'); + this.load.image(Textures.Tiles2, 'maps/tilesets_deviant_milkian_1.png'); this.load.tilemapTiledJSON(Textures.Map, 'maps/map.json'); this.load.image(Textures.Rock, 'resources/objects/rockSprite.png'); this.load.spritesheet(Textures.Player, @@ -61,6 +62,8 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ this.Map = this.add.tilemap("map"); this.Terrain = this.Map.addTilesetImage("tiles", "tiles"); this.Map.createStaticLayer("tiles", "tiles"); + this.Terrain = this.Map.addTilesetImage("tiles2", "tiles2"); + this.Map.createStaticLayer("tiles2", "tiles2"); //permit to set bound collision this.physics.world.setBounds(0,0, this.Map.widthInPixels, this.Map.heightInPixels); From a2ed7164e447fdf92c823d6d59781ec8d95a9993 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Tue, 14 Apr 2020 20:04:55 +0200 Subject: [PATCH 52/73] implemented basic e2e testing --- docker-compose.ci.yml | 20 + docker-compose.yaml | 3 + e2e/.gitignore | 3 + e2e/CYPRESS.md | 28 + e2e/cypress.json | 6 + e2e/cypress/integration/spec.js | 25 + e2e/package-lock.json | 1406 ++++++++++++++++++++++++++ e2e/package.json | 9 + front/src/Cypress/CypressAsserter.ts | 32 + front/src/Phaser/Game/GameScene.ts | 5 + front/src/index.ts | 3 + 11 files changed, 1540 insertions(+) create mode 100644 docker-compose.ci.yml create mode 100644 e2e/.gitignore create mode 100644 e2e/CYPRESS.md create mode 100644 e2e/cypress.json create mode 100644 e2e/cypress/integration/spec.js create mode 100644 e2e/package-lock.json create mode 100644 e2e/package.json create mode 100644 front/src/Cypress/CypressAsserter.ts diff --git a/docker-compose.ci.yml b/docker-compose.ci.yml new file mode 100644 index 00000000..eee558cc --- /dev/null +++ b/docker-compose.ci.yml @@ -0,0 +1,20 @@ +version: '3.3' + +services: + + wait_app: + image: dadarek/wait-for-dependencies + depends_on: + - traefik + command: front:8080 + cypress: + # the Docker image to use from https://github.com/cypress-io/cypress-docker-images + image: "cypress/included:3.2.0" + depends_on: + - traefik + environment: + # pass base url to test pointing at the web application + - CYPRESS_baseUrl=http://front:8080 + working_dir: /e2e + volumes: + - ./e2e/:/e2e \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index 03842238..ca2197a8 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -7,6 +7,9 @@ services: - "80:80" # The Web UI (enabled by --api.insecure=true) - "8080:8080" + depends_on: + - back + - front volumes: - /var/run/docker.sock:/var/run/docker.sock diff --git a/e2e/.gitignore b/e2e/.gitignore new file mode 100644 index 00000000..92fb84fd --- /dev/null +++ b/e2e/.gitignore @@ -0,0 +1,3 @@ +screenshots/ +videos/ +node_modules/ \ No newline at end of file diff --git a/e2e/CYPRESS.md b/e2e/CYPRESS.md new file mode 100644 index 00000000..4ab56cbb --- /dev/null +++ b/e2e/CYPRESS.md @@ -0,0 +1,28 @@ +# Testing with cypress + +This project use [cypress](https://www.cypress.io/) to do functional testing of the website. +Unfortunately we cannot integrate it with docker-compose for the moment, so you will need to install some packages locally on your pc. + +You will need to install theses dependancies on linux (don't know about mac): + +```bash +sudo apt update +sudo apt install libgtk2.0-0 libgtk-3-0 libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb +``` + +Cypress can be installed locally in the e2e directory +```bash +cd e2e +npm install +``` + + +How to use: +```bash +npm run cy:run +npm run cy:open +``` + +The first command will run all tests in the terminal, while the second will open the interactive task runner which allow you to easily manage the test workflow + +[More details here](https://docs.cypress.io/guides/getting-started/testing-your-app.html#Step-1-Start-your-server) diff --git a/e2e/cypress.json b/e2e/cypress.json new file mode 100644 index 00000000..3c5c7d1c --- /dev/null +++ b/e2e/cypress.json @@ -0,0 +1,6 @@ +{ + "baseUrl": "http://workadventure.localhost", + "video": false, + "pluginsFile": false, + "supportFile": false +} \ No newline at end of file diff --git a/e2e/cypress/integration/spec.js b/e2e/cypress/integration/spec.js new file mode 100644 index 00000000..7f94a502 --- /dev/null +++ b/e2e/cypress/integration/spec.js @@ -0,0 +1,25 @@ +Cypress.on('window:before:load', (win) => { + // because this is called before any scripts + // have loaded - the ga function is undefined + // so we need to create it. + win.cypressAsserter = cy.stub().as('ca') +}) + +describe('WorkAdventureGame', () => { + beforeEach(() => { + cy.visit('/', { + onBeforeLoad (win) { + cy.spy(win.console, 'log').as('console.log') + }, + }) + + }); + + it('loads', () => { + cy.get('@console.log').should('be.calledWith', 'Started the game') + cy.get('@console.log').should('be.calledWith', 'Preloading') + cy.get('@console.log').should('be.calledWith', 'Preloading done') + cy.get('@console.log').should('be.calledWith', 'startInit') + cy.get('@console.log').should('be.calledWith', 'startInit done') + }); +}); \ No newline at end of file diff --git a/e2e/package-lock.json b/e2e/package-lock.json new file mode 100644 index 00000000..bfd6255c --- /dev/null +++ b/e2e/package-lock.json @@ -0,0 +1,1406 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "@cypress/listr-verbose-renderer": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@cypress/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz", + "integrity": "sha1-p3SS9LEdzHxEajSz4ochr9M8ZCo=", + "requires": { + "chalk": "1.1.3", + "cli-cursor": "1.0.2", + "date-fns": "1.30.1", + "figures": "1.7.0" + }, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "@cypress/xvfb": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", + "integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==", + "requires": { + "debug": "3.2.6", + "lodash.once": "4.1.1" + } + }, + "@types/sizzle": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz", + "integrity": "sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg==" + }, + "ajv": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.11.0.tgz", + "integrity": "sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA==", + "requires": { + "fast-deep-equal": "3.1.1", + "fast-json-stable-stringify": "2.1.0", + "json-schema-traverse": "0.4.1", + "uri-js": "4.2.2" + } + }, + "ansi-escapes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=" + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "arch": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.1.1.tgz", + "integrity": "sha512-BLM56aPo9vLLFVa8+/+pJLnrZ7QGGTVHWsCwieAWT9o9K8UeGaQbzZbGoabWLOo2ksBCztoXdqBZBplqLDDCSg==" + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "2.1.2" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", + "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", + "requires": { + "lodash": "4.17.15" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", + "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "0.14.5" + } + }, + "bluebird": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz", + "integrity": "sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw=" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "cachedir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-1.3.0.tgz", + "integrity": "sha512-O1ji32oyON9laVPJL1IZ5bmwd2cB46VfpxkDequezH+15FDzzVddEyrGEeX4WusDSqKxdyFdDQDEG1yo1GoWkg==", + "requires": { + "os-homedir": "1.0.2" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "1.9.3" + } + } + } + }, + "check-more-types": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", + "integrity": "sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA=" + }, + "ci-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", + "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==" + }, + "cli-cursor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "requires": { + "restore-cursor": "1.0.1" + } + }, + "cli-spinners": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-0.1.2.tgz", + "integrity": "sha1-u3ZNiOGF+54eaiofGXcjGPYF4xw=" + }, + "cli-truncate": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz", + "integrity": "sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=", + "requires": { + "slice-ansi": "0.0.4", + "string-width": "1.0.2" + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "1.0.0" + } + }, + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==" + }, + "common-tags": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz", + "integrity": "sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "1.1.1", + "inherits": "2.0.4", + "readable-stream": "2.3.7", + "typedarray": "0.0.6" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "requires": { + "nice-try": "1.0.5", + "path-key": "2.0.1", + "semver": "5.7.1", + "shebang-command": "1.2.0", + "which": "1.3.1" + } + }, + "cypress": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-3.8.3.tgz", + "integrity": "sha512-I9L/d+ilTPPA4vq3NC1OPKmw7jJIpMKNdyfR8t1EXYzYCjyqbc59migOm1YSse/VRbISLJ+QGb5k4Y3bz2lkYw==", + "requires": { + "@cypress/listr-verbose-renderer": "0.4.1", + "@cypress/xvfb": "1.2.4", + "@types/sizzle": "2.3.2", + "arch": "2.1.1", + "bluebird": "3.5.0", + "cachedir": "1.3.0", + "chalk": "2.4.2", + "check-more-types": "2.24.0", + "commander": "2.15.1", + "common-tags": "1.8.0", + "debug": "3.2.6", + "eventemitter2": "4.1.2", + "execa": "0.10.0", + "executable": "4.1.1", + "extract-zip": "1.6.7", + "fs-extra": "5.0.0", + "getos": "3.1.1", + "is-ci": "1.2.1", + "is-installed-globally": "0.1.0", + "lazy-ass": "1.6.0", + "listr": "0.12.0", + "lodash": "4.17.15", + "log-symbols": "2.2.0", + "minimist": "1.2.0", + "moment": "2.24.0", + "ramda": "0.24.1", + "request": "2.88.0", + "request-progress": "3.0.0", + "supports-color": "5.5.0", + "tmp": "0.1.0", + "untildify": "3.0.3", + "url": "0.11.0", + "yauzl": "2.10.0" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "1.0.0" + } + }, + "date-fns": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", + "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==" + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "2.1.2" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "0.1.1", + "safer-buffer": "2.1.2" + } + }, + "elegant-spinner": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz", + "integrity": "sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "eventemitter2": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-4.1.2.tgz", + "integrity": "sha1-DhqEd6+CGm7zmVsxG/dMI6UkfxU=" + }, + "execa": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", + "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", + "requires": { + "cross-spawn": "6.0.5", + "get-stream": "3.0.0", + "is-stream": "1.1.0", + "npm-run-path": "2.0.2", + "p-finally": "1.0.0", + "signal-exit": "3.0.2", + "strip-eof": "1.0.0" + } + }, + "executable": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", + "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", + "requires": { + "pify": "2.3.0" + } + }, + "exit-hook": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", + "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=" + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extract-zip": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz", + "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=", + "requires": { + "concat-stream": "1.6.2", + "debug": "2.6.9", + "mkdirp": "0.5.1", + "yauzl": "2.4.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "yauzl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", + "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", + "requires": { + "fd-slicer": "1.0.1" + } + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", + "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==" + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "fd-slicer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", + "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", + "requires": { + "pend": "1.2.0" + } + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "requires": { + "escape-string-regexp": "1.0.5", + "object-assign": "4.1.1" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.8", + "mime-types": "2.1.26" + } + }, + "fs-extra": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz", + "integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==", + "requires": { + "graceful-fs": "4.2.3", + "jsonfile": "4.0.0", + "universalify": "0.1.2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" + }, + "getos": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/getos/-/getos-3.1.1.tgz", + "integrity": "sha512-oUP1rnEhAr97rkitiszGP9EgDVYnmchgFzfqRzSkgtfv7ai6tEi7Ko8GgjNXts7VLWEqrTWyhsOKLe5C5b/Zkg==", + "requires": { + "async": "2.6.1" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "1.0.0" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.4", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "global-dirs": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", + "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", + "requires": { + "ini": "1.3.5" + } + }, + "graceful-fs": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==" + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "requires": { + "ajv": "6.11.0", + "har-schema": "2.0.0" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "requires": { + "ansi-regex": "2.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.16.1" + } + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "requires": { + "repeating": "2.0.1" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + }, + "is-ci": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", + "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", + "requires": { + "ci-info": "1.6.0" + } + }, + "is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-installed-globally": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", + "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", + "requires": { + "global-dirs": "0.1.1", + "is-path-inside": "1.0.1" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "requires": { + "path-is-inside": "1.0.2" + } + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "4.2.3" + } + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "lazy-ass": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", + "integrity": "sha1-eZllXoZGwX8In90YfRUNMyTVRRM=" + }, + "listr": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/listr/-/listr-0.12.0.tgz", + "integrity": "sha1-a84sD1YD+klYDqF81qAMwOX6RRo=", + "requires": { + "chalk": "1.1.3", + "cli-truncate": "0.2.1", + "figures": "1.7.0", + "indent-string": "2.1.0", + "is-promise": "2.1.0", + "is-stream": "1.1.0", + "listr-silent-renderer": "1.1.1", + "listr-update-renderer": "0.2.0", + "listr-verbose-renderer": "0.4.1", + "log-symbols": "1.0.2", + "log-update": "1.0.2", + "ora": "0.2.3", + "p-map": "1.2.0", + "rxjs": "5.5.12", + "stream-to-observable": "0.1.0", + "strip-ansi": "3.0.1" + }, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "log-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", + "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", + "requires": { + "chalk": "1.1.3" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "listr-silent-renderer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz", + "integrity": "sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=" + }, + "listr-update-renderer": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.2.0.tgz", + "integrity": "sha1-yoDhd5tOcCZoB+ju0a1qvjmFUPk=", + "requires": { + "chalk": "1.1.3", + "cli-truncate": "0.2.1", + "elegant-spinner": "1.0.1", + "figures": "1.7.0", + "indent-string": "3.2.0", + "log-symbols": "1.0.2", + "log-update": "1.0.2", + "strip-ansi": "3.0.1" + }, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=" + }, + "log-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", + "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", + "requires": { + "chalk": "1.1.3" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "listr-verbose-renderer": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz", + "integrity": "sha1-ggb0z21S3cWCfl/RSYng6WWTOjU=", + "requires": { + "chalk": "1.1.3", + "cli-cursor": "1.0.2", + "date-fns": "1.30.1", + "figures": "1.7.0" + }, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "requires": { + "chalk": "2.4.2" + } + }, + "log-update": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-1.0.2.tgz", + "integrity": "sha1-GZKfZMQJPS0ucHWh2tivWcKWuNE=", + "requires": { + "ansi-escapes": "1.4.0", + "cli-cursor": "1.0.2" + } + }, + "mime-db": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", + "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" + }, + "mime-types": { + "version": "2.1.26", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", + "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", + "requires": { + "mime-db": "1.43.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "1.1.11" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + } + } + }, + "moment": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "requires": { + "path-key": "2.0.1" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1.0.2" + } + }, + "onetime": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=" + }, + "ora": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/ora/-/ora-0.2.3.tgz", + "integrity": "sha1-N1J9Igrc1Tw5tzVx11QVbV22V6Q=", + "requires": { + "chalk": "1.1.3", + "cli-cursor": "1.0.2", + "cli-spinners": "0.1.2", + "object-assign": "4.1.1" + }, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, + "p-map": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", + "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "psl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz", + "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==" + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + }, + "ramda": { + "version": "0.24.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.24.1.tgz", + "integrity": "sha1-w7d1UZfzW43DUCIoJixMkd22uFc=" + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.4", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" + } + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "requires": { + "is-finite": "1.0.2" + } + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "requires": { + "aws-sign2": "0.7.0", + "aws4": "1.9.1", + "caseless": "0.12.0", + "combined-stream": "1.0.8", + "extend": "3.0.2", + "forever-agent": "0.6.1", + "form-data": "2.3.3", + "har-validator": "5.1.3", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.26", + "oauth-sign": "0.9.0", + "performance-now": "2.1.0", + "qs": "6.5.2", + "safe-buffer": "5.1.2", + "tough-cookie": "2.4.3", + "tunnel-agent": "0.6.0", + "uuid": "3.4.0" + } + }, + "request-progress": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", + "integrity": "sha1-TKdUCBx/7GP1BeT6qCWqBs1mnb4=", + "requires": { + "throttleit": "1.0.0" + } + }, + "restore-cursor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "requires": { + "exit-hook": "1.1.1", + "onetime": "1.1.0" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "requires": { + "glob": "7.1.6" + } + }, + "rxjs": { + "version": "5.5.12", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.12.tgz", + "integrity": "sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw==", + "requires": { + "symbol-observable": "1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "requires": { + "shebang-regex": "1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "slice-ansi": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=" + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "0.2.4", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.2", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.2", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "safer-buffer": "2.1.2", + "tweetnacl": "0.14.5" + } + }, + "stream-to-observable": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/stream-to-observable/-/stream-to-observable-0.1.0.tgz", + "integrity": "sha1-Rb8dny19wJvtgfHDB8Qw5ouEz/4=" + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "3.0.0" + } + }, + "symbol-observable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", + "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=" + }, + "throttleit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", + "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=" + }, + "tmp": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz", + "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==", + "requires": { + "rimraf": "2.7.1" + } + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "1.7.0", + "punycode": "1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + } + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, + "untildify": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-3.0.3.tgz", + "integrity": "sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA==" + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "2.1.1" + } + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + } + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "1.3.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "2.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "requires": { + "buffer-crc32": "0.2.13", + "fd-slicer": "1.1.0" + }, + "dependencies": { + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "requires": { + "pend": "1.2.0" + } + } + } + } + } +} diff --git a/e2e/package.json b/e2e/package.json new file mode 100644 index 00000000..97bd098c --- /dev/null +++ b/e2e/package.json @@ -0,0 +1,9 @@ +{ + "dependencies": { + "cypress": "^3.8.3" + }, + "scripts": { + "cy:run": "cypress run", + "cy:open": "cypress open" + } +} diff --git a/front/src/Cypress/CypressAsserter.ts b/front/src/Cypress/CypressAsserter.ts new file mode 100644 index 00000000..95adb156 --- /dev/null +++ b/front/src/Cypress/CypressAsserter.ts @@ -0,0 +1,32 @@ +declare let window:any; + +//this class is used to communicate with cypress, our e2e testing client +//Since cypress cannot manipulate canvas, we notified it with console logs +class CypressAsserter { + + constructor() { + window.cypressAsserter = this + } + + gameStarted() { + console.log('Started the game') + } + + preloadStarted() { + console.log('Preloading') + } + + preloadFinished() { + console.log('Preloading done') + } + + initStarted() { + console.log('startInit') + } + + initFinished() { + console.log('startInit done') + } +} + +export const cypressAsserter = new CypressAsserter() \ No newline at end of file diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 582312dc..6bd26190 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -3,6 +3,7 @@ import {MessageUserPositionInterface} from "../../Connexion"; import {CurrentGamerInterface, GamerInterface, Player} from "../Player/Player"; import {DEBUG_MODE, RESOLUTION, ZOOM_LEVEL} from "../../Enum/EnvironmentVariable"; import Tile = Phaser.Tilemaps.Tile; +import {cypressAsserter} from "../../Cypress/CypressAsserter"; export enum Textures { Rock = 'rock', @@ -39,6 +40,7 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ //hook preload scene preload(): void { + cypressAsserter.preloadStarted(); this.load.image(Textures.Tiles, 'maps/tiles.png'); this.load.tilemapTiledJSON(Textures.Map, 'maps/map2.json'); this.load.image(Textures.Rock, 'resources/objects/rockSprite.png'); @@ -46,6 +48,7 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ 'resources/characters/pipoya/Male 01-1.png', { frameWidth: 32, frameHeight: 32 } ); + cypressAsserter.preloadFinished(); } //hook initialisation @@ -53,6 +56,7 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ //hook create scene create(): void { + cypressAsserter.initStarted(); //initalise map this.Map = this.add.tilemap("map"); @@ -83,6 +87,7 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ //initialise camera this.initCamera(); + cypressAsserter.initFinished(); } //todo: in a dedicated class/function? diff --git a/front/src/index.ts b/front/src/index.ts index f3dfb22f..e5306389 100644 --- a/front/src/index.ts +++ b/front/src/index.ts @@ -2,6 +2,7 @@ import 'phaser'; import GameConfig = Phaser.Types.Core.GameConfig; import {GameManager} from "./Phaser/Game/GameManager"; import {DEBUG_MODE, RESOLUTION} from "./Enum/EnvironmentVariable"; +import {cypressAsserter} from "./Cypress/CypressAsserter"; let gameManager = new GameManager(); @@ -20,6 +21,8 @@ const config: GameConfig = { } }; +cypressAsserter.gameStarted(); + gameManager.createGame().then(() => { let game = new Phaser.Game(config); From 56093201fb6b636e19f77d2e3ff8a0576205f866 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Tue, 14 Apr 2020 20:16:51 +0200 Subject: [PATCH 53/73] integrate it into the ci --- .github/workflows/continuous_integration.yml | 18 ++++++++++++++++++ docker-compose.ci.yml | 6 +++--- e2e/CYPRESS.md | 8 ++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 6e6745e4..35311fd1 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -65,3 +65,21 @@ jobs: - name: "Jasmine" run: yarn test working-directory: "back" + + e2e: + name: "End to end testing with cypress" + + runs-on: "ubuntu-latest" + + steps: + - name: "Checkout" + uses: "actions/checkout@v2.0.0" + + - name: "Init .env" + run: "cp .env.template .env" + + - name: "Init containers" + run: "docker-compose -f docker-compose.yaml -f docker-compose.ci.yml run --rm wait_app" #start the containers and then wait for the website to be online + + - name: "Run cypress" + run: "docker-compose -f docker-compose.yaml -f docker-compose.ci.yml up --exit-code-from cypress cypress" # run cypress in docker-compose and get its exit code diff --git a/docker-compose.ci.yml b/docker-compose.ci.yml index eee558cc..614a6c82 100644 --- a/docker-compose.ci.yml +++ b/docker-compose.ci.yml @@ -1,17 +1,17 @@ -version: '3.3' +version: '3' services: wait_app: image: dadarek/wait-for-dependencies depends_on: - - traefik + - reverse-proxy command: front:8080 cypress: # the Docker image to use from https://github.com/cypress-io/cypress-docker-images image: "cypress/included:3.2.0" depends_on: - - traefik + - reverse-proxy environment: # pass base url to test pointing at the web application - CYPRESS_baseUrl=http://front:8080 diff --git a/e2e/CYPRESS.md b/e2e/CYPRESS.md index 4ab56cbb..dce9d698 100644 --- a/e2e/CYPRESS.md +++ b/e2e/CYPRESS.md @@ -3,6 +3,8 @@ This project use [cypress](https://www.cypress.io/) to do functional testing of the website. Unfortunately we cannot integrate it with docker-compose for the moment, so you will need to install some packages locally on your pc. +## Getting Started + You will need to install theses dependancies on linux (don't know about mac): ```bash @@ -26,3 +28,9 @@ npm run cy:open The first command will run all tests in the terminal, while the second will open the interactive task runner which allow you to easily manage the test workflow [More details here](https://docs.cypress.io/guides/getting-started/testing-your-app.html#Step-1-Start-your-server) + +## How to test a game + +Cypress cannot "see" and so cannot directly manipulate the canva created by Phaser. + +This means we have to do workarounds such as exposing core objects in the window so that cypress can manipulate them or doing console that cypress can catch. \ No newline at end of file From 5fb3968234db69cb64bae98c4845a40caf00f314 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Tue, 14 Apr 2020 20:42:57 +0200 Subject: [PATCH 54/73] added artifact download --- .github/workflows/continuous_integration.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 35311fd1..eee27553 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -83,3 +83,10 @@ jobs: - name: "Run cypress" run: "docker-compose -f docker-compose.yaml -f docker-compose.ci.yml up --exit-code-from cypress cypress" # run cypress in docker-compose and get its exit code + + - name: "Upload the screenshot on test failure" + uses: actions/upload-artifact@v1 + if: failure() + with: + name: "screenshot" + path: "./e2e/cypress/screenshots/spec.js/WorkAdventureGame -- loads (failed).png" \ No newline at end of file From 935745b3b52709de213592575d067873eae5f9d5 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Tue, 14 Apr 2020 20:54:27 +0200 Subject: [PATCH 55/73] fixed the version mismatch of cypress between local and ci --- docker-compose.ci.yml | 2 +- e2e/cypress.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docker-compose.ci.yml b/docker-compose.ci.yml index 614a6c82..bdf5855a 100644 --- a/docker-compose.ci.yml +++ b/docker-compose.ci.yml @@ -9,7 +9,7 @@ services: command: front:8080 cypress: # the Docker image to use from https://github.com/cypress-io/cypress-docker-images - image: "cypress/included:3.2.0" + image: "cypress/included:3.8.3" depends_on: - reverse-proxy environment: diff --git a/e2e/cypress.json b/e2e/cypress.json index 3c5c7d1c..cea4979b 100644 --- a/e2e/cypress.json +++ b/e2e/cypress.json @@ -1,6 +1,7 @@ { "baseUrl": "http://workadventure.localhost", "video": false, + "defaultCommandTimeout": 20000, "pluginsFile": false, "supportFile": false } \ No newline at end of file From 482a344f459fe64d95ddb5f95cf834503ba30ad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Wed, 15 Apr 2020 19:23:06 +0200 Subject: [PATCH 56/73] Autoload tiles This commit adds a listener in the preload function that will be triggered as soon as the map is loaded. This function will load the resources from the map (tilesets) defined in the map. That way, we don't have to define manually the list of tiles that have to be loaded (at the expense of a slight delay in loading since we must wait for the map to be loaded to start loading the tiles). --- front/src/Phaser/Game/GameScene.ts | 14 +++- front/src/Phaser/Map/ITiledMap.ts | 113 +++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 front/src/Phaser/Map/ITiledMap.ts diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 582312dc..a089842b 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -3,6 +3,7 @@ import {MessageUserPositionInterface} from "../../Connexion"; import {CurrentGamerInterface, GamerInterface, Player} from "../Player/Player"; import {DEBUG_MODE, RESOLUTION, ZOOM_LEVEL} from "../../Enum/EnvironmentVariable"; import Tile = Phaser.Tilemaps.Tile; +import {ITiledMap} from "../Map/ITiledMap"; export enum Textures { Rock = 'rock', @@ -39,8 +40,17 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ //hook preload scene preload(): void { - this.load.image(Textures.Tiles, 'maps/tiles.png'); - this.load.tilemapTiledJSON(Textures.Map, 'maps/map2.json'); + let mapUrl = 'maps/map2.json'; + this.load.on('filecomplete-tilemapJSON-'+Textures.Map, (key: string, type: string, data: any) => { + // Triggered when the map is loaded + // Load tiles attached to the map recursively + let map: ITiledMap = data.data; + map.tilesets.forEach((tileset) => { + let path = mapUrl.substr(0, mapUrl.lastIndexOf('/')); + this.load.image(tileset.name, path + '/' + tileset.image); + }) + }); + this.load.tilemapTiledJSON(Textures.Map, mapUrl); this.load.image(Textures.Rock, 'resources/objects/rockSprite.png'); this.load.spritesheet(Textures.Player, 'resources/characters/pipoya/Male 01-1.png', diff --git a/front/src/Phaser/Map/ITiledMap.ts b/front/src/Phaser/Map/ITiledMap.ts new file mode 100644 index 00000000..ca10f218 --- /dev/null +++ b/front/src/Phaser/Map/ITiledMap.ts @@ -0,0 +1,113 @@ +/** + * Tiled Map Interface + * + * Represents the interface for the Tiled exported data structure (JSON). Used + * when loading resources via Resource loader. + */ +export interface ITiledMap { + width: number; + height: number; + layers: ITiledMapLayer[]; + nextobjectid: number; + + /** + * Map orientation (orthogonal) + */ + orientation: string; + properties: {[key: string]: string}; + + /** + * Render order (right-down) + */ + renderorder: string; + tileheight: number; + tilewidth: number; + tilesets: ITiledTileSet[]; + version: number; +} + +export interface ITiledMapLayer { + data: number[]|string; + height: number; + name: string; + opacity: number; + properties: {[key: string]: string}; + encoding: string; + compression?: string; + + /** + * Type of layer (tilelayer, objectgroup) + */ + type: string; + visible: boolean; + width: number; + x: number; + y: number; + + /** + * Draw order (topdown (default), index) + */ + draworder: string; + objects: ITiledMapObject[]; +} + +export interface ITiledMapObject { + id: number; + + /** + * Tile object id + */ + gid: number; + height: number; + name: string; + properties: {[key: string]: string}; + rotation: number; + type: string; + visible: boolean; + width: number; + x: number; + y: number; + + /** + * Whether or not object is an ellipse + */ + ellipse: boolean; + + /** + * Polygon points + */ + polygon: {x: number, y: number}[]; + + /** + * Polyline points + */ + polyline: {x: number, y: number}[]; +} + +export interface ITiledTileSet { + firstgid: number; + image: string; + + imageheight: number; + imagewidth: number; + margin: number; + name: string; + properties: {[key: string]: string}; + spacing: number; + tilecount: number; + tileheight: number; + tilewidth: number; + transparentcolor: string; + terrains: ITiledMapTerrain[]; + tiles: {[key: string]: { terrain: number[] }}; + + /** + * Refers to external tileset file (should be JSON) + */ + source: string; +} + +export interface ITiledMapTerrain { + name: string; + tile: number; +} From 5f118c2a4a5eb13867f6f60735ef80442e74bbdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Wed, 15 Apr 2020 19:39:26 +0200 Subject: [PATCH 57/73] Removing all reference to 'Tiles' constant in code --- front/src/Phaser/Game/GameScene.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index a089842b..e8597d73 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -8,8 +8,7 @@ import {ITiledMap} from "../Map/ITiledMap"; export enum Textures { Rock = 'rock', Player = 'playerModel', - Map = 'map', - Tiles = 'tiles' + Map = 'map' } export interface GameSceneInterface extends Phaser.Scene { @@ -21,21 +20,25 @@ export interface GameSceneInterface extends Phaser.Scene { export class GameScene extends Phaser.Scene implements GameSceneInterface{ GameManager : GameManagerInterface; RoomId : string; - Terrain : Phaser.Tilemaps.Tileset; + Terrains : Array; CurrentPlayer: CurrentGamerInterface; MapPlayers : Phaser.Physics.Arcade.Group; Map: Phaser.Tilemaps.Tilemap; Layers : Array; Objects : Array; + tilesetKeys : Array; startX = (window.innerWidth / 2) / RESOLUTION; startY = (window.innerHeight / 2) / RESOLUTION; + constructor(RoomId : string, GameManager : GameManagerInterface) { super({ key: "GameScene" }); this.RoomId = RoomId; this.GameManager = GameManager; + this.tilesetKeys = []; + this.Terrains = []; } //hook preload scene @@ -48,6 +51,7 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ map.tilesets.forEach((tileset) => { let path = mapUrl.substr(0, mapUrl.lastIndexOf('/')); this.load.image(tileset.name, path + '/' + tileset.image); + this.tilesetKeys.push(tileset.name); }) }); this.load.tilemapTiledJSON(Textures.Map, mapUrl); @@ -66,16 +70,17 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ //initalise map this.Map = this.add.tilemap("map"); - this.Terrain = this.Map.addTilesetImage("tiles", "tiles"); - this.Map.createStaticLayer("tiles", "tiles"); + this.tilesetKeys.forEach((key: string) => { + this.Terrains.push(this.Map.addTilesetImage(key, key)); + }); //permit to set bound collision this.physics.world.setBounds(0,0, this.Map.widthInPixels, this.Map.heightInPixels); //add layer on map this.Layers = new Array(); - this.addLayer( this.Map.createStaticLayer("Calque 1", [this.Terrain], 0, 0).setDepth(-2) ); - this.addLayer( this.Map.createStaticLayer("Calque 2", [this.Terrain], 0, 0).setDepth(-1) ); + this.addLayer( this.Map.createStaticLayer("Calque 1", this.Terrains, 0, 0).setDepth(-2) ); + this.addLayer( this.Map.createStaticLayer("Calque 2", this.Terrains, 0, 0).setDepth(-1) ); //add entities this.Objects = new Array(); From 8ddd4656e64c27bb47a5da82067c5d97893dd4c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Wed, 15 Apr 2020 23:10:12 +0200 Subject: [PATCH 58/73] Adding automatic loading of layers --- README.md | 13 ++++++++++++- doc/images/tiled_screenshot_1.png | Bin 0 -> 26594 bytes front/dist/maps/map2.json | 25 ++++++++++++++++++------- front/src/Phaser/Game/GameScene.ts | 28 ++++++++++++++++++---------- 4 files changed, 48 insertions(+), 18 deletions(-) create mode 100644 doc/images/tiled_screenshot_1.png diff --git a/README.md b/README.md index d71c3f0b..6953dace 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,17 @@ Note: on some OSes, you will need to add this line to your `/etc/hosts` file: workadventure.localhost 127.0.0.1 ``` +## Designing a map + +If you want to design your own map, you can use [Tiled](https://www.mapeditor.org/). + +A few things to notice: + +- your map can have as many layers as your want +- your map MUST contain a layer named "floorLayer" of type "objectgroup" that represents the layer on which characters will be drawn. + +![](doc/images/tiled_screenshot_1.png) + ### MacOS developers, your environment with Vagrant If you are using MacOS, you can increase Docker performance using Vagrant. If you want more explanations, you can read [this medium article](https://medium.com/better-programming/vagrant-to-increase-docker-performance-with-macos-25b354b0c65c). @@ -98,4 +109,4 @@ Vagrant destroy * `Vagrant destroy`: delete your VM Vagrant. ## Features developed -You have more details of features developed in back [README.md](./back/README.md). \ No newline at end of file +You have more details of features developed in back [README.md](./back/README.md). diff --git a/doc/images/tiled_screenshot_1.png b/doc/images/tiled_screenshot_1.png new file mode 100644 index 0000000000000000000000000000000000000000..108dfed8989bddcb327a1a962a5bea4a31a1e1b8 GIT binary patch literal 26594 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 Objects : Array; - tilesetKeys : Array; + map: ITiledMap; startX = (window.innerWidth / 2) / RESOLUTION; startY = (window.innerHeight / 2) / RESOLUTION; @@ -37,7 +37,6 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ }); this.RoomId = RoomId; this.GameManager = GameManager; - this.tilesetKeys = []; this.Terrains = []; } @@ -47,11 +46,10 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ this.load.on('filecomplete-tilemapJSON-'+Textures.Map, (key: string, type: string, data: any) => { // Triggered when the map is loaded // Load tiles attached to the map recursively - let map: ITiledMap = data.data; - map.tilesets.forEach((tileset) => { + this.map = data.data; + this.map.tilesets.forEach((tileset) => { let path = mapUrl.substr(0, mapUrl.lastIndexOf('/')); this.load.image(tileset.name, path + '/' + tileset.image); - this.tilesetKeys.push(tileset.name); }) }); this.load.tilemapTiledJSON(Textures.Map, mapUrl); @@ -70,8 +68,8 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ //initalise map this.Map = this.add.tilemap("map"); - this.tilesetKeys.forEach((key: string) => { - this.Terrains.push(this.Map.addTilesetImage(key, key)); + this.map.tilesets.forEach((tileset: ITiledTileSet) => { + this.Terrains.push(this.Map.addTilesetImage(tileset.name, tileset.name)); }); //permit to set bound collision @@ -79,8 +77,18 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ //add layer on map this.Layers = new Array(); - this.addLayer( this.Map.createStaticLayer("Calque 1", this.Terrains, 0, 0).setDepth(-2) ); - this.addLayer( this.Map.createStaticLayer("Calque 2", this.Terrains, 0, 0).setDepth(-1) ); + let depth = -2; + this.map.layers.forEach((layer) => { + if (layer.type === 'tilelayer') { + this.addLayer( this.Map.createStaticLayer(layer.name, this.Terrains, 0, 0).setDepth(depth) ); + } else if (layer.type === 'objectgroup' && layer.name === 'floorLayer') { + depth = -1; + } + }); + + 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.'); + } //add entities this.Objects = new Array(); From 84edc1a2c421d192847a14e6d30c34c7204a2d9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Wed, 15 Apr 2020 23:54:55 +0200 Subject: [PATCH 59/73] Running e2e tests on K8S environment --- .github/workflows/build-and-deploy.yml | 15 +++++++++++++++ README.md | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 2b003230..0f745b36 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -83,3 +83,18 @@ jobs: with: msg: Environment deployed at http://${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com check_for_duplicate_msg: true + + - name: Run Cypress tests + uses: cypress-io/github-action@v1 + with: + env: host=${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com,port=80 + spec: cypress/integration/spec1.js + wait-on: http://${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com + working-directory: e2e + + - name: "Upload the screenshot on test failure" + uses: actions/upload-artifact@v1 + if: failure() + with: + name: "screenshot" + path: "./e2e/cypress/screenshots/spec.js/WorkAdventureGame -- loads (failed).png" diff --git a/README.md b/README.md index d71c3f0b..3d753c5e 100644 --- a/README.md +++ b/README.md @@ -98,4 +98,4 @@ Vagrant destroy * `Vagrant destroy`: delete your VM Vagrant. ## Features developed -You have more details of features developed in back [README.md](./back/README.md). \ No newline at end of file +You have more details of features developed in back [README.md](./back/README.md). From 75941a5c685435a1124dea6c7360df5bd7f85688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 16 Apr 2020 00:06:24 +0200 Subject: [PATCH 60/73] Trying to change Cypress basu URL --- .github/workflows/build-and-deploy.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 0f745b36..6d677899 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -86,6 +86,8 @@ jobs: - name: Run Cypress tests uses: cypress-io/github-action@v1 + env: + CYPRESS_BASE_URL: http://${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com with: env: host=${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com,port=80 spec: cypress/integration/spec1.js From c0f2b890ef80877eb725eca02f38bc57dd4de94f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 16 Apr 2020 00:13:40 +0200 Subject: [PATCH 61/73] Fixing bad file name --- .github/workflows/build-and-deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 6d677899..6f6ebe8a 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -90,7 +90,7 @@ jobs: CYPRESS_BASE_URL: http://${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com with: env: host=${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com,port=80 - spec: cypress/integration/spec1.js + spec: cypress/integration/spec.js wait-on: http://${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com working-directory: e2e From 7e8283ddf03589552cf8527cc3bddc9b51948c60 Mon Sep 17 00:00:00 2001 From: NIP Date: Thu, 16 Apr 2020 21:37:31 +0200 Subject: [PATCH 62/73] Add floorLayer to the map --- front/dist/maps/map.json | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/front/dist/maps/map.json b/front/dist/maps/map.json index a490c3b2..d880e36e 100644 --- a/front/dist/maps/map.json +++ b/front/dist/maps/map.json @@ -1,4 +1,12 @@ { "compressionlevel":-1, + "editorsettings": + { + "export": + { + "format":"json", + "target":"map.json" + } + }, "height":18, "infinite":false, "layers":[ @@ -14,6 +22,17 @@ "x":0, "y":0 }, + { + "draworder":"topdown", + "id":3, + "name":"floorLayer", + "objects":[], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + }, { "data":[0, 0, 115, 51, 52, 116, 115, 51, 52, 116, 115, 51, 52, 116, 0, 0, 57, 58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 200, 245, 246, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 68, 0, 0, 67, 68, 0, 0, 67, 68, 0, 0, 0, 73, 74, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 216, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 232, 245, 246, 247, 0, 0, 153, 155, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 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, 153, 155, 0, 0, 0, 217, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 30, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 153, 155, 0, 0, 0, 199, 0, 0, 0, 0, 0, 0, 0, 0, 115, 67, 68, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 30, 0, 0, 29, 30, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 169, 171, 0, 0, 0, 215, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 98, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 29, 30, 29, 30, 0, 0, 0, 0, 185, 187, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 116, 0, 0, 51, 52, 116, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 67, 68, 0, 0, 115, 67, 68, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 116, 0, 0, 51, 52, 116, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 98, 0, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 67, 68, 0, 0, 115, 67, 68, 0, 0, 0, 0, 13, 14, 13, 14, 13, 14, 13, 14, 13, 14, 13, 14, 13, 14, 13, 14, 165, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 116, 0, 0, 51, 52, 116, 0, 0, 0, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 181, 0, 0, 0, 0, 98, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 67, 68, 0, 0, 115, 67, 68, 0, 0, 0, 0, 45, 46, 45, 46, 45, 46, 45, 46, 45, 46, 45, 46, 45, 46, 45, 46, 197, 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, 113, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 113, 0, 0, 0, 0, 0, 0, 0, 113, 0, 113, 0, 113, 0, 0, 51, 52, 0, 0, 0, 113, 0, 113, 0, 113, 0, 0, 51, 49, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49, 50, 0, 0, 0, 0, 0, 51, 49, 50, 49, 50, 49, 50, 0, 115, 67, 68, 116, 0, 49, 50, 49, 50, 49, 50, 0, 115, 67, 65, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 0, 0, 0, 0, 115, 67, 65, 66, 65, 66, 65, 66, 0, 0, 51, 52, 0, 0, 65, 66, 65, 66, 65, 66, 0, 0, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 67, 68, 116, 0, 0, 0, 0, 0, 114, 0, 114, 0, 114, 0, 0, 115, 67, 68, 116, 0, 114, 0, 114, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "height":18, @@ -26,7 +45,7 @@ "x":0, "y":0 }], - "nextlayerid":3, + "nextlayerid":4, "nextobjectid":1, "orientation":"orthogonal", "renderorder":"right-down", From c4b9c02d75f7970bbaa22499e5b1bd16a36eda5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 16 Apr 2020 22:31:48 +0200 Subject: [PATCH 63/73] Removing E2E test from running in CI (it is run in K8S now) --- .github/workflows/continuous_integration.yml | 24 -------------------- 1 file changed, 24 deletions(-) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index eee27553..a6c86baa 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -65,28 +65,4 @@ jobs: - name: "Jasmine" run: yarn test working-directory: "back" - - e2e: - name: "End to end testing with cypress" - - runs-on: "ubuntu-latest" - steps: - - name: "Checkout" - uses: "actions/checkout@v2.0.0" - - - name: "Init .env" - run: "cp .env.template .env" - - - name: "Init containers" - run: "docker-compose -f docker-compose.yaml -f docker-compose.ci.yml run --rm wait_app" #start the containers and then wait for the website to be online - - - name: "Run cypress" - run: "docker-compose -f docker-compose.yaml -f docker-compose.ci.yml up --exit-code-from cypress cypress" # run cypress in docker-compose and get its exit code - - - name: "Upload the screenshot on test failure" - uses: actions/upload-artifact@v1 - if: failure() - with: - name: "screenshot" - path: "./e2e/cypress/screenshots/spec.js/WorkAdventureGame -- loads (failed).png" \ No newline at end of file From 91dd9039047a69c2fb2c1584d079c7c786e2eef3 Mon Sep 17 00:00:00 2001 From: NIP Date: Fri, 17 Apr 2020 18:39:54 +0200 Subject: [PATCH 64/73] Add new map with embed tileset --- front/dist/maps/map.json | 630 +++++++++++++++++++++++++++++++++------ 1 file changed, 545 insertions(+), 85 deletions(-) diff --git a/front/dist/maps/map.json b/front/dist/maps/map.json index 773e3b9f..2fa06b31 100644 --- a/front/dist/maps/map.json +++ b/front/dist/maps/map.json @@ -1,86 +1,546 @@ -{ "compressionlevel":-1, - "editorsettings": - { - "export": - { - "format":"json", - "target":"map.json" - } - }, - "height":18, - "infinite":false, - "layers":[ - { - "data":[294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294], - "height":18, - "id":2, - "name":"bottom", - "opacity":1, - "type":"tilelayer", - "visible":true, - "width":46, - "x":0, - "y":0 - }, - { - "draworder":"topdown", - "id":3, - "name":"floorLayer", - "objects":[], - "opacity":1, - "type":"objectgroup", - "visible":true, - "x":0, - "y":0 - }, - { - "data":[0, 0, 115, 51, 52, 116, 115, 51, 52, 116, 115, 51, 52, 116, 0, 0, 57, 58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 200, 245, 246, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 68, 0, 0, 67, 68, 0, 0, 67, 68, 0, 0, 0, 73, 74, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 216, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 232, 245, 246, 247, 0, 0, 153, 155, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 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, 153, 155, 0, 0, 0, 217, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 30, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 153, 155, 0, 0, 0, 199, 0, 0, 0, 0, 0, 0, 0, 0, 115, 67, 68, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 30, 0, 0, 29, 30, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 169, 171, 0, 0, 0, 215, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 98, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 29, 30, 29, 30, 0, 0, 0, 0, 185, 187, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 116, 0, 0, 51, 52, 116, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 67, 68, 0, 0, 115, 67, 68, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 116, 0, 0, 51, 52, 116, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 98, 0, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 67, 68, 0, 0, 115, 67, 68, 0, 0, 0, 0, 13, 14, 13, 14, 13, 14, 13, 14, 13, 14, 13, 14, 13, 14, 13, 14, 165, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 116, 0, 0, 51, 52, 116, 0, 0, 0, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 181, 0, 0, 0, 0, 98, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 67, 68, 0, 0, 115, 67, 68, 0, 0, 0, 0, 45, 46, 45, 46, 45, 46, 45, 46, 45, 46, 45, 46, 45, 46, 45, 46, 197, 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, 113, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 113, 0, 0, 0, 0, 0, 0, 0, 113, 0, 113, 0, 113, 0, 0, 51, 52, 0, 0, 0, 113, 0, 113, 0, 113, 0, 0, 51, 49, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49, 50, 0, 0, 0, 0, 0, 51, 49, 50, 49, 50, 49, 50, 0, 115, 67, 68, 116, 0, 49, 50, 49, 50, 49, 50, 0, 115, 67, 65, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 0, 0, 0, 0, 115, 67, 65, 66, 65, 66, 65, 66, 0, 0, 51, 52, 0, 0, 65, 66, 65, 66, 65, 66, 0, 0, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 67, 68, 116, 0, 0, 0, 0, 0, 114, 0, 114, 0, 114, 0, 0, 115, 67, 68, 116, 0, 114, 0, 114, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - "height":18, - "id":1, - "name":"top", - "opacity":1, - "type":"tilelayer", - "visible":true, - "width":46, - "x":0, - "y":0 - }], - "nextlayerid":4, - "nextobjectid":1, - "orientation":"orthogonal", - "renderorder":"right-down", - "tiledversion":"1.3.3", - "tileheight":32, - "tilesets":[ - { - "firstgid":1, - "source":"office_1.tsx" - }, - { - "columns":8, - "firstgid":257, - "image":"floortileset.png", - "imageheight":256, - "imagewidth":256, - "margin":0, - "name":"floortileset", - "spacing":0, - "tilecount":64, - "tileheight":32, - "tiles":[ - { - "id":37, - "properties":[ - { - "name":"collides", - "type":"bool", - "value":true - }] - }], - "tilewidth":32 - }], - "tilewidth":32, - "type":"map", - "version":1.2, - "width":46 +{ "compressionlevel":-1, + "editorsettings": + { + "export": + { + "format":"json", + "target":"map.json" + } + }, + "height":18, + "infinite":false, + "layers":[ + { + "data":[294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294], + "height":18, + "id":2, + "name":"bottom", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":46, + "x":0, + "y":0 + }, + { + "draworder":"topdown", + "id":3, + "name":"floorLayer", + "objects":[], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + }, + { + "data":[0, 0, 115, 51, 52, 116, 115, 51, 52, 116, 115, 51, 52, 116, 0, 0, 57, 58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 200, 245, 246, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 68, 0, 0, 67, 68, 0, 0, 67, 68, 0, 0, 0, 73, 74, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 216, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 232, 245, 246, 247, 0, 0, 153, 155, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 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, 153, 155, 0, 0, 0, 217, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 30, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 153, 155, 0, 0, 0, 199, 0, 0, 0, 0, 0, 0, 0, 0, 115, 67, 68, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 30, 0, 0, 29, 30, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 169, 171, 0, 0, 0, 215, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 98, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 29, 30, 29, 30, 0, 0, 0, 0, 185, 187, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 116, 0, 0, 51, 52, 116, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 67, 68, 0, 0, 115, 67, 68, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 116, 0, 0, 51, 52, 116, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 98, 0, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 67, 68, 0, 0, 115, 67, 68, 0, 0, 0, 0, 13, 14, 13, 14, 13, 14, 13, 14, 13, 14, 13, 14, 13, 14, 13, 14, 165, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 116, 0, 0, 51, 52, 116, 0, 0, 0, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 181, 0, 0, 0, 0, 98, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 67, 68, 0, 0, 115, 67, 68, 0, 0, 0, 0, 45, 46, 45, 46, 45, 46, 45, 46, 45, 46, 45, 46, 45, 46, 45, 46, 197, 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, 113, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 113, 0, 0, 0, 0, 0, 0, 0, 113, 0, 113, 0, 113, 0, 0, 51, 52, 0, 0, 0, 113, 0, 113, 0, 113, 0, 0, 51, 49, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49, 50, 0, 0, 0, 0, 0, 51, 49, 50, 49, 50, 49, 50, 0, 115, 67, 68, 116, 0, 49, 50, 49, 50, 49, 50, 0, 115, 67, 65, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 0, 0, 0, 0, 115, 67, 65, 66, 65, 66, 65, 66, 0, 0, 51, 52, 0, 0, 65, 66, 65, 66, 65, 66, 0, 0, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 67, 68, 116, 0, 0, 0, 0, 0, 114, 0, 114, 0, 114, 0, 0, 115, 67, 68, 116, 0, 114, 0, 114, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":18, + "id":1, + "name":"top", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":46, + "x":0, + "y":0 + }], + "nextlayerid":4, + "nextobjectid":1, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"1.3.3", + "tileheight":32, + "tilesets":[ + { + "columns":16, + "firstgid":1, + "image":"tilesets_deviant_milkian_1.png", + "imageheight":512, + "imagewidth":512, + "margin":0, + "name":"office_1", + "spacing":0, + "tilecount":256, + "tileheight":32, + "tiles":[ + { + "id":7, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":12, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":13, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":14, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":15, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":23, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":28, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":29, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":30, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":31, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":39, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":44, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":45, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":48, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":49, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":50, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":51, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":52, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":56, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":57, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":64, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":65, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":66, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":67, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":68, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":72, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":73, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":84, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":152, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":153, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":154, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":155, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":156, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":157, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":161, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":162, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":168, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":169, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":170, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":171, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":172, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":173, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":177, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":178, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":184, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":185, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":186, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":196, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":198, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":214, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }], + "tilewidth":32 + }, + { + "columns":8, + "firstgid":257, + "image":"floortileset.png", + "imageheight":256, + "imagewidth":256, + "margin":0, + "name":"floortileset", + "spacing":0, + "tilecount":64, + "tileheight":32, + "tiles":[ + { + "id":37, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }], + "tilewidth":32 + }], + "tilewidth":32, + "type":"map", + "version":1.2, + "width":46 } \ No newline at end of file From 46fcb86b28ecaf58cb06e97a2737c579ae9c2889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Sat, 18 Apr 2020 17:16:39 +0200 Subject: [PATCH 65/73] Computing movement amount from framerate Depending on the amount of power a computer has, the framerate will not be the same. Hence, the amount of movement of a user should be constant on each frame. If a frame was slow to print, the movement should be higher to keep a constant speed. This PR takes the framerate into account when moving the players. --- front/src/Phaser/Game/GameScene.ts | 8 ++++++-- front/src/Phaser/Player/Player.ts | 17 +++++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index a08c8fd2..337d7651 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -182,8 +182,12 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ }); } - update() : void { - this.CurrentPlayer.moveUser(); + /** + * @param time + * @param delta The delta time in ms since the last frame. This is a smoothed and capped value based on the FPS rate. + */ + update(time: number, delta: number) : void { + this.CurrentPlayer.moveUser(delta); } /** diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index 4cd1a6aa..e99fa42a 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -9,7 +9,7 @@ export interface CurrentGamerInterface extends PlayableCaracter{ userId : string; PlayerValue : string; initAnimation() : void; - moveUser() : void; + moveUser(delta: number) : void; say(text : string) : void; } @@ -57,31 +57,32 @@ export class Player extends PlayableCaracter implements CurrentGamerInterface, G }) } - moveUser(): void { + moveUser(delta: number): void { //if user client on shift, camera and player speed let haveMove = false; let direction = null; let activeEvents = this.userInputManager.getEventListForGameTick(); - let speedMultiplier = activeEvents.get(UserInputEvent.SpeedUp) ? 500 : 100; + let speedMultiplier = activeEvents.get(UserInputEvent.SpeedUp) ? 25 : 9; + let moveAmount = speedMultiplier * delta; if (activeEvents.get(UserInputEvent.MoveUp)) { - this.move(0, -speedMultiplier); + this.move(0, -moveAmount); haveMove = true; direction = PlayerAnimationNames.WalkUp; } if (activeEvents.get(UserInputEvent.MoveLeft)) { - this.move(-speedMultiplier, 0); + this.move(-moveAmount, 0); haveMove = true; direction = PlayerAnimationNames.WalkLeft; } if (activeEvents.get(UserInputEvent.MoveDown)) { - this.move(0, speedMultiplier); + this.move(0, moveAmount); haveMove = true; direction = PlayerAnimationNames.WalkDown; } if (activeEvents.get(UserInputEvent.MoveRight)) { - this.move(speedMultiplier, 0); + this.move(moveAmount, 0); haveMove = true; direction = PlayerAnimationNames.WalkRight; } @@ -103,4 +104,4 @@ export class Player extends PlayableCaracter implements CurrentGamerInterface, G this.setX(MessageUserPosition.position.x); this.setY(MessageUserPosition.position.y); } -} \ No newline at end of file +} From a567cd2e119b20132885f288f8544fb0a53db21e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Wed, 22 Apr 2020 12:31:17 +0200 Subject: [PATCH 66/73] Adapting deeployer to new format --- deeployer.libsonnet | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/deeployer.libsonnet b/deeployer.libsonnet index 4e44db34..7927dd5a 100644 --- a/deeployer.libsonnet +++ b/deeployer.libsonnet @@ -6,7 +6,9 @@ "containers": { "back": { "image": "thecodingmachine/workadventure-back:"+tag, - "host": "api."+namespace+".workadventure.test.thecodingmachine.com", + "host": { + "url": "api."+namespace+".workadventure.test.thecodingmachine.com" + }, "ports": [8080], "env": { "SECRET_KEY": "tempSecretKeyNeedsToChange" @@ -14,7 +16,9 @@ }, "front": { "image": "thecodingmachine/workadventure-front:"+tag, - "host": namespace+".workadventure.test.thecodingmachine.com", + "host": { + "url": namespace+".workadventure.test.thecodingmachine.com" + }, "ports": [80], "env": { "API_URL": "http://api."+namespace+".workadventure.test.thecodingmachine.com" From 312940761cd1111366afd26ce966aa480dd1557d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Wed, 22 Apr 2020 19:16:04 +0200 Subject: [PATCH 67/73] Enabling HTTPS in test deployment --- deeployer.libsonnet | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/deeployer.libsonnet b/deeployer.libsonnet index 7927dd5a..105f9025 100644 --- a/deeployer.libsonnet +++ b/deeployer.libsonnet @@ -7,7 +7,8 @@ "back": { "image": "thecodingmachine/workadventure-back:"+tag, "host": { - "url": "api."+namespace+".workadventure.test.thecodingmachine.com" + "url": "api."+namespace+".workadventure.test.thecodingmachine.com", + "https": "enable" }, "ports": [8080], "env": { @@ -17,12 +18,18 @@ "front": { "image": "thecodingmachine/workadventure-front:"+tag, "host": { - "url": namespace+".workadventure.test.thecodingmachine.com" + "url": namespace+".workadventure.test.thecodingmachine.com", + "https": "enable" }, "ports": [80], "env": { "API_URL": "http://api."+namespace+".workadventure.test.thecodingmachine.com" } } + }, + "config": { + "https": { + "mail": "d.negrier@thecodingmachine.com" + } } } From 321254bc2cd5f8ceed7f083d0e21dca3c89e55ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Wed, 22 Apr 2020 23:27:30 +0200 Subject: [PATCH 68/73] Fixing API link to HTTPS --- deeployer.libsonnet | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deeployer.libsonnet b/deeployer.libsonnet index 105f9025..84ea3eca 100644 --- a/deeployer.libsonnet +++ b/deeployer.libsonnet @@ -23,7 +23,7 @@ }, "ports": [80], "env": { - "API_URL": "http://api."+namespace+".workadventure.test.thecodingmachine.com" + "API_URL": "https://api."+namespace+".workadventure.test.thecodingmachine.com" } } }, From 6a349e3ef71e12b11abc86ea8823d95b00f39854 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Wed, 22 Apr 2020 23:34:56 +0200 Subject: [PATCH 69/73] Switching tests on https too --- .github/workflows/build-and-deploy.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 6f6ebe8a..88bd4543 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -81,17 +81,17 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - msg: Environment deployed at http://${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com + msg: Environment deployed at https://${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com check_for_duplicate_msg: true - name: Run Cypress tests uses: cypress-io/github-action@v1 env: - CYPRESS_BASE_URL: http://${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com + CYPRESS_BASE_URL: https://${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com with: env: host=${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com,port=80 spec: cypress/integration/spec.js - wait-on: http://${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com + wait-on: https://${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com working-directory: e2e - name: "Upload the screenshot on test failure" From 5b6c7a9b5f3b99bcf80a738b539405d06f74533d Mon Sep 17 00:00:00 2001 From: NIP Date: Sun, 26 Apr 2020 11:46:00 +0200 Subject: [PATCH 70/73] Resize chunksize from 16 to 32 pixels --- front/dist/maps/map.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/front/dist/maps/map.json b/front/dist/maps/map.json index 2fa06b31..712aa0a9 100644 --- a/front/dist/maps/map.json +++ b/front/dist/maps/map.json @@ -1,6 +1,11 @@ { "compressionlevel":-1, "editorsettings": { + "chunksize": + { + "height":32, + "width":32 + }, "export": { "format":"json", @@ -34,7 +39,7 @@ "y":0 }, { - "data":[0, 0, 115, 51, 52, 116, 115, 51, 52, 116, 115, 51, 52, 116, 0, 0, 57, 58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 200, 245, 246, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 68, 0, 0, 67, 68, 0, 0, 67, 68, 0, 0, 0, 73, 74, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 216, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 232, 245, 246, 247, 0, 0, 153, 155, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 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, 153, 155, 0, 0, 0, 217, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 30, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 153, 155, 0, 0, 0, 199, 0, 0, 0, 0, 0, 0, 0, 0, 115, 67, 68, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 30, 0, 0, 29, 30, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 169, 171, 0, 0, 0, 215, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 98, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 29, 30, 29, 30, 0, 0, 0, 0, 185, 187, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 116, 0, 0, 51, 52, 116, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 67, 68, 0, 0, 115, 67, 68, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 116, 0, 0, 51, 52, 116, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 98, 0, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 67, 68, 0, 0, 115, 67, 68, 0, 0, 0, 0, 13, 14, 13, 14, 13, 14, 13, 14, 13, 14, 13, 14, 13, 14, 13, 14, 165, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 116, 0, 0, 51, 52, 116, 0, 0, 0, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 181, 0, 0, 0, 0, 98, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 67, 68, 0, 0, 115, 67, 68, 0, 0, 0, 0, 45, 46, 45, 46, 45, 46, 45, 46, 45, 46, 45, 46, 45, 46, 45, 46, 197, 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, 113, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 113, 0, 0, 0, 0, 0, 0, 0, 113, 0, 113, 0, 113, 0, 0, 51, 52, 0, 0, 0, 113, 0, 113, 0, 113, 0, 0, 51, 49, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49, 50, 0, 0, 0, 0, 0, 51, 49, 50, 49, 50, 49, 50, 0, 115, 67, 68, 116, 0, 49, 50, 49, 50, 49, 50, 0, 115, 67, 65, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 0, 0, 0, 0, 115, 67, 65, 66, 65, 66, 65, 66, 0, 0, 51, 52, 0, 0, 65, 66, 65, 66, 65, 66, 0, 0, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 67, 68, 116, 0, 0, 0, 0, 0, 114, 0, 114, 0, 114, 0, 0, 115, 67, 68, 116, 0, 114, 0, 114, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "data":[0, 0, 115, 51, 52, 116, 115, 51, 52, 116, 115, 51, 52, 116, 0, 0, 57, 58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 200, 245, 246, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 68, 0, 0, 67, 68, 0, 0, 67, 68, 0, 0, 0, 73, 74, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 216, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 232, 245, 246, 247, 0, 0, 153, 155, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 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, 153, 155, 0, 0, 0, 217, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 30, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 153, 155, 0, 0, 0, 199, 0, 0, 0, 0, 0, 0, 0, 0, 115, 67, 68, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 30, 0, 0, 29, 30, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 169, 171, 0, 0, 0, 215, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 98, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 185, 187, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 116, 0, 0, 51, 52, 116, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 67, 68, 0, 0, 115, 67, 68, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 116, 0, 0, 51, 52, 116, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 98, 0, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 67, 68, 0, 0, 115, 67, 68, 0, 0, 0, 0, 13, 14, 13, 14, 13, 14, 13, 14, 13, 14, 13, 14, 13, 14, 13, 14, 165, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 116, 0, 0, 51, 52, 116, 0, 0, 0, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 181, 0, 0, 0, 0, 98, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 67, 68, 0, 0, 115, 67, 68, 0, 0, 0, 0, 45, 46, 45, 46, 45, 46, 45, 46, 45, 46, 45, 46, 45, 46, 45, 46, 197, 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, 113, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 113, 0, 0, 0, 0, 0, 0, 0, 113, 0, 113, 0, 113, 0, 0, 51, 52, 0, 0, 0, 113, 0, 113, 0, 113, 0, 0, 51, 49, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49, 50, 0, 0, 0, 0, 0, 51, 49, 50, 49, 50, 49, 50, 0, 115, 67, 68, 116, 0, 49, 50, 49, 50, 49, 50, 0, 115, 67, 65, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 0, 0, 0, 0, 115, 67, 65, 66, 65, 66, 65, 66, 0, 0, 51, 52, 0, 0, 65, 66, 65, 66, 65, 66, 0, 0, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 67, 68, 116, 0, 0, 0, 0, 0, 114, 0, 114, 0, 114, 0, 0, 115, 67, 68, 116, 0, 114, 0, 114, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "height":18, "id":1, "name":"top", From d691b58d0bc93245c6471cfc40a0c7ce49862480 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Sun, 26 Apr 2020 17:54:56 +0200 Subject: [PATCH 71/73] basic login page with a text input and a click button --- front/dist/resources/objects/play_button.png | Bin 0 -> 969 bytes front/dist/resources/objects/rockSprite.png | Bin 3659 -> 0 bytes front/src/Phaser/Components/ClickButton.ts | 11 +++++ front/src/Phaser/Components/TextField.ts | 7 +++ front/src/Phaser/Components/TextInput.ts | 24 ++++++++++ front/src/Phaser/Game/GameManager.ts | 24 +++++----- front/src/Phaser/Game/GameScene.ts | 2 - front/src/Phaser/Login/LogincScene.ts | 47 +++++++++++++++++++ front/src/index.ts | 17 +++---- 9 files changed, 107 insertions(+), 25 deletions(-) create mode 100644 front/dist/resources/objects/play_button.png delete mode 100644 front/dist/resources/objects/rockSprite.png create mode 100644 front/src/Phaser/Components/ClickButton.ts create mode 100644 front/src/Phaser/Components/TextField.ts create mode 100644 front/src/Phaser/Components/TextInput.ts create mode 100644 front/src/Phaser/Login/LogincScene.ts diff --git a/front/dist/resources/objects/play_button.png b/front/dist/resources/objects/play_button.png new file mode 100644 index 0000000000000000000000000000000000000000..36aa309b6b26ddb120f8562bcb8c5090c8e8f38f GIT binary patch literal 969 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 YM$joE+UD)Q$@RdIgSOVi2i$4@0|kzR_y7O^ diff --git a/front/src/Phaser/Components/ClickButton.ts b/front/src/Phaser/Components/ClickButton.ts new file mode 100644 index 00000000..204e9542 --- /dev/null +++ b/front/src/Phaser/Components/ClickButton.ts @@ -0,0 +1,11 @@ + +export class ClickButton extends Phaser.GameObjects.Image{ + + constructor(scene: Phaser.Scene, x: number, y: number, textureName: string, callback: Function) { + super(scene, x, y, textureName); + this.scene.add.existing(this); + this.setInteractive(); + this.on("pointerup", callback); + } + +} \ No newline at end of file diff --git a/front/src/Phaser/Components/TextField.ts b/front/src/Phaser/Components/TextField.ts new file mode 100644 index 00000000..c2a5897c --- /dev/null +++ b/front/src/Phaser/Components/TextField.ts @@ -0,0 +1,7 @@ + +export class TextField extends Phaser.GameObjects.Text { + constructor(scene: Phaser.Scene, x: number, y: number, text: string | string[]) { + super(scene, x, y, text, { fontSize: '32px', fontStyle: 'Courier', color: '#ffffff'}); + this.scene.add.existing(this) + } +} \ No newline at end of file diff --git a/front/src/Phaser/Components/TextInput.ts b/front/src/Phaser/Components/TextInput.ts new file mode 100644 index 00000000..e1ea5e8e --- /dev/null +++ b/front/src/Phaser/Components/TextInput.ts @@ -0,0 +1,24 @@ + +export class TextInput extends Phaser.GameObjects.Text { + constructor(scene: Phaser.Scene, x: number, y: number) { + super(scene, x, y, '', { fontSize: '32px', fontStyle: 'Courier', color: '#fff'}); + this.scene.add.existing(this); + + + let keySpace = this.scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE); + let keyBackspace = this.scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.BACKSPACE); + this.scene.input.keyboard.on('keydown', (event: any) => { + if (event.keyCode === 8 && this.text.length > 0) { + this.text = this.text.substr(0, this.text.length - 1); + } else if (event.keyCode === 32 || (event.keyCode >= 48 && event.keyCode < 90)) { + this.text += event.key; + } + }); + } + + getText(): string { + return this.text; + } + + +} \ No newline at end of file diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts index d03a3152..e18bbdd6 100644 --- a/front/src/Phaser/Game/GameManager.ts +++ b/front/src/Phaser/Game/GameManager.ts @@ -18,20 +18,16 @@ export interface GameManagerInterface { export class GameManager implements GameManagerInterface { GameScenes: Array = []; status: number; + private ConnexionInstance: Connexion; constructor() { this.status = StatusGameManagerEnum.IN_PROGRESS; - ConnexionInstance = new Connexion("test@gmail.com", this); + this.configureGame(); } - - createGame(){ - return ConnexionInstance.createConnexion().then(() => { - this.configureGame(); - /** TODO add loader in the page **/ - }).catch((err) => { - console.error(err); - throw err; - }); + + connect(email:string) { + this.ConnexionInstance = new Connexion(email, this); + return this.ConnexionInstance.createConnexion() } /** @@ -51,8 +47,8 @@ export class GameManager implements GameManagerInterface { */ createCurrentPlayer(): void { //Get started room send by the backend - let game: GameSceneInterface = this.GameScenes.find((Game: GameSceneInterface) => Game.RoomId === ConnexionInstance.startedRoom); - game.createCurrentPlayer(ConnexionInstance.userId); + let game: GameSceneInterface = this.GameScenes.find((Game: GameSceneInterface) => Game.RoomId === this.ConnexionInstance.startedRoom); + game.createCurrentPlayer(this.ConnexionInstance.userId); this.status = StatusGameManagerEnum.CURRENT_USER_CREATED; } @@ -74,4 +70,6 @@ export class GameManager implements GameManagerInterface { console.error(e); } } -} \ No newline at end of file +} + +export const gameManager = new GameManager(); \ No newline at end of file diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 32f57ae3..494508e9 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -59,7 +59,6 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ }) }); this.load.tilemapTiledJSON(Textures.Map, mapUrl); - this.load.image(Textures.Rock, 'resources/objects/rockSprite.png'); this.load.spritesheet(Textures.Player, 'resources/characters/pipoya/Male 01-1.png', { frameWidth: 32, frameHeight: 32 } @@ -100,7 +99,6 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ //add entities this.Objects = new Array(); - this.addSpite(this.physics.add.sprite(200, 400, Textures.Rock, 26)); //init event click this.EventToClickOnTile(); diff --git a/front/src/Phaser/Login/LogincScene.ts b/front/src/Phaser/Login/LogincScene.ts new file mode 100644 index 00000000..845219c7 --- /dev/null +++ b/front/src/Phaser/Login/LogincScene.ts @@ -0,0 +1,47 @@ +import KeyboardKeydownCallback = Phaser.Types.Input.Keyboard.KeyboardKeydownCallback; +import {gameManager} from "../Game/GameManager"; +import {ROOM} from "../../Enum/EnvironmentVariable"; +import {TextField} from "../Components/TextField"; +import {TextInput} from "../Components/TextInput"; +import {ClickButton} from "../Components/ClickButton"; + +//todo: put this constants in a dedicated file +export const LoginSceneName = "LoginScene"; +enum LoginTextures { + playButton = "play_button", +} + +export class LogincScene extends Phaser.Scene { + private emailInput: TextInput; + private textField: TextField; + private playButton: ClickButton; + constructor() { + super({ + key: LoginSceneName + }); + } + + preload() { + this.load.image(LoginTextures.playButton, "resources/objects/play_button.png"); + } + + create() { + this.textField = new TextField(this, 10, 10, 'Enter your email:'); + this.emailInput = new TextInput(this, 10, 50); + + let x = this.game.renderer.width / 2; + let y = this.game.renderer.height / 2; + this.playButton = new ClickButton(this, x, y, LoginTextures.playButton, this.login.bind(this)); + } + + update(time: number, delta: number): void { + + } + + async login() { + let email = this.emailInput.text; + if (!email) return; + await gameManager.connect(email); + this.scene.start("GameScene"); + } +} \ No newline at end of file diff --git a/front/src/index.ts b/front/src/index.ts index e5306389..c55a5d0d 100644 --- a/front/src/index.ts +++ b/front/src/index.ts @@ -1,17 +1,16 @@ import 'phaser'; import GameConfig = Phaser.Types.Core.GameConfig; -import {GameManager} from "./Phaser/Game/GameManager"; +import {gameManager, GameManager} from "./Phaser/Game/GameManager"; import {DEBUG_MODE, RESOLUTION} from "./Enum/EnvironmentVariable"; import {cypressAsserter} from "./Cypress/CypressAsserter"; - -let gameManager = new GameManager(); +import {LogincScene} from "./Phaser/Login/LogincScene"; const config: GameConfig = { title: "Office game", width: window.innerWidth / RESOLUTION, height: window.innerHeight / RESOLUTION, parent: "game", - scene: gameManager.GameScenes, + scene: [LogincScene, ...gameManager.GameScenes as any], zoom: RESOLUTION, physics: { default: "arcade", @@ -23,10 +22,8 @@ const config: GameConfig = { cypressAsserter.gameStarted(); -gameManager.createGame().then(() => { - let game = new Phaser.Game(config); +let game = new Phaser.Game(config); - window.addEventListener('resize', function (event) { - game.scale.resize(window.innerWidth / RESOLUTION, window.innerHeight / RESOLUTION); - }); -}); +window.addEventListener('resize', function (event) { + game.scale.resize(window.innerWidth / RESOLUTION, window.innerHeight / RESOLUTION); +}); \ No newline at end of file From 25ac579a90dea6ec0eb50c7b6deeddcb52d99094 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Sun, 26 Apr 2020 18:48:41 +0200 Subject: [PATCH 72/73] prettier login page --- front/src/Phaser/Components/TextField.ts | 2 +- front/src/Phaser/Components/TextInput.ts | 25 +++++++++++++++++++++--- front/src/Phaser/Login/LogincScene.ts | 6 +++++- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/front/src/Phaser/Components/TextField.ts b/front/src/Phaser/Components/TextField.ts index c2a5897c..427a25ab 100644 --- a/front/src/Phaser/Components/TextField.ts +++ b/front/src/Phaser/Components/TextField.ts @@ -1,7 +1,7 @@ export class TextField extends Phaser.GameObjects.Text { constructor(scene: Phaser.Scene, x: number, y: number, text: string | string[]) { - super(scene, x, y, text, { fontSize: '32px', fontStyle: 'Courier', color: '#ffffff'}); + super(scene, x, y, text, { fontFamily: 'Arial', fontSize: "20px", color: '#ffffff'}); this.scene.add.existing(this) } } \ No newline at end of file diff --git a/front/src/Phaser/Components/TextInput.ts b/front/src/Phaser/Components/TextInput.ts index e1ea5e8e..b6cfd9f4 100644 --- a/front/src/Phaser/Components/TextInput.ts +++ b/front/src/Phaser/Components/TextInput.ts @@ -1,21 +1,40 @@ export class TextInput extends Phaser.GameObjects.Text { + private underLineLength = 10; + private underLine: Phaser.GameObjects.Text; constructor(scene: Phaser.Scene, x: number, y: number) { - super(scene, x, y, '', { fontSize: '32px', fontStyle: 'Courier', color: '#fff'}); + super(scene, x, y, '', { fontFamily: 'Arial', fontSize: "20px", color: '#ffffff'}); this.scene.add.existing(this); + this.underLine = this.scene.add.text(x, y+1, '__________', { fontFamily: 'Arial', fontSize: "20px", color: '#ffffff'}) + let keySpace = this.scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE); let keyBackspace = this.scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.BACKSPACE); this.scene.input.keyboard.on('keydown', (event: any) => { if (event.keyCode === 8 && this.text.length > 0) { - this.text = this.text.substr(0, this.text.length - 1); + this.deleteLetter(); } else if (event.keyCode === 32 || (event.keyCode >= 48 && event.keyCode < 90)) { - this.text += event.key; + this.addLetter(event.key); } }); } + private deleteLetter() { + this.text = this.text.substr(0, this.text.length - 1); + if (this.underLine.text.length > this.underLineLength) { + this.underLine.text = this.underLine.text.substr(0, this.underLine.text.length - 1); + } + } + + + private addLetter(letter: string) { + this.text += letter; + if (this.text.length > this.underLineLength) { + this.underLine.text += '_'; + } + } + getText(): string { return this.text; } diff --git a/front/src/Phaser/Login/LogincScene.ts b/front/src/Phaser/Login/LogincScene.ts index 845219c7..0fb4d2a4 100644 --- a/front/src/Phaser/Login/LogincScene.ts +++ b/front/src/Phaser/Login/LogincScene.ts @@ -15,6 +15,7 @@ export class LogincScene extends Phaser.Scene { private emailInput: TextInput; private textField: TextField; private playButton: ClickButton; + private infoTextField: TextField; constructor() { super({ key: LoginSceneName @@ -28,10 +29,13 @@ export class LogincScene extends Phaser.Scene { create() { this.textField = new TextField(this, 10, 10, 'Enter your email:'); this.emailInput = new TextInput(this, 10, 50); - + let x = this.game.renderer.width / 2; let y = this.game.renderer.height / 2; this.playButton = new ClickButton(this, x, y, LoginTextures.playButton, this.login.bind(this)); + + let infoText = "Commandes de base: \n - Z,Q,S,D (ou les flèches de direction) pour bouger\n - SHIFT pour accélerer"; + this.infoTextField = new TextField(this, 10, 300, infoText); } update(time: number, delta: number): void { From 0d6c4032d51c05e7e4d2f328c19c97779e9148c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 27 Apr 2020 18:12:36 +0200 Subject: [PATCH 73/73] Playing with zoom levels --- front/src/Enum/EnvironmentVariable.ts | 6 +++--- front/src/Phaser/Entity/PlayableCaracter.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/front/src/Enum/EnvironmentVariable.ts b/front/src/Enum/EnvironmentVariable.ts index db5b912c..6c0226a2 100644 --- a/front/src/Enum/EnvironmentVariable.ts +++ b/front/src/Enum/EnvironmentVariable.ts @@ -1,8 +1,8 @@ const DEBUG_MODE: boolean = !!process.env.DEBUG_MODE || false; const API_URL = process.env.API_URL || "http://api.workadventure.localhost"; const ROOM = [process.env.ROOM || "THECODINGMACHINE"]; -const RESOLUTION = 2; -const ZOOM_LEVEL = 3/4; +const RESOLUTION = 4; +const ZOOM_LEVEL = 1/*3/4*/; export { DEBUG_MODE, @@ -10,4 +10,4 @@ export { RESOLUTION, ZOOM_LEVEL, ROOM -} \ No newline at end of file +} diff --git a/front/src/Phaser/Entity/PlayableCaracter.ts b/front/src/Phaser/Entity/PlayableCaracter.ts index 3a18ea73..b84d2dd8 100644 --- a/front/src/Phaser/Entity/PlayableCaracter.ts +++ b/front/src/Phaser/Entity/PlayableCaracter.ts @@ -10,7 +10,7 @@ export class PlayableCaracter extends Phaser.Physics.Arcade.Sprite { this.scene.sys.updateList.add(this); this.scene.sys.displayList.add(this); - this.setScale(2); + //this.setScale(2); this.scene.physics.world.enableBody(this); this.setImmovable(true); this.setCollideWorldBounds(true); @@ -45,7 +45,7 @@ export class PlayableCaracter extends Phaser.Physics.Arcade.Sprite { this.setVelocity(0, 0); this.play(PlayerAnimationNames.None, true); } - + say(text: string) { if (this.bubble) return; this.bubble = new SpeechBubble(this.scene, this, text) @@ -55,4 +55,4 @@ export class PlayableCaracter extends Phaser.Physics.Arcade.Sprite { this.bubble = null; }, 3000) } -} \ No newline at end of file +}