From f7466994c571e721578787d71a7dfc243af6838d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Wed, 15 Jul 2020 23:44:01 +0200 Subject: [PATCH 1/7] Playing with pipeline to display outline --- front/src/Phaser/Items/ActionableItem.ts | 0 front/src/Phaser/Items/ActionableSprite.ts | 0 front/src/Phaser/Player/Player.ts | 5 ++ front/src/Phaser/Shaders/OutlinePipeline.ts | 61 ++++++++++++++++++++ front/src/index.ts | 9 +++ maps/objects/computer.ts | 0 maps/package.json | 63 +++++++++++++++++++++ maps/tsconfig.json | 24 ++++++++ 8 files changed, 162 insertions(+) create mode 100644 front/src/Phaser/Items/ActionableItem.ts create mode 100644 front/src/Phaser/Items/ActionableSprite.ts create mode 100644 front/src/Phaser/Shaders/OutlinePipeline.ts create mode 100644 maps/objects/computer.ts create mode 100644 maps/package.json create mode 100644 maps/tsconfig.json diff --git a/front/src/Phaser/Items/ActionableItem.ts b/front/src/Phaser/Items/ActionableItem.ts new file mode 100644 index 00000000..e69de29b diff --git a/front/src/Phaser/Items/ActionableSprite.ts b/front/src/Phaser/Items/ActionableSprite.ts new file mode 100644 index 00000000..e69de29b diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index f912d110..1a3a3a03 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -3,6 +3,7 @@ import {GameScene, Textures} from "../Game/GameScene"; import {MessageUserPositionInterface, PointInterface} from "../../Connection"; import {ActiveEventList, UserInputEvent, UserInputManager} from "../UserInput/UserInputManager"; import {Character} from "../Entity/Character"; +import {OutlinePipeline} from "../Shaders/OutlinePipeline"; export const hasMovedEventName = "hasMoved"; @@ -32,6 +33,10 @@ export class Player extends Character implements CurrentGamerInterface { //the current player model should be push away by other players to prevent conflict this.setImmovable(false); + + this.setPipeline(OutlinePipeline.KEY); + this.pipeline.setFloat2('uTextureSize', + this.texture.getSourceImage().width, this.texture.getSourceImage().height); } moveUser(delta: number): void { diff --git a/front/src/Phaser/Shaders/OutlinePipeline.ts b/front/src/Phaser/Shaders/OutlinePipeline.ts new file mode 100644 index 00000000..f65a66d2 --- /dev/null +++ b/front/src/Phaser/Shaders/OutlinePipeline.ts @@ -0,0 +1,61 @@ +export class OutlinePipeline extends Phaser.Renderer.WebGL.Pipelines.TextureTintPipeline +{ + + // the unique id of this pipeline + public static readonly KEY = 'Outline'; + + /** + * @param {Phaser.Game} game - the controller of the game instance + */ + constructor(game: Phaser.Game) + { + super({ + game: game, + renderer: game.renderer, + fragShader: ` + precision mediump float; + + uniform sampler2D uMainSampler; + uniform vec2 uTextureSize; + + varying vec2 outTexCoord; + varying float outTintEffect; + varying vec4 outTint; + + void main(void) + { + vec4 texture = texture2D(uMainSampler, outTexCoord); + vec4 texel = vec4(outTint.rgb * outTint.a, outTint.a); + vec4 color = texture; + + if (outTintEffect == 0.0) + { + color = texture * texel; + } + else if (outTintEffect == 1.0) + { + color.rgb = mix(texture.rgb, outTint.rgb * outTint.a, texture.a); + color.a = texture.a * texel.a; + } + else if (outTintEffect == 2.0) + { + color = texel; + } + + vec2 onePixel = vec2(1.0, 1.0) / uTextureSize; + float upAlpha = texture2D(uMainSampler, outTexCoord + vec2(0.0, onePixel.y)).a; + float leftAlpha = texture2D(uMainSampler, outTexCoord + vec2(-onePixel.x, 0.0)).a; + float downAlpha = texture2D(uMainSampler, outTexCoord + vec2(0.0, -onePixel.y)).a; + float rightAlpha = texture2D(uMainSampler, outTexCoord + vec2(onePixel.x, 0.0)).a; + + if (texture.a == 0.0 && max(max(upAlpha, downAlpha), max(leftAlpha, rightAlpha)) == 1.0) + { + color = vec4(1.0, 1.0, 1.0, 1.0); + } + + gl_FragColor = color; + } + ` + }); + } +} diff --git a/front/src/index.ts b/front/src/index.ts index eb63b6d0..b847fa74 100644 --- a/front/src/index.ts +++ b/front/src/index.ts @@ -8,6 +8,8 @@ import {gameManager} from "./Phaser/Game/GameManager"; import {SelectCharacterScene} from "./Phaser/Login/SelectCharacterScene"; import {EnableCameraScene} from "./Phaser/Login/EnableCameraScene"; import {FourOFourScene} from "./Phaser/Reconnecting/FourOFourScene"; +import WebGLRenderer = Phaser.Renderer.WebGL.WebGLRenderer; +import {OutlinePipeline} from "./Phaser/Shaders/OutlinePipeline"; const config: GameConfig = { title: "Office game", @@ -21,6 +23,13 @@ const config: GameConfig = { arcade: { debug: DEBUG_MODE } + }, + callbacks: { + postBoot: game => { + // FIXME: we should fore WebGL in the config. + let renderer = game.renderer as WebGLRenderer; + renderer.addPipeline(OutlinePipeline.KEY, new OutlinePipeline(game)); + } } }; diff --git a/maps/objects/computer.ts b/maps/objects/computer.ts new file mode 100644 index 00000000..e69de29b diff --git a/maps/package.json b/maps/package.json new file mode 100644 index 00000000..a20c876f --- /dev/null +++ b/maps/package.json @@ -0,0 +1,63 @@ +{ + "name": "workadventureback", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "tsc": "tsc", + "dev": "ts-node-dev --respawn --transpileOnly ./server.ts", + "prod": "tsc && node ./dist/server.js", + "profile": "tsc && node --prof ./dist/server.js", + "test": "ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json", + "lint": "node_modules/.bin/eslint src/ . --ext .ts", + "fix": "node_modules/.bin/eslint --fix src/ . --ext .ts" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/thecodingmachine/workadventure.git" + }, + "contributors": [ + { + "name": "Grégoire Parant", + "email": "g.parant@thecodingmachine.com" + }, + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + }, + { + "name": "Arthmaël Poly", + "email": "a.poly@thecodingmachine.com" + } + ], + "license": "SEE LICENSE IN LICENSE.txt", + "bugs": { + "url": "https://github.com/thecodingmachine/workadventure/issues" + }, + "homepage": "https://github.com/thecodingmachine/workadventure#readme", + "dependencies": { + "@types/express": "^4.17.4", + "@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", + "generic-type-guard": "^3.2.0", + "http-status-codes": "^1.4.0", + "jsonwebtoken": "^8.5.1", + "prom-client": "^12.0.0", + "socket.io": "^2.3.0", + "systeminformation": "^4.26.5", + "ts-node-dev": "^1.0.0-pre.44", + "typescript": "^3.8.3", + "uuidv4": "^6.0.7" + }, + "devDependencies": { + "@types/jasmine": "^3.5.10", + "@typescript-eslint/eslint-plugin": "^2.26.0", + "@typescript-eslint/parser": "^2.26.0", + "eslint": "^6.8.0", + "jasmine": "^3.5.0" + } +} diff --git a/maps/tsconfig.json b/maps/tsconfig.json new file mode 100644 index 00000000..9a140744 --- /dev/null +++ b/maps/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "outDir": "./dist/", + "sourceMap": true, + "moduleResolution": "node", + "module": "ESNext", + "target": "ES2015", + "downlevelIteration": true, + "jsx": "react", + "allowJs": true, + + "strict": false, /* Enable all strict type-checking options. */ + "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + "strictNullChecks": true, /* Enable strict null checks. */ + "strictFunctionTypes": true, /* Enable strict checking of function types. */ + "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + "strictPropertyInitialization": false, /* Enable strict checking of property initialization in classes. */ + "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */ + } +} From ed146226cffba914fb0418676b7ef8c95d2ac871 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 23 Jul 2020 18:09:24 +0200 Subject: [PATCH 2/7] Adding Outline capabilities and a ActionableItem notion. --- back/src/Assets/Maps/Floor0/floor0.json | 16 +- docker-compose.yaml | 22 +++ front/package.json | 3 +- front/src/Phaser/Game/GameScene.ts | 141 +++++++++++++++++- front/src/Phaser/Items/ActionableItem.ts | 60 ++++++++ front/src/Phaser/Items/ActionableSprite.ts | 0 front/src/Phaser/Items/Computer/computer.ts | 24 +++ .../src/Phaser/Items/ItemFactoryInterface.ts | 10 ++ front/src/Phaser/Player/Player.ts | 4 - front/src/Phaser/Shaders/OutlinePipeline.ts | 2 +- .../src/Phaser/UserInput/UserInputManager.ts | 1 + front/tsconfig.json | 5 +- front/webpack.config.js | 3 + front/yarn.lock | 29 +++- maps/.gitignore | 1 + maps/dist/.htaccess | 1 + maps/objects/computer.ts | 56 +++++++ maps/package.json | 24 +-- 18 files changed, 369 insertions(+), 33 deletions(-) delete mode 100644 front/src/Phaser/Items/ActionableSprite.ts create mode 100644 front/src/Phaser/Items/Computer/computer.ts create mode 100644 front/src/Phaser/Items/ItemFactoryInterface.ts create mode 100644 maps/.gitignore create mode 100644 maps/dist/.htaccess diff --git a/back/src/Assets/Maps/Floor0/floor0.json b/back/src/Assets/Maps/Floor0/floor0.json index 987004e6..94a215b3 100644 --- a/back/src/Assets/Maps/Floor0/floor0.json +++ b/back/src/Assets/Maps/Floor0/floor0.json @@ -199,7 +199,19 @@ "draworder":"topdown", "id":3, "name":"floorLayer", - "objects":[], + "objects":[ + { + "height":0, + "id":1, + "name":"computer", + "point":true, + "rotation":0, + "type":"computer", + "visible":true, + "width":0, + "x":431, + "y":142 + }], "opacity":1, "type":"objectgroup", "visible":true, @@ -225,7 +237,7 @@ "y":0 }], "nextlayerid":18, - "nextobjectid":1, + "nextobjectid":2, "orientation":"orthogonal", "renderorder":"right-down", "tiledversion":"1.3.3", diff --git a/docker-compose.yaml b/docker-compose.yaml index 74bbafbf..53208c76 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -38,6 +38,28 @@ services: - "traefik.http.routers.front-ssl.tls=true" - "traefik.http.routers.front-ssl.service=front" + maps: + image: thecodingmachine/nodejs:12-apache + environment: + DEBUG_MODE: "$DEBUG_MODE" + HOST: "0.0.0.0" + NODE_ENV: development + APACHE_DOCUMENT_ROOT: dist/ + #APACHE_EXTENSIONS: headers + #APACHE_EXTENSION_HEADERS: 1 + STARTUP_COMMAND_0: sudo a2enmod headers + STARTUP_COMMAND_1: yarn install + STARTUP_COMMAND_2: yarn run dev & + volumes: + - ./maps:/var/www/html + labels: + - "traefik.http.routers.maps.rule=Host(`maps.workadventure.localhost`)" + - "traefik.http.routers.maps.entryPoints=web,traefik" + - "traefik.http.services.maps.loadbalancer.server.port=80" + - "traefik.http.routers.maps-ssl.rule=Host(`maps.workadventure.localhost`)" + - "traefik.http.routers.maps-ssl.entryPoints=websecure" + - "traefik.http.routers.maps-ssl.tls=true" + - "traefik.http.routers.maps-ssl.service=maps" back: image: thecodingmachine/nodejs:12 diff --git a/front/package.json b/front/package.json index b0c5502b..e5ea5b66 100644 --- a/front/package.json +++ b/front/package.json @@ -25,7 +25,8 @@ "phaser": "^3.22.0", "queue-typescript": "^1.0.1", "simple-peer": "^9.6.2", - "socket.io-client": "^2.3.0" + "socket.io-client": "^2.3.0", + "webpack-require-http": "^0.4.3" }, "scripts": { "start": "webpack-dev-server --open", diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index a46672ee..6d311e33 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -10,7 +10,7 @@ import { DEBUG_MODE, ZOOM_LEVEL, POSITION_DELAY } from "../../Enum/EnvironmentVa import { ITiledMap, ITiledMapLayer, - ITiledMapLayerProperty, + ITiledMapLayerProperty, ITiledMapObject, ITiledTileSet } from "../Map/ITiledMap"; import {PLAYER_RESOURCES, PlayerResourceDescriptionInterface} from "../Entity/Character"; @@ -28,6 +28,8 @@ import {SimplePeer} from "../../WebRtc/SimplePeer"; import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene"; import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR; import {FourOFourSceneName} from "../Reconnecting/FourOFourScene"; +import {ItemFactoryInterface} from "../Items/ItemFactoryInterface"; +import {ActionableItem} from "../Items/ActionableItem"; export enum Textures { @@ -89,6 +91,9 @@ export class GameScene extends Phaser.Scene { private connection: Connection; private simplePeer : SimplePeer; private connectionPromise: Promise + // A promise that will resolve when the "create" method is called (signaling loading is ended) + private createPromise: Promise; + private createPromiseResolve: (value?: void | PromiseLike) => void; MapKey: string; MapUrlFile: string; @@ -106,6 +111,9 @@ export class GameScene extends Phaser.Scene { private PositionNextScene: Array> = new Array>(); private startLayerName: string|undefined; + private actionableItems: Array = new Array(); + // The item that can be selected by pressing the space key. + private outlinedItem: ActionableItem|null = null; static createFromUrl(mapUrlFile: string, instance: string, key: string|null = null): GameScene { const mapKey = GameScene.getMapKeyByUrl(mapUrlFile); @@ -128,6 +136,10 @@ export class GameScene extends Phaser.Scene { this.MapKey = MapKey; this.MapUrlFile = MapUrlFile; this.RoomId = this.instance + '__' + MapKey; + + this.createPromise = new Promise((resolve, reject): void => { + this.createPromiseResolve = resolve; + }) } //hook preload scene @@ -225,7 +237,7 @@ export class GameScene extends Phaser.Scene { // FIXME: we need to put a "unknown" instead of a "any" and validate the structure of the JSON we are receiving. // eslint-disable-next-line @typescript-eslint/no-explicit-any - private onMapLoad(data: any): void { + private async onMapLoad(data: any): Promise { // Triggered when the map is loaded // Load tiles attached to the map recursively this.mapFile = data.data; @@ -238,6 +250,85 @@ export class GameScene extends Phaser.Scene { //TODO strategy to add access token this.load.image(`${url}/${tileset.image}`, `${url}/${tileset.image}`); }) + + // Scan the object layers for objects to load and load them. + let objects = new Map(); + + for (let layer of this.mapFile.layers) { + if (layer.type === 'objectgroup') { + for (let object of layer.objects) { + let objectsOfType: ITiledMapObject[]|undefined; + if (!objects.has(object.type)) { + objectsOfType = new Array(); + } else { + objectsOfType = objects.get(object.type); + if (objectsOfType === undefined) { + throw new Error('Unexpected object type not found'); + } + } + objectsOfType.push(object); + objects.set(object.type, objectsOfType); + } + } + } + + for (let [itemType, objectsOfType] of objects) { + // FIXME: we would ideally need for the loader to WAIT for the import to be performed, which means writing our own loader plugin. + + let itemFactory: ItemFactoryInterface; + + switch (itemType) { + case 'computer': + let module = await import('../Items/Computer/computer'); + itemFactory = module.default as ItemFactoryInterface; + break; + default: + throw new Error('Unsupported object type: "'+ itemType +'"'); + } + + itemFactory.preload(this.load); + this.load.start(); // Let's manually start the loader because the import might be over AFTER the loading ends. + + this.load.on('complete', () => { + // FIXME: the factory might fail because the resources might not be loaded yet... + // We would need to add a loader ended event in addition to the createPromise + this.createPromise.then(() => { + itemFactory.create(this); + + for (let object of objectsOfType) { + // TODO: we should pass here a factory to create sprites (maybe?) + let actionableItem = itemFactory.factory(this, object); + this.actionableItems.push(actionableItem); + } + }); + }); + + // import(/* webpackIgnore: true */ scriptUrl).then(result => { + // + // result.default.preload(this.load); + // + // this.load.start(); // Let's manually start the loader because the import might be over AFTER the loading ends. + // this.load.on('complete', () => { + // // FIXME: the factory might fail because the resources might not be loaded yet... + // // We would need to add a loader ended event in addition to the createPromise + // this.createPromise.then(() => { + // result.default.create(this); + // + // for (let object of objectsOfType) { + // // TODO: we should pass here a factory to create sprites (maybe?) + // let objectSprite = result.default.factory(this, object); + // } + // }); + // }); + // }); + } + + // TEST: let's load a module dynamically! + /*let foo = "http://maps.workadventure.localhost/computer.js"; + import(/* webpackIgnore: true * / foo).then(result => { + console.log(result); + + });*/ } //hook initialisation @@ -361,6 +452,8 @@ export class GameScene extends Phaser.Scene { } }, 500); } + + this.createPromiseResolve(); } private getExitSceneUrl(layer: ITiledMapLayer): string|undefined { @@ -526,6 +619,7 @@ export class GameScene extends Phaser.Scene { //listen event to share position of user this.CurrentPlayer.on(hasMovedEventName, this.pushPlayerPosition.bind(this)) + this.CurrentPlayer.on(hasMovedEventName, this.outlineItem.bind(this)) }); } @@ -555,6 +649,49 @@ export class GameScene extends Phaser.Scene { // Otherwise, do nothing. } + /** + * Finds the correct item to outline and outline it (if there is an item to be outlined) + * @param event + */ + private outlineItem(event: HasMovedEvent): void { + let x = event.x; + let y = event.y; + switch (event.direction) { + case PlayerAnimationNames.WalkUp: + y -= 32; + break; + case PlayerAnimationNames.WalkDown: + y += 32; + break; + case PlayerAnimationNames.WalkLeft: + x -= 32; + break; + case PlayerAnimationNames.WalkRight: + x += 32; + break; + default: + throw new Error('Unexpected direction "' + event.direction + '"'); + } + + let shortestDistance: number = Infinity; + let selectedItem: ActionableItem|null = null; + for (let item of this.actionableItems) { + let distance = item.actionableDistance(x, y); + if (distance !== null && distance < shortestDistance) { + shortestDistance = distance; + selectedItem = item; + } + } + + if (this.outlinedItem === selectedItem) { + return; + } + + this.outlinedItem?.notSelectable(); + this.outlinedItem = selectedItem; + this.outlinedItem?.selectable(); + } + private doPushPlayerPosition(event: HasMovedEvent): void { this.lastMoveEventSent = event; this.lastSentTick = this.currentTick; diff --git a/front/src/Phaser/Items/ActionableItem.ts b/front/src/Phaser/Items/ActionableItem.ts index e69de29b..229b0888 100644 --- a/front/src/Phaser/Items/ActionableItem.ts +++ b/front/src/Phaser/Items/ActionableItem.ts @@ -0,0 +1,60 @@ +/** + * An actionable item represents an in-game object that can be activated using the space-bar. + * It has coordinates and an "activation radius" + */ +import Sprite = Phaser.GameObjects.Sprite; +import {OutlinePipeline} from "../Shaders/OutlinePipeline"; + +export class ActionableItem { + private readonly activationRadiusSquared : number; + private isSelectable: boolean = false; + + public constructor(private sprite: Sprite, private activationRadius: number) { + this.activationRadiusSquared = activationRadius * activationRadius; + } + + /** + * Returns the square of the distance to the object center IF we are in item action range + * OR null if we are out of range. + */ + public actionableDistance(x: number, y: number): number|null { + let distanceSquared = (x - this.sprite.x)*(x - this.sprite.x) + (y - this.sprite.y)*(y - this.sprite.y); + if (distanceSquared < this.activationRadiusSquared) { + return distanceSquared; + } else { + return null; + } + } + + /** + * Show the outline of the sprite. + */ + public selectable(): void { + if (this.isSelectable) { + return; + } + this.isSelectable = true; + this.sprite.setPipeline(OutlinePipeline.KEY); + this.sprite.pipeline.setFloat2('uTextureSize', + this.sprite.texture.getSourceImage().width, this.sprite.texture.getSourceImage().height); + } + + /** + * Hide the outline of the sprite + */ + public notSelectable(): void { + if (!this.isSelectable) { + return; + } + this.isSelectable = false; + this.sprite.resetPipeline(); + } + + /** + * Triggered when the "space" key is pressed and the object is in range of being activated. + */ + public activate(): void { + + } +} + diff --git a/front/src/Phaser/Items/ActionableSprite.ts b/front/src/Phaser/Items/ActionableSprite.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/front/src/Phaser/Items/Computer/computer.ts b/front/src/Phaser/Items/Computer/computer.ts new file mode 100644 index 00000000..99eddbb7 --- /dev/null +++ b/front/src/Phaser/Items/Computer/computer.ts @@ -0,0 +1,24 @@ +import * as Phaser from 'phaser'; +import {Scene} from "phaser"; +import Sprite = Phaser.GameObjects.Sprite; +import {ITiledMapObject} from "../../Map/ITiledMap"; +import {ItemFactoryInterface} from "../ItemFactoryInterface"; +import {GameScene} from "../../Game/GameScene"; +import {ActionableItem} from "../ActionableItem"; + +export default { + preload: (loader: Phaser.Loader.LoaderPlugin): void => { + loader.atlas('computer', 'http://maps.workadventure.localhost/computer/computer.png', 'http://maps.workadventure.localhost/computer/computer_atlas.json'); + }, + create: (scene: GameScene): void => { + + }, + factory: (scene: GameScene, object: ITiledMapObject): ActionableItem => { + // Idée: ESSAYER WebPack? https://paultavares.wordpress.com/2018/07/02/webpack-how-to-generate-an-es-module-bundle/ + let foo = new Sprite(scene, object.x, object.y, 'computer'); + scene.add.existing(foo); + + return new ActionableItem(foo, 32); + //scene.add.sprite(object.x, object.y, 'computer'); + } +} as ItemFactoryInterface; diff --git a/front/src/Phaser/Items/ItemFactoryInterface.ts b/front/src/Phaser/Items/ItemFactoryInterface.ts new file mode 100644 index 00000000..0f88f76b --- /dev/null +++ b/front/src/Phaser/Items/ItemFactoryInterface.ts @@ -0,0 +1,10 @@ +import LoaderPlugin = Phaser.Loader.LoaderPlugin; +import {GameScene} from "../Game/GameScene"; +import {ITiledMapObject} from "../Map/ITiledMap"; +import {ActionableItem} from "./ActionableItem"; + +export interface ItemFactoryInterface { + preload: (loader: LoaderPlugin) => void; + create: (scene: GameScene) => void; + factory: (scene: GameScene, object: ITiledMapObject) => ActionableItem; +} diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index 1a3a3a03..64adc246 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -33,10 +33,6 @@ export class Player extends Character implements CurrentGamerInterface { //the current player model should be push away by other players to prevent conflict this.setImmovable(false); - - this.setPipeline(OutlinePipeline.KEY); - this.pipeline.setFloat2('uTextureSize', - this.texture.getSourceImage().width, this.texture.getSourceImage().height); } moveUser(delta: number): void { diff --git a/front/src/Phaser/Shaders/OutlinePipeline.ts b/front/src/Phaser/Shaders/OutlinePipeline.ts index f65a66d2..6b416b8a 100644 --- a/front/src/Phaser/Shaders/OutlinePipeline.ts +++ b/front/src/Phaser/Shaders/OutlinePipeline.ts @@ -50,7 +50,7 @@ export class OutlinePipeline extends Phaser.Renderer.WebGL.Pipelines.TextureTint if (texture.a == 0.0 && max(max(upAlpha, downAlpha), max(leftAlpha, rightAlpha)) == 1.0) { - color = vec4(1.0, 1.0, 1.0, 1.0); + color = vec4(1.0, 1.0, 0.0, 1.0); } gl_FragColor = color; diff --git a/front/src/Phaser/UserInput/UserInputManager.ts b/front/src/Phaser/UserInput/UserInputManager.ts index eddbbf74..d40d149e 100644 --- a/front/src/Phaser/UserInput/UserInputManager.ts +++ b/front/src/Phaser/UserInput/UserInputManager.ts @@ -47,6 +47,7 @@ export class UserInputManager { {event: UserInputEvent.SpeedUp, keyInstance: Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SHIFT) }, {event: UserInputEvent.Interact, keyInstance: Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.E) }, + {event: UserInputEvent.Interact, keyInstance: Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE) }, {event: UserInputEvent.Shout, keyInstance: Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.F) }, ]; } diff --git a/front/tsconfig.json b/front/tsconfig.json index 1661efa2..9a140744 100644 --- a/front/tsconfig.json +++ b/front/tsconfig.json @@ -3,9 +3,8 @@ "outDir": "./dist/", "sourceMap": true, "moduleResolution": "node", - "noImplicitAny": true, - "module": "CommonJS", - "target": "es5", + "module": "ESNext", + "target": "ES2015", "downlevelIteration": true, "jsx": "react", "allowJs": true, diff --git a/front/webpack.config.js b/front/webpack.config.js index e162b4f8..68b1bc7e 100644 --- a/front/webpack.config.js +++ b/front/webpack.config.js @@ -33,6 +33,9 @@ module.exports = { path: path.resolve(__dirname, 'dist'), publicPath: '/' }, + externals:[ + require('webpack-require-http') + ], plugins: [ new HtmlWebpackPlugin( { diff --git a/front/yarn.lock b/front/yarn.lock index 05e9b368..b943ebf0 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -801,6 +801,11 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== +charenc@~0.0.1: + version "0.0.2" + resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" + integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= + chokidar@^2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" @@ -1063,6 +1068,11 @@ cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" +crypt@~0.0.1: + version "0.0.2" + resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" + integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= + crypto-browserify@^3.11.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" @@ -2350,7 +2360,7 @@ is-binary-path@^1.0.0: dependencies: binary-extensions "^1.0.0" -is-buffer@^1.1.5: +is-buffer@^1.1.5, is-buffer@~1.1.1: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" @@ -2691,6 +2701,15 @@ md5.js@^1.3.4: inherits "^2.0.1" safe-buffer "^5.1.2" +md5@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9" + integrity sha1-U6s41f48iJG6RlMp6iP6wFQBJvk= + dependencies: + charenc "~0.0.1" + crypt "~0.0.1" + is-buffer "~1.1.1" + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -4501,6 +4520,14 @@ webpack-merge@^4.2.2: dependencies: lodash "^4.17.15" +webpack-require-http@^0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/webpack-require-http/-/webpack-require-http-0.4.3.tgz#5690d8cc57246a53a81f1ccffd20d0394d70261c" + integrity sha1-VpDYzFckalOoHxzP/SDQOU1wJhw= + dependencies: + md5 "^2.0.0" + url "^0.11.0" + webpack-sources@^1.4.0, webpack-sources@^1.4.1: version "1.4.3" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" diff --git a/maps/.gitignore b/maps/.gitignore new file mode 100644 index 00000000..2ccbe465 --- /dev/null +++ b/maps/.gitignore @@ -0,0 +1 @@ +/node_modules/ diff --git a/maps/dist/.htaccess b/maps/dist/.htaccess new file mode 100644 index 00000000..f2895509 --- /dev/null +++ b/maps/dist/.htaccess @@ -0,0 +1 @@ +Header set Access-Control-Allow-Origin "*" diff --git a/maps/objects/computer.ts b/maps/objects/computer.ts index e69de29b..145d19df 100644 --- a/maps/objects/computer.ts +++ b/maps/objects/computer.ts @@ -0,0 +1,56 @@ +import * as Phaser from 'phaser'; +import {Scene} from "phaser"; +import Sprite = Phaser.GameObjects.Sprite; + +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}[]; +} + +class MySprite extends Sprite { + +} + + +export default { + preload: (loader: Phaser.Loader.LoaderPlugin) => { + loader.atlas('computer', 'http://maps.workadventure.localhost/computer/computer.png', 'http://maps.workadventure.localhost/computer/computer_atlas.json'); + }, + create: (scene: Scene) => { + + }, + factory: (scene: Scene, object: ITiledMapObject) => { + // Idée: ESSAYER WebPack? https://paultavares.wordpress.com/2018/07/02/webpack-how-to-generate-an-es-module-bundle/ + let foo = new MySprite(scene, object.x, object.y, 'computer'); + scene.add.existing(foo); + //scene.add.sprite(object.x, object.y, 'computer'); + } +}; diff --git a/maps/package.json b/maps/package.json index a20c876f..3623c205 100644 --- a/maps/package.json +++ b/maps/package.json @@ -1,13 +1,12 @@ { - "name": "workadventureback", + "name": "workadventuremaps", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "tsc": "tsc", - "dev": "ts-node-dev --respawn --transpileOnly ./server.ts", - "prod": "tsc && node ./dist/server.js", - "profile": "tsc && node --prof ./dist/server.js", + "dev": "tsc -w", + "prod": "tsc", "test": "ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json", "lint": "node_modules/.bin/eslint src/ . --ext .ts", "fix": "node_modules/.bin/eslint --fix src/ . --ext .ts" @@ -36,22 +35,9 @@ }, "homepage": "https://github.com/thecodingmachine/workadventure#readme", "dependencies": { - "@types/express": "^4.17.4", - "@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", - "generic-type-guard": "^3.2.0", - "http-status-codes": "^1.4.0", - "jsonwebtoken": "^8.5.1", - "prom-client": "^12.0.0", - "socket.io": "^2.3.0", - "systeminformation": "^4.26.5", + "phaser": "^3.24.1", "ts-node-dev": "^1.0.0-pre.44", - "typescript": "^3.8.3", - "uuidv4": "^6.0.7" + "typescript": "^3.8.3" }, "devDependencies": { "@types/jasmine": "^3.5.10", From 2484e4f1df75876cf01875f00e9fcd472c9360c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 23 Jul 2020 18:43:51 +0200 Subject: [PATCH 3/7] Moving maps to their own container --- .github/workflows/build-and-deploy.yml | 24 +++++++++ back/src/Controller/MapController.ts | 3 +- deeployer.libsonnet | 8 +++ docker-compose.yaml | 2 +- .../resources/items/computer/computer.png | Bin 0 -> 577 bytes .../items/computer/computer_atlas.json | 47 ++++++++++++++++++ .../items/computer/original/computer.png | Bin 0 -> 577 bytes .../computer/original/computer_atlas.json | 47 ++++++++++++++++++ .../items/computer/unpack/computer_off.png | Bin 0 -> 379 bytes .../items/computer/unpack/computer_on1.png | Bin 0 -> 492 bytes .../items/computer/unpack/computer_on2.png | Bin 0 -> 452 bytes front/src/Phaser/Items/Computer/computer.ts | 2 +- maps/{dist => }/.htaccess | 0 maps/Dockerfile | 9 ++++ .../Assets/Maps => maps}/Floor0/floor0.json | 0 .../Maps => maps}/Floor0/floortileset.png | Bin .../Floor0/tilesets_deviant_milkian_1.png | Bin .../Maps => maps}/Floor1/FloorTile_S.jpg | Bin .../Assets/Maps => maps}/Floor1/floor1.json | 0 .../Maps => maps}/Floor1/floortileset.png | Bin .../Floor1/tilesets_deviant_milkian_1.png | Bin .../Maps => maps}/Lyon/floortileset.png | Bin {back/src/Assets/Maps => maps}/Lyon/lyon.json | 0 .../Lyon/tilesets_deviant_milkian_1.png | Bin maps/objects/computer.ts | 2 +- 25 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 front/dist/resources/items/computer/computer.png create mode 100644 front/dist/resources/items/computer/computer_atlas.json create mode 100644 front/dist/resources/items/computer/original/computer.png create mode 100644 front/dist/resources/items/computer/original/computer_atlas.json create mode 100644 front/dist/resources/items/computer/unpack/computer_off.png create mode 100644 front/dist/resources/items/computer/unpack/computer_on1.png create mode 100644 front/dist/resources/items/computer/unpack/computer_on2.png rename maps/{dist => }/.htaccess (100%) create mode 100644 maps/Dockerfile rename {back/src/Assets/Maps => maps}/Floor0/floor0.json (100%) rename {back/src/Assets/Maps => maps}/Floor0/floortileset.png (100%) rename {back/src/Assets/Maps => maps}/Floor0/tilesets_deviant_milkian_1.png (100%) rename {back/src/Assets/Maps => maps}/Floor1/FloorTile_S.jpg (100%) rename {back/src/Assets/Maps => maps}/Floor1/floor1.json (100%) rename {back/src/Assets/Maps => maps}/Floor1/floortileset.png (100%) rename {back/src/Assets/Maps => maps}/Floor1/tilesets_deviant_milkian_1.png (100%) rename {back/src/Assets/Maps => maps}/Lyon/floortileset.png (100%) rename {back/src/Assets/Maps => maps}/Lyon/lyon.json (100%) rename {back/src/Assets/Maps => maps}/Lyon/tilesets_deviant_milkian_1.png (100%) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index e79fe2a9..edd7b553 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -79,6 +79,30 @@ jobs: tags: ${{ env.GITHUB_REF_SLUG }} add_git_labels: true + build-maps: + + runs-on: ubuntu-latest + + steps: + + - 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: + dockerfile: maps/Dockerfile + path: maps/ + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + repository: thecodingmachine/workadventure-maps + tags: ${{ env.GITHUB_REF_SLUG }} + add_git_labels: true + deeploy: needs: - build-front diff --git a/back/src/Controller/MapController.ts b/back/src/Controller/MapController.ts index e3730898..af2ba848 100644 --- a/back/src/Controller/MapController.ts +++ b/back/src/Controller/MapController.ts @@ -19,8 +19,9 @@ export class MapController { // Returns a map mapping map name to file name of the map getStartMap() { this.App.get("/start-map", (req: Request, res: Response) => { + let url = req.headers.host?.replace('api.', 'maps.') + URL_ROOM_STARTED; res.status(OK).send({ - mapUrlStart: req.headers.host + "/map/files" + URL_ROOM_STARTED, + mapUrlStart: url, startInstance: "global" }); }); diff --git a/deeployer.libsonnet b/deeployer.libsonnet index 09074148..bb0ce34d 100644 --- a/deeployer.libsonnet +++ b/deeployer.libsonnet @@ -28,6 +28,14 @@ "API_URL": "api."+url } }, + "maps": { + "image": "thecodingmachine/workadventure-maps:"+tag, + "host": { + "url": "maps."+url, + "https": "enable" + }, + "ports": [80] + }, "website": { "image": "thecodingmachine/workadventure-website:"+tag, "host": { diff --git a/docker-compose.yaml b/docker-compose.yaml index 53208c76..6d9380b8 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -44,7 +44,7 @@ services: DEBUG_MODE: "$DEBUG_MODE" HOST: "0.0.0.0" NODE_ENV: development - APACHE_DOCUMENT_ROOT: dist/ + #APACHE_DOCUMENT_ROOT: dist/ #APACHE_EXTENSIONS: headers #APACHE_EXTENSION_HEADERS: 1 STARTUP_COMMAND_0: sudo a2enmod headers diff --git a/front/dist/resources/items/computer/computer.png b/front/dist/resources/items/computer/computer.png new file mode 100644 index 0000000000000000000000000000000000000000..cd3a261991801be8a1a79cd6a4ecb1656fec500e GIT binary patch literal 577 zcmV-H0>1r;P) z_y>hR@q`gY@w|$(;XC*1>V@psjnth@yDHtP+YRj3|nyiLH*mTJE@Z(AGU+ zL{U7=-8ZuI#1uvGB&2afM2rD?Tm=ONRmQ)NTEn0W`ZK|WwEFcd3Jj`Da3L?BMTbEd zbeL4o_b<7b*!kG&hx1Gh=NW@CXeL~FWf)YMu|eJH`YW7T>C{^XgEHtaQ?I-q&_BdHs4_4=wMKzKm4SI&1qB9GhAXcOgDL~_xC#mks?5K+;VuP(l4kh^iEayrv_vb( P00000NkvXXu0mjfFhUGc literal 0 HcmV?d00001 diff --git a/front/dist/resources/items/computer/computer_atlas.json b/front/dist/resources/items/computer/computer_atlas.json new file mode 100644 index 00000000..881f1189 --- /dev/null +++ b/front/dist/resources/items/computer/computer_atlas.json @@ -0,0 +1,47 @@ +{ + "frames": [ + { + "filename": "computer_off", + "frame": { + "w": 42, + "h": 40, + "x": 0, + "y": 0 + }, + "anchor": { + "x": 0.5, + "y": 0.5 + } + }, + { + "filename": "computer_on1", + "frame": { + "w": 42, + "h": 40, + "x": 0, + "y": 40 + }, + "anchor": { + "x": 0.5, + "y": 0.5 + } + }, + { + "filename": "computer_on2", + "frame": { + "w": 42, + "h": 40, + "x": 42, + "y": 0 + }, + "anchor": { + "x": 0.5, + "y": 0.5 + } + } + ], + "meta": { + "description": "Atlas generado con Atlas Packer Gamma V2", + "web": "https://gammafp.github.io/atlas-packer-phaser/" + } +} \ No newline at end of file diff --git a/front/dist/resources/items/computer/original/computer.png b/front/dist/resources/items/computer/original/computer.png new file mode 100644 index 0000000000000000000000000000000000000000..cd3a261991801be8a1a79cd6a4ecb1656fec500e GIT binary patch literal 577 zcmV-H0>1r;P) z_y>hR@q`gY@w|$(;XC*1>V@psjnth@yDHtP+YRj3|nyiLH*mTJE@Z(AGU+ zL{U7=-8ZuI#1uvGB&2afM2rD?Tm=ONRmQ)NTEn0W`ZK|WwEFcd3Jj`Da3L?BMTbEd zbeL4o_b<7b*!kG&hx1Gh=NW@CXeL~FWf)YMu|eJH`YW7T>C{^XgEHtaQ?I-q&_BdHs4_4=wMKzKm4SI&1qB9GhAXcOgDL~_xC#mks?5K+;VuP(l4kh^iEayrv_vb( P00000NkvXXu0mjfFhUGc literal 0 HcmV?d00001 diff --git a/front/dist/resources/items/computer/original/computer_atlas.json b/front/dist/resources/items/computer/original/computer_atlas.json new file mode 100644 index 00000000..881f1189 --- /dev/null +++ b/front/dist/resources/items/computer/original/computer_atlas.json @@ -0,0 +1,47 @@ +{ + "frames": [ + { + "filename": "computer_off", + "frame": { + "w": 42, + "h": 40, + "x": 0, + "y": 0 + }, + "anchor": { + "x": 0.5, + "y": 0.5 + } + }, + { + "filename": "computer_on1", + "frame": { + "w": 42, + "h": 40, + "x": 0, + "y": 40 + }, + "anchor": { + "x": 0.5, + "y": 0.5 + } + }, + { + "filename": "computer_on2", + "frame": { + "w": 42, + "h": 40, + "x": 42, + "y": 0 + }, + "anchor": { + "x": 0.5, + "y": 0.5 + } + } + ], + "meta": { + "description": "Atlas generado con Atlas Packer Gamma V2", + "web": "https://gammafp.github.io/atlas-packer-phaser/" + } +} \ No newline at end of file diff --git a/front/dist/resources/items/computer/unpack/computer_off.png b/front/dist/resources/items/computer/unpack/computer_off.png new file mode 100644 index 0000000000000000000000000000000000000000..544838fe0b24506c5934cf8fbc7574a7bd98d2fe GIT binary patch literal 379 zcmeAS@N?(olHy`uVBq!ia0vp^T0pG9!3HEdxHAHP6lZ})WHAE+-w_aIoT|+y&A`B@ z>*?Yc64Cm0#(uv;0V3`DrQ}-G1ER?a)SK<0SnWFt~zv#r*TkhPxT!AHf_lMg0rQR$DKHD(FTxU%% zkYU^~!+))CZdi5Eu2_pc=A3&j;#W_-J!d?#vn}$Bw`V0ozy|e+dmnpD@v&0uHFck` zLNK%!1NpxBp7H;mwkAIV+u;}UX1Fj{*B;c%y8C+ZgzeYmf3+X6+aHoG+&sfYw;^o% zUZxjUaTa&)uc$SKhFz@-4inQxn`HrY$F+K@OXTR(7OlImCbL-b`(^*|p9D$+9 N;OXk;vd$@?2>_4Go6P_K literal 0 HcmV?d00001 diff --git a/front/dist/resources/items/computer/unpack/computer_on1.png b/front/dist/resources/items/computer/unpack/computer_on1.png new file mode 100644 index 0000000000000000000000000000000000000000..e2492441e294b71049346902c083c246cc1d5eaa GIT binary patch literal 492 zcmV?00009a7bBm000ie z000ie0hKEb8vp*MgxL5L7NRJEA|#-MK)^;MZAAP7wKE{W zMyxE;hy;{W3TiB@B^E&tL_`dT4`9Tnut>8OW{kUW*3BwA%;4$ny@cExK4vouh9CeO z$8m%ZqS7MS?gqFgQm?O&pch56ZzUQELt zp@{np>u)Pj?Eyfd_eeRj!zd$z5v|eUMLn{_sE2AX<*H}4ZoloT=2w?J0PNjdcef?B z8kL5E_B-d@o0QmTR8mN1T(!)c?EjQORo4Bm+o;M|P?KJRvcHp4f7TeIp%$$cQ?8s9 z(wUCj`t`$LjL}dT^=2$+W>rrz*-!XOOSKZiycNw6OU2t0YJru|D&#Jaw0jY z^#o1z>b)9Dq_b|1ro7xoZuWY4qgy5NKy|<6{5Y`o5LXC^6HhH?00009a7bBm000ie z000ie0hKEb8vp^8j!h2Y^=l5rD4yYE5OfRYIl*i)OP4{60xm?CoxcdNPd$>AD+f?Eyfc_!2qG zhf$UnQd&cid!qsDdPS={=eI-qQFF_k0pR)RGTD~o)u=M`-+p+W+@vC(Mir%UO{-Pr zRNo|nf01vaQO3q$QX$5u(V}*}qMe)q5M$H=IJ&6z0nYYM0JwU%A+Fxp zvH^Ji{LnH!PQ)11&e4NI|BGm4a}9uoUG)d$MZL}dnMUvL{I4RF$zR%hpo-b|BoR5IVW@ocS^muqVP0000 { - loader.atlas('computer', 'http://maps.workadventure.localhost/computer/computer.png', 'http://maps.workadventure.localhost/computer/computer_atlas.json'); + loader.atlas('computer', '/resources/items/computer/computer.png', '/resources/items/computer/computer_atlas.json'); }, create: (scene: GameScene): void => { diff --git a/maps/dist/.htaccess b/maps/.htaccess similarity index 100% rename from maps/dist/.htaccess rename to maps/.htaccess diff --git a/maps/Dockerfile b/maps/Dockerfile new file mode 100644 index 00000000..550d87f1 --- /dev/null +++ b/maps/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 --chown=docker:docker . . +#RUN yarn install + +#ENV NODE_ENV=production +#ENV STARTUP_COMMAND_1="yarn run build" +#ENV APACHE_DOCUMENT_ROOT=dist/ diff --git a/back/src/Assets/Maps/Floor0/floor0.json b/maps/Floor0/floor0.json similarity index 100% rename from back/src/Assets/Maps/Floor0/floor0.json rename to maps/Floor0/floor0.json diff --git a/back/src/Assets/Maps/Floor0/floortileset.png b/maps/Floor0/floortileset.png similarity index 100% rename from back/src/Assets/Maps/Floor0/floortileset.png rename to maps/Floor0/floortileset.png diff --git a/back/src/Assets/Maps/Floor0/tilesets_deviant_milkian_1.png b/maps/Floor0/tilesets_deviant_milkian_1.png similarity index 100% rename from back/src/Assets/Maps/Floor0/tilesets_deviant_milkian_1.png rename to maps/Floor0/tilesets_deviant_milkian_1.png diff --git a/back/src/Assets/Maps/Floor1/FloorTile_S.jpg b/maps/Floor1/FloorTile_S.jpg similarity index 100% rename from back/src/Assets/Maps/Floor1/FloorTile_S.jpg rename to maps/Floor1/FloorTile_S.jpg diff --git a/back/src/Assets/Maps/Floor1/floor1.json b/maps/Floor1/floor1.json similarity index 100% rename from back/src/Assets/Maps/Floor1/floor1.json rename to maps/Floor1/floor1.json diff --git a/back/src/Assets/Maps/Floor1/floortileset.png b/maps/Floor1/floortileset.png similarity index 100% rename from back/src/Assets/Maps/Floor1/floortileset.png rename to maps/Floor1/floortileset.png diff --git a/back/src/Assets/Maps/Floor1/tilesets_deviant_milkian_1.png b/maps/Floor1/tilesets_deviant_milkian_1.png similarity index 100% rename from back/src/Assets/Maps/Floor1/tilesets_deviant_milkian_1.png rename to maps/Floor1/tilesets_deviant_milkian_1.png diff --git a/back/src/Assets/Maps/Lyon/floortileset.png b/maps/Lyon/floortileset.png similarity index 100% rename from back/src/Assets/Maps/Lyon/floortileset.png rename to maps/Lyon/floortileset.png diff --git a/back/src/Assets/Maps/Lyon/lyon.json b/maps/Lyon/lyon.json similarity index 100% rename from back/src/Assets/Maps/Lyon/lyon.json rename to maps/Lyon/lyon.json diff --git a/back/src/Assets/Maps/Lyon/tilesets_deviant_milkian_1.png b/maps/Lyon/tilesets_deviant_milkian_1.png similarity index 100% rename from back/src/Assets/Maps/Lyon/tilesets_deviant_milkian_1.png rename to maps/Lyon/tilesets_deviant_milkian_1.png diff --git a/maps/objects/computer.ts b/maps/objects/computer.ts index 145d19df..3c48f91a 100644 --- a/maps/objects/computer.ts +++ b/maps/objects/computer.ts @@ -42,7 +42,7 @@ class MySprite extends Sprite { export default { preload: (loader: Phaser.Loader.LoaderPlugin) => { - loader.atlas('computer', 'http://maps.workadventure.localhost/computer/computer.png', 'http://maps.workadventure.localhost/computer/computer_atlas.json'); + loader.atlas('computer', '/resources/items/computer/computer.png', '/resources/items/computer/computer_atlas.json'); }, create: (scene: Scene) => { From d48d5b0285ef0729c2c86f4920cc0ac6a8adf562 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 23 Jul 2020 18:47:28 +0200 Subject: [PATCH 4/7] Fix CS --- back/src/Controller/MapController.ts | 2 +- front/src/Phaser/Game/GameScene.ts | 23 +++++++++++---------- front/src/Phaser/Items/ActionableItem.ts | 2 +- front/src/Phaser/Items/Computer/computer.ts | 2 +- front/src/index.ts | 2 +- 5 files changed, 16 insertions(+), 15 deletions(-) diff --git a/back/src/Controller/MapController.ts b/back/src/Controller/MapController.ts index af2ba848..58ce40a9 100644 --- a/back/src/Controller/MapController.ts +++ b/back/src/Controller/MapController.ts @@ -19,7 +19,7 @@ export class MapController { // Returns a map mapping map name to file name of the map getStartMap() { this.App.get("/start-map", (req: Request, res: Response) => { - let url = req.headers.host?.replace('api.', 'maps.') + URL_ROOM_STARTED; + const url = req.headers.host?.replace('api.', 'maps.') + URL_ROOM_STARTED; res.status(OK).send({ mapUrlStart: url, startInstance: "global" diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 6d311e33..9ee54058 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -252,11 +252,11 @@ export class GameScene extends Phaser.Scene { }) // Scan the object layers for objects to load and load them. - let objects = new Map(); + const objects = new Map(); - for (let layer of this.mapFile.layers) { + for (const layer of this.mapFile.layers) { if (layer.type === 'objectgroup') { - for (let object of layer.objects) { + for (const object of layer.objects) { let objectsOfType: ITiledMapObject[]|undefined; if (!objects.has(object.type)) { objectsOfType = new Array(); @@ -272,16 +272,17 @@ export class GameScene extends Phaser.Scene { } } - for (let [itemType, objectsOfType] of objects) { + for (const [itemType, objectsOfType] of objects) { // FIXME: we would ideally need for the loader to WAIT for the import to be performed, which means writing our own loader plugin. let itemFactory: ItemFactoryInterface; switch (itemType) { - case 'computer': - let module = await import('../Items/Computer/computer'); - itemFactory = module.default as ItemFactoryInterface; + case 'computer': { + const module = await import('../Items/Computer/computer'); + itemFactory = module.default; break; + } default: throw new Error('Unsupported object type: "'+ itemType +'"'); } @@ -295,9 +296,9 @@ export class GameScene extends Phaser.Scene { this.createPromise.then(() => { itemFactory.create(this); - for (let object of objectsOfType) { + for (const object of objectsOfType) { // TODO: we should pass here a factory to create sprites (maybe?) - let actionableItem = itemFactory.factory(this, object); + const actionableItem = itemFactory.factory(this, object); this.actionableItems.push(actionableItem); } }); @@ -675,8 +676,8 @@ export class GameScene extends Phaser.Scene { let shortestDistance: number = Infinity; let selectedItem: ActionableItem|null = null; - for (let item of this.actionableItems) { - let distance = item.actionableDistance(x, y); + for (const item of this.actionableItems) { + const distance = item.actionableDistance(x, y); if (distance !== null && distance < shortestDistance) { shortestDistance = distance; selectedItem = item; diff --git a/front/src/Phaser/Items/ActionableItem.ts b/front/src/Phaser/Items/ActionableItem.ts index 229b0888..01e85c64 100644 --- a/front/src/Phaser/Items/ActionableItem.ts +++ b/front/src/Phaser/Items/ActionableItem.ts @@ -18,7 +18,7 @@ export class ActionableItem { * OR null if we are out of range. */ public actionableDistance(x: number, y: number): number|null { - let distanceSquared = (x - this.sprite.x)*(x - this.sprite.x) + (y - this.sprite.y)*(y - this.sprite.y); + const distanceSquared = (x - this.sprite.x)*(x - this.sprite.x) + (y - this.sprite.y)*(y - this.sprite.y); if (distanceSquared < this.activationRadiusSquared) { return distanceSquared; } else { diff --git a/front/src/Phaser/Items/Computer/computer.ts b/front/src/Phaser/Items/Computer/computer.ts index 7ea7b318..b979ebf6 100644 --- a/front/src/Phaser/Items/Computer/computer.ts +++ b/front/src/Phaser/Items/Computer/computer.ts @@ -15,7 +15,7 @@ export default { }, factory: (scene: GameScene, object: ITiledMapObject): ActionableItem => { // Idée: ESSAYER WebPack? https://paultavares.wordpress.com/2018/07/02/webpack-how-to-generate-an-es-module-bundle/ - let foo = new Sprite(scene, object.x, object.y, 'computer'); + const foo = new Sprite(scene, object.x, object.y, 'computer'); scene.add.existing(foo); return new ActionableItem(foo, 32); diff --git a/front/src/index.ts b/front/src/index.ts index b847fa74..bf744a36 100644 --- a/front/src/index.ts +++ b/front/src/index.ts @@ -27,7 +27,7 @@ const config: GameConfig = { callbacks: { postBoot: game => { // FIXME: we should fore WebGL in the config. - let renderer = game.renderer as WebGLRenderer; + const renderer = game.renderer as WebGLRenderer; renderer.addPipeline(OutlinePipeline.KEY, new OutlinePipeline(game)); } } From 513244ae1f55df558ee4de17a54e6f2a3e55ca17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 23 Jul 2020 19:04:33 +0200 Subject: [PATCH 5/7] Enabling header module on Apache maps container --- maps/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/maps/Dockerfile b/maps/Dockerfile index 550d87f1..33ca48b5 100644 --- a/maps/Dockerfile +++ b/maps/Dockerfile @@ -7,3 +7,4 @@ COPY --chown=docker:docker . . #ENV NODE_ENV=production #ENV STARTUP_COMMAND_1="yarn run build" #ENV APACHE_DOCUMENT_ROOT=dist/ +RUN sudo a2enmod headers From ee612f65859b5629c8db945ad25c4eb647c38e4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 27 Jul 2020 22:36:07 +0200 Subject: [PATCH 6/7] Adding event support to items --- back/src/Controller/IoSocketController.ts | 38 +++++++++- back/src/Model/Websocket/ItemEventMessage.ts | 10 +++ back/src/Model/World.ts | 10 +++ front/package.json | 1 + front/src/Connection.ts | 34 ++++++++- front/src/Phaser/Game/GameScene.ts | 68 +++++++++++++---- front/src/Phaser/Items/ActionableItem.ts | 36 ++++++++- front/src/Phaser/Items/Computer/computer.ts | 74 +++++++++++++++++-- .../src/Phaser/Items/ItemFactoryInterface.ts | 2 +- front/src/Phaser/Player/Player.ts | 11 +-- front/yarn.lock | 5 ++ 11 files changed, 253 insertions(+), 36 deletions(-) create mode 100644 back/src/Model/Websocket/ItemEventMessage.ts diff --git a/back/src/Controller/IoSocketController.ts b/back/src/Controller/IoSocketController.ts index edd29e7b..f0ac0337 100644 --- a/back/src/Controller/IoSocketController.ts +++ b/back/src/Controller/IoSocketController.ts @@ -19,6 +19,7 @@ import {isJoinRoomMessageInterface} from "../Model/Websocket/JoinRoomMessage"; import {isPointInterface, PointInterface} from "../Model/Websocket/PointInterface"; import {isWebRtcSignalMessageInterface} from "../Model/Websocket/WebRtcSignalMessage"; import {UserInGroupInterface} from "../Model/Websocket/UserInGroupInterface"; +import {isItemEventMessageInterface} from "../Model/Websocket/ItemEventMessage"; enum SockerIoEvent { CONNECTION = "connection", @@ -33,7 +34,8 @@ enum SockerIoEvent { MESSAGE_ERROR = "message-error", GROUP_CREATE_UPDATE = "group-create-update", GROUP_DELETE = "group-delete", - SET_PLAYER_DETAILS = "set-player-details" + SET_PLAYER_DETAILS = "set-player-details", + ITEM_EVENT = 'item-event', } export class IoSocketController { @@ -190,7 +192,16 @@ export class IoSocketController { } return new MessageUserPosition(user.id, player.name, player.character, player.position); }).filter((item: MessageUserPosition|null) => item !== null); - answerFn(listOfUsers); + + const listOfItems: {[itemId: string]: unknown} = {}; + for (const [itemId, item] of world.getItemsState().entries()) { + listOfItems[itemId] = item; + } + + answerFn({ + users: listOfUsers, + items: listOfItems + }); } catch (e) { console.error('An error occurred on "join_room" event'); console.error(e); @@ -281,6 +292,29 @@ export class IoSocketController { Client.character = playerDetails.character; answerFn(Client.userId); }); + + socket.on(SockerIoEvent.ITEM_EVENT, (itemEvent: unknown) => { + if (!isItemEventMessageInterface(itemEvent)) { + socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid ITEM_EVENT message.'}); + console.warn('Invalid ITEM_EVENT message received: ', itemEvent); + return; + } + try { + const Client = (socket as ExSocketInterface); + + socket.to(Client.roomId).emit(SockerIoEvent.ITEM_EVENT, itemEvent); + + const world = this.Worlds.get(Client.roomId); + if (!world) { + console.error("Could not find world with id '", Client.roomId, "'"); + return; + } + world.setItemState(itemEvent.itemId, itemEvent.state); + } catch (e) { + console.error('An error occurred on "item_event"'); + console.error(e); + } + }); }); } diff --git a/back/src/Model/Websocket/ItemEventMessage.ts b/back/src/Model/Websocket/ItemEventMessage.ts new file mode 100644 index 00000000..b1f9203e --- /dev/null +++ b/back/src/Model/Websocket/ItemEventMessage.ts @@ -0,0 +1,10 @@ +import * as tg from "generic-type-guard"; + +export const isItemEventMessageInterface = + new tg.IsInterface().withProperties({ + itemId: tg.isNumber, + event: tg.isString, + state: tg.isUnknown, + parameters: tg.isUnknown, + }).get(); +export type ItemEventMessageInterface = tg.GuardedType; diff --git a/back/src/Model/World.ts b/back/src/Model/World.ts index 6d4fc205..5d26f817 100644 --- a/back/src/Model/World.ts +++ b/back/src/Model/World.ts @@ -27,6 +27,8 @@ export class World { private readonly groupUpdatedCallback: GroupUpdatedCallback; private readonly groupDeletedCallback: GroupDeletedCallback; + private itemsState: Map = new Map(); + constructor(connectCallback: ConnectCallback, disconnectCallback: DisconnectCallback, minDistance: number, @@ -227,6 +229,14 @@ export class World { return Math.sqrt(Math.pow(position2.x - position1.x, 2) + Math.pow(position2.y - position1.y, 2)); } + public setItemState(itemId: number, state: unknown) { + this.itemsState.set(itemId, state); + } + + public getItemsState(): Map { + return this.itemsState; + } + /*getDistancesBetweenGroupUsers(group: Group): Distance[] { let i = 0; diff --git a/front/package.json b/front/package.json index e5ea5b66..a9c7b3f8 100644 --- a/front/package.json +++ b/front/package.json @@ -22,6 +22,7 @@ "@types/axios": "^0.14.0", "@types/simple-peer": "^9.6.0", "@types/socket.io-client": "^1.4.32", + "generic-type-guard": "^3.2.0", "phaser": "^3.22.0", "queue-typescript": "^1.0.1", "simple-peer": "^9.6.2", diff --git a/front/src/Connection.ts b/front/src/Connection.ts index c4ac92c6..5e51974f 100644 --- a/front/src/Connection.ts +++ b/front/src/Connection.ts @@ -22,6 +22,7 @@ enum EventMessage{ GROUP_CREATE_UPDATE = "group-create-update", GROUP_DELETE = "group-delete", SET_PLAYER_DETAILS = "set-player-details", // Send the name and character to the server (on connect), receive back the id. + ITEM_EVENT = 'item-event', CONNECT_ERROR = "connect_error", } @@ -91,6 +92,18 @@ export interface StartMapInterface { startInstance: string } +export interface ItemEventMessageInterface { + itemId: number, + event: string, + state: unknown, + parameters: unknown +} + +export interface RoomJoinedMessageInterface { + users: MessageUserPositionInterface[] + items: { [itemId: number] : unknown } +} + export class Connection implements Connection { private readonly socket: Socket; private userId: string|null = null; @@ -147,10 +160,10 @@ export class Connection implements Connection { } - public joinARoom(roomId: string, startX: number, startY: number, direction: string, moving: boolean): Promise { - const promise = new Promise((resolve, reject) => { - this.socket.emit(EventMessage.JOIN_ROOM, { roomId, position: {x: startX, y: startY, direction, moving }}, (userPositions: MessageUserPositionInterface[]) => { - resolve(userPositions); + public joinARoom(roomId: string, startX: number, startY: number, direction: string, moving: boolean): Promise { + const promise = new Promise((resolve, reject) => { + this.socket.emit(EventMessage.JOIN_ROOM, { roomId, position: {x: startX, y: startY, direction, moving }}, (roomJoinedMessage: RoomJoinedMessageInterface) => { + resolve(roomJoinedMessage); }); }) return promise; @@ -223,4 +236,17 @@ export class Connection implements Connection { disconnectMessage(callback: (message: WebRtcDisconnectMessageInterface) => void): void { this.socket.on(EventMessage.WEBRTC_DISCONNECT, callback); } + + emitActionableEvent(itemId: number, event: string, state: unknown, parameters: unknown) { + return this.socket.emit(EventMessage.ITEM_EVENT, { + itemId, + event, + state, + parameters + }); + } + + onActionableEvent(callback: (message: ItemEventMessageInterface) => void): void { + this.socket.on(EventMessage.ITEM_EVENT, callback); + } } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 9ee54058..c7c9e626 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -3,7 +3,7 @@ import { Connection, GroupCreatedUpdatedMessageInterface, MessageUserJoined, MessageUserMovedInterface, - MessageUserPositionInterface, PointInterface, PositionInterface + MessageUserPositionInterface, PointInterface, PositionInterface, RoomJoinedMessageInterface } from "../../Connection"; import {CurrentGamerInterface, hasMovedEventName, Player} from "../Player/Player"; import { DEBUG_MODE, ZOOM_LEVEL, POSITION_DELAY } from "../../Enum/EnvironmentVariable"; @@ -30,6 +30,7 @@ import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR; import {FourOFourSceneName} from "../Reconnecting/FourOFourScene"; import {ItemFactoryInterface} from "../Items/ItemFactoryInterface"; import {ActionableItem} from "../Items/ActionableItem"; +import {UserInputManager} from "../UserInput/UserInputManager"; export enum Textures { @@ -91,6 +92,8 @@ export class GameScene extends Phaser.Scene { private connection: Connection; private simplePeer : SimplePeer; private connectionPromise: Promise + private connectionAnswerPromise: Promise; + private connectionAnswerPromiseResolve: (value?: RoomJoinedMessageInterface | PromiseLike) => void; // A promise that will resolve when the "create" method is called (signaling loading is ended) private createPromise: Promise; private createPromiseResolve: (value?: void | PromiseLike) => void; @@ -111,9 +114,10 @@ export class GameScene extends Phaser.Scene { private PositionNextScene: Array> = new Array>(); private startLayerName: string|undefined; - private actionableItems: Array = new Array(); + private actionableItems: Map = new Map(); // The item that can be selected by pressing the space key. private outlinedItem: ActionableItem|null = null; + private userInputManager: UserInputManager; static createFromUrl(mapUrlFile: string, instance: string, key: string|null = null): GameScene { const mapKey = GameScene.getMapKeyByUrl(mapUrlFile); @@ -140,6 +144,9 @@ export class GameScene extends Phaser.Scene { this.createPromise = new Promise((resolve, reject): void => { this.createPromiseResolve = resolve; }) + this.connectionAnswerPromise = new Promise((resolve, reject): void => { + this.connectionAnswerPromiseResolve = resolve; + }) } //hook preload scene @@ -225,6 +232,15 @@ export class GameScene extends Phaser.Scene { this.scene.remove(this.scene.key); }) + connection.onActionableEvent((message => { + const item = this.actionableItems.get(message.itemId); + if (item === undefined) { + console.warn('Received an event about object "'+message.itemId+'" but cannot find this item on the map.'); + return; + } + item.fire(message.event, message.state, message.parameters); + })); + // When connection is performed, let's connect SimplePeer this.simplePeer = new SimplePeer(this.connection); @@ -293,13 +309,19 @@ export class GameScene extends Phaser.Scene { this.load.on('complete', () => { // FIXME: the factory might fail because the resources might not be loaded yet... // We would need to add a loader ended event in addition to the createPromise - this.createPromise.then(() => { + this.createPromise.then(async () => { itemFactory.create(this); + const roomJoinedAnswer = await this.connectionAnswerPromise; + for (const object of objectsOfType) { // TODO: we should pass here a factory to create sprites (maybe?) - const actionableItem = itemFactory.factory(this, object); - this.actionableItems.push(actionableItem); + + // Do we have a state for this object? + const state = roomJoinedAnswer.items[object.id]; + + const actionableItem = itemFactory.factory(this, object, state); + this.actionableItems.set(actionableItem.getId(), actionableItem); } }); }); @@ -414,13 +436,15 @@ export class GameScene extends Phaser.Scene { //initialise list of other player this.MapPlayers = this.physics.add.group({ immovable: true }); + //create input to move + this.userInputManager = new UserInputManager(this); + //notify game manager can to create currentUser in map this.createCurrentPlayer(); //initialise camera this.initCamera(); - // Let's generate the circle for the group delimiter const circleElement = Object.values(this.textures.list).find((object: Texture) => object.key === 'circleSprite'); if(circleElement) { @@ -455,6 +479,11 @@ export class GameScene extends Phaser.Scene { } this.createPromiseResolve(); + + // TODO: use inputmanager instead + this.input.keyboard.on('keyup-SPACE', () => { + this.outlinedItem?.activate(); + }); } private getExitSceneUrl(layer: ITiledMapLayer): string|undefined { @@ -605,7 +634,8 @@ export class GameScene extends Phaser.Scene { this.GameManager.getPlayerName(), this.GameManager.getCharacterSelected(), PlayerAnimationNames.WalkDown, - false + false, + this.userInputManager ); //create collision @@ -614,8 +644,9 @@ export class GameScene extends Phaser.Scene { //join room this.connectionPromise.then((connection: Connection) => { - connection.joinARoom(this.RoomId, this.startX, this.startY, PlayerAnimationNames.WalkDown, false).then((userPositions: MessageUserPositionInterface[]) => { - this.initUsersPosition(userPositions); + connection.joinARoom(this.RoomId, this.startX, this.startY, PlayerAnimationNames.WalkDown, false).then((roomJoinedMessage: RoomJoinedMessageInterface) => { + this.initUsersPosition(roomJoinedMessage.users); + this.connectionAnswerPromiseResolve(roomJoinedMessage); }); //listen event to share position of user @@ -676,7 +707,7 @@ export class GameScene extends Phaser.Scene { let shortestDistance: number = Infinity; let selectedItem: ActionableItem|null = null; - for (const item of this.actionableItems) { + for (const item of this.actionableItems.values()) { const distance = item.actionableDistance(x, y); if (distance !== null && distance < shortestDistance) { shortestDistance = distance; @@ -766,10 +797,7 @@ export class GameScene extends Phaser.Scene { } } - /** - * - */ - checkToExit(): {key: string, hash: string} | null { + private checkToExit(): {key: string, hash: string} | null { const x = Math.floor(this.CurrentPlayer.x / 32); const y = Math.floor(this.CurrentPlayer.y / 32); @@ -947,4 +975,16 @@ export class GameScene extends Phaser.Scene { const endPos = mapUrlStart.indexOf(".json"); return mapUrlStart.substring(startPos, endPos); } + + /** + * Sends to the server an event emitted by one of the ActionableItems. + * + * @param itemId + * @param eventName + * @param state + * @param parameters + */ + emitActionableEvent(itemId: number, eventName: string, state: unknown, parameters: unknown) { + this.connection.emitActionableEvent(itemId, eventName, state, parameters); + } } diff --git a/front/src/Phaser/Items/ActionableItem.ts b/front/src/Phaser/Items/ActionableItem.ts index 01e85c64..36b14921 100644 --- a/front/src/Phaser/Items/ActionableItem.ts +++ b/front/src/Phaser/Items/ActionableItem.ts @@ -4,15 +4,23 @@ */ import Sprite = Phaser.GameObjects.Sprite; import {OutlinePipeline} from "../Shaders/OutlinePipeline"; +import {GameScene} from "../Game/GameScene"; + +type EventCallback = (state: unknown, parameters: unknown) => void; export class ActionableItem { private readonly activationRadiusSquared : number; private isSelectable: boolean = false; + private callbacks: Map> = new Map>(); - public constructor(private sprite: Sprite, private activationRadius: number) { + public constructor(private id: number, private sprite: Sprite, private eventHandler: GameScene, private activationRadius: number, private onActivateCallback: (item: ActionableItem) => void) { this.activationRadiusSquared = activationRadius * activationRadius; } + public getId(): number { + return this.id; + } + /** * Returns the square of the distance to the object center IF we are in item action range * OR null if we are out of range. @@ -54,7 +62,31 @@ export class ActionableItem { * Triggered when the "space" key is pressed and the object is in range of being activated. */ public activate(): void { + this.onActivateCallback(this); + } + public emit(eventName: string, state: unknown, parameters: unknown = null): void { + this.eventHandler.emitActionableEvent(this.id, eventName, state, parameters); + // Also, execute the action locally. + this.fire(eventName, state, parameters); + } + + public on(eventName: string, callback: EventCallback): void { + let callbacksArray: Array|undefined = this.callbacks.get(eventName); + if (callbacksArray === undefined) { + callbacksArray = new Array(); + this.callbacks.set(eventName, callbacksArray); + } + callbacksArray.push(callback); + } + + public fire(eventName: string, state: unknown, parameters: unknown): void { + const callbacksArray = this.callbacks.get(eventName); + if (callbacksArray === undefined) { + return; + } + for (const callback of callbacksArray) { + callback(state, parameters); + } } } - diff --git a/front/src/Phaser/Items/Computer/computer.ts b/front/src/Phaser/Items/Computer/computer.ts index b979ebf6..fdc7a358 100644 --- a/front/src/Phaser/Items/Computer/computer.ts +++ b/front/src/Phaser/Items/Computer/computer.ts @@ -5,20 +5,82 @@ import {ITiledMapObject} from "../../Map/ITiledMap"; import {ItemFactoryInterface} from "../ItemFactoryInterface"; import {GameScene} from "../../Game/GameScene"; import {ActionableItem} from "../ActionableItem"; +import * as tg from "generic-type-guard"; + +const isComputerState = + new tg.IsInterface().withProperties({ + status: tg.isString, + }).get(); +type ComputerState = tg.GuardedType; + +let state: ComputerState = { + 'status': 'off' +}; export default { preload: (loader: Phaser.Loader.LoaderPlugin): void => { loader.atlas('computer', '/resources/items/computer/computer.png', '/resources/items/computer/computer_atlas.json'); }, create: (scene: GameScene): void => { - + scene.anims.create({ + key: 'computer_off', + frames: [ + { + key: 'computer', + frame: 'computer_off' + } + ], + frameRate: 10, + repeat: -1 + }); + scene.anims.create({ + key: 'computer_run', + frames: [ + { + key: 'computer', + frame: 'computer_on1' + }, + { + key: 'computer', + frame: 'computer_on2' + } + ], + frameRate: 5, + repeat: -1 + }); }, - factory: (scene: GameScene, object: ITiledMapObject): ActionableItem => { - // Idée: ESSAYER WebPack? https://paultavares.wordpress.com/2018/07/02/webpack-how-to-generate-an-es-module-bundle/ - const foo = new Sprite(scene, object.x, object.y, 'computer'); - scene.add.existing(foo); + factory: (scene: GameScene, object: ITiledMapObject, initState: unknown): ActionableItem => { + if (initState !== undefined) { + if (!isComputerState(initState)) { + throw new Error('Invalid state received for computer object'); + } + state = initState; + } - return new ActionableItem(foo, 32); + // Idée: ESSAYER WebPack? https://paultavares.wordpress.com/2018/07/02/webpack-how-to-generate-an-es-module-bundle/ + const computer = new Sprite(scene, object.x, object.y, 'computer'); + scene.add.existing(computer); + if (state.status === 'on') { + computer.anims.play('computer_run'); + } + + const item = new ActionableItem(object.id, computer, scene, 32, (item: ActionableItem) => { + if (state.status === 'off') { + state.status = 'on'; + item.emit('TURN_ON', state); + } else { + state.status = 'off'; + item.emit('TURN_OFF', state); + } + }); + item.on('TURN_ON', () => { + computer.anims.play('computer_run'); + }); + item.on('TURN_OFF', () => { + computer.anims.play('computer_off'); + }); + + return item; //scene.add.sprite(object.x, object.y, 'computer'); } } as ItemFactoryInterface; diff --git a/front/src/Phaser/Items/ItemFactoryInterface.ts b/front/src/Phaser/Items/ItemFactoryInterface.ts index 0f88f76b..e3e52517 100644 --- a/front/src/Phaser/Items/ItemFactoryInterface.ts +++ b/front/src/Phaser/Items/ItemFactoryInterface.ts @@ -6,5 +6,5 @@ import {ActionableItem} from "./ActionableItem"; export interface ItemFactoryInterface { preload: (loader: LoaderPlugin) => void; create: (scene: GameScene) => void; - factory: (scene: GameScene, object: ITiledMapObject) => ActionableItem; + factory: (scene: GameScene, object: ITiledMapObject, state: unknown) => ActionableItem; } diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index 64adc246..1ddd8b87 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -13,9 +13,8 @@ export interface CurrentGamerInterface extends Character{ } export class Player extends Character implements CurrentGamerInterface { - userInputManager: UserInputManager; - previousDirection: string; - wasMoving: boolean; + private previousDirection: string; + private wasMoving: boolean; constructor( Scene: GameScene, @@ -24,13 +23,11 @@ export class Player extends Character implements CurrentGamerInterface { name: string, PlayerTexture: string, direction: string, - moving: boolean + moving: boolean, + private userInputManager: UserInputManager ) { super(Scene, x, y, PlayerTexture, name, direction, moving, 1); - //create input to move - this.userInputManager = new UserInputManager(Scene); - //the current player model should be push away by other players to prevent conflict this.setImmovable(false); } diff --git a/front/yarn.lock b/front/yarn.lock index b943ebf0..ae7b5558 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -1904,6 +1904,11 @@ functional-red-black-tree@^1.0.1: resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= +generic-type-guard@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/generic-type-guard/-/generic-type-guard-3.2.0.tgz#1fb136f934730c776486526b8a21fe96b067e691" + integrity sha512-EkkrXYbOtJ3VPB+SOrU7EhwY65rZErItGtBg5wAqywaj07BOubwOZqMYaxOWekJ9akioGqXIsw1fYk3wwbWsDQ== + get-browser-rtc@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/get-browser-rtc/-/get-browser-rtc-1.0.2.tgz#bbcd40c8451a7ed4ef5c373b8169a409dd1d11d9" From 2d818ec79d4c5d2e4cb07029a1853d7be8743523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 27 Jul 2020 22:48:04 +0200 Subject: [PATCH 7/7] Fixing broken Jasmine test due to module config --- docker-compose.yaml | 2 +- front/Dockerfile | 2 +- front/tsconfig.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 6d9380b8..d691f204 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -19,7 +19,7 @@ services: - /var/run/docker.sock:/var/run/docker.sock front: - image: thecodingmachine/nodejs:12 + image: thecodingmachine/nodejs:14 environment: DEBUG_MODE: "$DEBUG_MODE" HOST: "0.0.0.0" diff --git a/front/Dockerfile b/front/Dockerfile index ee15270e..c5c605a8 100644 --- a/front/Dockerfile +++ b/front/Dockerfile @@ -1,5 +1,5 @@ # we are rebuilding on each deploy to cope with the API_URL environment URL -FROM thecodingmachine/nodejs:12-apache +FROM thecodingmachine/nodejs:14-apache COPY --chown=docker:docker . . RUN yarn install diff --git a/front/tsconfig.json b/front/tsconfig.json index 9a140744..b0eac1c0 100644 --- a/front/tsconfig.json +++ b/front/tsconfig.json @@ -3,7 +3,7 @@ "outDir": "./dist/", "sourceMap": true, "moduleResolution": "node", - "module": "ESNext", + "module": "CommonJS", "target": "ES2015", "downlevelIteration": true, "jsx": "react",