diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 48a7bae9..3bf00b99 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -2,7 +2,7 @@ name: Build, push and deploy Docker image on: push: - branches: [master] + branches: [master, develop] release: types: [created] pull_request: @@ -149,6 +149,37 @@ jobs: # Create a slugified value of the branch - uses: rlespinasse/github-slug-action@3.1.0 + - name: Write certificate + run: echo "${CERTS_PRIVATE_KEY}" > secret.key && chmod 0600 secret.key + env: + CERTS_PRIVATE_KEY: ${{ secrets.CERTS_PRIVATE_KEY }} + + - name: Download certificate + run: mkdir secrets && scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i secret.key ubuntu@cert.workadventu.re:./config/live/workadventu.re/* secrets/ + + - name: Create namespace + uses: steebchen/kubectl@v1.0.0 + env: + KUBE_CONFIG_DATA: ${{ secrets.KUBE_CONFIG_FILE_BASE64 }} + with: + args: create namespace workadventure-${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + continue-on-error: true + + - name: Delete old certificates in namespace + uses: steebchen/kubectl@v1.0.0 + env: + KUBE_CONFIG_DATA: ${{ secrets.KUBE_CONFIG_FILE_BASE64 }} + with: + args: -n workadventure-${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} delete secret certificate-tls + continue-on-error: true + + - name: Install certificates in namespace + uses: steebchen/kubectl@v1.0.0 + env: + KUBE_CONFIG_DATA: ${{ secrets.KUBE_CONFIG_FILE_BASE64 }} + with: + args: -n workadventure-${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} create secret tls certificate-tls --key="secrets/privkey.pem" --cert="secrets/fullchain.pem" + - name: Deploy uses: thecodingmachine/deeployer-action@master env: @@ -168,4 +199,4 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - msg: Environment deployed at https://play.${{ env.GITHUB_HEAD_REF_SLUG }}.test.workadventu.re + msg: Environment deployed at https://play-${{ env.GITHUB_HEAD_REF_SLUG }}.test.workadventu.re diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 06c1c58e..2036e4e6 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -52,6 +52,10 @@ jobs: PUSHER_URL: "//localhost:8080" working-directory: "front" + - name: "Svelte check" + run: yarn run svelte-check + working-directory: "front" + - name: "Lint" run: yarn run lint working-directory: "front" diff --git a/CHANGELOG.md b/CHANGELOG.md index dec14540..41f72510 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,28 @@ -## Version 1.3.9 - in dev +## Version 1.4.x-dev + +### Updates + +- Added the ability to have animated tiles in maps #1216 #1217 +- Enabled outlines on actionable item again (they were disabled when migrating to Phaser 3.50) #1218 +- Enabled outlines on player names (when the mouse hovers on a player you can interact with) #1219 +- Migrated the admin console to Svelte, and redesigned the console #1211 +- Layer properties (like `exitUrl`, `silent`, etc...) can now also used in tile properties #1210 (@jonnytest1) +- New scripting API features : + - Use `WA.room.showLayer(): void` to show a layer + - Use `WA.room.hideLayer(): void` to hide a layer + - Use `WA.room.setProperty() : void` to add or change existing property of a layer + - Use `WA.player.onPlayerMove(): void` to track the movement of the current player + - Use `WA.room.getCurrentUser(): Promise` to get the ID, name and tags of the current player + - Use `WA.room.getCurrentRoom(): Promise` to get the ID, JSON map file, url of the map of the current room and the layer where the current player started + - Use `WA.ui.registerMenuCommand(): void` to add a custom menu + +## Version 1.4.1 + +### Bugfixes + +- Loading errors after the preload stage should not crash the game anymore + +## Version 1.4.0 ### BREAKING CHANGES @@ -12,14 +36,29 @@ - The emote menu can be opened by clicking on your character. - Clicking on one of its element will close the menu and play an emote above your character. - This emote can be seen by other players. +- Player names were improved. (@Kharhamel) + - We now create a GameObject.Text instead of GameObject.BitmapText + - now use the 'Press Start 2P' font family and added an outline + - As a result, we can now allow non-standard letters like french accents or chinese characters! + +- Added the contact card feature. (@Kharhamel) + - Click on another player to see its contact info. + - Premium-only feature unfortunately. I need to find a way to make it available for all. + - If no contact data is found (either because the user is anonymous or because no admin backend), display an error card. + - Mobile support has been improved - WorkAdventure automatically sets the zoom level based on the viewport size to ensure a sensible size of the map is visible, whatever the viewport used - Mouse wheel support to zoom in / out - Pinch support on mobile to zoom in / out - Improved virtual joystick size (adapts to the zoom level) +- Redesigned intermediate scenes + - Redesigned Select Companion scene + - Redesigned Enter Your Name scene + - Added a new `DISPLAY_TERMS_OF_USE` environment variable to trigger the display of terms of use - New scripting API features: - Use `WA.loadSound(): Sound` to load / play / stop a sound + ### Bug Fixes - Pinch gesture does no longer move the character diff --git a/back/package.json b/back/package.json index 09d6794a..a6ff7319 100644 --- a/back/package.json +++ b/back/package.json @@ -38,23 +38,17 @@ "homepage": "https://github.com/thecodingmachine/workadventure#readme", "dependencies": { "axios": "^0.21.1", - "body-parser": "^1.19.0", "busboy": "^0.3.1", "circular-json": "^0.5.9", "debug": "^4.3.1", "generic-type-guard": "^3.2.0", "google-protobuf": "^3.13.0", "grpc": "^1.24.4", - "http-status-codes": "^1.4.0", - "iterall": "^1.3.0", "jsonwebtoken": "^8.5.1", "mkdirp": "^1.0.4", - "multer": "^1.4.2", "prom-client": "^12.0.0", "query-string": "^6.13.3", "systeminformation": "^4.31.1", - "ts-node-dev": "^1.0.0-pre.44", - "typescript": "^3.8.3", "uWebSockets.js": "uNetworking/uWebSockets.js#v18.5.0", "uuidv4": "^6.0.7" }, @@ -71,6 +65,8 @@ "@typescript-eslint/eslint-plugin": "^2.26.0", "@typescript-eslint/parser": "^2.26.0", "eslint": "^6.8.0", - "jasmine": "^3.5.0" + "jasmine": "^3.5.0", + "ts-node-dev": "^1.0.0-pre.44", + "typescript": "^3.8.3" } } diff --git a/back/src/Model/Admin.ts b/back/src/Model/Admin.ts index 0be74b85..29b53385 100644 --- a/back/src/Model/Admin.ts +++ b/back/src/Model/Admin.ts @@ -1,9 +1,6 @@ import { - BatchMessage, - PusherToBackMessage, ServerToAdminClientMessage, - ServerToClientMessage, - SubMessage, UserJoinedRoomMessage, UserLeftRoomMessage + UserJoinedRoomMessage, UserLeftRoomMessage } from "../Messages/generated/messages_pb"; import {AdminSocket} from "../RoomManager"; diff --git a/back/src/Model/GameRoom.ts b/back/src/Model/GameRoom.ts index be3e5cd3..53d0a855 100644 --- a/back/src/Model/GameRoom.ts +++ b/back/src/Model/GameRoom.ts @@ -89,7 +89,10 @@ export class GameRoom { public getUserByUuid(uuid: string): User|undefined { return this.usersByUuid.get(uuid); } - + public getUserById(id: number): User|undefined { + return this.users.get(id); + } + public join(socket : UserSocket, joinRoomMessage: JoinRoomMessage): User { const positionMessage = joinRoomMessage.getPositionmessage(); if (positionMessage === undefined) { @@ -105,6 +108,7 @@ export class GameRoom { this.positionNotifier, socket, joinRoomMessage.getTagList(), + joinRoomMessage.getVisitcardurl(), joinRoomMessage.getName(), ProtobufUtils.toCharacterLayerObjects(joinRoomMessage.getCharacterlayerList()), joinRoomMessage.getCompanion() diff --git a/back/src/Model/User.ts b/back/src/Model/User.ts index 52a96b61..4a3e75ec 100644 --- a/back/src/Model/User.ts +++ b/back/src/Model/User.ts @@ -22,6 +22,7 @@ export class User implements Movable { private positionNotifier: PositionNotifier, public readonly socket: UserSocket, public readonly tags: string[], + public readonly visitCardUrl: string|null, public readonly name: string, public readonly characterLayers: CharacterLayer[], public readonly companion?: CompanionMessage diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index c58b3d9f..a56a1ac4 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -299,6 +299,9 @@ export class SocketManager { userJoinedZoneMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers)); userJoinedZoneMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition())); userJoinedZoneMessage.setFromzone(this.toProtoZone(fromZone)); + if (thing.visitCardUrl) { + userJoinedZoneMessage.setVisitcardurl(thing.visitCardUrl); + } userJoinedZoneMessage.setCompanion(thing.companion); const subMessage = new SubToPusherMessage(); @@ -604,6 +607,9 @@ export class SocketManager { userJoinedMessage.setName(thing.name); userJoinedMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers)); userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition())); + if (thing.visitCardUrl) { + userJoinedMessage.setVisitcardurl(thing.visitCardUrl); + } userJoinedMessage.setCompanion(thing.companion); const subMessage = new SubToPusherMessage(); diff --git a/back/tests/PositionNotifierTest.ts b/back/tests/PositionNotifierTest.ts index 24b171d9..1aaf2e13 100644 --- a/back/tests/PositionNotifierTest.ts +++ b/back/tests/PositionNotifierTest.ts @@ -1,10 +1,6 @@ import "jasmine"; -import {GameRoom, ConnectCallback, DisconnectCallback } from "_Model/GameRoom"; -import {Point} from "../src/Model/Websocket/MessageUserPosition"; -import { Group } from "../src/Model/Group"; import {PositionNotifier} from "../src/Model/PositionNotifier"; import {User, UserSocket} from "../src/Model/User"; -import {PointInterface} from "../src/Model/Websocket/PointInterface"; import {Zone} from "_Model/Zone"; import {Movable} from "_Model/Movable"; import {PositionInterface} from "_Model/PositionInterface"; @@ -30,14 +26,14 @@ describe("PositionNotifier", () => { y: 500, moving: false, direction: 'down' - }, false, positionNotifier, {} as UserSocket, [], 'foo', []); + }, false, positionNotifier, {} as UserSocket, [], null, 'foo', []); const user2 = new User(2, 'test', '10.0.0.2', { x: -9999, y: -9999, moving: false, direction: 'down' - }, false, positionNotifier, {} as UserSocket, [], 'foo', []); + }, false, positionNotifier, {} as UserSocket, [], null, 'foo', []); positionNotifier.addZoneListener({} as ZoneSocket, 0, 0); positionNotifier.addZoneListener({} as ZoneSocket, 0, 1); @@ -105,14 +101,14 @@ describe("PositionNotifier", () => { y: 500, moving: false, direction: 'down' - }, false, positionNotifier, {} as UserSocket, [], 'foo', []); + }, false, positionNotifier, {} as UserSocket, [], null, 'foo', []); const user2 = new User(2, 'test', '10.0.0.2', { x: 0, y: 0, moving: false, direction: 'down' - }, false, positionNotifier, {} as UserSocket, [], 'foo', []); + }, false, positionNotifier, {} as UserSocket, [], null, 'foo', []); const listener = {} as ZoneSocket; positionNotifier.addZoneListener(listener, 0, 0); diff --git a/back/yarn.lock b/back/yarn.lock index 8af760c8..3ac4b0a8 100644 --- a/back/yarn.lock +++ b/back/yarn.lock @@ -257,11 +257,6 @@ anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" -append-field@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/append-field/-/append-field-1.0.0.tgz#1e3440e915f0b1203d23748e78edd7b9b5b43e56" - integrity sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY= - aproba@^1.0.3: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" @@ -370,22 +365,6 @@ bintrees@1.0.1: resolved "https://registry.yarnpkg.com/bintrees/-/bintrees-1.0.1.tgz#0e655c9b9c2435eaab68bf4027226d2b55a34524" integrity sha1-DmVcm5wkNeqraL9AJyJtK1WjRSQ= -body-parser@^1.19.0: - version "1.19.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" - integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== - dependencies: - bytes "3.1.0" - content-type "~1.0.4" - debug "2.6.9" - depd "~1.1.2" - http-errors "1.7.2" - iconv-lite "0.4.24" - on-finished "~2.3.0" - qs "6.7.0" - raw-body "2.4.0" - type-is "~1.6.17" - brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -427,14 +406,6 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== -busboy@^0.2.11: - version "0.2.14" - resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.2.14.tgz#6c2a622efcf47c57bbbe1e2a9c37ad36c7925453" - integrity sha1-bCpiLvz0fFe7vh4qnDetNseSVFM= - dependencies: - dicer "0.2.5" - readable-stream "1.1.x" - busboy@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.3.1.tgz#170899274c5bf38aae27d5c62b71268cd585fd1b" @@ -449,11 +420,6 @@ bytebuffer@~5: dependencies: long "~3" -bytes@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" - integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== - cache-base@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" @@ -622,26 +588,11 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@^1.5.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" - integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^2.2.2" - typedarray "^0.0.6" - console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= -content-type@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" - integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== - copy-descriptor@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" @@ -678,7 +629,7 @@ dateformat@~1.0.4-1.2.3: get-stdin "^4.0.1" meow "^3.3.0" -debug@2.6.9, debug@^2.2.0, debug@^2.3.3: +debug@^2.2.0, debug@^2.3.3: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -753,24 +704,11 @@ delegates@^1.0.0: resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= -depd@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= - detect-libc@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= -dicer@0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.2.5.tgz#5996c086bb33218c812c090bddc09cd12facb70f" - integrity sha1-WZbAhrszIYyBLAkL3cCc0S+stw8= - dependencies: - readable-stream "1.1.x" - streamsearch "0.1.2" - dicer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.3.0.tgz#eacd98b3bfbf92e8ab5c2fdb71aaac44bb06b872" @@ -804,11 +742,6 @@ ecdsa-sig-formatter@1.0.11: dependencies: safe-buffer "^5.0.1" -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= - emoji-regex@^7.0.1: version "7.0.3" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" @@ -1255,28 +1188,12 @@ hosted-git-info@^2.1.4: resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== -http-errors@1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" - integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - http-status-codes@*: version "2.1.4" resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-2.1.4.tgz#453d99b4bd9424254c4f6a9a3a03715923052798" integrity sha512-MZVIsLKGVOVE1KEnldppe6Ij+vmemMuApDfjhVSLzyYP+td0bREEYyAoIw9yFePoBXManCuBqmiNP5FqJS5Xkg== -http-status-codes@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-1.4.0.tgz#6e4c15d16ff3a9e2df03b89f3a55e1aae05fb477" - integrity sha512-JrT3ua+WgH8zBD3HEJYbeEgnuQaAnUeRRko/YojPAJjGmIfGD3KPU/asLdsLwKjfxOmQe5nXMQ0pt/7MyapVbQ== - -iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4: +iconv-lite@^0.4.24, iconv-lite@^0.4.4: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -1323,16 +1240,11 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= - ini@~1.3.0: version "1.3.7" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" @@ -1507,11 +1419,6 @@ is-windows@^1.0.2: resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= - isarray@1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -1534,11 +1441,6 @@ isobject@^3.0.0, isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= -iterall@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.3.0.tgz#afcb08492e2915cbd8a0884eb93a8c94d0d72fea" - integrity sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg== - jasmine-core@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.6.0.tgz#491f3bb23941799c353ceb7a45b38a950ebc5a20" @@ -1743,11 +1645,6 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= - meow@^3.3.0: version "3.7.0" resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" @@ -1788,18 +1685,6 @@ micromatch@^3.1.10: snapdragon "^0.8.1" to-regex "^3.0.2" -mime-db@1.44.0: - version "1.44.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" - integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== - -mime-types@~2.1.24: - version "2.1.27" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" - integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== - dependencies: - mime-db "1.44.0" - mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" @@ -1862,20 +1747,6 @@ ms@2.1.2, ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -multer@^1.4.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/multer/-/multer-1.4.2.tgz#2f1f4d12dbaeeba74cb37e623f234bf4d3d2057a" - integrity sha512-xY8pX7V+ybyUpbYMxtjM9KAiD9ixtg5/JkeKUTD6xilfDv0vzzOFcCp4Ljb1UU3tSOM3VTZtKo63OmzOrGi3Cg== - dependencies: - append-field "^1.0.0" - busboy "^0.2.11" - concat-stream "^1.5.2" - mkdirp "^0.5.1" - object-assign "^4.1.1" - on-finished "^2.3.0" - type-is "^1.6.4" - xtend "^4.0.0" - mute-stream@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" @@ -1997,7 +1868,7 @@ number-is-nan@^1.0.0: resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= -object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4.0.1, object-assign@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -2025,13 +1896,6 @@ object.pick@^1.3.0: dependencies: isobject "^3.0.1" -on-finished@^2.3.0, on-finished@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= - dependencies: - ee-first "1.1.1" - once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -2207,11 +2071,6 @@ punycode@^2.1.0: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -qs@6.7.0: - version "6.7.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" - integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== - query-string@^6.13.3: version "6.13.4" resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.13.4.tgz#b35a9a3bd4955bce55f94feb0e819b3d0be6f66f" @@ -2221,16 +2080,6 @@ query-string@^6.13.3: split-on-first "^1.0.0" strict-uri-encode "^2.0.0" -raw-body@2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" - integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== - dependencies: - bytes "3.1.0" - http-errors "1.7.2" - iconv-lite "0.4.24" - unpipe "1.0.0" - rc@^1.2.7: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" @@ -2258,17 +2107,7 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" -readable-stream@1.1.x: - version "1.1.14" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" - integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk= - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - -readable-stream@^2.0.6, readable-stream@^2.2.2: +readable-stream@^2.0.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -2444,11 +2283,6 @@ set-value@^2.0.0, set-value@^2.0.1: is-plain-object "^2.0.3" split-string "^3.0.1" -setprototypeof@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" - integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== - shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -2590,11 +2424,6 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" -"statuses@>= 1.5.0 < 2": - version "1.5.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= - streamsearch@0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" @@ -2640,11 +2469,6 @@ string-width@^4.1.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" -string_decoder@~0.10.x: - version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= - string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" @@ -2807,11 +2631,6 @@ to-regex@^3.0.1, to-regex@^3.0.2: regex-not "^1.0.2" safe-regex "^1.1.0" -toidentifier@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" - integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== - tree-kill@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" @@ -2889,19 +2708,6 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -type-is@^1.6.4, type-is@~1.6.17: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - -typedarray@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" - integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= - typescript@^3.8.3: version "3.9.7" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa" @@ -2921,11 +2727,6 @@ union-value@^1.0.0: is-extendable "^0.1.1" set-value "^2.0.1" -unpipe@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= - unset-value@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" diff --git a/benchmark/package-lock.json b/benchmark/package-lock.json index 72d0aae4..2d25c58a 100644 --- a/benchmark/package-lock.json +++ b/benchmark/package-lock.json @@ -209,9 +209,9 @@ } }, "glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "requires": { "is-glob": "^4.0.1" } @@ -688,9 +688,9 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "ws": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", - "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==" + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==" }, "xtend": { "version": "4.0.2", diff --git a/benchmark/package.json b/benchmark/package.json index b0cd6a23..586e97e6 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -24,7 +24,7 @@ "@types/ws": "^7.2.6", "ts-node-dev": "^1.0.0-pre.62", "typescript": "^4.0.2", - "ws": "^7.3.1" + "ws": "^7.4.6" }, "devDependencies": {} } diff --git a/benchmark/yarn.lock b/benchmark/yarn.lock index f1209dcf..8dcffe52 100644 --- a/benchmark/yarn.lock +++ b/benchmark/yarn.lock @@ -148,8 +148,8 @@ get-stdin@^4.0.1: resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" glob-parent@~5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" dependencies: is-glob "^4.0.1" @@ -515,9 +515,9 @@ wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" -ws@^7.3.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.1.tgz#d0547bf67f7ce4f12a72dfe31262c68d7dc551c8" +ws@^7.4.6: + version "7.4.6" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" xtend@^4.0.0: version "4.0.2" diff --git a/deeployer.libsonnet b/deeployer.libsonnet index f9dd87bd..8d9c2bfd 100644 --- a/deeployer.libsonnet +++ b/deeployer.libsonnet @@ -11,8 +11,7 @@ "back1": { "image": "thecodingmachine/workadventure-back:"+tag, "host": { - "url": "api1."+url, - "https": "enable", + "url": "api1-"+url, "containerPort": 8080 }, "ports": [8080, 50051], @@ -30,8 +29,7 @@ "back2": { "image": "thecodingmachine/workadventure-back:"+tag, "host": { - "url": "api2."+url, - "https": "enable", + "url": "api2-"+url, "containerPort": 8080 }, "ports": [8080, 50051], @@ -50,8 +48,7 @@ "replicas": 2, "image": "thecodingmachine/workadventure-pusher:"+tag, "host": { - "url": "pusher."+url, - "https": "enable" + "url": "pusher-"+url, }, "ports": [8080], "env": { @@ -68,27 +65,25 @@ "front": { "image": "thecodingmachine/workadventure-front:"+tag, "host": { - "url": "play."+url, - "https": "enable" + "url": "play-"+url, }, "ports": [80], "env": { - "PUSHER_URL": "//pusher."+url, - "UPLOADER_URL": "//uploader."+url, + "PUSHER_URL": "//pusher-"+url, + "UPLOADER_URL": "//uploader-"+url, "ADMIN_URL": "//"+url, "JITSI_URL": env.JITSI_URL, "SECRET_JITSI_KEY": env.SECRET_JITSI_KEY, "TURN_SERVER": "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443", "JITSI_PRIVATE_MODE": if env.SECRET_JITSI_KEY != '' then "true" else "false", - "START_ROOM_URL": "/_/global/maps."+url+"/Floor0/floor0.json" + "START_ROOM_URL": "/_/global/maps-"+url+"/Floor0/floor0.json" //"GA_TRACKING_ID": "UA-10196481-11" } }, "uploader": { "image": "thecodingmachine/workadventure-uploader:"+tag, "host": { - "url": "uploader."+url, - "https": "enable", + "url": "uploader-"+url, "containerPort": 8080 }, "ports": [8080], @@ -98,16 +93,12 @@ "maps": { "image": "thecodingmachine/workadventure-maps:"+tag, "host": { - "url": "maps."+url, - "https": "enable" + "url": "maps-"+url }, "ports": [80] }, }, "config": { - "https": { - "mail": "d.negrier@thecodingmachine.com" - }, k8sextension(k8sConf):: k8sConf + { back1+: { @@ -122,6 +113,14 @@ } } } + }, + ingress+: { + spec+: { + tls+: [{ + hosts: ["api1-"+url], + secretName: "certificate-tls" + }] + } } }, back2+: { @@ -136,6 +135,14 @@ } } } + }, + ingress+: { + spec+: { + tls+: [{ + hosts: ["api2-"+url], + secretName: "certificate-tls" + }] + } } }, pusher+: { @@ -150,8 +157,46 @@ } } } - } - } + }, + ingress+: { + spec+: { + tls+: [{ + hosts: ["pusher-"+url], + secretName: "certificate-tls" + }] + } + } + }, + front+: { + ingress+: { + spec+: { + tls+: [{ + hosts: ["play-"+url], + secretName: "certificate-tls" + }] + } + } + }, + uploader+: { + ingress+: { + spec+: { + tls+: [{ + hosts: ["uploader-"+url], + secretName: "certificate-tls" + }] + } + } + }, + maps+: { + ingress+: { + spec+: { + tls+: [{ + hosts: ["maps-"+url], + secretName: "certificate-tls" + }] + } + } + }, } } } diff --git a/docs/maps/animations.md b/docs/maps/animations.md new file mode 100644 index 00000000..276f2332 --- /dev/null +++ b/docs/maps/animations.md @@ -0,0 +1,33 @@ +{.section-title.accent.text-primary} +# Animating WorkAdventure maps + +A tile can run an animation in loops, for example to render water or blinking lights. Each animation frame is a single +32x32 tile. To create an animation, edit the tileset in Tiled and click on the tile to animate (or pick a free tile to +not overwrite existing ones) and click on the animation editor: + + +
+ +
+ +You can now add all tiles that should be part of the animation via drag and drop to the "playlist" and adjust the frame duration: + +
+
+ +
The tile animation editor
+
+
+ +You can preview animations directly in Tiled, using the "Show tile animations" option: + + +
+
+ +
The Show Tile Animations option
+
+
+ +{.alert.alert-info} +**Tip:** The engine does tile-updates every 100ms, animations with a shorter frame duration will most likely not look that good or may even do not work. diff --git a/docs/maps/api-chat.md b/docs/maps/api-chat.md new file mode 100644 index 00000000..49a40f97 --- /dev/null +++ b/docs/maps/api-chat.md @@ -0,0 +1,37 @@ +{.section-title.accent.text-primary} +# API Chat functions reference + +### Sending a message in the chat + +``` +WA.chat.sendChatMessage(message: string, author: string): void +``` + +Sends a message in the chat. The message is only visible in the browser of the current user. + +* **message**: the message to be displayed in the chat +* **author**: the name displayed for the author of the message. It does not have to be a real user. + +Example: + +```javascript +WA.chat.sendChatMessage('Hello world', 'Mr Robot'); +``` + +### Listening to messages from the chat + +```javascript +WA.chat.onChatMessage(callback: (message: string) => void): void +``` + +Listens to messages typed by the current user and calls the callback. Messages from other users in the chat cannot be listened to. + +* **callback**: the function that will be called when a message is received. It contains the message typed by the user. + +Example: + +```javascript +WA.chat.onChatMessage((message => { + console.log('The user typed a message', message); +})); +``` diff --git a/docs/maps/api-controls.md b/docs/maps/api-controls.md new file mode 100644 index 00000000..dcb0f17b --- /dev/null +++ b/docs/maps/api-controls.md @@ -0,0 +1,29 @@ +{.section-title.accent.text-primary} +# API Controls functions Reference + +### Disabling / restoring controls + +``` +WA.controls.disablePlayerControls(): void +WA.controls.restorePlayerControls(): void +``` + +These 2 methods can be used to completely disable player controls and to enable them again. + +When controls are disabled, the user cannot move anymore using keyboard input. This can be useful in a "First Time User Experience" part, to display an important message to a user before letting him/her move again. + +Example: + +```javascript +WA.room.onEnterZone('myZone', () => { + WA.controls.disablePlayerControls(); + WA.ui.openPopup("popupRectangle", 'This is an imporant message!', [{ + label: "Got it!", + className: "primary", + callback: (popup) => { + WA.controls.restorePlayerControls(); + popup.close(); + } + }]); +}); +``` diff --git a/docs/maps/api-deprecated.md b/docs/maps/api-deprecated.md new file mode 100644 index 00000000..930caebe --- /dev/null +++ b/docs/maps/api-deprecated.md @@ -0,0 +1,20 @@ +{.section-title.accent.text-primary} +# API Reference - Deprecated functions + +The list of functions below is **deprecated**. You should not use those but. use the replacement functions. + +- Method `WA.sendChatMessage` is deprecated. It has been renamed to `WA.chat.sendChatMessage`. +- Method `WA.disablePlayerControls` is deprecated. It has been renamed to `WA.controls.disablePlayerControls`. +- Method `WA.restorePlayerControls` is deprecated. It has been renamed to `WA.controls.restorePlayerControls`. +- Method `WA.displayBubble` is deprecated. It has been renamed to `WA.ui.displayBubble`. +- Method `WA.removeBubble` is deprecated. It has been renamed to `WA.ui.removeBubble`. +- Method `WA.openTab` is deprecated. It has been renamed to `WA.nav.openTab`. +- Method `WA.loadSound` is deprecated. It has been renamed to `WA.sound.loadSound`. +- Method `WA.goToPage` is deprecated. It has been renamed to `WA.nav.goToPage`. +- Method `WA.goToRoom` is deprecated. It has been renamed to `WA.nav.goToRoom`. +- Method `WA.openCoWebSite` is deprecated. It has been renamed to `WA.nav.openCoWebSite`. +- Method `WA.closeCoWebSite` is deprecated. It has been renamed to `WA.nav.closeCoWebSite`. +- Method `WA.openPopup` is deprecated. It has been renamed to `WA.ui.openPopup`. +- Method `WA.onChatMessage` is deprecated. It has been renamed to `WA.chat.onChatMessage`. +- Method `WA.onEnterZone` is deprecated. It has been renamed to `WA.room.onEnterZone`. +- Method `WA.onLeaveZone` is deprecated. It has been renamed to `WA.room.onLeaveZone`. diff --git a/docs/maps/api-nav.md b/docs/maps/api-nav.md new file mode 100644 index 00000000..29323632 --- /dev/null +++ b/docs/maps/api-nav.md @@ -0,0 +1,68 @@ +{.section-title.accent.text-primary} +# API Navigation functions reference + +### Opening a web page in a new tab + +``` +WA.nav.openTab(url: string): void +``` + +Opens the webpage at "url" in your browser, in a new tab. + +Example: + +```javascript +WA.nav.openTab('https://www.wikipedia.org/'); +``` + +### Opening a web page in the current tab + +``` +WA.nav.goToPage(url: string): void +``` + +Opens the webpage at "url" in your browser in place of WorkAdventure. WorkAdventure will be completely unloaded. + +Example: + +```javascript +WA.nav.goToPage('https://www.wikipedia.org/'); +``` + +### Going to a different map from the script + +``` + +WA.nav.goToRoom(url: string): void +``` + +Load the map at url without unloading workadventure + +relative urls: "../subFolder/map.json[#start-layer-name]" +global urls: "/_/global/domain/path/map.json[#start-layer-name]" + +Example: + +```javascript +WA.nav.goToRoom("/@/tcm/workadventure/floor0") // workadventure urls +WA.nav.goToRoom('../otherMap/map.json'); +WA.nav.goToRoom("/_/global/.json#start-layer-2") +``` + +### Opening/closing a web page in an iFrame + +``` +WA.nav.openCoWebSite(url: string): void +WA.nav.closeCoWebSite(): void +``` + +Opens the webpage at "url" in an iFrame (on the right side of the screen) or close that iFrame. + +Example: + +```javascript +WA.nav.openCoWebSite('https://www.wikipedia.org/'); +// ... +WA.nav.closeCoWebSite(); +``` + diff --git a/docs/maps/api-player.md b/docs/maps/api-player.md new file mode 100644 index 00000000..f483731e --- /dev/null +++ b/docs/maps/api-player.md @@ -0,0 +1,21 @@ +{.section-title.accent.text-primary} +# API Player functions Reference + +### Listen to player movement +``` +WA.player.onPlayerMove(callback: HasPlayerMovedEventCallback): void; +``` +Listens to the movement of the current user and calls the callback. Sends an event when the user stops moving, changes direction and every 200ms when moving in the same direction. + +The event has the following attributes : +* **moving (boolean):** **true** when the current player is moving, **false** otherwise. +* **direction (string):** **"right"** | **"left"** | **"down"** | **"top"** the direction where the current player is moving. +* **x (number):** coordinate X of the current player. +* **y (number):** coordinate Y of the current player. + +**callback:** the function that will be called when the current player is moving. It contains the event. + +Example : +```javascript +WA.player.onPlayerMove(console.log); +``` \ No newline at end of file diff --git a/docs/maps/api-reference.md b/docs/maps/api-reference.md index 6a4dd7ab..30a11b2a 100644 --- a/docs/maps/api-reference.md +++ b/docs/maps/api-reference.md @@ -1,352 +1,12 @@ {.section-title.accent.text-primary} # API Reference -### Sending a message in the chat - -``` -sendChatMessage(message: string, author: string): void -``` - -Sends a message in the chat. The message is only visible in the browser of the current user. - -* **message**: the message to be displayed in the chat -* **author**: the name displayed for the author of the message. It does not have to be a real user. - -Example: - -```javascript -WA.sendChatMessage('Hello world', 'Mr Robot'); -``` - -### Listening to messages from the chat - -```javascript -onChatMessage(callback: (message: string) => void): void -``` - -Listens to messages typed by the current user and calls the callback. Messages from other users in the chat cannot be listened to. - -* **callback**: the function that will be called when a message is received. It contains the message typed by the user. - -Example: - -```javascript -WA.onChatMessage((message => { - console.log('The user typed a message', message); -})); -``` - -### Detecting when the user enters/leaves a zone - -``` -onEnterZone(name: string, callback: () => void): void -onLeaveZone(name: string, callback: () => void): void -``` - -Listens to the position of the current user. The event is triggered when the user enters or leaves a given zone. The name of the zone is stored in the map, on a dedicated layer with the `zone` property. - -
-
- -
The `zone` property, applied on a layer
-
-
- -* **name**: the name of the zone, as defined in the `zone` property. -* **callback**: the function that will be called when a user enters or leaves the zone. - -Example: - -```javascript -WA.onEnterZone('myZone', () => { - WA.sendChatMessage("Hello!", 'Mr Robot'); -}) - -WA.onLeaveZone('myZone', () => { - WA.sendChatMessage("Goodbye!", 'Mr Robot'); -}) -``` - -### Opening a popup - -In order to open a popup window, you must first define the position of the popup on your map. - -You can position this popup by using a "rectangle" object in Tiled that you will place on an "object" layer. - -
-
- -
-
- -
-
- -``` -openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup -``` - -* **targetObject**: the name of the rectangle object defined in Tiled. -* **message**: the message to display in the popup. -* **buttons**: an array of action buttons defined underneath the popup. - -Action buttons are `ButtonDescriptor` objects containing these properties. - -* **label (_string_)**: The label of the button. -* **className (_string_)**: The visual type of the button. Can be one of "normal", "primary", "success", "warning", "error", "disabled". -* **callback (_(popup: Popup)=>void_)**: Callback called when the button is pressed. - -Please note that `openPopup` returns an object of the `Popup` class. Also, the callback called when a button is clicked is passed a `Popup` object. - -The `Popup` class that represents an open popup contains a single method: `close()`. This will obviously close the popup when called. - -```javascript -class Popup { - /** - * Closes the popup - */ - close() {}; -} -``` - -Example: - -```javascript -let helloWorldPopup; - -// Open the popup when we enter a given zone -helloWorldPopup = WA.onEnterZone('myZone', () => { - WA.openPopup("popupRectangle", 'Hello world!', [{ - label: "Close", - className: "primary", - callback: (popup) => { - // Close the popup when the "Close" button is pressed. - popup.close(); - } - }); -}]); - -// Close the popup when we leave the zone. -WA.onLeaveZone('myZone', () => { - helloWorldPopup.close(); -}); -``` - -### Disabling / restoring controls - -``` -disablePlayerControls(): void -restorePlayerControls(): void -``` - -These 2 methods can be used to completely disable player controls and to enable them again. - -When controls are disabled, the user cannot move anymore using keyboard input. This can be useful in a "First Time User Experience" part, to display an important message to a user before letting him/her move again. - -Example: - -```javascript -WA.onEnterZone('myZone', () => { - WA.disablePlayerControls(); - WA.openPopup("popupRectangle", 'This is an imporant message!', [{ - label: "Got it!", - className: "primary", - callback: (popup) => { - WA.restorePlayerControls(); - popup.close(); - } - }]); -}); -``` - -### Opening a web page in a new tab - -``` -openTab(url: string): void -``` - -Opens the webpage at "url" in your browser, in a new tab. - -Example: - -```javascript -WA.openTab('https://www.wikipedia.org/'); -``` - -### Opening a web page in the current tab - -``` -goToPage(url: string): void -``` - -Opens the webpage at "url" in your browser in place of WorkAdventure. WorkAdventure will be completely unloaded. - -Example: - -```javascript -WA.goToPage('https://www.wikipedia.org/'); -``` - -### Opening/closing a web page in an iFrame - -``` -openCoWebSite(url: string): void -closeCoWebSite(): void -``` - -Opens the webpage at "url" in an iFrame (on the right side of the screen) or close that iFrame. - -Example: - -```javascript -WA.openCoWebSite('https://www.wikipedia.org/'); -// ... -WA.closeCoWebSite(); -``` - -### Load a sound from an url - -``` -loadSound(url: string): Sound -``` - -Load a sound from an url - -Please note that `loadSound` returns an object of the `Sound` class - -The `Sound` class that represents a loaded sound contains two methods: `play(soundConfig : SoundConfig|undefined)` and `stop()` - -The parameter soundConfig is optional, if you call play without a Sound config the sound will be played with the basic configuration. - -Example: - -```javascript -var mySound = WA.loadSound("Sound.ogg"); -var config = { - volume : 0.5, - loop : false, - rate : 1, - detune : 1, - delay : 0, - seek : 0, - mute : false -} -mySound.play(config); -// ... -mySound.stop(); -``` - -### Show / Hide a layer -``` -WA.showLayer(layerName : string): void -WA.hideLayer(layerName : string) : void -``` -These 2 methods can be used to show and hide a layer. - -Example : -```javascript -WA.showLayer('bottom'); -//... -WA.hideLayer('bottom'); -``` - -### Set/Create properties in a layer - -``` -WA.setProperty(layerName : string, propertyName : string, propertyValue : string | number | boolean | undefined) : void; -``` - -Set the value of the `propertyName` property of the layer `layerName` at `propertyValue`. If the property doesn't exist, create the property `propertyName` and set the value of the property at `propertyValue`. - -Example : -```javascript -WA.setProperty('wikiLayer', 'openWebsite', 'https://www.wikipedia.org/'); -``` - -### Listen to player movement - -``` -onPlayerMove(callback: HasPlayerMovedEventCallback): void; -``` -Listens to the movement of the current user and calls the callback. Sends an event when the user stops moving, changes direction and every 200ms when moving in the same direction. - -The event has the following attributes : -* **moving (boolean):** **true** when the current player is moving, **false** otherwise. -* **direction (string):** **"right"** | **"left"** | **"down"** | **"top"** the direction where the current player is moving. -* **x (number):** coordinate X of the current player. -* **y (number):** coordinate Y of the current player. - -**callback:** the function that will be called when the current player is moving. It contains the event. - -Example : -```javascript -WA.onPlayerMove(console.log); -``` - -### Getting informations on the current user -``` -getCurrentUser(): Promise -``` -Return a promise that resolves to a `User` object with the following attributes : -* **id (string) :** ID of the current user -* **nickName (string) :** name displayed above the current user -* **tags (string[]) :** list of all the tags of the current user - -Example : -```javascript -WA.getCurrentUser().then((user) => { - if (user.nickName === 'ABC') { - console.log(user.tags); - } -}) -``` - -### Getting informations on the current room -``` -getCurrentRoom(): Promise -``` -Return a promise that resolves to a `Room` object with the following attributes : -* **id (string) :** ID of the current room -* **map (ITiledMap) :** contains the JSON map file with the properties that were setted by the script if `setProperty` was called. -* **mapUrl (string) :** Url of the JSON map file -* **startLayer (string | null) :** Name of the layer where the current user started, only if different from `start` layer - -Example : -```javascript -WA.getCurrentRoom((room) => { - if (room.id === '42') { - console.log(room.map); - window.open(room.mapUrl, '_blank'); - } -}) -``` - -### Add a custom menu -``` -registerMenuCommand(commandDescriptor: string, callback: (commandDescriptor: string) => void) -``` -Add a custom menu item containing the text `commandDescriptor`. A click on the menu will trigger the `callback`. - -Example : -```javascript -WA.registerMenuCommand('About', () => { - console.log("The About menu was clicked"); -}); -``` - - -### Working with group layers -If you use group layers in your map, to reference a layer in a group you will need to use a `/` to join layer names together. - -Example : -
-
- -
-
- -The name of the layers of this map are : -* `entries/start` -* `bottom/ground/under` -* `bottom/build/carpet` -* `wall` \ No newline at end of file +- [Navigation functions](api-nav.md) +- [Chat functions](api-chat.md) +- [Room functions](api-room.md) +- [Player functions](api-player.md) +- [UI functions](api-ui.md) +- [Sound functions](api-sound.md) +- [Controls functions](api-controls.md) + +- [List of deprecated functions](api-deprecated.md) \ No newline at end of file diff --git a/docs/maps/api-room.md b/docs/maps/api-room.md new file mode 100644 index 00000000..d8381cc6 --- /dev/null +++ b/docs/maps/api-room.md @@ -0,0 +1,114 @@ +{.section-title.accent.text-primary} +# API Room functions Reference + +### Working with group layers +If you use group layers in your map, to reference a layer in a group you will need to use a `/` to join layer names together. + +Example : +
+
+ +
+
+ +The name of the layers of this map are : +* `entries/start` +* `bottom/ground/under` +* `bottom/build/carpet` +* `wall` + +### Detecting when the user enters/leaves a zone + +``` +WA.room.onEnterZone(name: string, callback: () => void): void +WA.room.onLeaveZone(name: string, callback: () => void): void +``` + +Listens to the position of the current user. The event is triggered when the user enters or leaves a given zone. The name of the zone is stored in the map, on a dedicated layer with the `zone` property. + +
+
+ +
The `zone` property, applied on a layer
+
+
+ +* **name**: the name of the zone, as defined in the `zone` property. +* **callback**: the function that will be called when a user enters or leaves the zone. + +Example: + +```javascript +WA.room.onEnterZone('myZone', () => { + WA.chat.sendChatMessage("Hello!", 'Mr Robot'); +}) + +WA.room.onLeaveZone('myZone', () => { + WA.chat.sendChatMessage("Goodbye!", 'Mr Robot'); +}) +``` + +### Show / Hide a layer +``` +WA.room.showLayer(layerName : string): void +WA.room.hideLayer(layerName : string) : void +``` +These 2 methods can be used to show and hide a layer. + +Example : +```javascript +WA.room.showLayer('bottom'); +//... +WA.room.hideLayer('bottom'); +``` + +### Set/Create properties in a layer + +``` +WA.room.setProperty(layerName : string, propertyName : string, propertyValue : string | number | boolean | undefined) : void; +``` + +Set the value of the `propertyName` property of the layer `layerName` at `propertyValue`. If the property doesn't exist, create the property `propertyName` and set the value of the property at `propertyValue`. + +Example : +```javascript +WA.room.setProperty('wikiLayer', 'openWebsite', 'https://www.wikipedia.org/'); +``` + +### Getting information on the current room +``` +WA.room.getCurrentRoom(): Promise +``` +Return a promise that resolves to a `Room` object with the following attributes : +* **id (string) :** ID of the current room +* **map (ITiledMap) :** contains the JSON map file with the properties that were setted by the script if `setProperty` was called. +* **mapUrl (string) :** Url of the JSON map file +* **startLayer (string | null) :** Name of the layer where the current user started, only if different from `start` layer + +Example : +```javascript +WA.room.getCurrentRoom((room) => { + if (room.id === '42') { + console.log(room.map); + window.open(room.mapUrl, '_blank'); + } +}) +``` + +### Getting information on the current user +``` +WA.player.getCurrentUser(): Promise +``` +Return a promise that resolves to a `User` object with the following attributes : +* **id (string) :** ID of the current user +* **nickName (string) :** name displayed above the current user +* **tags (string[]) :** list of all the tags of the current user + +Example : +```javascript +WA.room.getCurrentUser().then((user) => { + if (user.nickName === 'ABC') { + console.log(user.tags); + } +}) +``` diff --git a/docs/maps/api-sound.md b/docs/maps/api-sound.md new file mode 100644 index 00000000..3c57ecae --- /dev/null +++ b/docs/maps/api-sound.md @@ -0,0 +1,34 @@ +{.section-title.accent.text-primary} +# API Sound functions Reference + +### Load a sound from an url + +``` +WA.sound.loadSound(url: string): Sound +``` + +Load a sound from an url + +Please note that `loadSound` returns an object of the `Sound` class + +The `Sound` class that represents a loaded sound contains two methods: `play(soundConfig : SoundConfig|undefined)` and `stop()` + +The parameter soundConfig is optional, if you call play without a Sound config the sound will be played with the basic configuration. + +Example: + +```javascript +var mySound = WA.sound.loadSound("Sound.ogg"); +var config = { + volume : 0.5, + loop : false, + rate : 1, + detune : 1, + delay : 0, + seek : 0, + mute : false +} +mySound.play(config); +// ... +mySound.stop(); +``` diff --git a/docs/maps/api-ui.md b/docs/maps/api-ui.md new file mode 100644 index 00000000..fc38dc41 --- /dev/null +++ b/docs/maps/api-ui.md @@ -0,0 +1,89 @@ +{.section-title.accent.text-primary} +# API UI functions Reference + +### Opening a popup + +In order to open a popup window, you must first define the position of the popup on your map. + +You can position this popup by using a "rectangle" object in Tiled that you will place on an "object" layer. + +
+
+ +
+
+ +
+
+ +``` +WA.ui.openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup +``` + +* **targetObject**: the name of the rectangle object defined in Tiled. +* **message**: the message to display in the popup. +* **buttons**: an array of action buttons defined underneath the popup. + +Action buttons are `ButtonDescriptor` objects containing these properties. + +* **label (_string_)**: The label of the button. +* **className (_string_)**: The visual type of the button. Can be one of "normal", "primary", "success", "warning", "error", "disabled". +* **callback (_(popup: Popup)=>void_)**: Callback called when the button is pressed. + +Please note that `openPopup` returns an object of the `Popup` class. Also, the callback called when a button is clicked is passed a `Popup` object. + +The `Popup` class that represents an open popup contains a single method: `close()`. This will obviously close the popup when called. + +```javascript +class Popup { + /** + * Closes the popup + */ + close() {}; +} +``` + +Example: + +```javascript +let helloWorldPopup; + +// Open the popup when we enter a given zone +helloWorldPopup = WA.room.onEnterZone('myZone', () => { + WA.ui.openPopup("popupRectangle", 'Hello world!', [{ + label: "Close", + className: "primary", + callback: (popup) => { + // Close the popup when the "Close" button is pressed. + popup.close(); + } + }); +}]); + +// Close the popup when we leave the zone. +WA.room.onLeaveZone('myZone', () => { + helloWorldPopup.close(); +}); +``` + +### Add custom menu + +```typescript +WA.ui.registerMenuCommand(menuCommand: string, callback: (menuCommand: string) => void): void +``` +Add a custom menu item containing the text `commandDescriptor` in the main menu. A click on the menu will trigger the `callback`. +Custom menu exist only until the map is unloaded, or you leave the iframe zone of the script. + +Example: + +```javascript + +WA.ui.registerMenuCommand("test", () => { + WA.chat.sendChatMessage("test clicked", "menu cmd") +}) + +``` + +
+ +
\ No newline at end of file diff --git a/docs/maps/assets/menu-command.png b/docs/maps/assets/menu-command.png new file mode 100644 index 00000000..0caf75c9 Binary files /dev/null and b/docs/maps/assets/menu-command.png differ diff --git a/docs/maps/scripting.md b/docs/maps/scripting.md index b9dee484..5f645b81 100644 --- a/docs/maps/scripting.md +++ b/docs/maps/scripting.md @@ -101,7 +101,7 @@ You can now start by testing this with a simple message sent to the chat. ```html ... ... ``` diff --git a/docs/maps/wa-maps.md b/docs/maps/wa-maps.md new file mode 100644 index 00000000..98fdce2d --- /dev/null +++ b/docs/maps/wa-maps.md @@ -0,0 +1,90 @@ +{.section-title.accent.text-primary} +# About WorkAdventure maps + +A WorkAdventure map is a map in "JSON" format generated by [Tiled](https://www.mapeditor.org/). + +## Tiles + +A map is made of "tiles" (we can also call them "sprites"). In WorkAdventure, the tiles are small images of 32x32 pixels. + +Tiles may have transparent parts. Many tiles can be stored in a single PNG file. We call this file a "tileset". + +There are many tilesets available on the internet. Some examples of websites offering awesome tiles: + +* [itch.io](https://itch.io/) +* [opengameart.org](https://opengameart.org/) +* [deviantart.com](https://www.deviantart.com/) + +Keep in mind the size of tiles and do not forget to check the license of the tileset you are using! + + +## How to design "pixel" tiles + +You can design your own tiles as well as change existing tiles, this is usually referred to as "pixeling". You can start drawing your own tiles with [Piskel](https://www.piskelapp.com/). It is easy to use and well targeted at "pixeling". If you are getting serious about pixeling, the awesome folks at the Chaos Computer Club recommend the use of the editor [Krita](https://krita.org/). There are plenty of other editors as well. + +If you are using Krita: + +* Please double check that your tiles are 32x32 pixels in size. You can enable a grid under view -> show grid and under settings -> dockers -> grid you can select the grid size. +* Use transparency if you have to model transitions between different materials. This is more flexible and saves you time by not modeling every transition. +* You can follow the Pixel-Art Workshop by blinry: [media.ccc.de/v/34C3-jugend-hackt-1016-pixel_art_workshop](https://media.ccc.de/v/34C3-jugend-hackt-1016-pixel_art_workshop) + +## WorkAdventure Map Rules + +In order to design a map that will be readable by WorkAdventure, you will have to respect some constraints. + +In particular, you will need to: + +* set a start position for the players +* configure the "floor layer" (so that WorkAdventure can correctly display characters above the floor, but under the ceiling) +* eventually, you can place exits that link to other maps + +A few things to notice: + +* your map can have as many layers as you want +* your map MUST contain a layer named "floorLayer" of type "objectgroup" that represents the layer on which characters will be drawn. Every layer above the "floorLayer" will be displayed on top of the characters. +* the tilesets in your map MUST be embedded. You cannot refer to an external typeset in a TSX file. Click the "embed tileset" button in the tileset tab to embed tileset data. +* your map MUST be exported in JSON format. You need to use a recent version of Tiled to get JSON format export (1.3+) +* WorkAdventure doesn't support object layers and will ignore them +* If you are starting from a blank map, your map MUST be orthogonal and tiles size should be 32x32. + +
+
+ +
"floorLayer" is compulsory
+
+
+ +## Building walls and "collidable" areas + +By default, the characters can traverse any tiles. If you want to prevent your characeter from going through a tile (like a wall or a desktop), you must make this tile "collidable". You can do this by settings the `collides` property on a given tile. + +To make a tile "collidable", you should: + +1. select the relevant tileset and switch to "edit" mode: + {.document-img} + ![](https://workadventu.re/img/docs/collides-1.png) +2. right click on a tile of the tileset to select it: + {.document-img} + ![](https://workadventu.re/img/docs/collides-2.png) +3. on the left pane in the custom properties section, right click and select "Add properties": + {.document-img} + ![](https://workadventu.re/img/docs/collides-3.png) + + Please add a `collides` property. The type of the property must be **bool**. + +4. finally, check the checkbox for the `collides` property: + {.document-img} + ![](https://workadventu.re/img/docs/collides-4.png) + +Repeat for every tile that should be "collidable". + +## Adding behaviour with properties + +In the next sections, you will see how you can add behaviour on your map by adding "properties". +You can add properties for a variety of features: putting exits, opening websites, meeting rooms, silent zones, etc... + +You can add properties either on individual tiles of a tileset OR on a complete layer. + +If you put a property on a layer, it will be triggered if your Woka walks on any tile of the layer. + +The exception is the "collides" property that can only be set on tiles, but not on a complete layer. diff --git a/front/dist/index.tmpl.html b/front/dist/index.tmpl.html index 7ef44116..aa63229f 100644 --- a/front/dist/index.tmpl.html +++ b/front/dist/index.tmpl.html @@ -29,7 +29,6 @@ - WorkAdventure @@ -47,32 +46,6 @@ -
-
- -
- - - - - -
-
-
-
-
- - -
-
- - -
-
- - -
-
-
- -
-
- - -
- - diff --git a/front/dist/resources/fonts/fonts.css b/front/dist/resources/fonts/fonts.css new file mode 100644 index 00000000..a3d3cf71 --- /dev/null +++ b/front/dist/resources/fonts/fonts.css @@ -0,0 +1,5 @@ +/*This file is a workaround to allow phaser to load directly this font */ +@font-face { + font-family: "Press Start 2P"; + src: url("/fonts/press-start-2p-latin-400-normal.woff2") format('woff2'); +} \ No newline at end of file diff --git a/front/dist/resources/html/CustomCharacterScene.html b/front/dist/resources/html/CustomCharacterScene.html deleted file mode 100644 index 0bc050ea..00000000 --- a/front/dist/resources/html/CustomCharacterScene.html +++ /dev/null @@ -1,160 +0,0 @@ - - - diff --git a/front/dist/resources/html/EnableCameraScene.html b/front/dist/resources/html/EnableCameraScene.html deleted file mode 100644 index 2dda6cc1..00000000 --- a/front/dist/resources/html/EnableCameraScene.html +++ /dev/null @@ -1,129 +0,0 @@ - - - diff --git a/front/dist/resources/html/SelectCompanionScene.html b/front/dist/resources/html/SelectCompanionScene.html deleted file mode 100644 index cffa7880..00000000 --- a/front/dist/resources/html/SelectCompanionScene.html +++ /dev/null @@ -1,134 +0,0 @@ - - - diff --git a/front/dist/resources/html/gameMenu.html b/front/dist/resources/html/gameMenu.html index 6abf2753..26be2a1c 100644 --- a/front/dist/resources/html/gameMenu.html +++ b/front/dist/resources/html/gameMenu.html @@ -1,7 +1,4 @@ - - diff --git a/front/dist/resources/html/loginScene.html b/front/dist/resources/html/loginScene.html deleted file mode 100644 index 38e798e5..00000000 --- a/front/dist/resources/html/loginScene.html +++ /dev/null @@ -1,120 +0,0 @@ - - - diff --git a/front/dist/resources/html/selectCharacterScene.html b/front/dist/resources/html/selectCharacterScene.html deleted file mode 100644 index c51731df..00000000 --- a/front/dist/resources/html/selectCharacterScene.html +++ /dev/null @@ -1,142 +0,0 @@ - - - diff --git a/front/dist/resources/html/warningContainer.html b/front/dist/resources/html/warningContainer.html index 4989c49d..832ac4da 100644 --- a/front/dist/resources/html/warningContainer.html +++ b/front/dist/resources/html/warningContainer.html @@ -1,7 +1,4 @@ diff --git a/front/src/Components/ConsoleGlobalMessageManager/UploadAudioGlobalMessage.svelte b/front/src/Components/ConsoleGlobalMessageManager/UploadAudioGlobalMessage.svelte new file mode 100644 index 00000000..50954005 --- /dev/null +++ b/front/src/Components/ConsoleGlobalMessageManager/UploadAudioGlobalMessage.svelte @@ -0,0 +1,130 @@ + + + +
+
+ Upload a file {fileinput.click();}}> + {#if filename != undefined} + + {/if} + {#if errorfile} +

No file selected. You need to upload a file before sending it.

+ {/if} + {inputAudioFile(e)}}> +
+
+ +
+
+ + \ No newline at end of file diff --git a/front/src/Components/CustomCharacterScene/CustomCharacterScene.svelte b/front/src/Components/CustomCharacterScene/CustomCharacterScene.svelte new file mode 100644 index 00000000..1807f15d --- /dev/null +++ b/front/src/Components/CustomCharacterScene/CustomCharacterScene.svelte @@ -0,0 +1,119 @@ + + +
+
+

Customize your WOKA

+
+
+ + +
+
+ {#if activeRow === 0} + + {/if} + {#if activeRow !== 0} + + {/if} + {#if activeRow === 5} + + {/if} + {#if activeRow !== 5} + + {/if} +
+
+ + diff --git a/front/src/Components/EnableCamera/EnableCameraScene.svelte b/front/src/Components/EnableCamera/EnableCameraScene.svelte new file mode 100644 index 00000000..1f9ef027 --- /dev/null +++ b/front/src/Components/EnableCamera/EnableCameraScene.svelte @@ -0,0 +1,229 @@ + + +
+
+

Turn on your camera and microphone

+
+ {#if $localStreamStore.type === 'success' && $localStreamStore.stream} + + {:else } +
+ +
+ {/if} + + +
+ + {#if $cameraListStore.length > 1 } +
+ Camera +
+ +
+
+ {/if} + + {#if $microphoneListStore.length > 1 } +
+ Microphone +
+ +
+
+ {/if} + +
+
+ +
+
+ + + diff --git a/front/src/Components/EnableCamera/HorizontalSoundMeterWidget.svelte b/front/src/Components/EnableCamera/HorizontalSoundMeterWidget.svelte new file mode 100644 index 00000000..a22da2fa --- /dev/null +++ b/front/src/Components/EnableCamera/HorizontalSoundMeterWidget.svelte @@ -0,0 +1,82 @@ + + + +
+ {#each [...Array(NB_BARS).keys()] as i} +
+ {/each} +
+ + diff --git a/front/src/Components/HelpCameraSettings/HelpCameraSettingsPopup.svelte b/front/src/Components/HelpCameraSettings/HelpCameraSettingsPopup.svelte new file mode 100644 index 00000000..8f4de785 --- /dev/null +++ b/front/src/Components/HelpCameraSettings/HelpCameraSettingsPopup.svelte @@ -0,0 +1,73 @@ + + +
+
+

Camera / Microphone access needed

+

Permission denied

+

You must allow camera and microphone access in your browser.

+

+ {#if isFirefox } +

Please click the "Remember this decision" checkbox, if you don't want Firefox to keep asking you the authorization.

+ + {:else if isChrome && !isAndroid } + + {/if} +

+
+
+ + +
+
+ + + diff --git a/front/dist/resources/objects/help-setting-camera-permission-chrome.png b/front/src/Components/HelpCameraSettings/images/help-setting-camera-permission-chrome.png similarity index 100% rename from front/dist/resources/objects/help-setting-camera-permission-chrome.png rename to front/src/Components/HelpCameraSettings/images/help-setting-camera-permission-chrome.png diff --git a/front/dist/resources/objects/help-setting-camera-permission-firefox.png b/front/src/Components/HelpCameraSettings/images/help-setting-camera-permission-firefox.png similarity index 100% rename from front/dist/resources/objects/help-setting-camera-permission-firefox.png rename to front/src/Components/HelpCameraSettings/images/help-setting-camera-permission-firefox.png diff --git a/front/src/Components/Login/LoginScene.svelte b/front/src/Components/Login/LoginScene.svelte new file mode 100644 index 00000000..fe3c2c63 --- /dev/null +++ b/front/src/Components/Login/LoginScene.svelte @@ -0,0 +1,122 @@ + + +
+
+ WorkAdventure logo +
+
+

Enter your name

+
+ {startValidating = true}} class:is-error={name.trim() === '' && startValidating} /> +
+ {#if name.trim() === '' && startValidating } +

The name is empty

+ {/if} +
+ + {#if DISPLAY_TERMS_OF_USE} +
+

By continuing, you are agreeing our terms of use, privacy policy and cookie policy.

+
+ {/if} +
+ +
+
+ + diff --git a/front/src/Components/MyCamera.svelte b/front/src/Components/MyCamera.svelte new file mode 100644 index 00000000..ed4154a9 --- /dev/null +++ b/front/src/Components/MyCamera.svelte @@ -0,0 +1,46 @@ + + + +
+
+ {#if $localStreamStore.type === "success" && $localStreamStore.stream } + + + {/if} +
+
diff --git a/front/src/Components/SelectCompanion/SelectCompanionScene.svelte b/front/src/Components/SelectCompanion/SelectCompanionScene.svelte new file mode 100644 index 00000000..04efe124 --- /dev/null +++ b/front/src/Components/SelectCompanion/SelectCompanionScene.svelte @@ -0,0 +1,87 @@ + + +
+
+

Select your companion

+ + +
+
+ + +
+
+ + diff --git a/front/src/Components/SoundMeterWidget.svelte b/front/src/Components/SoundMeterWidget.svelte new file mode 100644 index 00000000..30650e3f --- /dev/null +++ b/front/src/Components/SoundMeterWidget.svelte @@ -0,0 +1,53 @@ + + + +
+ 1}> + 2}> + 3}> + 4}> + 5}> +
diff --git a/front/src/Components/UI/AudioPlaying.svelte b/front/src/Components/UI/AudioPlaying.svelte new file mode 100644 index 00000000..97f77429 --- /dev/null +++ b/front/src/Components/UI/AudioPlaying.svelte @@ -0,0 +1,52 @@ + + +
+ Audio playing +

Audio message

+ +
+ + diff --git a/front/src/Components/UI/ErrorDialog.svelte b/front/src/Components/UI/ErrorDialog.svelte new file mode 100644 index 00000000..3244de24 --- /dev/null +++ b/front/src/Components/UI/ErrorDialog.svelte @@ -0,0 +1,48 @@ + + +
+

Error

+
+ {#each $errorStore as error} +

{error}

+ {/each} +
+
+ +
+
+ + diff --git a/front/src/Components/UI/images/megaphone.svg b/front/src/Components/UI/images/megaphone.svg new file mode 100644 index 00000000..708f860c --- /dev/null +++ b/front/src/Components/UI/images/megaphone.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + diff --git a/front/src/Components/VisitCard/VisitCard.svelte b/front/src/Components/VisitCard/VisitCard.svelte new file mode 100644 index 00000000..78f10359 --- /dev/null +++ b/front/src/Components/VisitCard/VisitCard.svelte @@ -0,0 +1,84 @@ + + + + + +
+ {#if hidden} +
+ {/if} + + {#if !hidden} +
+ +
+ {/if} + +
+ + diff --git a/front/src/Components/images/cinema-close.svg b/front/src/Components/images/cinema-close.svg new file mode 100644 index 00000000..aa1d9b17 --- /dev/null +++ b/front/src/Components/images/cinema-close.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/front/dist/resources/logos/cinema.svg b/front/src/Components/images/cinema.svg similarity index 100% rename from front/dist/resources/logos/cinema.svg rename to front/src/Components/images/cinema.svg diff --git a/front/src/Components/images/logo.png b/front/src/Components/images/logo.png new file mode 100644 index 00000000..f4440ad5 Binary files /dev/null and b/front/src/Components/images/logo.png differ diff --git a/front/src/Components/images/microphone-close.svg b/front/src/Components/images/microphone-close.svg new file mode 100644 index 00000000..16731829 --- /dev/null +++ b/front/src/Components/images/microphone-close.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + diff --git a/front/dist/resources/logos/microphone.svg b/front/src/Components/images/microphone.svg similarity index 100% rename from front/dist/resources/logos/microphone.svg rename to front/src/Components/images/microphone.svg diff --git a/front/dist/resources/logos/monitor-close.svg b/front/src/Components/images/monitor-close.svg similarity index 100% rename from front/dist/resources/logos/monitor-close.svg rename to front/src/Components/images/monitor-close.svg diff --git a/front/dist/resources/logos/monitor.svg b/front/src/Components/images/monitor.svg similarity index 100% rename from front/dist/resources/logos/monitor.svg rename to front/src/Components/images/monitor.svg diff --git a/front/src/Components/images/music-file.svg b/front/src/Components/images/music-file.svg new file mode 100644 index 00000000..a97656ba --- /dev/null +++ b/front/src/Components/images/music-file.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + diff --git a/front/src/Components/selectCharacter/SelectCharacterScene.svelte b/front/src/Components/selectCharacter/SelectCharacterScene.svelte new file mode 100644 index 00000000..5a37efda --- /dev/null +++ b/front/src/Components/selectCharacter/SelectCharacterScene.svelte @@ -0,0 +1,92 @@ + + +
+
+

Select your WOKA

+ + +
+
+ + +
+
+ + diff --git a/front/src/Connexion/ConnectionManager.ts b/front/src/Connexion/ConnectionManager.ts index 932fb1fc..8112ba17 100644 --- a/front/src/Connexion/ConnectionManager.ts +++ b/front/src/Connexion/ConnectionManager.ts @@ -4,7 +4,7 @@ import {RoomConnection} from "./RoomConnection"; import type {OnConnectInterface, PositionInterface, ViewportInterface} from "./ConnexionModels"; import {GameConnexionTypes, urlManager} from "../Url/UrlManager"; import {localUserStore} from "./LocalUserStore"; -import {LocalUser} from "./LocalUser"; +import {CharacterTexture, LocalUser} from "./LocalUser"; import {Room} from "./Room"; @@ -46,8 +46,8 @@ class ConnectionManager { urlManager.pushRoomIdToUrl(room); return Promise.resolve(room); } else if (connexionType === GameConnexionTypes.organization || connexionType === GameConnexionTypes.anonymous || connexionType === GameConnexionTypes.empty) { - const localUser = localUserStore.getLocalUser(); + let localUser = localUserStore.getLocalUser(); if (localUser && localUser.jwtToken && localUser.uuid && localUser.textures) { this.localUser = localUser; try { @@ -57,16 +57,42 @@ class ConnectionManager { console.error('JWT token invalid. Did it expire? Login anonymously instead.'); await this.anonymousLogin(); } - } else { + }else{ await this.anonymousLogin(); } - let roomId: string + + localUser = localUserStore.getLocalUser(); + if(!localUser){ + throw "Error to store local user data"; + } + + let roomId: string; if (connexionType === GameConnexionTypes.empty) { roomId = START_ROOM_URL; } else { roomId = window.location.pathname + window.location.search + window.location.hash; } - return Promise.resolve(new Room(roomId)); + + //get detail map for anonymous login and set texture in local storage + const room = new Room(roomId); + const mapDetail = await room.getMapDetail(); + if(mapDetail.textures != undefined && mapDetail.textures.length > 0) { + //check if texture was changed + if(localUser.textures.length === 0){ + localUser.textures = mapDetail.textures; + }else{ + mapDetail.textures.forEach((newTexture) => { + const alreadyExistTexture = localUser?.textures.find((c) => newTexture.id === c.id); + if(localUser?.textures.findIndex((c) => newTexture.id === c.id) !== -1){ + return; + } + localUser?.textures.push(newTexture) + }); + } + this.localUser = localUser; + localUserStore.saveUser(localUser); + } + return Promise.resolve(room); } return Promise.reject(new Error('Invalid URL')); diff --git a/front/src/Connexion/ConnexionModels.ts b/front/src/Connexion/ConnexionModels.ts index 09447445..b5a66296 100644 --- a/front/src/Connexion/ConnexionModels.ts +++ b/front/src/Connexion/ConnexionModels.ts @@ -1,5 +1,3 @@ -import {PlayerAnimationDirections} from "../Phaser/Player/Animation"; -import {UserSimplePeerInterface} from "../WebRtc/SimplePeer"; import type {SignalData} from "simple-peer"; import type {RoomConnection} from "./RoomConnection"; import type {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures"; @@ -47,6 +45,7 @@ export interface MessageUserPositionInterface { name: string; characterLayers: BodyResourceDescriptionInterface[]; position: PointInterface; + visitCardUrl: string|null; companion: string|null; } @@ -60,6 +59,7 @@ export interface MessageUserJoined { name: string; characterLayers: BodyResourceDescriptionInterface[]; position: PointInterface; + visitCardUrl: string | null; companion: string|null; } @@ -85,11 +85,6 @@ export interface WebRtcSignalReceivedMessageInterface { webRtcPassword: string | undefined } -export interface StartMapInterface { - mapUrlStart: string, - startInstance: string -} - export interface ViewportInterface { left: number, top: number, diff --git a/front/src/Connexion/LocalUser.ts b/front/src/Connexion/LocalUser.ts index 0793a938..d240a90e 100644 --- a/front/src/Connexion/LocalUser.ts +++ b/front/src/Connexion/LocalUser.ts @@ -9,9 +9,8 @@ export interface CharacterTexture { export const maxUserNameLength: number = MAX_USERNAME_LENGTH; -export function isUserNameValid(value: string): boolean { - const regexp = new RegExp('^[A-Za-z0-9]{1,'+maxUserNameLength+'}$'); - return regexp.test(value); +export function isUserNameValid(value: unknown): boolean { + return typeof value === "string" && value.length > 0 && value.length <= maxUserNameLength && /\S/.test(value); } export function areCharacterLayersValid(value: string[] | null): boolean { @@ -25,6 +24,6 @@ export function areCharacterLayersValid(value: string[] | null): boolean { } export class LocalUser { - constructor(public readonly uuid:string, public readonly jwtToken: string, public readonly textures: CharacterTexture[]) { + constructor(public readonly uuid:string, public readonly jwtToken: string, public textures: CharacterTexture[]) { } } diff --git a/front/src/Connexion/Room.ts b/front/src/Connexion/Room.ts index 05d94440..3ae8d2ed 100644 --- a/front/src/Connexion/Room.ts +++ b/front/src/Connexion/Room.ts @@ -1,10 +1,17 @@ import Axios from "axios"; import {PUSHER_URL} from "../Enum/EnvironmentVariable"; +import type {CharacterTexture} from "./LocalUser"; + +export class MapDetail{ + constructor(public readonly mapUrl: string, public readonly textures : CharacterTexture[]|undefined) { + } +} export class Room { public readonly id: string; public readonly isPublic: boolean; private mapUrl: string|undefined; + private textures: CharacterTexture[]|undefined; private instance: string|undefined; private _search: URLSearchParams; @@ -50,10 +57,10 @@ export class Room { return {roomId, hash} } - public async getMapUrl(): Promise { - return new Promise((resolve, reject) => { - if (this.mapUrl !== undefined) { - resolve(this.mapUrl); + public async getMapDetail(): Promise { + return new Promise((resolve, reject) => { + if (this.mapUrl !== undefined && this.textures != undefined) { + resolve(new MapDetail(this.mapUrl, this.textures)); return; } @@ -61,7 +68,7 @@ export class Room { const match = /_\/[^/]+\/(.+)/.exec(this.id); if (!match) throw new Error('Could not extract url from "'+this.id+'"'); this.mapUrl = window.location.protocol+'//'+match[1]; - resolve(this.mapUrl); + resolve(new MapDetail(this.mapUrl, this.textures)); return; } else { // We have a private ID, we need to query the map URL from the server. @@ -71,7 +78,7 @@ export class Room { params: urlParts }).then(({data}) => { console.log('Map ', this.id, ' resolves to URL ', data.mapUrl); - resolve(data.mapUrl); + resolve(data); return; }).catch((reason) => { reject(reason); diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index 58c62a78..dc9830c3 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -30,7 +30,7 @@ import { EmoteEventMessage, EmotePromptMessage, SendUserMessage, - BanUserMessage + BanUserMessage, } from "../Messages/generated/messages_pb" import type { UserSimplePeerInterface } from "../WebRtc/SimplePeer"; @@ -177,9 +177,9 @@ export class RoomConnection implements RoomConnection { } else if (message.hasWorldfullmessage()) { worldFullMessageStream.onMessage(); this.closed = true; - } else if (message.hasWorldconnexionmessage()) { - worldFullMessageStream.onMessage(message.getWorldconnexionmessage()?.getMessage()); - this.closed = true; + } else if (message.hasWorldconnexionmessage()) { + worldFullMessageStream.onMessage(message.getWorldconnexionmessage()?.getMessage()); + this.closed = true; } else if (message.hasWebrtcsignaltoclientmessage()) { this.dispatch(EventMessage.WEBRTC_SIGNAL, message.getWebrtcsignaltoclientmessage()); } else if (message.hasWebrtcscreensharingsignaltoclientmessage()) { @@ -343,6 +343,7 @@ export class RoomConnection implements RoomConnection { userId: message.getUserid(), name: message.getName(), characterLayers, + visitCardUrl: message.getVisitcardurl(), position: ProtobufClientUtils.toPointInterface(position), companion: companion ? companion.getName() : null } @@ -617,7 +618,7 @@ export class RoomConnection implements RoomConnection { this.socket.send(clientToServerMessage.serializeBinary().buffer); } - public getAllTag() : string[] { + public getAllTags() : string[] { return this.tags; } } diff --git a/front/src/Enum/EnvironmentVariable.ts b/front/src/Enum/EnvironmentVariable.ts index 85b63335..73f6427c 100644 --- a/front/src/Enum/EnvironmentVariable.ts +++ b/front/src/Enum/EnvironmentVariable.ts @@ -14,6 +14,7 @@ const POSITION_DELAY = 200; // Wait 200ms between sending position events const MAX_EXTRAPOLATION_TIME = 100; // Extrapolate a maximum of 250ms if no new movement is sent by the player export const MAX_USERNAME_LENGTH = parseInt(process.env.MAX_USERNAME_LENGTH || '') || 8; export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || '4'); +export const DISPLAY_TERMS_OF_USE = process.env.DISPLAY_TERMS_OF_USE == 'true'; export const isMobile = ():boolean => ( ( window.innerWidth <= 800 ) || ( window.innerHeight <= 600 ) ); diff --git a/front/src/Phaser/Components/MobileJoystick.ts b/front/src/Phaser/Components/MobileJoystick.ts index 46efcbc2..b3fc021b 100644 --- a/front/src/Phaser/Components/MobileJoystick.ts +++ b/front/src/Phaser/Components/MobileJoystick.ts @@ -28,13 +28,13 @@ export class MobileJoystick extends VirtualJoystick { this.visible = false; this.enable = false; - this.scene.input.on('pointerdown', (pointer: { x: number; y: number; wasTouch: boolean; event: TouchEvent }) => { + this.scene.input.on('pointerdown', (pointer: Phaser.Input.Pointer) => { if (!pointer.wasTouch) { return; } // Let's only display the joystick if there is one finger on the screen - if (pointer.event.touches.length === 1) { + if ((pointer.event as TouchEvent).touches.length === 1) { this.x = pointer.x; this.y = pointer.y; this.visible = true; diff --git a/front/src/Phaser/Components/SoundMeter.ts b/front/src/Phaser/Components/SoundMeter.ts index 1d6f7eba..53802d31 100644 --- a/front/src/Phaser/Components/SoundMeter.ts +++ b/front/src/Phaser/Components/SoundMeter.ts @@ -1,3 +1,5 @@ +import type {IAnalyserNode, IAudioContext, IMediaStreamAudioSourceNode} from 'standardized-audio-context'; + /** * Class to measure the sound volume of a media stream */ @@ -5,10 +7,10 @@ export class SoundMeter { private instant: number; private clip: number; //private script: ScriptProcessorNode; - private analyser: AnalyserNode|undefined; + private analyser: IAnalyserNode|undefined; private dataArray: Uint8Array|undefined; - private context: AudioContext|undefined; - private source: MediaStreamAudioSourceNode|undefined; + private context: IAudioContext|undefined; + private source: IMediaStreamAudioSourceNode|undefined; constructor() { this.instant = 0.0; @@ -16,7 +18,7 @@ export class SoundMeter { //this.script = context.createScriptProcessor(2048, 1, 1); } - private init(context: AudioContext) { + private init(context: IAudioContext) { this.context = context; this.analyser = this.context.createAnalyser(); @@ -25,8 +27,12 @@ export class SoundMeter { this.dataArray = new Uint8Array(bufferLength); } - public connectToSource(stream: MediaStream, context: AudioContext): void + public connectToSource(stream: MediaStream, context: IAudioContext): void { + if (this.source !== undefined) { + this.stop(); + } + this.init(context); this.source = this.context?.createMediaStreamSource(stream); @@ -81,56 +87,3 @@ export class SoundMeter { } - -// Meter class that generates a number correlated to audio volume. -// The meter class itself displays nothing, but it makes the -// instantaneous and time-decaying volumes available for inspection. -// It also reports on the fraction of samples that were at or near -// the top of the measurement range. -/*function SoundMeter(context) { - this.context = context; - this.instant = 0.0; - this.slow = 0.0; - this.clip = 0.0; - this.script = context.createScriptProcessor(2048, 1, 1); - const that = this; - this.script.onaudioprocess = function(event) { - const input = event.inputBuffer.getChannelData(0); - let i; - let sum = 0.0; - let clipcount = 0; - for (i = 0; i < input.length; ++i) { - sum += input[i] * input[i]; - if (Math.abs(input[i]) > 0.99) { - clipcount += 1; - } - } - that.instant = Math.sqrt(sum / input.length); - that.slow = 0.95 * that.slow + 0.05 * that.instant; - that.clip = clipcount / input.length; - }; -} - -SoundMeter.prototype.connectToSource = function(stream, callback) { - console.log('SoundMeter connecting'); - try { - this.mic = this.context.createMediaStreamSource(stream); - this.mic.connect(this.script); - // necessary to make sample run, but should not be. - this.script.connect(this.context.destination); - if (typeof callback !== 'undefined') { - callback(null); - } - } catch (e) { - console.error(e); - if (typeof callback !== 'undefined') { - callback(e); - } - } -}; - -SoundMeter.prototype.stop = function() { - this.mic.disconnect(); - this.script.disconnect(); -}; -*/ diff --git a/front/src/Phaser/Components/SoundMeterSprite.ts b/front/src/Phaser/Components/SoundMeterSprite.ts deleted file mode 100644 index 582617f9..00000000 --- a/front/src/Phaser/Components/SoundMeterSprite.ts +++ /dev/null @@ -1,44 +0,0 @@ -import Container = Phaser.GameObjects.Container; -import type {Scene} from "phaser"; -import GameObject = Phaser.GameObjects.GameObject; -import Rectangle = Phaser.GameObjects.Rectangle; - - -export class SoundMeterSprite extends Container { - private rectangles: Rectangle[] = new Array(); - private static readonly NB_BARS = 20; - - constructor(scene: Scene, x?: number, y?: number, children?: GameObject[]) { - super(scene, x, y, children); - - for (let i = 0; i < SoundMeterSprite.NB_BARS; i++) { - const rectangle = new Rectangle(scene, i * 13, 0, 10, 20, (Math.round(255 - i * 255 / SoundMeterSprite.NB_BARS) << 8) + (Math.round(i * 255 / SoundMeterSprite.NB_BARS) << 16)); - this.add(rectangle); - this.rectangles.push(rectangle); - } - } - - /** - * A number between 0 and 100 - * - * @param volume - */ - public setVolume(volume: number): void { - - const normalizedVolume = volume / 100 * SoundMeterSprite.NB_BARS; - for (let i = 0; i < SoundMeterSprite.NB_BARS; i++) { - if (normalizedVolume < i) { - this.rectangles[i].alpha = 0.5; - } else { - this.rectangles[i].alpha = 1; - } - } - } - - public getWidth(): number { - return SoundMeterSprite.NB_BARS * 13; - } - - - -} diff --git a/front/src/Phaser/Entity/Character.ts b/front/src/Phaser/Entity/Character.ts index b1a85943..7263a584 100644 --- a/front/src/Phaser/Entity/Character.ts +++ b/front/src/Phaser/Entity/Character.ts @@ -1,6 +1,6 @@ import {PlayerAnimationDirections, PlayerAnimationTypes} from "../Player/Animation"; import {SpeechBubble} from "./SpeechBubble"; -import BitmapText = Phaser.GameObjects.BitmapText; +import Text = Phaser.GameObjects.Text; import Container = Phaser.GameObjects.Container; import Sprite = Phaser.GameObjects.Sprite; import {TextureError} from "../../Exception/TextureError"; @@ -8,6 +8,7 @@ import {Companion} from "../Companion/Companion"; import type {GameScene} from "../Game/GameScene"; import {DEPTH_INGAME_TEXT_INDEX} from "../Game/DepthIndexes"; import {waScaleManager} from "../Services/WaScaleManager"; +import type OutlinePipelinePlugin from "phaser3-rex-plugins/plugins/outlinepipeline-plugin.js"; const playerNameY = - 25; @@ -23,7 +24,7 @@ const interactiveRadius = 35; export abstract class Character extends Container { private bubble: SpeechBubble|null = null; - private readonly playerName: BitmapText; + private readonly playerName: Text; public PlayerValue: string; public sprites: Map; private lastDirection: PlayerAnimationDirections = PlayerAnimationDirections.Down; @@ -32,6 +33,7 @@ export abstract class Character extends Container { public companion?: Companion; private emote: Phaser.GameObjects.Sprite | null = null; private emoteTween: Phaser.Tweens.Tween|null = null; + scene: GameScene; constructor(scene: GameScene, x: number, @@ -41,10 +43,12 @@ export abstract class Character extends Container { direction: PlayerAnimationDirections, moving: boolean, frame: string | number, + isClickable: boolean, companion: string|null, companionTexturePromise?: Promise ) { super(scene, x, y/*, texture, frame*/); + this.scene = scene; this.PlayerValue = name; this.invisible = true @@ -56,16 +60,29 @@ export abstract class Character extends Container { this.invisible = false }) - this.playerName = new BitmapText(scene, 0, playerNameY, 'main_font', name, 7); - this.playerName.setOrigin(0.5).setCenterAlign().setDepth(DEPTH_INGAME_TEXT_INDEX); + this.playerName = new Text(scene, 0, playerNameY, name, {fontFamily: '"Press Start 2P"', fontSize: '8px', strokeThickness: 2, stroke: "gray"}); + this.playerName.setOrigin(0.5).setDepth(DEPTH_INGAME_TEXT_INDEX); this.add(this.playerName); - if (this.isClickable()) { + if (isClickable) { this.setInteractive({ hitArea: new Phaser.Geom.Circle(0, 0, interactiveRadius), hitAreaCallback: Phaser.Geom.Circle.Contains, //eslint-disable-line @typescript-eslint/unbound-method useHandCursor: true, }); + + this.on('pointerover',() => { + this.getOutlinePlugin()?.add(this.playerName, { + thickness: 2, + outlineColor: 0xffff00 + }); + this.scene.markDirty(); + }); + this.on('pointerout',() => { + this.getOutlinePlugin()?.remove(this.playerName); + this.scene.markDirty(); + }) + } scene.add.existing(this); @@ -79,23 +96,25 @@ export abstract class Character extends Container { this.setDepth(-1); this.playAnimation(direction, moving); - + if (typeof companion === 'string') { this.addCompanion(companion, companionTexturePromise); } } + private getOutlinePlugin(): OutlinePipelinePlugin|undefined { + return this.scene.plugins.get('rexOutlinePipeline') as unknown as OutlinePipelinePlugin|undefined; + } + public addCompanion(name: string, texturePromise?: Promise): void { if (typeof texturePromise !== 'undefined') { this.companion = new Companion(this.scene, this.x, this.y, name, texturePromise); } } - - public abstract isClickable(): boolean; public addTextures(textures: string[], frame?: string | number): void { for (const texture of textures) { - if(!this.scene.textures.exists(texture)){ + if(this.scene && !this.scene.textures.exists(texture)){ throw new TextureError('texture not found'); } const sprite = new Sprite(this.scene, 0, 0, texture, frame); @@ -240,28 +259,28 @@ export abstract class Character extends Container { this.scene.sys.updateList.remove(sprite); } } - this.list.forEach(objectContaining => objectContaining.destroy()) + this.list.forEach(objectContaining => objectContaining.destroy()) super.destroy(); } - + playEmote(emoteKey: string) { this.cancelPreviousEmote(); const scalingFactor = waScaleManager.uiScalingFactor * 0.05; const emoteY = -30 - scalingFactor * 10; - + this.playerName.setVisible(false); this.emote = new Sprite(this.scene, 0, 0, emoteKey); this.emote.setAlpha(0); this.emote.setScale(0.1 * scalingFactor); this.add(this.emote); this.scene.sys.updateList.add(this.emote); - + this.createStartTransition(scalingFactor, emoteY); } private createStartTransition(scalingFactor: number, emoteY: number) { - this.emoteTween = this.scene.tweens.add({ + this.emoteTween = this.scene?.tweens.add({ targets: this.emote, props: { scale: scalingFactor, @@ -277,7 +296,7 @@ export abstract class Character extends Container { } private startPulseTransition(emoteY: number, scalingFactor: number) { - this.emoteTween = this.scene.tweens.add({ + this.emoteTween = this.scene?.tweens.add({ targets: this.emote, props: { y: emoteY * 1.3, @@ -294,7 +313,7 @@ export abstract class Character extends Container { } private startExitTransition(emoteY: number) { - this.emoteTween = this.scene.tweens.add({ + this.emoteTween = this.scene?.tweens.add({ targets: this.emote, props: { alpha: 0, diff --git a/front/src/Phaser/Entity/CustomizedCharacter.ts b/front/src/Phaser/Entity/CustomizedCharacter.ts new file mode 100644 index 00000000..3a7f1597 --- /dev/null +++ b/front/src/Phaser/Entity/CustomizedCharacter.ts @@ -0,0 +1,20 @@ +import Container = Phaser.GameObjects.Container; +import type {Scene} from "phaser"; +import Sprite = Phaser.GameObjects.Sprite; + +/** + * A sprite of a customized character (used in the Customize Scene only) + */ +export class CustomizedCharacter extends Container { + public constructor(scene: Scene, x: number, y: number, layers: string[]) { + super(scene, x, y); + this.updateSprites(layers); + } + + public updateSprites(layers: string[]): void { + this.removeAll(true); + for (const layer of layers) { + this.add(new Sprite(this.scene, 0, 0, layer)); + } + } +} diff --git a/front/src/Phaser/Entity/RemotePlayer.ts b/front/src/Phaser/Entity/RemotePlayer.ts index 4e00f102..32881e97 100644 --- a/front/src/Phaser/Entity/RemotePlayer.ts +++ b/front/src/Phaser/Entity/RemotePlayer.ts @@ -2,12 +2,15 @@ import type {GameScene} from "../Game/GameScene"; import type {PointInterface} from "../../Connexion/ConnexionModels"; import {Character} from "../Entity/Character"; import type {PlayerAnimationDirections} from "../Player/Animation"; +import {requestVisitCardsStore} from "../../Stores/GameStore"; + /** * Class representing the sprite of a remote player (a player that plays on another computer) */ export class RemotePlayer extends Character { userId: number; + private visitCardUrl: string|null; constructor( userId: number, @@ -18,13 +21,19 @@ export class RemotePlayer extends Character { texturesPromise: Promise, direction: PlayerAnimationDirections, moving: boolean, + visitCardUrl: string|null, companion: string|null, companionTexturePromise?: Promise ) { - super(Scene, x, y, texturesPromise, name, direction, moving, 1, companion, companionTexturePromise); + super(Scene, x, y, texturesPromise, name, direction, moving, 1, !!visitCardUrl, companion, companionTexturePromise); //set data this.userId = userId; + this.visitCardUrl = visitCardUrl; + + this.on('pointerdown', () => { + requestVisitCardsStore.set(this.visitCardUrl); + }) } updatePosition(position: PointInterface): void { @@ -38,8 +47,4 @@ export class RemotePlayer extends Character { this.companion.setTarget(position.x, position.y, position.direction as PlayerAnimationDirections); } } - - isClickable(): boolean { - return false; //todo: make remote players clickable if they are logged in. - } } diff --git a/front/src/Phaser/Game/AddPlayerInterface.ts b/front/src/Phaser/Game/AddPlayerInterface.ts index 70c341ee..1a5176f0 100644 --- a/front/src/Phaser/Game/AddPlayerInterface.ts +++ b/front/src/Phaser/Game/AddPlayerInterface.ts @@ -6,5 +6,6 @@ export interface AddPlayerInterface { name: string; characterLayers: BodyResourceDescriptionInterface[]; position: PointInterface; + visitCardUrl: string|null; companion: string|null; } diff --git a/front/src/Phaser/Game/DirtyScene.ts b/front/src/Phaser/Game/DirtyScene.ts index ecc81a99..20602cca 100644 --- a/front/src/Phaser/Game/DirtyScene.ts +++ b/front/src/Phaser/Game/DirtyScene.ts @@ -70,7 +70,11 @@ export abstract class DirtyScene extends ResizableScene { return this.dirty || this.objectListChanged; } - public onResize(ev: UIEvent): void { + public markDirty(): void { + this.events.once(Phaser.Scenes.Events.POST_UPDATE, () => this.dirty = true); + } + + public onResize(): void { this.objectListChanged = true; } } diff --git a/front/src/Phaser/Game/Game.ts b/front/src/Phaser/Game/Game.ts index 01aecf9f..e267e80a 100644 --- a/front/src/Phaser/Game/Game.ts +++ b/front/src/Phaser/Game/Game.ts @@ -21,14 +21,22 @@ export class Game extends Phaser.Game { constructor(GameConfig: Phaser.Types.Core.GameConfig) { super(GameConfig); - window.addEventListener('resize', (event) => { + this.scale.on(Phaser.Scale.Events.RESIZE, () => { + for (const scene of this.scene.getScenes(true)) { + if (scene instanceof ResizableScene) { + scene.onResize(); + } + } + }) + + /*window.addEventListener('resize', (event) => { // Let's trigger the onResize method of any active scene that is a ResizableScene for (const scene of this.scene.getScenes(true)) { if (scene instanceof ResizableScene) { scene.onResize(event); } } - }); + });*/ } public step(time: number, delta: number) diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts index 9b699889..a694b32e 100644 --- a/front/src/Phaser/Game/GameManager.ts +++ b/front/src/Phaser/Game/GameManager.ts @@ -2,11 +2,13 @@ import {GameScene} from "./GameScene"; import {connectionManager} from "../../Connexion/ConnectionManager"; import type {Room} from "../../Connexion/Room"; import {MenuScene, MenuSceneName} from "../Menu/MenuScene"; -import {HelpCameraSettingsScene, HelpCameraSettingsSceneName} from "../Menu/HelpCameraSettingsScene"; import {LoginSceneName} from "../Login/LoginScene"; import {SelectCharacterSceneName} from "../Login/SelectCharacterScene"; import {EnableCameraSceneName} from "../Login/EnableCameraScene"; import {localUserStore} from "../../Connexion/LocalUserStore"; +import {get} from "svelte/store"; +import {requestedCameraState, requestedMicrophoneState} from "../../Stores/MediaStore"; +import {helpCameraSettingsVisibleStore} from "../../Stores/HelpCameraSettingsStore"; @@ -71,11 +73,11 @@ export class GameManager { public async loadMap(room: Room, scenePlugin: Phaser.Scenes.ScenePlugin): Promise { const roomID = room.id; - const mapUrl = await room.getMapUrl(); + const mapDetail = await room.getMapDetail(); const gameIndex = scenePlugin.getIndex(roomID); if(gameIndex === -1){ - const game : Phaser.Scene = new GameScene(room, mapUrl); + const game : Phaser.Scene = new GameScene(room, mapDetail.mapUrl); scenePlugin.add(roomID, game, false); } } @@ -84,7 +86,11 @@ export class GameManager { console.log('starting '+ (this.currentGameSceneName || this.startRoom.id)) scenePlugin.start(this.currentGameSceneName || this.startRoom.id); scenePlugin.launch(MenuSceneName); - scenePlugin.launch(HelpCameraSettingsSceneName);//700 + + if(!localUserStore.getHelpCameraSettingsShown() && (!get(requestedMicrophoneState) || !get(requestedCameraState))){ + helpCameraSettingsVisibleStore.set(true); + localUserStore.setHelpCameraSettingsShown(); + } } public gameSceneIsCreated(scene: GameScene) { diff --git a/front/src/Phaser/Game/GameMap.ts b/front/src/Phaser/Game/GameMap.ts index 34f55d0b..fba410d9 100644 --- a/front/src/Phaser/Game/GameMap.ts +++ b/front/src/Phaser/Game/GameMap.ts @@ -1,7 +1,7 @@ -import type {ITiledMap, ITiledMapLayer, ITiledMapTileLayer} from "../Map/ITiledMap"; +import type {ITiledMap, ITiledMapLayer, ITiledMapLayerProperty} from "../Map/ITiledMap"; import { flattenGroupLayersMap } from "../Map/LayersFlattener"; import TilemapLayer = Phaser.Tilemaps.TilemapLayer; -import {DEPTH_OVERLAY_INDEX} from "./DepthIndexes"; +import { DEPTH_OVERLAY_INDEX } from "./DepthIndexes"; export type PropertyChangeCallback = (newValue: string | number | boolean | undefined, oldValue: string | number | boolean | undefined, allProps: Map) => void; @@ -10,12 +10,16 @@ export type PropertyChangeCallback = (newValue: string | number | boolean | unde * It is used to handle layer properties. */ export class GameMap { - private key: number|undefined; - private lastProperties = new Map(); + private key: number | undefined; + private lastProperties = new Map(); private callbacks = new Map>(); + + private tileSetPropertyMap: { [tile_index: number]: Array } = {} public readonly flatLayers: ITiledMapLayer[]; public readonly phaserLayers: TilemapLayer[] = []; + public exitUrls: Array = [] + public constructor(private map: ITiledMap, phaserMap: Phaser.Tilemaps.Tilemap, terrains: Array) { this.flatLayers = flattenGroupLayersMap(map); let depth = -2; @@ -27,8 +31,22 @@ export class GameMap { depth = DEPTH_OVERLAY_INDEX; } } + for (const tileset of map.tilesets) { + tileset?.tiles?.forEach(tile => { + if (tile.properties) { + this.tileSetPropertyMap[tileset.firstgid + tile.id] = tile.properties + tile.properties.forEach(prop => { + if (prop.name == "exitUrl" && typeof prop.value == "string") { + this.exitUrls.push(prop.value); + } + }) + } + }) + } } + + /** * Sets the position of the current player (in pixels) * This will trigger events if properties are changing. @@ -63,21 +81,27 @@ export class GameMap { } } - public getCurrentProperties(): Map { + public getCurrentProperties(): Map { return this.lastProperties; } - private getProperties(key: number): Map { - const properties = new Map(); + private getProperties(key: number): Map { + const properties = new Map(); for (const layer of this.flatLayers) { if (layer.type !== 'tilelayer') { continue; } - const tiles = layer.data as number[]; - if (tiles[key] == 0) { - continue; + + let tileIndex: number | undefined = undefined; + if (layer.data) { + const tiles = layer.data as number[]; + if (tiles[key] == 0) { + continue; + } + tileIndex = tiles[key] } + // There is a tile in this layer, let's embed the properties if (layer.properties !== undefined) { for (const layerProperty of layer.properties) { @@ -87,6 +111,16 @@ export class GameMap { properties.set(layerProperty.name, layerProperty.value); } } + + if (tileIndex) { + this.tileSetPropertyMap[tileIndex]?.forEach(property => { + if (property.value) { + properties.set(property.name, property.value) + } else if (properties.has(property.name)) { + properties.delete(property.name) + } + }) + } } return properties; @@ -118,11 +152,11 @@ export class GameMap { } public findLayer(layerName: string): ITiledMapLayer | undefined { - return this.flatLayers.find((layer) => layer.name = layerName); + return this.flatLayers.find((layer) => layer.name === layerName); } public findPhaserLayer(layerName: string): TilemapLayer | undefined { - return this.phaserLayers.find((layer) => layer.layer.name = layerName); + return this.phaserLayers.find((layer) => layer.layer.name === layerName); } public addTerrain(terrain : Phaser.Tilemaps.Tileset): void { diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index cb820ba4..0202773d 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1,4 +1,9 @@ -import {gameManager} from "./GameManager"; +import { Queue } from 'queue-typescript'; +import type { Subscription } from "rxjs"; +import { GlobalMessageManager } from "../../Administration/GlobalMessageManager"; +import { userMessageManager } from "../../Administration/UserMessageManager"; +import { iframeListener } from "../../Api/IframeListener"; +import { connectionManager } from "../../Connexion/ConnectionManager"; import type { GroupCreatedUpdatedMessageInterface, MessageUserJoined, @@ -9,72 +14,73 @@ import type { PositionInterface, RoomJoinedMessageInterface } from "../../Connexion/ConnexionModels"; -import { hasMovedEventName, Player , requestEmoteEventName} from "../Player/Player"; +import { localUserStore } from "../../Connexion/LocalUserStore"; +import { Room } from "../../Connexion/Room"; +import type { RoomConnection } from "../../Connexion/RoomConnection"; +import { worldFullMessageStream } from "../../Connexion/WorldFullMessageStream"; import { DEBUG_MODE, JITSI_PRIVATE_MODE, MAX_PER_GROUP, - POSITION_DELAY, + POSITION_DELAY } from "../../Enum/EnvironmentVariable"; -import type { - ITiledMap, - ITiledMapLayer, - ITiledMapLayerProperty, - ITiledMapObject, - ITiledText, - ITiledMapTileLayer, - ITiledTileSet -} from "../Map/ITiledMap"; -import type { AddPlayerInterface } from "./AddPlayerInterface"; -import { PlayerAnimationDirections } from "../Player/Animation"; -import { PlayerMovement } from "./PlayerMovement"; -import { PlayersPositionInterpolator } from "./PlayersPositionInterpolator"; -import { RemotePlayer } from "../Entity/RemotePlayer"; -import { Queue } from 'queue-typescript'; -import { SimplePeer, UserSimplePeerInterface } from "../../WebRtc/SimplePeer"; -import { ReconnectingSceneName } from "../Reconnecting/ReconnectingScene"; -import { lazyLoadPlayerCharacterTextures, loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager"; +import { TextureError } from "../../Exception/TextureError"; +import type { UserMovedMessage } from "../../Messages/generated/messages_pb"; +import { ProtobufClientUtils } from "../../Network/ProtobufClientUtils"; +import { peerStore } from "../../Stores/PeerStore"; +import { touchScreenManager } from "../../Touch/TouchScreenManager"; +import { urlManager } from "../../Url/UrlManager"; +import { audioManager } from "../../WebRtc/AudioManager"; +import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager"; +import { HtmlUtils } from "../../WebRtc/HtmlUtils"; +import { jitsiFactory } from "../../WebRtc/JitsiFactory"; import { - CenterListener, + AUDIO_LOOP_PROPERTY, AUDIO_VOLUME_PROPERTY, CenterListener, JITSI_MESSAGE_PROPERTIES, layoutManager, LayoutMode, ON_ACTION_TRIGGER_BUTTON, TRIGGER_JITSI_PROPERTIES, TRIGGER_WEBSITE_PROPERTIES, - WEBSITE_MESSAGE_PROPERTIES, - AUDIO_VOLUME_PROPERTY, - AUDIO_LOOP_PROPERTY + WEBSITE_MESSAGE_PROPERTIES } from "../../WebRtc/LayoutManager"; -import { GameMap } from "./GameMap"; -import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager"; import { mediaManager } from "../../WebRtc/MediaManager"; -import type { ItemFactoryInterface } from "../Items/ItemFactoryInterface"; -import type { ActionableItem } from "../Items/ActionableItem"; -import { UserInputManager } from "../UserInput/UserInputManager"; -import {soundManager} from "./SoundManager"; -import type { UserMovedMessage } from "../../Messages/generated/messages_pb"; -import { ProtobufClientUtils } from "../../Network/ProtobufClientUtils"; -import { connectionManager } from "../../Connexion/ConnectionManager"; -import type { RoomConnection } from "../../Connexion/RoomConnection"; -import { GlobalMessageManager } from "../../Administration/GlobalMessageManager"; -import { userMessageManager } from "../../Administration/UserMessageManager"; -import { ConsoleGlobalMessageManager } from "../../Administration/ConsoleGlobalMessageManager"; -import { ResizableScene } from "../Login/ResizableScene"; -import { Room } from "../../Connexion/Room"; -import { jitsiFactory } from "../../WebRtc/JitsiFactory"; -import { urlManager } from "../../Url/UrlManager"; -import { audioManager } from "../../WebRtc/AudioManager"; -import { PresentationModeIcon } from "../Components/PresentationModeIcon"; +import { SimplePeer, UserSimplePeerInterface } from "../../WebRtc/SimplePeer"; +import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager"; import { ChatModeIcon } from "../Components/ChatModeIcon"; -import { OpenChatIcon, openChatIconName } from "../Components/OpenChatIcon"; -import { SelectCharacterScene, SelectCharacterSceneName } from "../Login/SelectCharacterScene"; -import { TextureError } from "../../Exception/TextureError"; import { addLoader } from "../Components/Loader"; +import { joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey } from "../Components/MobileJoystick"; +import { OpenChatIcon, openChatIconName } from "../Components/OpenChatIcon"; +import { PresentationModeIcon } from "../Components/PresentationModeIcon"; +import { TextUtils } from "../Components/TextUtils"; +import { lazyLoadPlayerCharacterTextures, loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager"; +import { RemotePlayer } from "../Entity/RemotePlayer"; +import type { ActionableItem } from "../Items/ActionableItem"; +import type { ItemFactoryInterface } from "../Items/ItemFactoryInterface"; +import { SelectCharacterScene, SelectCharacterSceneName } from "../Login/SelectCharacterScene"; +import type { + ITiledMap, + ITiledMapLayer, + ITiledMapLayerProperty, + ITiledMapObject, + ITiledMapTileLayer, + ITiledTileSet } from "../Map/ITiledMap"; +import { MenuScene, MenuSceneName } from '../Menu/MenuScene'; +import { PlayerAnimationDirections } from "../Player/Animation"; +import { hasMovedEventName, Player, requestEmoteEventName } from "../Player/Player"; import { ErrorSceneName } from "../Reconnecting/ErrorScene"; -import { localUserStore } from "../../Connexion/LocalUserStore"; -import { iframeListener } from "../../Api/IframeListener"; -import { HtmlUtils } from "../../WebRtc/HtmlUtils"; +import { ReconnectingSceneName } from "../Reconnecting/ReconnectingScene"; +import { waScaleManager } from "../Services/WaScaleManager"; +import { PinchManager } from "../UserInput/PinchManager"; +import { UserInputManager } from "../UserInput/UserInputManager"; +import type { AddPlayerInterface } from "./AddPlayerInterface"; +import { DEPTH_OVERLAY_INDEX } from "./DepthIndexes"; +import { DirtyScene } from "./DirtyScene"; +import { EmoteManager } from "./EmoteManager"; +import { gameManager } from "./GameManager"; +import { GameMap } from "./GameMap"; +import { PlayerMovement } from "./PlayerMovement"; +import { PlayersPositionInterpolator } from "./PlayersPositionInterpolator"; import Texture = Phaser.Textures.Texture; import Sprite = Phaser.GameObjects.Sprite; import CanvasTexture = Phaser.Textures.CanvasTexture; @@ -82,22 +88,12 @@ import GameObject = Phaser.GameObjects.GameObject; import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR; import DOMElement = Phaser.GameObjects.DOMElement; import EVENT_TYPE = Phaser.Scenes.Events -import type { Subscription } from "rxjs"; -import { worldFullMessageStream } from "../../Connexion/WorldFullMessageStream"; -import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager"; import RenderTexture = Phaser.GameObjects.RenderTexture; import Tilemap = Phaser.Tilemaps.Tilemap; -import {DirtyScene} from "./DirtyScene"; -import {TextUtils} from "../Components/TextUtils"; -import {touchScreenManager} from "../../Touch/TouchScreenManager"; -import {PinchManager} from "../UserInput/PinchManager"; -import {joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey} from "../Components/MobileJoystick"; -import {DEPTH_OVERLAY_INDEX} from "./DepthIndexes"; -import {waScaleManager} from "../Services/WaScaleManager"; -import {peerStore} from "../../Stores/PeerStore"; -import {EmoteManager} from "./EmoteManager"; import type { HasPlayerMovedEvent } from '../../Api/Events/HasPlayerMovedEvent'; -import { MenuScene, MenuSceneName } from '../Menu/MenuScene'; + +import AnimatedTiles from "phaser-animated-tiles"; +import {soundManager} from "./SoundManager"; export interface GameSceneInitInterface { initPosition: PointInterface | null, @@ -144,6 +140,7 @@ export class GameScene extends DirtyScene implements CenterListener { Map!: Phaser.Tilemaps.Tilemap; Objects!: Array; mapFile!: ITiledMap; + animatedTiles!: AnimatedTiles; groups: Map; startX!: number; startY!: number; @@ -155,13 +152,13 @@ export class GameScene extends DirtyScene implements CenterListener { public connection: RoomConnection | undefined; private simplePeer!: SimplePeer; private GlobalMessageManager!: GlobalMessageManager; - public ConsoleGlobalMessageManager!: ConsoleGlobalMessageManager; 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; private iframeSubscriptionList!: Array; + private peerStoreUnsubscribe!: () => void; MapUrlFile: string; RoomId: string; instance: string; @@ -230,11 +227,11 @@ export class GameScene extends DirtyScene implements CenterListener { this.load.image(joystickBaseKey, joystickBaseImg); this.load.image(joystickThumbKey, joystickThumbImg); } - //todo: in an emote manager. - this.load.spritesheet('emote-music', 'resources/emotes/pipo-popupemotes005.png', { - frameHeight: 32, - frameWidth: 32, - }); + this.load.audio('audio-webrtc-in', '/resources/objects/webrtc-in.mp3'); + this.load.audio('audio-webrtc-out', '/resources/objects/webrtc-out.mp3'); + //this.load.audio('audio-report-message', '/resources/objects/report-message.mp3'); + this.sound.pauseOnBlur = false; + this.load.on(FILE_LOAD_ERROR, (file: { src: string }) => { // If we happen to be in HTTP and we are trying to load a URL in HTTPS only... (this happens only in dev environments) if (window.location.protocol === 'http:' && file.src === this.MapUrlFile && file.src.startsWith('http:') && this.originalMapUrl === undefined) { @@ -267,6 +264,7 @@ export class GameScene extends DirtyScene implements CenterListener { message: this.originalMapUrl ?? file.src }); }); + this.load.scenePlugin('AnimatedTiles', AnimatedTiles, 'animatedTiles', 'animatedTiles'); this.load.on('filecomplete-tilemapJSON-' + this.MapUrlFile, (key: string, type: string, data: unknown) => { this.onMapLoad(data); }); @@ -281,6 +279,14 @@ export class GameScene extends DirtyScene implements CenterListener { this.load.spritesheet('layout_modes', 'resources/objects/layout_modes.png', { frameWidth: 32, frameHeight: 32 }); this.load.bitmapFont('main_font', 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml'); + //eslint-disable-next-line @typescript-eslint/no-explicit-any + (this.load as any).rexWebFont({ + custom: { + families: ['Press Start 2P'], + urls: ['/resources/fonts/fonts.css'], + testString: 'abcdefg' + }, + }); //this function must stay at the end of preload function addLoader(this); @@ -435,6 +441,10 @@ export class GameScene extends DirtyScene implements CenterListener { } } + this.gameMap.exitUrls.forEach(exitUrl => { + this.loadNextGame(exitUrl) + }) + this.initStartXAndStartY(); //add entities @@ -458,6 +468,9 @@ export class GameScene extends DirtyScene implements CenterListener { this.initCamera(); + this.animatedTiles.init(this.Map); + this.events.on('tileanimationupdate', () => this.dirty = true); + this.initCirclesCanvas(); // Let's pause the scene if the connection is not established yet @@ -509,6 +522,21 @@ export class GameScene extends DirtyScene implements CenterListener { } this.emoteManager = new EmoteManager(this); + + let oldPeerNumber = 0; + this.peerStoreUnsubscribe = peerStore.subscribe((peers) => { + const newPeerNumber = peers.size; + if (newPeerNumber > oldPeerNumber) { + this.sound.play('audio-webrtc-in', { + volume: 0.2 + }); + } else if (newPeerNumber < oldPeerNumber) { + this.sound.play('audio-webrtc-out', { + volume: 0.2 + }); + } + oldPeerNumber = newPeerNumber; + }); } /** @@ -541,6 +569,7 @@ export class GameScene extends DirtyScene implements CenterListener { characterLayers: message.characterLayers, name: message.name, position: message.position, + visitCardUrl: message.visitCardUrl, companion: message.companion } this.addPlayer(userMessage); @@ -645,7 +674,6 @@ export class GameScene extends DirtyScene implements CenterListener { //this.initUsersPosition(roomJoinedMessage.users); this.connectionAnswerPromiseResolve(onConnect.room); // Analyze tags to find if we are admin. If yes, show console. - this.ConsoleGlobalMessageManager = new ConsoleGlobalMessageManager(this.connection, this.userInputManager, this.connection.isAdmin()); this.scene.wake(); @@ -868,49 +896,31 @@ ${escapedMessage} this.userInputManager.disableControls(); })); - this.iframeSubscriptionList.push(iframeListener.playSoundStream.subscribe((playSoundEvent)=> - { - const url = new URL(playSoundEvent.url, this.MapUrlFile); - soundManager.playSound(this.load,this.sound,url.toString(),playSoundEvent.config); - })) - - this.iframeSubscriptionList.push(iframeListener.stopSoundStream.subscribe((stopSoundEvent)=> - { - const url = new URL(stopSoundEvent.url, this.MapUrlFile); - soundManager.stopSound(this.sound,url.toString()); + this.iframeSubscriptionList.push(iframeListener.playSoundStream.subscribe((playSoundEvent) => { + const url = new URL(playSoundEvent.url, this.MapUrlFile); + soundManager.playSound(this.load, this.sound, url.toString(), playSoundEvent.config); })) - this.iframeSubscriptionList.push(iframeListener.loadSoundStream.subscribe((loadSoundEvent)=> - { + this.iframeSubscriptionList.push(iframeListener.stopSoundStream.subscribe((stopSoundEvent) => { + const url = new URL(stopSoundEvent.url, this.MapUrlFile); + soundManager.stopSound(this.sound, url.toString()); + })) + + this.iframeSubscriptionList.push(iframeListener.loadSoundStream.subscribe((loadSoundEvent) => { const url = new URL(loadSoundEvent.url, this.MapUrlFile); - soundManager.loadSound(this.load,this.sound,url.toString()); + soundManager.loadSound(this.load, this.sound, url.toString()); })) this.iframeSubscriptionList.push(iframeListener.enablePlayerControlStream.subscribe(()=>{ this.userInputManager.restoreControls(); - })) - - this.iframeSubscriptionList.push(iframeListener.changeTileStream.subscribe(event => { - for (const eventTile of event) { - const layer = this.gameMap.findPhaserLayer(eventTile.layer); - if (layer) { - console.log('layer : ', layer); - const tile = layer.getTileAt(eventTile.x, eventTile.y, true) - if (typeof eventTile.tile == "string") { - const tileIndex = this.getIndexForTileType(eventTile.tile); - if (tileIndex) { - tile.index = tileIndex - } else { - return - } - } else { - tile.index = eventTile.tile //+ firsrtgid du layer - } - } - } - //this.dirty = true; - })) - + })); + this.iframeSubscriptionList.push(iframeListener.loadPageStream.subscribe((url: string) => { + this.loadNextGame(url).then(() => { + this.events.once(EVENT_TYPE.POST_UPDATE, () => { + this.onMapExit(url); + }) + }) + })); let scriptedBubbleSprite: Sprite; this.iframeSubscriptionList.push(iframeListener.displayBubbleStream.subscribe(() => { scriptedBubbleSprite = new Sprite(this, this.CurrentPlayer.x + 25, this.CurrentPlayer.y, 'circleSprite-white'); @@ -939,23 +949,16 @@ ${escapedMessage} })) this.iframeSubscriptionList.push(iframeListener.gameStateStream.subscribe(() => { - iframeListener.sendFrozenGameStateEvent({ + iframeListener.sendGameStateEvent({ mapUrl: this.MapUrlFile, startLayerName: this.startLayerName, uuid: localUserStore.getLocalUser()?.uuid, nickname: localUserStore.getName(), roomId: this.RoomId, - tags: this.connection ? this.connection.getAllTag() : [] + tags: this.connection ? this.connection.getAllTags() : [] }) })); -/* this.iframeSubscriptionList.push(iframeListener.tilesetLoaderStream.subscribe((tileset) => { - //this.load.tilemapTiledJSON('logo', tileset.imgUrl); - this.load.image('logo', tileset.imgUrl); - this.Terrains.push(this.Map.addTilesetImage(tileset.name, tileset.imgUrl, tileset.tilewidth, tileset.tileheight, tileset.margin, tileset.spacing)); - this.gameMap.addTerrain(this.Terrains[this.Terrains.length - 1]); - }))*/ - } private setPropertyLayer(layerName: string, propertyName: string, propertyValue: string | number | boolean | undefined): void { @@ -1045,6 +1048,9 @@ ${escapedMessage} this.userInputManager.destroy(); this.pinchManager?.destroy(); this.emoteManager.destroy(); + this.peerStoreUnsubscribe(); + + mediaManager.hideGameOverlay(); for (const iframeEvents of this.iframeSubscriptionList) { iframeEvents.unsubscribe(); @@ -1156,10 +1162,10 @@ ${escapedMessage} } //todo: push that into the gameManager - private loadNextGame(exitSceneIdentifier: string) : void{ + private loadNextGame(exitSceneIdentifier: string) : Promise{ const { roomId, hash } = Room.getIdFromIdentifier(exitSceneIdentifier, this.MapUrlFile, this.instance); const room = new Room(roomId); - gameManager.loadMap(room, this.scene).catch(() => {}); + return gameManager.loadMap(room, this.scene).catch(() => { }); } private startUser(layer: ITiledMapTileLayer): PositionInterface { @@ -1233,7 +1239,10 @@ ${escapedMessage} this.companion, this.companion !== null ? lazyLoadCompanionResource(this.load, this.companion) : undefined ); - this.CurrentPlayer.on('pointerdown', () => { + this.CurrentPlayer.on('pointerdown', (pointer: Phaser.Input.Pointer) => { + if (pointer.wasTouch && (pointer.event as TouchEvent).touches.length > 1) { + return; //we don't want the menu to open when pinching on a touch screen. + } this.emoteManager.getMenuImages().then((emoteMenuElements) => this.CurrentPlayer.openOrCloseEmoteMenu(emoteMenuElements)) }) this.CurrentPlayer.on(requestEmoteEventName, (emoteKey: string) => { @@ -1433,6 +1442,7 @@ ${escapedMessage} texturesPromise, addPlayerData.position.direction as PlayerAnimationDirections, addPlayerData.position.moving, + addPlayerData.visitCardUrl, addPlayerData.companion, addPlayerData.companion !== null ? lazyLoadCompanionResource(this.load, addPlayerData.companion) : undefined ); @@ -1539,8 +1549,8 @@ ${escapedMessage} this.connection?.emitActionableEvent(itemId, eventName, state, parameters); } - public onResize(ev: UIEvent): void { - super.onResize(ev); + public onResize(): void { + super.onResize(); this.reposition(); // Send new viewport to server diff --git a/front/src/Phaser/Game/SoundManager.ts b/front/src/Phaser/Game/SoundManager.ts index f0210494..47614fca 100644 --- a/front/src/Phaser/Game/SoundManager.ts +++ b/front/src/Phaser/Game/SoundManager.ts @@ -17,7 +17,9 @@ class SoundManager { return res(sound); } loadPlugin.audio(soundUrl, soundUrl); - loadPlugin.once('filecomplete-audio-' + soundUrl, () => res(soundManager.add(soundUrl))); + loadPlugin.once('filecomplete-audio-' + soundUrl, () => { + res(soundManager.add(soundUrl)); + }); loadPlugin.start(); }); this.soundPromises.set(soundUrl,soundPromise); diff --git a/front/src/Phaser/Items/ActionableItem.ts b/front/src/Phaser/Items/ActionableItem.ts index f012b525..7c7090b0 100644 --- a/front/src/Phaser/Items/ActionableItem.ts +++ b/front/src/Phaser/Items/ActionableItem.ts @@ -3,8 +3,8 @@ * It has coordinates and an "activation radius" */ import Sprite = Phaser.GameObjects.Sprite; -import {OutlinePipeline} from "../Shaders/OutlinePipeline"; import type {GameScene} from "../Game/GameScene"; +import type OutlinePipelinePlugin from "phaser3-rex-plugins/plugins/outlinepipeline-plugin.js"; type EventCallback = (state: unknown, parameters: unknown) => void; @@ -42,11 +42,11 @@ export class ActionableItem { return; } this.isSelectable = true; - if (this.sprite.pipeline) { - // Commented out to try to fix MacOS issue - /*this.sprite.setPipeline(OutlinePipeline.KEY); - this.sprite.pipeline.set2f('uTextureSize', this.sprite.texture.getSourceImage().width, this.sprite.texture.getSourceImage().height);*/ - } + + this.getOutlinePlugin()?.add(this.sprite, { + thickness: 2, + outlineColor: 0xffff00 + }); } /** @@ -57,8 +57,11 @@ export class ActionableItem { return; } this.isSelectable = false; - // Commented out to try to fix MacOS issue - //this.sprite.resetPipeline(); + this.getOutlinePlugin()?.remove(this.sprite); + } + + private getOutlinePlugin(): OutlinePipelinePlugin|undefined { + return this.sprite.scene.plugins.get('rexOutlinePipeline') as unknown as OutlinePipelinePlugin|undefined; } /** diff --git a/front/src/Phaser/Login/CustomizeScene.ts b/front/src/Phaser/Login/CustomizeScene.ts index 8b9a9a7a..715cb67d 100644 --- a/front/src/Phaser/Login/CustomizeScene.ts +++ b/front/src/Phaser/Login/CustomizeScene.ts @@ -2,30 +2,32 @@ import {EnableCameraSceneName} from "./EnableCameraScene"; import Rectangle = Phaser.GameObjects.Rectangle; import {loadAllLayers} from "../Entity/PlayerTexturesLoadingManager"; import Sprite = Phaser.GameObjects.Sprite; -import Container = Phaser.GameObjects.Container; import {gameManager} from "../Game/GameManager"; import {localUserStore} from "../../Connexion/LocalUserStore"; import {addLoader} from "../Components/Loader"; import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures"; import {AbstractCharacterScene} from "./AbstractCharacterScene"; import {areCharacterLayersValid} from "../../Connexion/LocalUser"; -import { MenuScene } from "../Menu/MenuScene"; import { SelectCharacterSceneName } from "./SelectCharacterScene"; +import {customCharacterSceneVisibleStore} from "../../Stores/CustomCharacterStore"; +import {waScaleManager} from "../Services/WaScaleManager"; +import {isMobile} from "../../Enum/EnvironmentVariable"; +import {CustomizedCharacter} from "../Entity/CustomizedCharacter"; export const CustomizeSceneName = "CustomizeScene"; -export const CustomizeSceneKey = "CustomizeScene"; -const customizeSceneKey = 'customizeScene'; - export class CustomizeScene extends AbstractCharacterScene { private Rectangle!: Rectangle; private selectedLayers: number[] = [0]; - private containersRow: Container[][] = []; - private activeRow:number = 0; + private containersRow: CustomizedCharacter[][] = []; + public activeRow:number = 0; private layers: BodyResourceDescriptionInterface[][] = []; - private customizeSceneElement!: Phaser.GameObjects.DOMElement; + protected lazyloadingAttempt = true; //permit to update texture loaded after renderer + + private moveHorizontally: number = 0; + private moveVertically: number = 0; constructor() { super({ @@ -34,9 +36,7 @@ export class CustomizeScene extends AbstractCharacterScene { } preload() { - this.load.html(customizeSceneKey, 'resources/html/CustomCharacterScene.html'); - this.layers = loadAllLayers(this.load); this.loadCustomSceneSelectCharacters().then((bodyResourceDescriptions) => { bodyResourceDescriptions.forEach((bodyResourceDescription) => { if(bodyResourceDescription.level == undefined || bodyResourceDescription.level < 0 || bodyResourceDescription.level > 5 ){ @@ -44,43 +44,28 @@ export class CustomizeScene extends AbstractCharacterScene { } this.layers[bodyResourceDescription.level].unshift(bodyResourceDescription); }); + this.lazyloadingAttempt = true; }); + this.layers = loadAllLayers(this.load); + this.lazyloadingAttempt = false; + + //this function must stay at the end of preload function addLoader(this); } create() { - this.customizeSceneElement = this.add.dom(-1000, 0).createFromCache(customizeSceneKey); - this.centerXDomElement(this.customizeSceneElement, 150); - MenuScene.revealMenusAfterInit(this.customizeSceneElement, customizeSceneKey); - - this.customizeSceneElement.addListener('click'); - this.customizeSceneElement.on('click', (event:MouseEvent) => { - event.preventDefault(); - if((event?.target as HTMLInputElement).id === 'customizeSceneButtonLeft') { - this.moveCursorHorizontally(-1); - }else if((event?.target as HTMLInputElement).id === 'customizeSceneButtonRight') { - this.moveCursorHorizontally(1); - }else if((event?.target as HTMLInputElement).id === 'customizeSceneButtonDown') { - this.moveCursorVertically(1); - }else if((event?.target as HTMLInputElement).id === 'customizeSceneButtonUp') { - this.moveCursorVertically(-1); - }else if((event?.target as HTMLInputElement).id === 'customizeSceneFormBack') { - if(this.activeRow > 0){ - this.moveCursorVertically(-1); - }else{ - this.backToPreviousScene(); - } - }else if((event?.target as HTMLButtonElement).id === 'customizeSceneFormSubmit') { - if(this.activeRow < 5){ - this.moveCursorVertically(1); - }else{ - this.nextSceneToCamera(); - } - } + customCharacterSceneVisibleStore.set(true); + this.events.addListener('wake', () => { + waScaleManager.saveZoom(); + waScaleManager.zoomModifier = isMobile() ? 3 : 1; + customCharacterSceneVisibleStore.set(true); }); + waScaleManager.saveZoom(); + waScaleManager.zoomModifier = isMobile() ? 3 : 1; + this.Rectangle = this.add.rectangle(this.cameras.main.worldView.x + this.cameras.main.width / 2, this.cameras.main.worldView.y + this.cameras.main.height / 3, 32, 33) this.Rectangle.setStrokeStyle(2, 0xFFFFFF); this.add.existing(this.Rectangle); @@ -100,10 +85,13 @@ export class CustomizeScene extends AbstractCharacterScene { this.backToPreviousScene(); }); - this.input.keyboard.on('keyup-RIGHT', () => this.moveCursorHorizontally(1)); - this.input.keyboard.on('keyup-LEFT', () => this.moveCursorHorizontally(-1)); - this.input.keyboard.on('keyup-DOWN', () => this.moveCursorVertically(1)); - this.input.keyboard.on('keyup-UP', () => this.moveCursorVertically(-1)); + // Note: the key bindings are not directly put on the moveCursorVertically or moveCursorHorizontally methods + // because if 2 such events are fired close to one another, it makes the whole application crawl to a halt (for a reason I cannot + // explain, the list of sprites managed by the update list become immense + this.input.keyboard.on('keyup-RIGHT', () => this.moveHorizontally = 1); + this.input.keyboard.on('keyup-LEFT', () => this.moveHorizontally = -1); + this.input.keyboard.on('keyup-DOWN', () => this.moveVertically = 1); + this.input.keyboard.on('keyup-UP', () => this.moveVertically = -1); const customCursorPosition = localUserStore.getCustomCursorPosition(); if (customCursorPosition) { @@ -116,7 +104,15 @@ export class CustomizeScene extends AbstractCharacterScene { this.onResize(); } - private moveCursorHorizontally(index: number): void { + public moveCursorHorizontally(index: number): void { + this.moveHorizontally = index; + } + + public moveCursorVertically(index: number): void { + this.moveVertically = index; + } + + private doMoveCursorHorizontally(index: number): void { this.selectedLayers[this.activeRow] += index; if (this.selectedLayers[this.activeRow] < 0) { this.selectedLayers[this.activeRow] = 0 @@ -128,27 +124,7 @@ export class CustomizeScene extends AbstractCharacterScene { this.saveInLocalStorage(); } - private moveCursorVertically(index:number): void { - - if(index === -1 && this.activeRow === 5){ - const button = this.customizeSceneElement.getChildByID('customizeSceneFormSubmit') as HTMLButtonElement; - button.innerHTML = `Next `; - } - - if(index === 1 && this.activeRow === 4){ - const button = this.customizeSceneElement.getChildByID('customizeSceneFormSubmit') as HTMLButtonElement; - button.innerText = 'Finish'; - } - - if(index === -1 && this.activeRow === 1){ - const button = this.customizeSceneElement.getChildByID('customizeSceneFormBack') as HTMLButtonElement; - button.innerText = `Return`; - } - - if(index === 1 && this.activeRow === 0){ - const button = this.customizeSceneElement.getChildByID('customizeSceneFormBack') as HTMLButtonElement; - button.innerHTML = `Back `; - } + private doMoveCursorVertically(index:number): void { this.activeRow += index; if (this.activeRow < 0) { @@ -197,20 +173,20 @@ export class CustomizeScene extends AbstractCharacterScene { * @param selectedItem, The number of the item select (0 for black body...) */ private generateCharacter(x: number, y: number, layerNumber: number, selectedItem: number) { - return new Container(this, x, y,this.getContainerChildren(layerNumber,selectedItem)); + return new CustomizedCharacter(this, x, y, this.getContainerChildren(layerNumber,selectedItem)); } - private getContainerChildren(layerNumber: number, selectedItem: number): Array { - const children: Array = new Array(); + private getContainerChildren(layerNumber: number, selectedItem: number): Array { + const children: Array = new Array(); for (let j = 0; j <= layerNumber; j++) { if (j === layerNumber) { - children.push(this.generateLayers(0, 0, this.layers[j][selectedItem].name)); + children.push(this.layers[j][selectedItem].name); } else { const layer = this.selectedLayers[j]; if (layer === undefined) { continue; } - children.push(this.generateLayers(0, 0, this.layers[j][layer].name)); + children.push(this.layers[j][layer].name); } } return children; @@ -247,33 +223,47 @@ export class CustomizeScene extends AbstractCharacterScene { * @return a new sprite */ private generateLayers(x: number, y: number, name: string): Sprite { - return new Sprite(this, x, y, name); + //return new Sprite(this, x, y, name); + return this.add.sprite(0, 0, name); } private updateSelectedLayer() { for(let i = 0; i < this.containersRow.length; i++){ for(let j = 0; j < this.containersRow[i].length; j++){ - const children = this.getContainerChildren(i, j); - this.containersRow[i][j].removeAll(true); - this.containersRow[i][j].add(children); + const children = this.getContainerChildren(i, j); + this.containersRow[i][j].updateSprites(children); } } } update(time: number, delta: number): void { + if(this.lazyloadingAttempt){ + this.moveLayers(); + this.lazyloadingAttempt = false; + } + + if (this.moveHorizontally !== 0) { + this.doMoveCursorHorizontally(this.moveHorizontally); + this.moveHorizontally = 0; + } + if (this.moveVertically !== 0) { + this.doMoveCursorVertically(this.moveVertically); + this.moveVertically = 0; + } } + + + public onResize(): void { this.moveLayers(); this.Rectangle.x = this.cameras.main.worldView.x + this.cameras.main.width / 2; this.Rectangle.y = this.cameras.main.worldView.y + this.cameras.main.height / 3; - - this.centerXDomElement(this.customizeSceneElement, 150); } - private nextSceneToCamera(){ + public nextSceneToCamera(){ const layers: string[] = []; let i = 0; for (const layerItem of this.selectedLayers) { @@ -288,12 +278,16 @@ export class CustomizeScene extends AbstractCharacterScene { gameManager.setCharacterLayers(layers); this.scene.sleep(CustomizeSceneName); - this.scene.remove(SelectCharacterSceneName); + waScaleManager.restoreZoom(); + this.events.removeListener('wake'); gameManager.tryResumingGame(this, EnableCameraSceneName); + customCharacterSceneVisibleStore.set(false); } - private backToPreviousScene(){ + public backToPreviousScene(){ this.scene.sleep(CustomizeSceneName); + waScaleManager.restoreZoom(); this.scene.run(SelectCharacterSceneName); + customCharacterSceneVisibleStore.set(false); } } diff --git a/front/src/Phaser/Login/EnableCameraScene.ts b/front/src/Phaser/Login/EnableCameraScene.ts index 6002da7b..ba27cd07 100644 --- a/front/src/Phaser/Login/EnableCameraScene.ts +++ b/front/src/Phaser/Login/EnableCameraScene.ts @@ -3,7 +3,6 @@ import {TextField} from "../Components/TextField"; import Image = Phaser.GameObjects.Image; import {mediaManager} from "../../WebRtc/MediaManager"; import {SoundMeter} from "../Components/SoundMeter"; -import {SoundMeterSprite} from "../Components/SoundMeterSprite"; import {HtmlUtils} from "../../WebRtc/HtmlUtils"; import {touchScreenManager} from "../../Touch/TouchScreenManager"; import {PinchManager} from "../UserInput/PinchManager"; @@ -11,304 +10,41 @@ import Zone = Phaser.GameObjects.Zone; import { MenuScene } from "../Menu/MenuScene"; import {ResizableScene} from "./ResizableScene"; import { - audioConstraintStore, enableCameraSceneVisibilityStore, - localStreamStore, - mediaStreamConstraintsStore, - videoConstraintStore } from "../../Stores/MediaStore"; -import type {Unsubscriber} from "svelte/store"; export const EnableCameraSceneName = "EnableCameraScene"; -enum LoginTextures { - playButton = "play_button", - icon = "icon", - mainFont = "main_font", - arrowRight = "arrow_right", - arrowUp = "arrow_up" -} - -const enableCameraSceneKey = 'enableCameraScene'; export class EnableCameraScene extends ResizableScene { - private textField!: TextField; - private cameraNameField!: TextField; - private arrowLeft!: Image; - private arrowRight!: Image; - private arrowDown!: Image; - private arrowUp!: Image; - private microphonesList: MediaDeviceInfo[] = new Array(); - private camerasList: MediaDeviceInfo[] = new Array(); - private cameraSelected: number = 0; - private microphoneSelected: number = 0; - private soundMeter: SoundMeter; - private soundMeterSprite!: SoundMeterSprite; - private microphoneNameField!: TextField; - - private enableCameraSceneElement!: Phaser.GameObjects.DOMElement; - - private mobileTapZone!: Zone; - private localStreamStoreUnsubscriber!: Unsubscriber; constructor() { super({ key: EnableCameraSceneName }); - this.soundMeter = new SoundMeter(); } preload() { - - this.load.html(enableCameraSceneKey, 'resources/html/EnableCameraScene.html'); - - this.load.image(LoginTextures.playButton, "resources/objects/play_button.png"); - this.load.image(LoginTextures.arrowRight, "resources/objects/arrow_right.png"); - this.load.image(LoginTextures.arrowUp, "resources/objects/arrow_up.png"); - // Note: arcade.png from the Phaser 3 examples at: https://github.com/photonstorm/phaser3-examples/tree/master/public/assets/fonts/bitmap - this.load.bitmapFont(LoginTextures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml'); } create() { - this.enableCameraSceneElement = this.add.dom(-1000, 0).createFromCache(enableCameraSceneKey); - this.centerXDomElement(this.enableCameraSceneElement, 300); - - MenuScene.revealMenusAfterInit(this.enableCameraSceneElement, enableCameraSceneKey); - - const continuingButton = this.enableCameraSceneElement.getChildByID('enableCameraSceneFormSubmit') as HTMLButtonElement; - continuingButton.addEventListener('click', (e) => { - e.preventDefault(); - this.login(); - }); - - if (touchScreenManager.supportTouchScreen) { - new PinchManager(this); - } - //this.scale.setZoom(ZOOM_LEVEL); - //Phaser.Display.Align.In.BottomCenter(this.pressReturnField, zone); - - /* FIX ME */ - this.textField = new TextField(this, this.scale.width / 2, 20, ''); - - // For mobile purposes - we need a big enough touchable area. - this.mobileTapZone = this.add.zone(this.scale.width / 2,this.scale.height - 30,200,50) - .setInteractive().on("pointerdown", () => { - this.login(); - }); - - this.cameraNameField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height - 60, ''); - - this.microphoneNameField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height - 40, ''); - - this.arrowRight = new Image(this, 0, 0, LoginTextures.arrowRight); - this.arrowRight.setVisible(false); - this.arrowRight.setInteractive().on('pointerdown', this.nextCam.bind(this)); - this.add.existing(this.arrowRight); - - this.arrowLeft = new Image(this, 0, 0, LoginTextures.arrowRight); - this.arrowLeft.setVisible(false); - this.arrowLeft.flipX = true; - this.arrowLeft.setInteractive().on('pointerdown', this.previousCam.bind(this)); - this.add.existing(this.arrowLeft); - - this.arrowUp = new Image(this, 0, 0, LoginTextures.arrowUp); - this.arrowUp.setVisible(false); - this.arrowUp.setInteractive().on('pointerdown', this.previousMic.bind(this)); - this.add.existing(this.arrowUp); - - this.arrowDown = new Image(this, 0, 0, LoginTextures.arrowUp); - this.arrowDown.setVisible(false); - this.arrowDown.flipY = true; - this.arrowDown.setInteractive().on('pointerdown', this.nextMic.bind(this)); - this.add.existing(this.arrowDown); - this.input.keyboard.on('keyup-ENTER', () => { this.login(); }); - HtmlUtils.getElementByIdOrFail('webRtcSetup').classList.add('active'); - - this.localStreamStoreUnsubscriber = localStreamStore.subscribe((result) => { - if (result.type === 'error') { - // TODO: proper handling of the error - throw result.error; - } - - this.getDevices(); - if (result.stream !== null) { - this.setupStream(result.stream); - } - }); - /*const mediaPromise = mediaManager.getCamera(); - mediaPromise.then(this.getDevices.bind(this)); - mediaPromise.then(this.setupStream.bind(this));*/ - - this.input.keyboard.on('keydown-RIGHT', this.nextCam.bind(this)); - this.input.keyboard.on('keydown-LEFT', this.previousCam.bind(this)); - this.input.keyboard.on('keydown-DOWN', this.nextMic.bind(this)); - this.input.keyboard.on('keydown-UP', this.previousMic.bind(this)); - - this.soundMeterSprite = new SoundMeterSprite(this, 50, 50); - this.soundMeterSprite.setVisible(false); - this.add.existing(this.soundMeterSprite); - - this.onResize(); - enableCameraSceneVisibilityStore.showEnableCameraScene(); } - private previousCam(): void { - if (this.cameraSelected === 0 || this.camerasList.length === 0) { - return; - } - this.cameraSelected--; - videoConstraintStore.setDeviceId(this.camerasList[this.cameraSelected].deviceId); - - //mediaManager.setCamera(this.camerasList[this.cameraSelected].deviceId).then(this.setupStream.bind(this)); - } - - private nextCam(): void { - if (this.cameraSelected === this.camerasList.length - 1 || this.camerasList.length === 0) { - return; - } - this.cameraSelected++; - videoConstraintStore.setDeviceId(this.camerasList[this.cameraSelected].deviceId); - - // TODO: the change of camera should be OBSERVED (reactive) - //mediaManager.setCamera(this.camerasList[this.cameraSelected].deviceId).then(this.setupStream.bind(this)); - } - - private previousMic(): void { - if (this.microphoneSelected === 0 || this.microphonesList.length === 0) { - return; - } - this.microphoneSelected--; - audioConstraintStore.setDeviceId(this.microphonesList[this.microphoneSelected].deviceId); - //mediaManager.setMicrophone(this.microphonesList[this.microphoneSelected].deviceId).then(this.setupStream.bind(this)); - } - - private nextMic(): void { - if (this.microphoneSelected === this.microphonesList.length - 1 || this.microphonesList.length === 0) { - return; - } - this.microphoneSelected++; - audioConstraintStore.setDeviceId(this.microphonesList[this.microphoneSelected].deviceId); - // TODO: the change of camera should be OBSERVED (reactive) - //mediaManager.setMicrophone(this.microphonesList[this.microphoneSelected].deviceId).then(this.setupStream.bind(this)); - } - - /** - * Function called each time a camera is changed - */ - private setupStream(stream: MediaStream): void { - const img = HtmlUtils.getElementByIdOrFail('webRtcSetupNoVideo'); - img.style.display = 'none'; - - const div = HtmlUtils.getElementByIdOrFail('myCamVideoSetup'); - div.srcObject = stream; - - this.soundMeter.connectToSource(stream, new window.AudioContext()); - this.soundMeterSprite.setVisible(true); - - this.updateWebCamName(); - } - - private updateWebCamName(): void { - if (this.camerasList.length > 1) { - let label = this.camerasList[this.cameraSelected].label; - // remove text in parenthesis - label = label.replace(/\([^()]*\)/g, '').trim(); - // remove accents - label = label.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); - this.cameraNameField.text = label; - - this.arrowRight.setVisible(this.cameraSelected < this.camerasList.length - 1); - this.arrowLeft.setVisible(this.cameraSelected > 0); - } - if (this.microphonesList.length > 1) { - let label = this.microphonesList[this.microphoneSelected].label; - // remove text in parenthesis - label = label.replace(/\([^()]*\)/g, '').trim(); - // remove accents - label = label.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); - - this.microphoneNameField.text = label; - - this.arrowDown.setVisible(this.microphoneSelected < this.microphonesList.length - 1); - this.arrowUp.setVisible(this.microphoneSelected > 0); - - } - } - public onResize(): void { - let div = HtmlUtils.getElementByIdOrFail('myCamVideoSetup'); - let bounds = div.getBoundingClientRect(); - if (!div.srcObject) { - div = HtmlUtils.getElementByIdOrFail('webRtcSetup'); - bounds = div.getBoundingClientRect(); - } - - this.textField.x = this.game.renderer.width / 2; - this.mobileTapZone.x = this.game.renderer.width / 2; - this.cameraNameField.x = this.game.renderer.width / 2; - this.microphoneNameField.x = this.game.renderer.width / 2; - - this.cameraNameField.y = bounds.top / this.scale.zoom - 8; - - this.soundMeterSprite.x = this.game.renderer.width / 2 - this.soundMeterSprite.getWidth() / 2; - this.soundMeterSprite.y = bounds.bottom / this.scale.zoom + 16; - - this.microphoneNameField.y = this.soundMeterSprite.y + 22; - - this.arrowRight.x = bounds.right / this.scale.zoom + 16; - this.arrowRight.y = (bounds.top + bounds.height / 2) / this.scale.zoom; - - this.arrowLeft.x = bounds.left / this.scale.zoom - 16; - this.arrowLeft.y = (bounds.top + bounds.height / 2) / this.scale.zoom; - - this.arrowDown.x = this.microphoneNameField.x + this.microphoneNameField.width / 2 + 16; - this.arrowDown.y = this.microphoneNameField.y; - - this.arrowUp.x = this.microphoneNameField.x - this.microphoneNameField.width / 2 - 16; - this.arrowUp.y = this.microphoneNameField.y; - - const actionBtn = document.querySelector('#enableCameraScene .action'); - if (actionBtn !== null) { - actionBtn.style.top = (this.scale.height - 65) + 'px'; - } } update(time: number, delta: number): void { - this.soundMeterSprite.setVolume(this.soundMeter.getVolume()); - - this.centerXDomElement(this.enableCameraSceneElement, 300); } - private login(): void { - HtmlUtils.getElementByIdOrFail('webRtcSetup').style.display = 'none'; - this.soundMeter.stop(); - + public login(): void { enableCameraSceneVisibilityStore.hideEnableCameraScene(); - this.localStreamStoreUnsubscriber(); - //mediaManager.stopCamera(); - //mediaManager.stopMicrophone(); this.scene.sleep(EnableCameraSceneName); gameManager.goToStartingMap(this.scene); } - - private async getDevices() { - // TODO: switch this in a store. - const mediaDeviceInfos = await navigator.mediaDevices.enumerateDevices(); - this.microphonesList = []; - this.camerasList = []; - for (const mediaDeviceInfo of mediaDeviceInfos) { - if (mediaDeviceInfo.kind === 'audioinput') { - this.microphonesList.push(mediaDeviceInfo); - } else if (mediaDeviceInfo.kind === 'videoinput') { - this.camerasList.push(mediaDeviceInfo); - } - } - this.updateWebCamName(); - } } diff --git a/front/src/Phaser/Login/LoginScene.ts b/front/src/Phaser/Login/LoginScene.ts index 435592f2..39a8f5f3 100644 --- a/front/src/Phaser/Login/LoginScene.ts +++ b/front/src/Phaser/Login/LoginScene.ts @@ -1,17 +1,12 @@ import {gameManager} from "../Game/GameManager"; import {SelectCharacterSceneName} from "./SelectCharacterScene"; import {ResizableScene} from "./ResizableScene"; -import { localUserStore } from "../../Connexion/LocalUserStore"; -import {MenuScene} from "../Menu/MenuScene"; -import { isUserNameValid } from "../../Connexion/LocalUser"; +import {loginSceneVisibleStore} from "../../Stores/LoginSceneStore"; export const LoginSceneName = "LoginScene"; -const loginSceneKey = 'loginScene'; - export class LoginScene extends ResizableScene { - private loginSceneElement!: Phaser.GameObjects.DOMElement; private name: string = ''; constructor() { @@ -22,65 +17,25 @@ export class LoginScene extends ResizableScene { } preload() { - this.load.html(loginSceneKey, 'resources/html/loginScene.html'); } create() { - this.loginSceneElement = this.add.dom(-1000, 0).createFromCache(loginSceneKey); - this.centerXDomElement(this.loginSceneElement, 200); - MenuScene.revealMenusAfterInit(this.loginSceneElement, loginSceneKey); - - const pErrorElement = this.loginSceneElement.getChildByID('errorLoginScene') as HTMLInputElement; - const inputElement = this.loginSceneElement.getChildByID('loginSceneName') as HTMLInputElement; - inputElement.value = localUserStore.getName() ?? ''; - inputElement.focus(); - inputElement.addEventListener('keypress', (event: KeyboardEvent) => { - if(inputElement.value.length > 7){ - event.preventDefault(); - return; - } - pErrorElement.innerHTML = ''; - if(inputElement.value && !isUserNameValid(inputElement.value)){ - pErrorElement.innerHTML = 'Invalid user name: only letters and numbers are allowed. No spaces.'; - } - if (event.key === 'Enter') { - event.preventDefault(); - this.login(inputElement); - return; - } - }); - - const continuingButton = this.loginSceneElement.getChildByID('loginSceneFormSubmit') as HTMLButtonElement; - continuingButton.addEventListener('click', (e) => { - e.preventDefault(); - this.login(inputElement); - }); + loginSceneVisibleStore.set(true); } - private login(inputElement: HTMLInputElement): void { - const pErrorElement = this.loginSceneElement.getChildByID('errorLoginScene') as HTMLInputElement; - this.name = inputElement.value; - if (this.name === '') { - pErrorElement.innerHTML = 'The name is empty'; - return - } - if(!isUserNameValid(this.name)){ - pErrorElement.innerHTML = 'Invalid user name: only letters and numbers are allowed. No spaces.'; - return - } - if (this.name === '') return - gameManager.setPlayerName(this.name); + public login(name: string): void { + name = name.trim(); + gameManager.setPlayerName(name); this.scene.stop(LoginSceneName) gameManager.tryResumingGame(this, SelectCharacterSceneName); - this.scene.remove(LoginSceneName) + this.scene.remove(LoginSceneName); + loginSceneVisibleStore.set(false); } update(time: number, delta: number): void { - } - public onResize(ev: UIEvent): void { - this.centerXDomElement(this.loginSceneElement, 200); + public onResize(): void { } } diff --git a/front/src/Phaser/Login/ResizableScene.ts b/front/src/Phaser/Login/ResizableScene.ts index 39e2d74b..d06cb66c 100644 --- a/front/src/Phaser/Login/ResizableScene.ts +++ b/front/src/Phaser/Login/ResizableScene.ts @@ -2,7 +2,7 @@ import {Scene} from "phaser"; import DOMElement = Phaser.GameObjects.DOMElement; export abstract class ResizableScene extends Scene { - public abstract onResize(ev: UIEvent): void; + public abstract onResize(): void; /** * Centers the DOM element on the X axis. @@ -17,7 +17,7 @@ export abstract class ResizableScene extends Scene { && object.node && object.node.getBoundingClientRect().width > 0 ? (object.node.getBoundingClientRect().width / 2 / this.scale.zoom) - : (300 / this.scale.zoom) + : (defaultWidth / this.scale.zoom) ); } } diff --git a/front/src/Phaser/Login/SelectCharacterMobileScene.ts b/front/src/Phaser/Login/SelectCharacterMobileScene.ts index b9c4b5a8..0d8e49d5 100644 --- a/front/src/Phaser/Login/SelectCharacterMobileScene.ts +++ b/front/src/Phaser/Login/SelectCharacterMobileScene.ts @@ -4,49 +4,50 @@ export class SelectCharacterMobileScene extends SelectCharacterScene { create(){ super.create(); + this.onResize(); this.selectedRectangle.destroy(); } - protected defineSetupPlayer(numero: number){ + protected defineSetupPlayer(num: number){ const deltaX = 30; const deltaY = 2; let [playerX, playerY] = this.getCharacterPosition(); let playerVisible = true; let playerScale = 1.5; - let playserOpactity = 1; + let playerOpacity = 1; - if( this.currentSelectUser !== numero ){ + if( this.currentSelectUser !== num ){ playerVisible = false; } - if( numero === (this.currentSelectUser + 1) ){ + if( num === (this.currentSelectUser + 1) ){ playerY -= deltaY; playerX += deltaX; playerScale = 0.8; - playserOpactity = 0.6; + playerOpacity = 0.6; playerVisible = true; } - if( numero === (this.currentSelectUser + 2) ){ + if( num === (this.currentSelectUser + 2) ){ playerY -= deltaY; playerX += (deltaX * 2); playerScale = 0.8; - playserOpactity = 0.6; + playerOpacity = 0.6; playerVisible = true; } - if( numero === (this.currentSelectUser - 1) ){ + if( num === (this.currentSelectUser - 1) ){ playerY -= deltaY; playerX -= deltaX; playerScale = 0.8; - playserOpactity = 0.6; + playerOpacity = 0.6; playerVisible = true; } - if( numero === (this.currentSelectUser - 2) ){ + if( num === (this.currentSelectUser - 2) ){ playerY -= deltaY; playerX -= (deltaX * 2); playerScale = 0.8; - playserOpactity = 0.6; + playerOpacity = 0.6; playerVisible = true; } - return {playerX, playerY, playerScale, playserOpactity, playerVisible} + return {playerX, playerY, playerScale, playerOpacity, playerVisible} } /** diff --git a/front/src/Phaser/Login/SelectCharacterScene.ts b/front/src/Phaser/Login/SelectCharacterScene.ts index ecbb9c64..0f590840 100644 --- a/front/src/Phaser/Login/SelectCharacterScene.ts +++ b/front/src/Phaser/Login/SelectCharacterScene.ts @@ -10,13 +10,13 @@ import {AbstractCharacterScene} from "./AbstractCharacterScene"; import {areCharacterLayersValid} from "../../Connexion/LocalUser"; import {touchScreenManager} from "../../Touch/TouchScreenManager"; import {PinchManager} from "../UserInput/PinchManager"; -import {MenuScene} from "../Menu/MenuScene"; +import {selectCharacterSceneVisibleStore} from "../../Stores/SelectCharacterStore"; +import {waScaleManager} from "../Services/WaScaleManager"; +import {isMobile} from "../../Enum/EnvironmentVariable"; //todo: put this constants in a dedicated file export const SelectCharacterSceneName = "SelectCharacterScene"; -const selectCharacterKey = 'selectCharacterScene'; - export class SelectCharacterScene extends AbstractCharacterScene { protected readonly nbCharactersPerRow = 6; protected selectedPlayer!: Phaser.Physics.Arcade.Sprite|null; // null if we are selecting the "customize" option @@ -25,8 +25,11 @@ export class SelectCharacterScene extends AbstractCharacterScene { protected selectedRectangle!: Rectangle; - protected selectCharacterSceneElement!: Phaser.GameObjects.DOMElement; protected currentSelectUser = 0; + protected pointerClicked: boolean = false; + protected pointerTimer: number = 0; + + protected lazyloadingAttempt = true; //permit to update texture loaded after renderer constructor() { super({ @@ -35,50 +38,41 @@ export class SelectCharacterScene extends AbstractCharacterScene { } preload() { - this.load.html(selectCharacterKey, 'resources/html/selectCharacterScene.html'); this.loadSelectSceneCharacters().then((bodyResourceDescriptions) => { bodyResourceDescriptions.forEach((bodyResourceDescription) => { this.playerModels.push(bodyResourceDescription); }); - }) + this.lazyloadingAttempt = true; + }); this.playerModels = loadAllDefaultModels(this.load); + this.lazyloadingAttempt = false; //this function must stay at the end of preload function addLoader(this); } create() { - - this.selectCharacterSceneElement = this.add.dom(-1000, 0).createFromCache(selectCharacterKey); - this.centerXDomElement(this.selectCharacterSceneElement, 150); - MenuScene.revealMenusAfterInit(this.selectCharacterSceneElement, selectCharacterKey); - - this.selectCharacterSceneElement.addListener('click'); - this.selectCharacterSceneElement.on('click', (event:MouseEvent) => { - event.preventDefault(); - if((event?.target as HTMLInputElement).id === 'selectCharacterButtonLeft') { - this.moveToLeft(); - }else if((event?.target as HTMLInputElement).id === 'selectCharacterButtonRight') { - this.moveToRight(); - }else if((event?.target as HTMLInputElement).id === 'selectCharacterSceneFormSubmit') { - this.nextSceneToCameraScene(); - }else if((event?.target as HTMLInputElement).id === 'selectCharacterSceneFormCustomYourOwnSubmit') { - this.nextSceneToCustomizeScene(); - } + selectCharacterSceneVisibleStore.set(true); + this.events.addListener('wake', () => { + waScaleManager.saveZoom(); + waScaleManager.zoomModifier = isMobile() ? 2 : 1; + selectCharacterSceneVisibleStore.set(true); }); if (touchScreenManager.supportTouchScreen) { new PinchManager(this); } + waScaleManager.saveZoom(); + waScaleManager.zoomModifier = isMobile() ? 2 : 1; + const rectangleXStart = this.game.renderer.width / 2 - (this.nbCharactersPerRow / 2) * 32 + 16; this.selectedRectangle = this.add.rectangle(rectangleXStart, 90, 32, 32).setStrokeStyle(2, 0xFFFFFF); this.selectedRectangle.setDepth(2); /*create user*/ this.createCurrentPlayer(); - const playerNumber = localUserStore.getPlayerCharacterIndex(); this.input.keyboard.on('keyup-ENTER', () => { return this.nextSceneToCameraScene(); @@ -98,7 +92,7 @@ export class SelectCharacterScene extends AbstractCharacterScene { }); } - protected nextSceneToCameraScene(): void { + public nextSceneToCameraScene(): void { if (this.selectedPlayer !== null && !areCharacterLayersValid([this.selectedPlayer.texture.key])) { return; } @@ -106,23 +100,33 @@ export class SelectCharacterScene extends AbstractCharacterScene { return; } this.scene.stop(SelectCharacterSceneName); + waScaleManager.restoreZoom(); gameManager.setCharacterLayers([this.selectedPlayer.texture.key]); gameManager.tryResumingGame(this, EnableCameraSceneName); - this.scene.remove(SelectCharacterSceneName); + this.players = []; + selectCharacterSceneVisibleStore.set(false); + this.events.removeListener('wake'); } - protected nextSceneToCustomizeScene(): void { + public nextSceneToCustomizeScene(): void { if (this.selectedPlayer !== null && !areCharacterLayersValid([this.selectedPlayer.texture.key])) { return; } this.scene.sleep(SelectCharacterSceneName); + waScaleManager.restoreZoom(); this.scene.run(CustomizeSceneName); + selectCharacterSceneVisibleStore.set(false); } createCurrentPlayer(): void { for (let i = 0; i c.texture.key === playerResource.name)){ + continue; + } + const [middleX, middleY] = this.getCharacterPosition(); const player = this.physics.add.sprite(middleX, middleY, playerResource.name, 0); this.setUpPlayer(player, i); @@ -133,15 +137,22 @@ export class SelectCharacterScene extends AbstractCharacterScene { repeat: -1 }); player.setInteractive().on("pointerdown", () => { - if(this.currentSelectUser === i){ + if (this.pointerClicked) { return; } + if (this.currentSelectUser === i) { + return; + } + //To not trigger two time the pointerdown events : + // We set a boolean to true so that pointerdown events does nothing when the boolean is true + // We set a timer that we decrease in update function to not trigger the pointerdown events twice + this.pointerClicked = true; + this.pointerTimer = 250; this.currentSelectUser = i; this.moveUser(); }); this.players.push(player); } - this.selectedPlayer = this.players[this.currentSelectUser]; this.selectedPlayer.play(this.playerModels[this.currentSelectUser].name); } @@ -154,7 +165,7 @@ export class SelectCharacterScene extends AbstractCharacterScene { this.updateSelectedPlayer(); } - protected moveToLeft(){ + public moveToLeft(){ if(this.currentSelectUser === 0){ return; } @@ -162,7 +173,7 @@ export class SelectCharacterScene extends AbstractCharacterScene { this.moveUser(); } - protected moveToRight(){ + public moveToRight(){ if(this.currentSelectUser === (this.players.length - 1)){ return; } @@ -186,35 +197,35 @@ export class SelectCharacterScene extends AbstractCharacterScene { this.moveUser(); } - protected defineSetupPlayer(numero: number){ + protected defineSetupPlayer(num: number){ const deltaX = 32; const deltaY = 32; let [playerX, playerY] = this.getCharacterPosition(); // player X and player y are middle of the - playerX = ( (playerX - (deltaX * 2.5)) + ((deltaX) * (numero % this.nbCharactersPerRow)) ); // calcul position on line users - playerY = ( (playerY - (deltaY * 2)) + ((deltaY) * ( Math.floor(numero / this.nbCharactersPerRow) )) ); // calcul position on column users + playerX = ( (playerX - (deltaX * 2.5)) + ((deltaX) * (num % this.nbCharactersPerRow)) ); // calcul position on line users + playerY = ( (playerY - (deltaY * 2)) + ((deltaY) * ( Math.floor(num / this.nbCharactersPerRow) )) ); // calcul position on column users const playerVisible = true; const playerScale = 1; - const playserOpactity = 1; + const playerOpacity = 1; // if selected - if( numero === this.currentSelectUser ){ + if( num === this.currentSelectUser ){ this.selectedRectangle.setX(playerX); this.selectedRectangle.setY(playerY); } - return {playerX, playerY, playerScale, playserOpactity, playerVisible} + return {playerX, playerY, playerScale, playerOpacity, playerVisible} } - protected setUpPlayer(player: Phaser.Physics.Arcade.Sprite, numero: number){ + protected setUpPlayer(player: Phaser.Physics.Arcade.Sprite, num: number){ - const {playerX, playerY, playerScale, playserOpactity, playerVisible} = this.defineSetupPlayer(numero); + const {playerX, playerY, playerScale, playerOpacity, playerVisible} = this.defineSetupPlayer(num); player.setBounce(0.2); - player.setCollideWorldBounds(true); + player.setCollideWorldBounds(false); player.setVisible( playerVisible ); player.setScale(playerScale, playerScale); - player.setAlpha(playserOpactity); + player.setAlpha(playerOpacity); player.setX(playerX); player.setY(playerY); } @@ -238,12 +249,23 @@ export class SelectCharacterScene extends AbstractCharacterScene { } update(time: number, delta: number): void { + // pointerTimer is set to 250 when pointerdown events is trigger + // After 250ms, pointerClicked is set to false and the pointerdown events can be trigger again + this.pointerTimer -= delta; + if (this.pointerTimer <= 0) { + this.pointerClicked = false; + } + + if(this.lazyloadingAttempt){ + //re-render players list + this.createCurrentPlayer(); + this.moveUser(); + this.lazyloadingAttempt = false; + } } - public onResize(ev: UIEvent): void { + public onResize(): void { //move position of user this.moveUser(); - - this.centerXDomElement(this.selectCharacterSceneElement, 150); } } diff --git a/front/src/Phaser/Login/SelectCompanionScene.ts b/front/src/Phaser/Login/SelectCompanionScene.ts index 203fd557..4c29f942 100644 --- a/front/src/Phaser/Login/SelectCompanionScene.ts +++ b/front/src/Phaser/Login/SelectCompanionScene.ts @@ -10,18 +10,21 @@ import { getAllCompanionResources } from "../Companion/CompanionTexturesLoadingM import {touchScreenManager} from "../../Touch/TouchScreenManager"; import {PinchManager} from "../UserInput/PinchManager"; import { MenuScene } from "../Menu/MenuScene"; +import {selectCompanionSceneVisibleStore} from "../../Stores/SelectCompanionStore"; +import {waScaleManager} from "../Services/WaScaleManager"; +import {isMobile} from "../../Enum/EnvironmentVariable"; export const SelectCompanionSceneName = "SelectCompanionScene"; -const selectCompanionSceneKey = 'selectCompanionScene'; - export class SelectCompanionScene extends ResizableScene { private selectedCompanion!: Phaser.Physics.Arcade.Sprite; private companions: Array = new Array(); private companionModels: Array = []; + private saveZoom: number = 0; - private selectCompanionSceneElement!: Phaser.GameObjects.DOMElement; private currentCompanion = 0; + private pointerClicked: boolean = false; + private pointerTimer: number = 0; constructor() { super({ @@ -30,8 +33,6 @@ export class SelectCompanionScene extends ResizableScene { } preload() { - this.load.html(selectCompanionSceneKey, 'resources/html/SelectCompanionScene.html'); - getAllCompanionResources(this.load).forEach(model => { this.companionModels.push(model); }); @@ -42,30 +43,17 @@ export class SelectCompanionScene extends ResizableScene { create() { - this.selectCompanionSceneElement = this.add.dom(-1000, 0).createFromCache(selectCompanionSceneKey); - this.centerXDomElement(this.selectCompanionSceneElement, 150); - MenuScene.revealMenusAfterInit(this.selectCompanionSceneElement, selectCompanionSceneKey); + selectCompanionSceneVisibleStore.set(true); - this.selectCompanionSceneElement.addListener('click'); - this.selectCompanionSceneElement.on('click', (event:MouseEvent) => { - event.preventDefault(); - if((event?.target as HTMLInputElement).id === 'selectCharacterButtonLeft') { - this.moveToLeft(); - }else if((event?.target as HTMLInputElement).id === 'selectCharacterButtonRight') { - this.moveToRight(); - }else if((event?.target as HTMLInputElement).id === 'selectCompanionSceneFormSubmit') { - this.nextScene(); - }else if((event?.target as HTMLInputElement).id === 'selectCompanionSceneFormBack') { - this._nextScene(); - } - }); + waScaleManager.saveZoom(); + waScaleManager.zoomModifier = isMobile() ? 2 : 1; if (touchScreenManager.supportTouchScreen) { new PinchManager(this); } // input events - this.input.keyboard.on('keyup-ENTER', this.nextScene.bind(this)); + this.input.keyboard.on('keyup-ENTER', this.selectCompanion.bind(this)); this.input.keyboard.on('keydown-RIGHT', this.moveToRight.bind(this)); this.input.keyboard.on('keydown-LEFT', this.moveToLeft.bind(this)); @@ -86,21 +74,28 @@ export class SelectCompanionScene extends ResizableScene { } update(time: number, delta: number): void { - + // pointerTimer is set to 250 when pointerdown events is trigger + // After 250ms, pointerClicked is set to false and the pointerdown events can be trigger again + this.pointerTimer -= delta; + if (this.pointerTimer <= 0) { + this.pointerClicked = false; + } } - private nextScene(): void { + public selectCompanion(): void { localUserStore.setCompanion(this.companionModels[this.currentCompanion].name); gameManager.setCompanion(this.companionModels[this.currentCompanion].name); - this._nextScene(); + this.closeScene(); } - private _nextScene(){ + public closeScene(){ // next scene this.scene.stop(SelectCompanionSceneName); + waScaleManager.restoreZoom(); gameManager.tryResumingGame(this, EnableCameraSceneName); this.scene.remove(SelectCompanionSceneName); + selectCompanionSceneVisibleStore.set(false); } private createCurrentCompanion(): void { @@ -117,6 +112,14 @@ export class SelectCompanionScene extends ResizableScene { }); companion.setInteractive().on("pointerdown", () => { + if (this.pointerClicked) { + return; + } + //To not trigger two time the pointerdown events : + // We set a boolean to true so that pointerdown events does nothing when the boolean is true + // We set a timer that we decrease in update function to not trigger the pointerdown events twice + this.pointerClicked = true; + this.pointerTimer = 250; this.currentCompanion = i; this.moveCompanion(); }); @@ -126,10 +129,8 @@ export class SelectCompanionScene extends ResizableScene { this.selectedCompanion = this.companions[this.currentCompanion]; } - public onResize(ev: UIEvent): void { + public onResize(): void { this.moveCompanion(); - - this.centerXDomElement(this.selectCompanionSceneElement, 150); } private updateSelectedCompanion(): void { @@ -147,15 +148,7 @@ export class SelectCompanionScene extends ResizableScene { this.updateSelectedCompanion(); } - private moveToLeft(){ - if(this.currentCompanion === 0){ - return; - } - this.currentCompanion -= 1; - this.moveCompanion(); - } - - private moveToRight(){ + public moveToRight(){ if(this.currentCompanion === (this.companions.length - 1)){ return; } @@ -163,38 +156,46 @@ export class SelectCompanionScene extends ResizableScene { this.moveCompanion(); } - private defineSetupCompanion(numero: number){ + public moveToLeft(){ + if(this.currentCompanion === 0){ + return; + } + this.currentCompanion -= 1; + this.moveCompanion(); + } + + private defineSetupCompanion(num: number){ const deltaX = 30; const deltaY = 2; let [companionX, companionY] = this.getCompanionPosition(); let companionVisible = true; let companionScale = 1.5; let companionOpactity = 1; - if( this.currentCompanion !== numero ){ + if( this.currentCompanion !== num ){ companionVisible = false; } - if( numero === (this.currentCompanion + 1) ){ + if( num === (this.currentCompanion + 1) ){ companionY -= deltaY; companionX += deltaX; companionScale = 0.8; companionOpactity = 0.6; companionVisible = true; } - if( numero === (this.currentCompanion + 2) ){ + if( num === (this.currentCompanion + 2) ){ companionY -= deltaY; companionX += (deltaX * 2); companionScale = 0.8; companionOpactity = 0.6; companionVisible = true; } - if( numero === (this.currentCompanion - 1) ){ + if( num === (this.currentCompanion - 1) ){ companionY -= deltaY; companionX -= deltaX; companionScale = 0.8; companionOpactity = 0.6; companionVisible = true; } - if( numero === (this.currentCompanion - 2) ){ + if( num === (this.currentCompanion - 2) ){ companionY -= deltaY; companionX -= (deltaX * 2); companionScale = 0.8; diff --git a/front/src/Phaser/Map/ITiledMap.ts b/front/src/Phaser/Map/ITiledMap.ts index 672231eb..8ea9baf1 100644 --- a/front/src/Phaser/Map/ITiledMap.ts +++ b/front/src/Phaser/Map/ITiledMap.ts @@ -170,7 +170,7 @@ export interface ITiledTileSet { tilewidth: number; transparentcolor: string; terrains: ITiledMapTerrain[]; - tiles: Array; + tiles?: Array; /** * Refers to external tileset file (should be JSON) @@ -181,6 +181,8 @@ export interface ITiledTileSet { export interface ITile { id: number, type?: string + + properties?: Array } export interface ITiledMapTerrain { diff --git a/front/src/Phaser/Menu/HelpCameraSettingsScene.ts b/front/src/Phaser/Menu/HelpCameraSettingsScene.ts deleted file mode 100644 index 6bc520c0..00000000 --- a/front/src/Phaser/Menu/HelpCameraSettingsScene.ts +++ /dev/null @@ -1,152 +0,0 @@ -import {mediaManager} from "../../WebRtc/MediaManager"; -import {HtmlUtils} from "../../WebRtc/HtmlUtils"; -import {localUserStore} from "../../Connexion/LocalUserStore"; -import {DirtyScene} from "../Game/DirtyScene"; -import {get} from "svelte/store"; -import {requestedCameraState, requestedMicrophoneState} from "../../Stores/MediaStore"; - -export const HelpCameraSettingsSceneName = 'HelpCameraSettingsScene'; -const helpCameraSettings = 'helpCameraSettings'; -/** - * The scene that show how to permit Camera and Microphone access if there are not already allowed - */ -export class HelpCameraSettingsScene extends DirtyScene { - private helpCameraSettingsElement!: Phaser.GameObjects.DOMElement; - private helpCameraSettingsOpened: boolean = false; - - constructor() { - super({key: HelpCameraSettingsSceneName}); - } - - preload() { - this.load.html(helpCameraSettings, 'resources/html/helpCameraSettings.html'); - } - - create(){ - this.createHelpCameraSettings(); - } - - private createHelpCameraSettings() : void { - const middleX = this.getMiddleX(); - this.helpCameraSettingsElement = this.add.dom(middleX, -800, undefined, {overflow: 'scroll'}).createFromCache(helpCameraSettings); - this.revealMenusAfterInit(this.helpCameraSettingsElement, helpCameraSettings); - this.helpCameraSettingsElement.addListener('click'); - this.helpCameraSettingsElement.on('click', (event:MouseEvent) => { - if((event?.target as HTMLInputElement).id === 'mailto') { - return; - } - event.preventDefault(); - if((event?.target as HTMLInputElement).id === 'helpCameraSettingsFormRefresh') { - window.location.reload(); - }else if((event?.target as HTMLInputElement).id === 'helpCameraSettingsFormContinue') { - this.closeHelpCameraSettingsOpened(); - } - }); - - if(!localUserStore.getHelpCameraSettingsShown() && (!get(requestedMicrophoneState) || !get(requestedCameraState))){ - this.openHelpCameraSettingsOpened(); - localUserStore.setHelpCameraSettingsShown(); - } - - mediaManager.setHelpCameraSettingsCallBack(() => { - this.openHelpCameraSettingsOpened(); - }); - } - - private openHelpCameraSettingsOpened(): void{ - HtmlUtils.getElementByIdOrFail('webRtcSetup').style.display = 'none'; - this.helpCameraSettingsOpened = true; - try{ - if(window.navigator.userAgent.includes('Firefox')){ - HtmlUtils.getElementByIdOrFail('browserHelpSetting').innerHTML =''; - }else if(window.navigator.userAgent.includes('Chrome')){ - HtmlUtils.getElementByIdOrFail('browserHelpSetting').innerHTML =''; - } - }catch(err) { - console.error('openHelpCameraSettingsOpened => getElementByIdOrFail => error', err); - } - const middleY = this.getMiddleY(); - const middleX = this.getMiddleX(); - this.tweens.add({ - targets: this.helpCameraSettingsElement, - y: middleY, - x: middleX, - duration: 1000, - ease: 'Power3', - overflow: 'scroll' - }); - - this.dirty = true; - } - - private closeHelpCameraSettingsOpened(): void{ - const middleX = this.getMiddleX(); - /*const helpCameraSettingsInfo = this.helpCameraSettingsElement.getChildByID('helpCameraSettings') as HTMLParagraphElement; - helpCameraSettingsInfo.innerText = ''; - helpCameraSettingsInfo.style.display = 'none';*/ - this.helpCameraSettingsOpened = false; - this.tweens.add({ - targets: this.helpCameraSettingsElement, - y: -1000, - x: middleX, - duration: 1000, - ease: 'Power3', - overflow: 'scroll' - }); - - this.dirty = true; - } - - private revealMenusAfterInit(menuElement: Phaser.GameObjects.DOMElement, rootDomId: string) { - //Dom elements will appear inside the viewer screen when creating before being moved out of it, which create a flicker effect. - //To prevent this, we put a 'hidden' attribute on the root element, we remove it only after the init is done. - setTimeout(() => { - (menuElement.getChildByID(rootDomId) as HTMLElement).hidden = false; - }, 250); - } - - update(time: number, delta: number): void { - this.dirty = false; - } - - public onResize(ev: UIEvent): void { - super.onResize(ev); - if (this.helpCameraSettingsOpened) { - const middleX = this.getMiddleX(); - const middleY = this.getMiddleY(); - this.tweens.add({ - targets: this.helpCameraSettingsElement, - x: middleX, - y: middleY, - duration: 1000, - ease: 'Power3' - }); - this.dirty = true; - } - } - - private getMiddleX() : number{ - return (this.scale.width / 2) - - ( - this.helpCameraSettingsElement - && this.helpCameraSettingsElement.node - && this.helpCameraSettingsElement.node.getBoundingClientRect().width > 0 - ? (this.helpCameraSettingsElement.node.getBoundingClientRect().width / (2 * this.scale.zoom)) - : (400 / 2) - ); - } - - private getMiddleY() : number{ - const middleY = ((this.scale.height) - ( - (this.helpCameraSettingsElement - && this.helpCameraSettingsElement.node - && this.helpCameraSettingsElement.node.getBoundingClientRect().height > 0 - ? this.helpCameraSettingsElement.node.getBoundingClientRect().height : 400 /*FIXME to use a const will be injected in HTMLElement*/)/this.scale.zoom)) / 2; - return (middleY > 0 ? middleY : 0); - } - - public isDirty(): boolean { - return this.dirty; - } -} - diff --git a/front/src/Phaser/Menu/MenuScene.ts b/front/src/Phaser/Menu/MenuScene.ts index 8957bbce..bf9c9a08 100644 --- a/front/src/Phaser/Menu/MenuScene.ts +++ b/front/src/Phaser/Menu/MenuScene.ts @@ -14,6 +14,10 @@ import { HtmlUtils } from '../../WebRtc/HtmlUtils'; import { iframeListener } from '../../Api/IframeListener'; import { Subscription } from 'rxjs'; import { videoConstraintStore } from "../../Stores/MediaStore"; +import {registerMenuCommandStream} from "../../Api/Events/ui/MenuItemRegisterEvent"; +import {sendMenuClickedEvent} from "../../Api/iframe/Ui/MenuItem"; +import {consoleGlobalMessageManagerVisibleStore} from "../../Stores/ConsoleGlobalMessageManagerStore"; +import {get} from "svelte/store"; export const MenuSceneName = 'MenuScene'; const gameMenuKey = 'gameMenu'; @@ -42,18 +46,39 @@ export class MenuScene extends Phaser.Scene { private warningContainerTimeout: NodeJS.Timeout | null = null; private subscriptions = new Subscription() constructor() { - super({key: MenuSceneName}); + super({ key: MenuSceneName }); this.gameQualityValue = localUserStore.getGameQualityValue(); this.videoQualityValue = localUserStore.getVideoQualityValue(); - this.subscriptions.add(iframeListener.registerMenuCommandStream.subscribe(menuCommand => { + this.subscriptions.add(registerMenuCommandStream.subscribe(menuCommand => { this.addMenuOption(menuCommand); + })) + this.subscriptions.add(iframeListener.unregisterMenuCommandStream.subscribe(menuCommand => { + this.destroyMenu(menuCommand); })) } - preload () { + reset() { + const addedMenuItems = [...this.menuElement.node.querySelectorAll(".fromApi")]; + for (let index = addedMenuItems.length - 1; index >= 0; index--) { + addedMenuItems[index].remove() + } + } + + public addMenuOption(menuText: string) { + const wrappingSection = document.createElement("section") + const escapedHtml = HtmlUtils.escapeHtml(menuText); + wrappingSection.innerHTML = `` + const menuItemContainer = this.menuElement.node.querySelector("#gameMenu main"); + if (menuItemContainer) { + menuItemContainer.querySelector(`#${escapedHtml}.fromApi`)?.remove() + menuItemContainer.insertBefore(wrappingSection, menuItemContainer.querySelector("#socialLinks")) + } + } + + preload() { this.load.html(gameMenuKey, 'resources/html/gameMenu.html'); this.load.html(gameMenuIconKey, 'resources/html/gameMenuIcon.html'); this.load.html(gameSettingsMenuKey, 'resources/html/gameQualityMenu.html'); @@ -62,13 +87,6 @@ export class MenuScene extends Phaser.Scene { this.load.html(warningContainerKey, warningContainerHtml); } - reset() { - const addedMenuItems=[...this.menuElement.node.querySelectorAll(".fromApi")]; - for(let index=addedMenuItems.length-1;index>=0;index--){ - addedMenuItems[index].remove() - } - } - create() { menuIconVisible.set(true); this.menuElement = this.add.dom(closedSideMenuX, 30).createFromCache(gameMenuKey); @@ -83,11 +101,11 @@ export class MenuScene extends Phaser.Scene { this.gameShareElement = this.add.dom(middleX, -400).createFromCache(gameShare); MenuScene.revealMenusAfterInit(this.gameShareElement, gameShare); this.gameShareElement.addListener('click'); - this.gameShareElement.on('click', (event:MouseEvent) => { + this.gameShareElement.on('click', (event: MouseEvent) => { event.preventDefault(); - if((event?.target as HTMLInputElement).id === 'gameShareFormSubmit') { + if ((event?.target as HTMLInputElement).id === 'gameShareFormSubmit') { this.copyLink(); - }else if((event?.target as HTMLInputElement).id === 'gameShareFormCancel') { + } else if ((event?.target as HTMLInputElement).id === 'gameShareFormCancel') { this.closeGameShare(); } }); @@ -143,8 +161,8 @@ export class MenuScene extends Phaser.Scene { } //TODO bind with future metadata of card //if (connectionManager.getConnexionType === GameConnexionTypes.anonymous){ - const adminSection = this.menuElement.getChildByID('socialLinks') as HTMLElement; - adminSection.hidden = false; + const adminSection = this.menuElement.getChildByID('socialLinks') as HTMLElement; + adminSection.hidden = false; //} this.tweens.add({ targets: this.menuElement, @@ -174,7 +192,7 @@ export class MenuScene extends Phaser.Scene { this.sideMenuOpened = false; this.closeAll(); this.menuButton.getChildByID('openMenuButton').innerHTML = ``; - gameManager.getCurrentGameScene(this).ConsoleGlobalMessageManager.disabledMessageConsole(); + consoleGlobalMessageManagerVisibleStore.set(false); this.tweens.add({ targets: this.menuElement, x: closedSideMenuX, @@ -194,28 +212,28 @@ export class MenuScene extends Phaser.Scene { this.settingsMenuOpened = true; const gameQualitySelect = this.gameQualityMenuElement.getChildByID('select-game-quality') as HTMLInputElement; - gameQualitySelect.value = ''+this.gameQualityValue; + gameQualitySelect.value = '' + this.gameQualityValue; const videoQualitySelect = this.gameQualityMenuElement.getChildByID('select-video-quality') as HTMLInputElement; - videoQualitySelect.value = ''+this.videoQualityValue; + videoQualitySelect.value = '' + this.videoQualityValue; this.gameQualityMenuElement.addListener('click'); - this.gameQualityMenuElement.on('click', (event:MouseEvent) => { + this.gameQualityMenuElement.on('click', (event: MouseEvent) => { event.preventDefault(); if ((event?.target as HTMLInputElement).id === 'gameQualityFormSubmit') { const gameQualitySelect = this.gameQualityMenuElement.getChildByID('select-game-quality') as HTMLInputElement; const videoQualitySelect = this.gameQualityMenuElement.getChildByID('select-video-quality') as HTMLInputElement; this.saveSetting(parseInt(gameQualitySelect.value), parseInt(videoQualitySelect.value)); - } else if((event?.target as HTMLInputElement).id === 'gameQualityFormCancel') { + } else if ((event?.target as HTMLInputElement).id === 'gameQualityFormCancel') { this.closeGameQualityMenu(); } }); - let middleY = this.scale.height / 2 - 392/2; - if(middleY < 0){ + let middleY = this.scale.height / 2 - 392 / 2; + if (middleY < 0) { middleY = 0; } - let middleX = this.scale.width / 2 - 457/2; - if(middleX < 0){ + let middleX = this.scale.width / 2 - 457 / 2; + if (middleX < 0) { middleX = 0; } this.tweens.add({ @@ -241,7 +259,7 @@ export class MenuScene extends Phaser.Scene { } - private openGameShare(): void{ + private openGameShare(): void { if (this.gameShareOpened) { this.closeGameShare(); return; @@ -255,11 +273,11 @@ export class MenuScene extends Phaser.Scene { this.gameShareOpened = true; let middleY = this.scale.height / 2 - 85; - if(middleY < 0){ + if (middleY < 0) { middleY = 0; } let middleX = this.scale.width / 2 - 200; - if(middleX < 0){ + if (middleX < 0) { middleX = 0; } this.tweens.add({ @@ -271,7 +289,7 @@ export class MenuScene extends Phaser.Scene { }); } - private closeGameShare(): void{ + private closeGameShare(): void { const gameShareInfo = this.gameShareElement.getChildByID('gameShareInfo') as HTMLParagraphElement; gameShareInfo.innerText = ''; gameShareInfo.style.display = 'none'; @@ -284,17 +302,6 @@ export class MenuScene extends Phaser.Scene { }); } - public addMenuOption(menuText: string) { - const wrappingSection = document.createElement("section") - const excapedHtml = HtmlUtils.escapeHtml(menuText); - wrappingSection.innerHTML = `` - const menuItemContainer = this.menuElement.node.querySelector("#gameMenu main"); - if (menuItemContainer) { - menuItemContainer.querySelector(`#${excapedHtml}.fromApi`)?.remove() - menuItemContainer.insertBefore(wrappingSection, menuItemContainer.querySelector("#socialLinks")) - } - } - private onMenuClick(event: MouseEvent) { const htmlMenuItem = (event?.target as HTMLInputElement); if (htmlMenuItem.classList.contains('not-button')) { @@ -303,11 +310,11 @@ export class MenuScene extends Phaser.Scene { event.preventDefault(); if (htmlMenuItem.classList.contains("fromApi")) { - iframeListener.sendMenuClickedEvent(htmlMenuItem.id) + sendMenuClickedEvent(htmlMenuItem.id) return } - switch (htmlMenuItem.id) { + switch ((event?.target as HTMLInputElement).id) { case 'changeNameButton': this.closeSideMenu(); gameManager.leaveGame(this, LoginSceneName, new LoginScene()); @@ -336,7 +343,11 @@ export class MenuScene extends Phaser.Scene { this.toggleFullscreen(); break; case 'adminConsoleButton': - gameManager.getCurrentGameScene(this).ConsoleGlobalMessageManager.activeMessageConsole(); + if (get(consoleGlobalMessageManagerVisibleStore)) { + consoleGlobalMessageManagerVisibleStore.set(false); + } else { + consoleGlobalMessageManagerVisibleStore.set(true); + } break; } } @@ -348,7 +359,7 @@ export class MenuScene extends Phaser.Scene { gameShareInfo.style.display = 'block'; } - private saveSetting(valueGame: number, valueVideo: number){ + private saveSetting(valueGame: number, valueVideo: number) { if (valueGame !== this.gameQualityValue) { this.gameQualityValue = valueGame; localUserStore.setGameQualityValue(valueGame); @@ -369,7 +380,7 @@ export class MenuScene extends Phaser.Scene { window.open(sparkHost, '_blank'); } - private closeAll(){ + private closeAll() { this.closeGameQualityMenu(); this.closeGameShare(); this.gameReportElement.close(); @@ -386,6 +397,10 @@ export class MenuScene extends Phaser.Scene { } } + public destroyMenu(menu: string) { + this.menuElement.getChildByID(menu).remove(); + } + public isDirty(): boolean { return false; } diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index b7f31aad..7ed366f7 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -26,7 +26,7 @@ export class Player extends Character { companion: string|null, companionTexturePromise?: Promise ) { - super(Scene, x, y, texturesPromise, name, direction, moving, 1, companion, companionTexturePromise); + super(Scene, x, y, texturesPromise, name, direction, moving, 1, true, companion, companionTexturePromise); //the current player model should be push away by other players to prevent conflict this.getBody().setImmovable(false); @@ -102,10 +102,6 @@ export class Player extends Character { } } - isClickable(): boolean { - return true; - } - openEmoteMenu(emotes:RadialMenuItem[]): void { this.cancelPreviousEmote(); this.emoteMenu = new RadialMenu(this.scene, this.x, this.y, emotes) diff --git a/front/src/Phaser/Services/WaScaleManager.ts b/front/src/Phaser/Services/WaScaleManager.ts index ca8b668d..abfd2a8b 100644 --- a/front/src/Phaser/Services/WaScaleManager.ts +++ b/front/src/Phaser/Services/WaScaleManager.ts @@ -2,6 +2,8 @@ import {HdpiManager} from "./HdpiManager"; import ScaleManager = Phaser.Scale.ScaleManager; import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager"; import type {Game} from "../Game/Game"; +import {ResizableScene} from "../Login/ResizableScene"; +import {HtmlUtils} from "../../WebRtc/HtmlUtils"; class WaScaleManager { @@ -9,6 +11,7 @@ class WaScaleManager { private scaleManager!: ScaleManager; private game!: Game; private actualZoom: number = 1; + private _saveZoom: number = 1; public constructor(private minGamePixelsNumber: number, private absoluteMinPixelNumber: number) { this.hdpiManager = new HdpiManager(minGamePixelsNumber, absoluteMinPixelNumber); @@ -30,7 +33,7 @@ class WaScaleManager { const { game: gameSize, real: realSize } = this.hdpiManager.getOptimalGameSize({width: width * devicePixelRatio, height: height * devicePixelRatio}); this.actualZoom = realSize.width / gameSize.width / devicePixelRatio; - this.scaleManager.setZoom(realSize.width / gameSize.width / devicePixelRatio); + this.scaleManager.setZoom(realSize.width / gameSize.width / devicePixelRatio) this.scaleManager.resize(gameSize.width, gameSize.height); // Override bug in canvas resizing in Phaser. Let's resize the canvas ourselves @@ -38,6 +41,19 @@ class WaScaleManager { style.width = Math.ceil(realSize.width / devicePixelRatio) + 'px'; style.height = Math.ceil(realSize.height / devicePixelRatio) + 'px'; + // Resize the game element at the same size at the canvas + const gameStyle = HtmlUtils.getElementByIdOrFail('game').style; + gameStyle.width = style.width; + gameStyle.height = style.height; + + // Note: onResize will be called twice (once here and once in Game.ts), but we have no better way. + for (const scene of this.game.scene.getScenes(true)) { + if (scene instanceof ResizableScene) { + // We are delaying the call to the "render" event because otherwise, the "camera" coordinates are not correctly updated. + scene.events.once(Phaser.Scenes.Events.RENDER, () => scene.onResize()); + } + } + this.game.markDirty(); } @@ -50,6 +66,15 @@ class WaScaleManager { this.applyNewSize(); } + public saveZoom(): void { + this._saveZoom = this.hdpiManager.zoomModifier; + } + + public restoreZoom(): void{ + this.hdpiManager.zoomModifier = this._saveZoom; + this.applyNewSize(); + } + /** * This is used to scale back the ui components to counter-act the zoom. */ diff --git a/front/src/Phaser/Shaders/OutlinePipeline.ts b/front/src/Phaser/Shaders/OutlinePipeline.ts deleted file mode 100644 index 0d074bc3..00000000 --- a/front/src/Phaser/Shaders/OutlinePipeline.ts +++ /dev/null @@ -1,59 +0,0 @@ -export class OutlinePipeline extends Phaser.Renderer.WebGL.Pipelines.MultiPipeline { - - // 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, - 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, 0.0, 1.0); - } - - gl_FragColor = color; - } - ` - }); - } -} diff --git a/front/src/Phaser/UserInput/UserInputManager.ts b/front/src/Phaser/UserInput/UserInputManager.ts index 70bb9b1b..068e84a2 100644 --- a/front/src/Phaser/UserInput/UserInputManager.ts +++ b/front/src/Phaser/UserInput/UserInputManager.ts @@ -2,6 +2,7 @@ import type { Direction } from "../../types"; import type {GameScene} from "../Game/GameScene"; import {touchScreenManager} from "../../Touch/TouchScreenManager"; import {MobileJoystick} from "../Components/MobileJoystick"; +import {enableUserInputsStore} from "../../Stores/UserInputStore"; interface UserInputManagerDatum { keyInstance: Phaser.Input.Keyboard.Key; @@ -58,6 +59,10 @@ export class UserInputManager { if (touchScreenManager.supportTouchScreen) { this.initVirtualJoystick(); } + + enableUserInputsStore.subscribe((enable) => { + enable ? this.restoreControls() : this.disableControls() + }) } initVirtualJoystick() { diff --git a/front/src/Stores/ConsoleGlobalMessageManagerStore.ts b/front/src/Stores/ConsoleGlobalMessageManagerStore.ts new file mode 100644 index 00000000..1fa04bfe --- /dev/null +++ b/front/src/Stores/ConsoleGlobalMessageManagerStore.ts @@ -0,0 +1,5 @@ +import { writable } from "svelte/store"; + +export const consoleGlobalMessageManagerVisibleStore = writable(false); + +export const consoleGlobalMessageManagerFocusStore = writable(false); \ No newline at end of file diff --git a/front/src/Stores/CustomCharacterStore.ts b/front/src/Stores/CustomCharacterStore.ts new file mode 100644 index 00000000..4bef7768 --- /dev/null +++ b/front/src/Stores/CustomCharacterStore.ts @@ -0,0 +1,3 @@ +import { derived, writable, Writable } from "svelte/store"; + +export const customCharacterSceneVisibleStore = writable(false); \ No newline at end of file diff --git a/front/src/Stores/ErrorStore.ts b/front/src/Stores/ErrorStore.ts new file mode 100644 index 00000000..2f1e3e40 --- /dev/null +++ b/front/src/Stores/ErrorStore.ts @@ -0,0 +1,33 @@ +import {writable} from "svelte/store"; + +/** + * A store that contains a list of error messages to be displayed. + */ +function createErrorStore() { + const { subscribe, set, update } = writable([]); + + return { + subscribe, + addErrorMessage: (e: string|Error): void => { + update((messages: string[]) => { + let message: string; + if (e instanceof Error) { + message = e.message; + } else { + message = e; + } + + if (!messages.includes(message)) { + messages.push(message); + } + + return messages; + }); + }, + clearMessages: (): void => { + set([]); + } + }; +} + +export const errorStore = createErrorStore(); diff --git a/front/src/Stores/Errors/BrowserTooOldError.ts b/front/src/Stores/Errors/BrowserTooOldError.ts new file mode 100644 index 00000000..bf934443 --- /dev/null +++ b/front/src/Stores/Errors/BrowserTooOldError.ts @@ -0,0 +1,8 @@ +export class BrowserTooOldError extends Error { + static NAME = 'BrowserTooOldError'; + + constructor() { + super('Unable to access your camera or microphone. Your browser is too old. Please consider upgrading your browser or try using a recent version of Chrome.'); + this.name = BrowserTooOldError.NAME; + } +} diff --git a/front/src/Stores/Errors/WebviewOnOldIOS.ts b/front/src/Stores/Errors/WebviewOnOldIOS.ts new file mode 100644 index 00000000..06c03f0e --- /dev/null +++ b/front/src/Stores/Errors/WebviewOnOldIOS.ts @@ -0,0 +1,8 @@ +export class WebviewOnOldIOS extends Error { + static NAME = 'WebviewOnOldIOS'; + + constructor() { + super('Your iOS version cannot use video/audio in the browser unless you are using Safari. Please switch to Safari or upgrade iOS to 14.3 or above.'); + this.name = WebviewOnOldIOS.NAME; + } +} diff --git a/front/src/Stores/GameStore.ts b/front/src/Stores/GameStore.ts index ee975f23..8899aa12 100644 --- a/front/src/Stores/GameStore.ts +++ b/front/src/Stores/GameStore.ts @@ -1,3 +1,5 @@ -import { derived, writable, Writable } from "svelte/store"; +import { writable } from "svelte/store"; export const userMovingStore = writable(false); + +export const requestVisitCardsStore = writable(null); diff --git a/front/src/Stores/HelpCameraSettingsStore.ts b/front/src/Stores/HelpCameraSettingsStore.ts new file mode 100644 index 00000000..88373dab --- /dev/null +++ b/front/src/Stores/HelpCameraSettingsStore.ts @@ -0,0 +1,3 @@ +import { writable } from "svelte/store"; + +export const helpCameraSettingsVisibleStore = writable(false); diff --git a/front/src/Stores/LoginSceneStore.ts b/front/src/Stores/LoginSceneStore.ts new file mode 100644 index 00000000..6e2ea18b --- /dev/null +++ b/front/src/Stores/LoginSceneStore.ts @@ -0,0 +1,3 @@ +import { writable } from "svelte/store"; + +export const loginSceneVisibleStore = writable(false); diff --git a/front/src/Stores/MediaStore.ts b/front/src/Stores/MediaStore.ts index 9e53aa3b..d622511e 100644 --- a/front/src/Stores/MediaStore.ts +++ b/front/src/Stores/MediaStore.ts @@ -4,6 +4,10 @@ import {localUserStore} from "../Connexion/LocalUserStore"; import {ITiledMapGroupLayer, ITiledMapObjectLayer, ITiledMapTileLayer} from "../Phaser/Map/ITiledMap"; import {userMovingStore} from "./GameStore"; import {HtmlUtils} from "../WebRtc/HtmlUtils"; +import {BrowserTooOldError} from "./Errors/BrowserTooOldError"; +import {errorStore} from "./ErrorStore"; +import {isIOS} from "../WebRtc/DeviceUtils"; +import {WebviewOnOldIOS} from "./Errors/WebviewOnOldIOS"; /** * A store that contains the camera state requested by the user (on or off). @@ -209,10 +213,14 @@ function createVideoConstraintStore() { return { subscribe, - setDeviceId: (deviceId: string) => update((constraints) => { - constraints.deviceId = { - exact: deviceId - }; + setDeviceId: (deviceId: string|undefined) => update((constraints) => { + if (deviceId !== undefined) { + constraints.deviceId = { + exact: deviceId + }; + } else { + delete constraints.deviceId; + } return constraints; }), @@ -241,15 +249,19 @@ function createAudioConstraintStore() { return { subscribe, - setDeviceId: (deviceId: string) => update((constraints) => { + setDeviceId: (deviceId: string|undefined) => update((constraints) => { selectedDeviceId = deviceId; if (typeof(constraints) === 'boolean') { constraints = {} } - constraints.deviceId = { - exact: selectedDeviceId - }; + if (deviceId !== undefined) { + constraints.deviceId = { + exact: selectedDeviceId + }; + } else { + delete constraints.deviceId; + } return constraints; }) @@ -410,13 +422,21 @@ export const localStreamStore = derived, LocalS error: new Error('Unable to access your camera or microphone. You need to use a HTTPS connection.'), constraints }); - } else { - //throw new Error('Unable to access your camera or microphone. Your browser is too old.'); + return; + } else if (isIOS()) { set({ type: 'error', - error: new Error('Unable to access your camera or microphone. Your browser is too old.'), + error: new WebviewOnOldIOS(), constraints }); + return; + } else { + set({ + type: 'error', + error: new BrowserTooOldError(), + constraints + }); + return; } } @@ -508,3 +528,87 @@ export const obtainedMediaConstraintStore = derived(localStreamStore, ($localStr return $localStreamStore.constraints; }); +/** + * Device list + */ +export const deviceListStore = readable([], function start(set) { + let deviceListCanBeQueried = false; + + const queryDeviceList = () => { + // Note: so far, we are ignoring any failures. + navigator.mediaDevices.enumerateDevices().then((mediaDeviceInfos) => { + set(mediaDeviceInfos); + }).catch((e) => { + console.error(e); + throw e; + }); + }; + + const unsubscribe = localStreamStore.subscribe((streamResult) => { + if (streamResult.type === "success" && streamResult.stream !== null) { + if (deviceListCanBeQueried === false) { + queryDeviceList(); + deviceListCanBeQueried = true; + } + } + }); + + if (navigator.mediaDevices) { + navigator.mediaDevices.addEventListener('devicechange', queryDeviceList); + } + + return function stop() { + unsubscribe(); + if (navigator.mediaDevices) { + navigator.mediaDevices.removeEventListener('devicechange', queryDeviceList); + } + }; +}); + +export const cameraListStore = derived(deviceListStore, ($deviceListStore) => { + return $deviceListStore.filter(device => device.kind === 'videoinput'); +}); + +export const microphoneListStore = derived(deviceListStore, ($deviceListStore) => { + return $deviceListStore.filter(device => device.kind === 'audioinput'); +}); + +// TODO: detect the new webcam and automatically switch on it. +cameraListStore.subscribe((devices) => { + // If the selected camera is unplugged, let's remove the constraint on deviceId + const constraints = get(videoConstraintStore); + if (!constraints.deviceId) { + return; + } + + // If we cannot find the device ID, let's remove it. + // @ts-ignore + if (!devices.find(device => device.deviceId === constraints.deviceId.exact)) { + videoConstraintStore.setDeviceId(undefined); + } +}); + +microphoneListStore.subscribe((devices) => { + // If the selected camera is unplugged, let's remove the constraint on deviceId + const constraints = get(audioConstraintStore); + if (typeof constraints === 'boolean') { + return; + } + if (!constraints.deviceId) { + return; + } + + // If we cannot find the device ID, let's remove it. + // @ts-ignore + if (!devices.find(device => device.deviceId === constraints.deviceId.exact)) { + audioConstraintStore.setDeviceId(undefined); + } +}); + +localStreamStore.subscribe(streamResult => { + if (streamResult.type === 'error') { + if (streamResult.error.name === BrowserTooOldError.NAME || streamResult.error.name === WebviewOnOldIOS.NAME) { + errorStore.addErrorMessage(streamResult.error); + } + } +}); diff --git a/front/src/Stores/ScreenSharingStore.ts b/front/src/Stores/ScreenSharingStore.ts index a55f5ab2..ec5aa46f 100644 --- a/front/src/Stores/ScreenSharingStore.ts +++ b/front/src/Stores/ScreenSharingStore.ts @@ -126,7 +126,7 @@ export const screenSharingLocalStreamStore = derived; if (navigator.getDisplayMedia) { currentStreamPromise = navigator.getDisplayMedia({constraints}); - } else if (navigator.mediaDevices.getDisplayMedia) { + } else if (navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia) { currentStreamPromise = navigator.mediaDevices.getDisplayMedia({constraints}); } else { stopScreenSharing(); @@ -169,6 +169,7 @@ export const screenSharingLocalStreamStore = derived { - if (!navigator.getDisplayMedia && !navigator.mediaDevices.getDisplayMedia) { + if (!navigator.getDisplayMedia && (!navigator.mediaDevices || !navigator.mediaDevices.getDisplayMedia)) { set(false); return; } diff --git a/front/src/Stores/SelectCharacterStore.ts b/front/src/Stores/SelectCharacterStore.ts new file mode 100644 index 00000000..094eaef3 --- /dev/null +++ b/front/src/Stores/SelectCharacterStore.ts @@ -0,0 +1,3 @@ +import { derived, writable, Writable } from "svelte/store"; + +export const selectCharacterSceneVisibleStore = writable(false); \ No newline at end of file diff --git a/front/src/Stores/SelectCompanionStore.ts b/front/src/Stores/SelectCompanionStore.ts new file mode 100644 index 00000000..e66f5de3 --- /dev/null +++ b/front/src/Stores/SelectCompanionStore.ts @@ -0,0 +1,3 @@ +import { derived, writable, Writable } from "svelte/store"; + +export const selectCompanionSceneVisibleStore = writable(false); diff --git a/front/src/Stores/SoundPlayingStore.ts b/front/src/Stores/SoundPlayingStore.ts new file mode 100644 index 00000000..cf1d681c --- /dev/null +++ b/front/src/Stores/SoundPlayingStore.ts @@ -0,0 +1,22 @@ +import { writable } from "svelte/store"; + +/** + * A store that contains the URL of the sound currently playing + */ +function createSoundPlayingStore() { + const { subscribe, set, update } = writable(null); + + return { + subscribe, + playSound: (url: string) => { + set(url); + }, + soundEnded: () => { + set(null); + } + + + }; +} + +export const soundPlayingStore = createSoundPlayingStore(); diff --git a/front/src/Stores/UserInputStore.ts b/front/src/Stores/UserInputStore.ts new file mode 100644 index 00000000..cbb7f0c3 --- /dev/null +++ b/front/src/Stores/UserInputStore.ts @@ -0,0 +1,10 @@ +import {derived} from "svelte/store"; +import {consoleGlobalMessageManagerFocusStore} from "./ConsoleGlobalMessageManagerStore"; + +//derived from the focus on Menu, ConsoleGlobal, Chat and ... +export const enableUserInputsStore = derived( + consoleGlobalMessageManagerFocusStore, + ($consoleGlobalMessageManagerFocusStore) => { + return !$consoleGlobalMessageManagerFocusStore; + } +); \ No newline at end of file diff --git a/front/src/WebRtc/CoWebsiteManager.ts b/front/src/WebRtc/CoWebsiteManager.ts index f00f6ecb..1fb28487 100644 --- a/front/src/WebRtc/CoWebsiteManager.ts +++ b/front/src/WebRtc/CoWebsiteManager.ts @@ -1,6 +1,7 @@ import {HtmlUtils} from "./HtmlUtils"; import {Subject} from "rxjs"; import {iframeListener} from "../Api/IframeListener"; +import {touchScreenManager} from "../Touch/TouchScreenManager"; enum iframeStates { closed = 1, @@ -11,12 +12,17 @@ enum iframeStates { const cowebsiteDivId = 'cowebsite'; // the id of the whole container. const cowebsiteMainDomId = 'cowebsite-main'; // the id of the parent div of the iframe. const cowebsiteAsideDomId = 'cowebsite-aside'; // the id of the parent div of the iframe. -const cowebsiteCloseButtonId = 'cowebsite-close'; +export const cowebsiteCloseButtonId = 'cowebsite-close'; const cowebsiteFullScreenButtonId = 'cowebsite-fullscreen'; const cowebsiteOpenFullScreenImageId = 'cowebsite-fullscreen-open'; const cowebsiteCloseFullScreenImageId = 'cowebsite-fullscreen-close'; const animationTime = 500; //time used by the css transitions, in ms. +interface TouchMoveCoordinates { + x: number; + y: number; +} + class CoWebsiteManager { private opened: iframeStates = iframeStates.closed; @@ -32,6 +38,7 @@ class CoWebsiteManager { private resizing: boolean = false; private cowebsiteMainDom: HTMLDivElement; private cowebsiteAsideDom: HTMLDivElement; + private previousTouchMoveCoordinates: TouchMoveCoordinates|null = null; //only use on touchscreens to track touch movement get width(): number { return this.cowebsiteDiv.clientWidth; @@ -62,32 +69,61 @@ class CoWebsiteManager { this.cowebsiteMainDom = HtmlUtils.getElementByIdOrFail(cowebsiteMainDomId); this.cowebsiteAsideDom = HtmlUtils.getElementByIdOrFail(cowebsiteAsideDomId); - this.initResizeListeners(); + if (touchScreenManager.supportTouchScreen) { + this.initResizeListeners(true); + } + this.initResizeListeners(false); - HtmlUtils.getElementByIdOrFail(cowebsiteCloseButtonId).addEventListener('click', () => { + const buttonCloseFrame = HtmlUtils.getElementByIdOrFail(cowebsiteCloseButtonId); + buttonCloseFrame.addEventListener('click', () => { + buttonCloseFrame.blur(); this.closeCoWebsite(); }); - HtmlUtils.getElementByIdOrFail(cowebsiteFullScreenButtonId).addEventListener('click', () => { + + const buttonFullScreenFrame = HtmlUtils.getElementByIdOrFail(cowebsiteFullScreenButtonId); + buttonFullScreenFrame.addEventListener('click', () => { + buttonFullScreenFrame.blur(); this.fullscreen(); }); } - private initResizeListeners() { - const movecallback = (event:MouseEvent) => { - this.verticalMode ? this.height += event.movementY / this.getDevicePixelRatio() : this.width -= event.movementX / this.getDevicePixelRatio(); + private initResizeListeners(touchMode:boolean) { + const movecallback = (event:MouseEvent|TouchEvent) => { + let x, y; + if (event.type === 'mousemove') { + x = (event as MouseEvent).movementX / this.getDevicePixelRatio(); + y = (event as MouseEvent).movementY / this.getDevicePixelRatio(); + } else { + const touchEvent = (event as TouchEvent).touches[0]; + const last = {x: touchEvent.pageX, y: touchEvent.pageY}; + const previous = this.previousTouchMoveCoordinates as TouchMoveCoordinates; + this.previousTouchMoveCoordinates = last; + x = last.x - previous.x; + y = last.y - previous.y; + } + + + this.verticalMode ? this.height += y : this.width -= x; this.fire(); } - this.cowebsiteAsideDom.addEventListener('mousedown', (event) => { + this.cowebsiteAsideDom.addEventListener( touchMode ? 'touchstart' : 'mousedown', (event) => { this.resizing = true; this.getIframeDom().style.display = 'none'; + if (touchMode) { + const touchEvent = (event as TouchEvent).touches[0]; + this.previousTouchMoveCoordinates = {x: touchEvent.pageX, y: touchEvent.pageY}; + } - document.addEventListener('mousemove', movecallback); + document.addEventListener(touchMode ? 'touchmove' : 'mousemove', movecallback); }); - document.addEventListener('mouseup', (event) => { + document.addEventListener(touchMode ? 'touchend' : 'mouseup', (event) => { if (!this.resizing) return; - document.removeEventListener('mousemove', movecallback); + if (touchMode) { + this.previousTouchMoveCoordinates = null; + } + document.removeEventListener(touchMode ? 'touchmove' : 'mousemove', movecallback); this.getIframeDom().style.display = 'block'; this.resizing = false; }); @@ -152,7 +188,10 @@ class CoWebsiteManager { setTimeout(() => { this.fire(); }, animationTime) - }).catch(() => this.closeCoWebsite()); + }).catch((err) => { + console.error('Error loadCoWebsite => ', err); + this.closeCoWebsite() + }); } /** @@ -166,7 +205,10 @@ class CoWebsiteManager { setTimeout(() => { this.fire(); }, animationTime); - }).catch(() => this.closeCoWebsite()); + }).catch((err) => { + console.error('Error insertCoWebsite => ', err); + this.closeCoWebsite(); + }); } public closeCoWebsite(): Promise { diff --git a/front/src/WebRtc/DeviceUtils.ts b/front/src/WebRtc/DeviceUtils.ts new file mode 100644 index 00000000..91a3dc31 --- /dev/null +++ b/front/src/WebRtc/DeviceUtils.ts @@ -0,0 +1,12 @@ +export function isIOS(): boolean { + return [ + 'iPad Simulator', + 'iPhone Simulator', + 'iPod Simulator', + 'iPad', + 'iPhone', + 'iPod' + ].includes(navigator.platform) + // iPad on iOS 13 detection + || (navigator.userAgent.includes("Mac") && "ontouchend" in document) +} diff --git a/front/src/WebRtc/DiscussionManager.ts b/front/src/WebRtc/DiscussionManager.ts index 9bbfac5a..504ee91b 100644 --- a/front/src/WebRtc/DiscussionManager.ts +++ b/front/src/WebRtc/DiscussionManager.ts @@ -171,6 +171,8 @@ export class DiscussionManager { const date = new Date(); if(isMe){ name = 'Me'; + } else { + name = HtmlUtils.escapeHtml(name); } pMessage.innerHTML = `${name} diff --git a/front/src/WebRtc/HtmlUtils.ts b/front/src/WebRtc/HtmlUtils.ts index 86f38216..942e553f 100644 --- a/front/src/WebRtc/HtmlUtils.ts +++ b/front/src/WebRtc/HtmlUtils.ts @@ -35,7 +35,12 @@ export class HtmlUtils { const urlRegex = /(https?:\/\/[^\s]+)/g; text = HtmlUtils.escapeHtml(text); return text.replace(urlRegex, (url: string) => { - return '' + url + ''; + const link = document.createElement('a'); + link.href = url; + link.target = "_blank"; + const text = document.createTextNode(url); + link.appendChild(text); + return link.outerHTML; }); } diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 7b8416d5..0d8a05f2 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -8,32 +8,11 @@ import { SoundMeter } from "../Phaser/Components/SoundMeter"; import { DISABLE_NOTIFICATIONS } from "../Enum/EnvironmentVariable"; import { gameOverlayVisibilityStore, localStreamStore, - mediaStreamConstraintsStore, - requestedCameraState, - requestedMicrophoneState } from "../Stores/MediaStore"; import { - requestedScreenSharingState, - screenSharingAvailableStore, screenSharingLocalStreamStore } from "../Stores/ScreenSharingStore"; - -declare const navigator: any; // eslint-disable-line @typescript-eslint/no-explicit-any - -const videoConstraint: boolean | MediaTrackConstraints = { - width: { min: 640, ideal: 1280, max: 1920 }, - height: { min: 400, ideal: 720 }, - frameRate: { ideal: localUserStore.getVideoQualityValue() }, - facingMode: "user", - resizeMode: 'crop-and-scale', - aspectRatio: 1.777777778 -}; -const audioConstraint: boolean | MediaTrackConstraints = { - //TODO: make these values configurable in the game settings menu and store them in localstorage - autoGainControl: false, - echoCancellation: true, - noiseSuppression: true -}; +import {helpCameraSettingsVisibleStore} from "../Stores/HelpCameraSettingsStore"; export type UpdatedLocalStreamCallback = (media: MediaStream | null) => void; export type StartScreenSharingCallback = (media: MediaStream) => void; @@ -42,31 +21,17 @@ export type ReportCallback = (message: string) => void; export type ShowReportCallBack = (userId: string, userName: string | undefined) => void; export type HelpCameraSettingsCallBack = () => void; +import {cowebsiteCloseButtonId} from "./CoWebsiteManager"; + export class MediaManager { - localStream: MediaStream | null = null; - localScreenCapture: MediaStream | null = null; private remoteVideo: Map = new Map(); - myCamVideo: HTMLVideoElement; - cinemaClose: HTMLImageElement; - cinema: HTMLImageElement; - monitorClose: HTMLImageElement; - monitor: HTMLImageElement; - microphoneClose: HTMLImageElement; - microphone: HTMLImageElement; - webrtcInAudio: HTMLAudioElement; //FIX ME SOUNDMETER: check stalability of sound meter calculation //mySoundMeterElement: HTMLDivElement; - private webrtcOutAudio: HTMLAudioElement; - updatedLocalStreamCallBacks: Set = new Set(); startScreenSharingCallBacks: Set = new Set(); stopScreenSharingCallBacks: Set = new Set(); showReportModalCallBacks: Set = new Set(); - helpCameraSettingsCallBacks: Set = new Set(); - private microphoneBtn: HTMLDivElement; - private cinemaBtn: HTMLDivElement; - private monitorBtn: HTMLDivElement; private focused: boolean = true; @@ -82,54 +47,6 @@ export class MediaManager { constructor() { - this.myCamVideo = HtmlUtils.getElementByIdOrFail('myCamVideo'); - this.webrtcInAudio = HtmlUtils.getElementByIdOrFail('audio-webrtc-in'); - this.webrtcOutAudio = HtmlUtils.getElementByIdOrFail('audio-webrtc-out'); - this.webrtcInAudio.volume = 0.2; - this.webrtcOutAudio.volume = 0.2; - - this.microphoneBtn = HtmlUtils.getElementByIdOrFail('btn-micro'); - this.microphoneClose = HtmlUtils.getElementByIdOrFail('microphone-close'); - this.microphoneClose.style.display = "none"; - this.microphoneClose.addEventListener('click', (e: MouseEvent) => { - e.preventDefault(); - requestedMicrophoneState.enableMicrophone(); - }); - this.microphone = HtmlUtils.getElementByIdOrFail('microphone'); - this.microphone.addEventListener('click', (e: MouseEvent) => { - e.preventDefault(); - requestedMicrophoneState.disableMicrophone(); - }); - - this.cinemaBtn = HtmlUtils.getElementByIdOrFail('btn-video'); - this.cinemaClose = HtmlUtils.getElementByIdOrFail('cinema-close'); - this.cinemaClose.style.display = "none"; - this.cinemaClose.addEventListener('click', (e: MouseEvent) => { - e.preventDefault(); - requestedCameraState.enableWebcam(); - }); - this.cinema = HtmlUtils.getElementByIdOrFail('cinema'); - this.cinema.addEventListener('click', (e: MouseEvent) => { - e.preventDefault(); - requestedCameraState.disableWebcam(); - }); - - this.monitorBtn = HtmlUtils.getElementByIdOrFail('btn-monitor'); - this.monitorClose = HtmlUtils.getElementByIdOrFail('monitor-close'); - this.monitorClose.style.display = "block"; - this.monitorClose.addEventListener('click', (e: MouseEvent) => { - e.preventDefault(); - //this.enableScreenSharing(); - requestedScreenSharingState.enableScreenSharing(); - }); - this.monitor = HtmlUtils.getElementByIdOrFail('monitor'); - this.monitor.style.display = "none"; - this.monitor.addEventListener('click', (e: MouseEvent) => { - e.preventDefault(); - //this.disableScreenSharing(); - requestedScreenSharingState.disableScreenSharing(); - }); - this.pingCameraStatus(); //FIX ME SOUNDMETER: check stability of sound meter calculation @@ -144,87 +61,43 @@ export class MediaManager { localStreamStore.subscribe((result) => { if (result.type === 'error') { console.error(result.error); - layoutManager.addInformation('warning', 'Camera access denied. Click here and check navigators permissions.', () => { - this.showHelpCameraSettingsCallBack(); + layoutManager.addInformation('warning', 'Camera access denied. Click here and check your browser permissions.', () => { + helpCameraSettingsVisibleStore.set(true); }, this.userInputManager); return; } - - if (result.constraints.video !== false) { - HtmlUtils.getElementByIdOrFail('div-myCamVideo').classList.remove('hide'); - } else { - HtmlUtils.getElementByIdOrFail('div-myCamVideo').classList.add('hide'); - }/* - if (result.constraints.audio !== false) { - this.enableMicrophoneStyle(); - } else { - this.disableMicrophoneStyle(); - }*/ - - this.localStream = result.stream; - this.myCamVideo.srcObject = this.localStream; - - // TODO: migrate all listeners to the store directly. - this.triggerUpdatedLocalStreamCallbacks(result.stream); }); - requestedCameraState.subscribe((enabled) => { - if (enabled) { - this.enableCameraStyle(); - } else { - this.disableCameraStyle(); - } - }); - requestedMicrophoneState.subscribe((enabled) => { - if (enabled) { - this.enableMicrophoneStyle(); - } else { - this.disableMicrophoneStyle(); - } - }); - //let screenSharingStream : MediaStream|null; + let isScreenSharing = false; screenSharingLocalStreamStore.subscribe((result) => { if (result.type === 'error') { console.error(result.error); - layoutManager.addInformation('warning', 'Screen sharing denied. Click here and check navigators permissions.', () => { - this.showHelpCameraSettingsCallBack(); + layoutManager.addInformation('warning', 'Screen sharing denied. Click here and check your browser permissions.', () => { + helpCameraSettingsVisibleStore.set(true); }, this.userInputManager); return; } if (result.stream !== null) { - this.enableScreenSharingStyle(); - mediaManager.localScreenCapture = result.stream; - - // TODO: migrate this out of MediaManager - this.triggerStartedScreenSharingCallbacks(result.stream); - - //screenSharingStream = result.stream; - + isScreenSharing = true; this.addScreenSharingActiveVideo('me', DivImportance.Normal); HtmlUtils.getElementByIdOrFail('screen-sharing-me').srcObject = result.stream; } else { - this.disableScreenSharingStyle(); - this.removeActiveScreenSharingVideo('me'); - - // FIXME: we need the old stream that is being stopped! - if (this.localScreenCapture) { - this.triggerStoppedScreenSharingCallbacks(this.localScreenCapture); - this.localScreenCapture = null; + if (isScreenSharing) { + isScreenSharing = false; + this.removeActiveScreenSharingVideo('me'); } - - //screenSharingStream = null; } }); - screenSharingAvailableStore.subscribe((available) => { + /*screenSharingAvailableStore.subscribe((available) => { if (available) { document.querySelector('.btn-monitor')?.classList.remove('hide'); } else { document.querySelector('.btn-monitor')?.classList.add('hide'); } - }); + });*/ } public updateScene(){ @@ -232,49 +105,18 @@ export class MediaManager { //this.updateSoudMeter(); } - public onUpdateLocalStream(callback: UpdatedLocalStreamCallback): void { - this.updatedLocalStreamCallBacks.add(callback); - } - - public onStartScreenSharing(callback: StartScreenSharingCallback): void { - this.startScreenSharingCallBacks.add(callback); - } - - public onStopScreenSharing(callback: StopScreenSharingCallback): void { - this.stopScreenSharingCallBacks.add(callback); - } - - removeUpdateLocalStreamEventListener(callback: UpdatedLocalStreamCallback): void { - this.updatedLocalStreamCallBacks.delete(callback); - } - - private triggerUpdatedLocalStreamCallbacks(stream: MediaStream | null): void { - for (const callback of this.updatedLocalStreamCallBacks) { - callback(stream); - } - } - - private triggerStartedScreenSharingCallbacks(stream: MediaStream): void { - for (const callback of this.startScreenSharingCallBacks) { - callback(stream); - } - } - - private triggerStoppedScreenSharingCallbacks(stream: MediaStream): void { - for (const callback of this.stopScreenSharingCallBacks) { - callback(stream); - } - } - public showGameOverlay(): void { const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay'); gameOverlay.classList.add('active'); - const buttonCloseFrame = HtmlUtils.getElementByIdOrFail('cowebsite-close'); + const buttonCloseFrame = HtmlUtils.getElementByIdOrFail(cowebsiteCloseButtonId); const functionTrigger = () => { this.triggerCloseJitsiFrameButton(); } - buttonCloseFrame.removeEventListener('click', functionTrigger); + buttonCloseFrame.removeEventListener('click', () => { + buttonCloseFrame.blur(); + functionTrigger(); + }); gameOverlayVisibilityStore.showGameOverlay(); } @@ -283,53 +125,22 @@ export class MediaManager { const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay'); gameOverlay.classList.remove('active'); - const buttonCloseFrame = HtmlUtils.getElementByIdOrFail('cowebsite-close'); + const buttonCloseFrame = HtmlUtils.getElementByIdOrFail(cowebsiteCloseButtonId); const functionTrigger = () => { this.triggerCloseJitsiFrameButton(); } - buttonCloseFrame.addEventListener('click', functionTrigger); + buttonCloseFrame.addEventListener('click', () => { + buttonCloseFrame.blur(); + functionTrigger(); + }); gameOverlayVisibilityStore.hideGameOverlay(); } - private enableCameraStyle() { - this.cinemaClose.style.display = "none"; - this.cinemaBtn.classList.remove("disabled"); - this.cinema.style.display = "block"; - } - private disableCameraStyle() { - this.cinemaClose.style.display = "block"; - this.cinema.style.display = "none"; - this.cinemaBtn.classList.add("disabled"); - } - - private enableMicrophoneStyle() { - this.microphoneClose.style.display = "none"; - this.microphone.style.display = "block"; - this.microphoneBtn.classList.remove("disabled"); - } - - private disableMicrophoneStyle() { - this.microphoneClose.style.display = "block"; - this.microphone.style.display = "none"; - this.microphoneBtn.classList.add("disabled"); - } - - private enableScreenSharingStyle(){ - this.monitorClose.style.display = "none"; - this.monitor.style.display = "block"; - this.monitorBtn.classList.add("enabled"); - } - - private disableScreenSharingStyle(){ - this.monitorClose.style.display = "block"; - this.monitor.style.display = "none"; - this.monitorBtn.classList.remove("enabled"); - } addActiveVideo(user: UserSimplePeerInterface, userName: string = "") { - this.webrtcInAudio.play(); + const userId = '' + user.userId userName = userName.toUpperCase(); @@ -345,7 +156,7 @@ export class MediaManager { Report/Block - +
@@ -382,7 +193,7 @@ export class MediaManager { userId = this.getScreenSharingId(userId); const html = `
- +
`; @@ -477,10 +288,6 @@ export class MediaManager { this.removeActiveVideo(this.getScreenSharingId(userId)) } - playWebrtcOutSound(): void { - this.webrtcOutAudio.play(); - } - isConnecting(userId: string): void { const connectingSpinnerDiv = this.getSpinner(userId); if (connectingSpinnerDiv === null) { @@ -595,16 +402,6 @@ export class MediaManager { this.showReportModalCallBacks.add(callback); } - public setHelpCameraSettingsCallBack(callback: HelpCameraSettingsCallBack) { - this.helpCameraSettingsCallBacks.add(callback); - } - - private showHelpCameraSettingsCallBack() { - for (const callBack of this.helpCameraSettingsCallBacks) { - callBack(); - } - } - //FIX ME SOUNDMETER: check stalability of sound meter calculation /*updateSoudMeter(){ try{ @@ -650,12 +447,32 @@ export class MediaManager { public getNotification(){ //Get notification if (!DISABLE_NOTIFICATIONS && window.Notification && Notification.permission !== "granted") { - Notification.requestPermission().catch((err) => { - console.error(`Notification permission error`, err); - }); + if (this.checkNotificationPromise()) { + Notification.requestPermission().catch((err) => { + console.error(`Notification permission error`, err); + }); + } else { + Notification.requestPermission(); + } } } + /** + * Return true if the browser supports the modern version of the Notification API (which is Promise based) or false + * if we are on Safari... + * + * See https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API/Using_the_Notifications_API + */ + private checkNotificationPromise(): boolean { + try { + Notification.requestPermission().then(); + } catch(e) { + return false; + } + + return true; + } + public createNotification(userName: string){ if(this.focused){ return; diff --git a/front/src/WebRtc/ScreenSharingPeer.ts b/front/src/WebRtc/ScreenSharingPeer.ts index f1786ef3..d797f59b 100644 --- a/front/src/WebRtc/ScreenSharingPeer.ts +++ b/front/src/WebRtc/ScreenSharingPeer.ts @@ -19,7 +19,7 @@ export class ScreenSharingPeer extends Peer { public _connected: boolean = false; private userId: number; - constructor(user: UserSimplePeerInterface, initiator: boolean, private connection: RoomConnection) { + constructor(user: UserSimplePeerInterface, initiator: boolean, private connection: RoomConnection, stream: MediaStream | null) { super({ initiator: initiator ? initiator : false, //reconnectTimer: 10000, @@ -60,6 +60,7 @@ export class ScreenSharingPeer extends Peer { const message = JSON.parse(chunk.toString('utf8')); if (message.streamEnded !== true) { console.error('Unexpected message on screen sharing peer connection'); + return; } mediaManager.removeActiveScreenSharingVideo("" + this.userId); }); @@ -81,7 +82,9 @@ export class ScreenSharingPeer extends Peer { this._onFinish(); }); - this.pushScreenSharingToRemoteUser(); + if (stream) { + this.addStream(stream); + } } private sendWebrtcScreenSharingSignal(data: unknown) { @@ -141,16 +144,6 @@ export class ScreenSharingPeer extends Peer { } } - private pushScreenSharingToRemoteUser() { - const localScreenCapture: MediaStream | null = mediaManager.localScreenCapture; - if(!localScreenCapture){ - return; - } - - this.addStream(localScreenCapture); - return; - } - public stopPushingScreenSharingToRemoteUser(stream: MediaStream) { this.removeStream(stream); this.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, streamEnded: true}))); diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index 4633374d..2a502bab 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -15,7 +15,10 @@ import {connectionManager} from "../Connexion/ConnectionManager"; import {GameConnexionTypes} from "../Url/UrlManager"; import {blackListManager} from "./BlackListManager"; import {get} from "svelte/store"; -import {localStreamStore, obtainedMediaConstraintStore} from "../Stores/MediaStore"; +import {localStreamStore, LocalStreamStoreValue, obtainedMediaConstraintStore} from "../Stores/MediaStore"; +import {screenSharingLocalStreamStore} from "../Stores/ScreenSharingStore"; +import {DivImportance, layoutManager} from "./LayoutManager"; +import {HtmlUtils} from "./HtmlUtils"; export interface UserSimplePeerInterface{ userId: number; @@ -39,9 +42,9 @@ export class SimplePeer { private PeerScreenSharingConnectionArray: Map = new Map(); private PeerConnectionArray: Map = new Map(); - private readonly sendLocalVideoStreamCallback: UpdatedLocalStreamCallback; private readonly sendLocalScreenSharingStreamCallback: StartScreenSharingCallback; private readonly stopLocalScreenSharingStreamCallback: StopScreenSharingCallback; + private readonly unsubscribers: (() => void)[] = []; private readonly peerConnectionListeners: Array = new Array(); private readonly userId: number; private lastWebrtcUserName: string|undefined; @@ -49,13 +52,32 @@ export class SimplePeer { constructor(private Connection: RoomConnection, private enableReporting: boolean, private myName: string) { // We need to go through this weird bound function pointer in order to be able to "free" this reference later. - this.sendLocalVideoStreamCallback = this.sendLocalVideoStream.bind(this); this.sendLocalScreenSharingStreamCallback = this.sendLocalScreenSharingStream.bind(this); this.stopLocalScreenSharingStreamCallback = this.stopLocalScreenSharingStream.bind(this); - mediaManager.onUpdateLocalStream(this.sendLocalVideoStreamCallback); - mediaManager.onStartScreenSharing(this.sendLocalScreenSharingStreamCallback); - mediaManager.onStopScreenSharing(this.stopLocalScreenSharingStreamCallback); + this.unsubscribers.push(localStreamStore.subscribe((streamResult) => { + this.sendLocalVideoStream(streamResult); + })); + + let localScreenCapture: MediaStream|null = null; + + this.unsubscribers.push(screenSharingLocalStreamStore.subscribe((streamResult) => { + if (streamResult.type === 'error') { + // Let's ignore screen sharing errors, we will deal with those in a different way. + return; + } + + if (streamResult.stream !== null) { + localScreenCapture = streamResult.stream; + this.sendLocalScreenSharingStream(localScreenCapture); + } else { + if (localScreenCapture) { + this.stopLocalScreenSharingStream(localScreenCapture); + localScreenCapture = null; + } + } + })); + this.userId = Connection.getUserId(); this.initialise(); } @@ -106,13 +128,19 @@ export class SimplePeer { if(!user.initiator){ return; } - this.createPeerConnection(user); + const streamResult = get(localStreamStore); + let stream : MediaStream | null = null; + if (streamResult.type === 'success' && streamResult.stream) { + stream = streamResult.stream; + } + + this.createPeerConnection(user, stream); } /** * create peer connection to bind users */ - private createPeerConnection(user : UserSimplePeerInterface) : VideoPeer | null { + private createPeerConnection(user : UserSimplePeerInterface, localStream: MediaStream | null) : VideoPeer | null { const peerConnection = this.PeerConnectionArray.get(user.userId) if (peerConnection) { if (peerConnection.destroyed) { @@ -122,11 +150,11 @@ export class SimplePeer { if (!peerConnexionDeleted) { throw 'Error to delete peer connection'; } - this.createPeerConnection(user); + //return this.createPeerConnection(user, localStream); } else { peerConnection.toClose = false; + return null; } - return null; } let name = user.name; @@ -144,7 +172,7 @@ export class SimplePeer { this.lastWebrtcUserName = user.webRtcUser; this.lastWebrtcPassword = user.webRtcPassword; - const peer = new VideoPeer(user, user.initiator ? user.initiator : false, this.Connection); + const peer = new VideoPeer(user, user.initiator ? user.initiator : false, this.Connection, localStream); //permit to send message mediaManager.addSendMessageCallback(user.userId,(message: string) => { @@ -155,8 +183,9 @@ export class SimplePeer { // When a connection is established to a video stream, and if a screen sharing is taking place, // the user sharing screen should also initiate a connection to the remote user! peer.on('connect', () => { - if (mediaManager.localScreenCapture) { - this.sendLocalScreenSharingStreamToUser(user.userId); + const streamResult = get(screenSharingLocalStreamStore); + if (streamResult.type === 'success' && streamResult.stream !== null) { + this.sendLocalScreenSharingStreamToUser(user.userId, streamResult.stream); } }); @@ -175,7 +204,7 @@ export class SimplePeer { /** * create peer connection to bind users */ - private createPeerScreenSharingConnection(user : UserSimplePeerInterface) : ScreenSharingPeer | null{ + private createPeerScreenSharingConnection(user : UserSimplePeerInterface, stream: MediaStream | null) : ScreenSharingPeer | null{ const peerConnection = this.PeerScreenSharingConnectionArray.get(user.userId); if(peerConnection){ if(peerConnection.destroyed){ @@ -185,7 +214,7 @@ export class SimplePeer { if(!peerConnexionDeleted){ throw 'Error to delete peer connection'; } - this.createPeerConnection(user); + this.createPeerConnection(user, stream); }else { peerConnection.toClose = false; } @@ -204,7 +233,7 @@ export class SimplePeer { user.webRtcPassword = this.lastWebrtcPassword; } - const peer = new ScreenSharingPeer(user, user.initiator ? user.initiator : false, this.Connection); + const peer = new ScreenSharingPeer(user, user.initiator ? user.initiator : false, this.Connection, stream); this.PeerScreenSharingConnectionArray.set(user.userId, peer); for (const peerConnectionListener of this.peerConnectionListeners) { @@ -217,7 +246,6 @@ export class SimplePeer { * This is triggered twice. Once by the server, and once by a remote client disconnecting */ private closeConnection(userId : number) { - mediaManager.playWebrtcOutSound(); try { const peer = this.PeerConnectionArray.get(userId); if (peer === undefined) { @@ -234,7 +262,7 @@ export class SimplePeer { const userIndex = this.Users.findIndex(user => user.userId === userId); if(userIndex < 0){ - throw 'Couln\'t delete user'; + throw 'Couldn\'t delete user'; } else { this.Users.splice(userIndex, 1); } @@ -294,7 +322,9 @@ export class SimplePeer { * Unregisters any held event handler. */ public unregister() { - mediaManager.removeUpdateLocalStreamEventListener(this.sendLocalVideoStreamCallback); + for (const unsubscriber of this.unsubscribers) { + unsubscriber(); + } } // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -302,7 +332,13 @@ export class SimplePeer { try { //if offer type, create peer connection if(data.signal.type === "offer"){ - this.createPeerConnection(data); + const streamResult = get(localStreamStore); + let stream : MediaStream | null = null; + if (streamResult.type === 'success' && streamResult.stream) { + stream = streamResult.stream; + } + + this.createPeerConnection(data, stream); } const peer = this.PeerConnectionArray.get(data.userId); if (peer !== undefined) { @@ -318,18 +354,26 @@ export class SimplePeer { private receiveWebrtcScreenSharingSignal(data: WebRtcSignalReceivedMessageInterface) { if (blackListManager.isBlackListed(data.userId)) return; console.log("receiveWebrtcScreenSharingSignal", data); + const streamResult = get(screenSharingLocalStreamStore); + let stream : MediaStream | null = null; + if (streamResult.type === 'success' && streamResult.stream !== null) { + stream = streamResult.stream; + } + try { //if offer type, create peer connection if(data.signal.type === "offer"){ - this.createPeerScreenSharingConnection(data); + this.createPeerScreenSharingConnection(data, stream); } const peer = this.PeerScreenSharingConnectionArray.get(data.userId); if (peer !== undefined) { peer.signal(data.signal); } else { console.error('Could not find peer whose ID is "'+data.userId+'" in receiveWebrtcScreenSharingSignal'); - console.info('tentative to create new peer connexion'); - this.sendLocalScreenSharingStreamToUser(data.userId); + console.info('Attempt to create new peer connexion'); + if (stream) { + this.sendLocalScreenSharingStreamToUser(data.userId, stream); + } } } catch (e) { console.error(`receiveWebrtcSignal => ${data.userId}`, e); @@ -339,21 +383,19 @@ export class SimplePeer { } } - private pushVideoToRemoteUser(userId : number) { + private pushVideoToRemoteUser(userId: number, streamResult: LocalStreamStoreValue) { try { const PeerConnection = this.PeerConnectionArray.get(userId); if (!PeerConnection) { throw new Error('While adding media, cannot find user with ID ' + userId); } - const result = get(localStreamStore); + PeerConnection.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, ...streamResult.constraints}))); - PeerConnection.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, ...result.constraints}))); - - if (result.type === 'error') { + if (streamResult.type === 'error') { return; } - const localStream: MediaStream | null = result.stream; + const localStream: MediaStream | null = streamResult.stream; if(!localStream){ return; @@ -370,15 +412,11 @@ export class SimplePeer { } } - private pushScreenSharingToRemoteUser(userId : number) { + private pushScreenSharingToRemoteUser(userId: number, localScreenCapture: MediaStream) { const PeerConnection = this.PeerScreenSharingConnectionArray.get(userId); if (!PeerConnection) { throw new Error('While pushing screen sharing, cannot find user with ID ' + userId); } - const localScreenCapture: MediaStream | null = mediaManager.localScreenCapture; - if(!localScreenCapture){ - return; - } for (const track of localScreenCapture.getTracks()) { PeerConnection.addTrack(track, localScreenCapture); @@ -386,23 +424,18 @@ export class SimplePeer { return; } - public sendLocalVideoStream(){ + public sendLocalVideoStream(streamResult: LocalStreamStoreValue){ for (const user of this.Users) { - this.pushVideoToRemoteUser(user.userId); + this.pushVideoToRemoteUser(user.userId, streamResult); } } /** * Triggered locally when clicking on the screen sharing button */ - public sendLocalScreenSharingStream() { - if (!mediaManager.localScreenCapture) { - console.error('Could not find localScreenCapture to share') - return; - } - + public sendLocalScreenSharingStream(localScreenCapture: MediaStream) { for (const user of this.Users) { - this.sendLocalScreenSharingStreamToUser(user.userId); + this.sendLocalScreenSharingStreamToUser(user.userId, localScreenCapture); } } @@ -415,11 +448,11 @@ export class SimplePeer { } } - private sendLocalScreenSharingStreamToUser(userId: number): void { + private sendLocalScreenSharingStreamToUser(userId: number, localScreenCapture: MediaStream): void { if (blackListManager.isBlackListed(userId)) return; // If a connection already exists with user (because it is already sharing a screen with us... let's use this connection) if (this.PeerScreenSharingConnectionArray.has(userId)) { - this.pushScreenSharingToRemoteUser(userId); + this.pushScreenSharingToRemoteUser(userId, localScreenCapture); return; } @@ -427,7 +460,7 @@ export class SimplePeer { userId, initiator: true }; - const PeerConnectionScreenSharing = this.createPeerScreenSharingConnection(screenSharingUser); + const PeerConnectionScreenSharing = this.createPeerScreenSharingConnection(screenSharingUser, localScreenCapture); if (!PeerConnectionScreenSharing) { return; } diff --git a/front/src/WebRtc/VideoPeer.ts b/front/src/WebRtc/VideoPeer.ts index 32e8e97f..5ca8952c 100644 --- a/front/src/WebRtc/VideoPeer.ts +++ b/front/src/WebRtc/VideoPeer.ts @@ -27,7 +27,7 @@ export class VideoPeer extends Peer { private onBlockSubscribe: Subscription; private onUnBlockSubscribe: Subscription; - constructor(public user: UserSimplePeerInterface, initiator: boolean, private connection: RoomConnection) { + constructor(public user: UserSimplePeerInterface, initiator: boolean, private connection: RoomConnection, localStream: MediaStream | null) { super({ initiator: initiator ? initiator : false, //reconnectTimer: 10000, @@ -107,7 +107,7 @@ export class VideoPeer extends Peer { this._onFinish(); }); - this.pushVideoToRemoteUser(); + this.pushVideoToRemoteUser(localStream); this.onBlockSubscribe = blackListManager.onBlockStream.subscribe((userId) => { if (userId === this.userId) { this.toggleRemoteStream(false); @@ -190,9 +190,8 @@ export class VideoPeer extends Peer { } } - private pushVideoToRemoteUser() { + private pushVideoToRemoteUser(localStream: MediaStream | null) { try { - const localStream: MediaStream | null = mediaManager.localStream; this.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, ...get(obtainedMediaConstraintStore)}))); if(!localStream){ diff --git a/front/src/ambient.d.ts b/front/src/ambient.d.ts new file mode 100644 index 00000000..eee8d6c1 --- /dev/null +++ b/front/src/ambient.d.ts @@ -0,0 +1,38 @@ +/** + * These declarations tell TypeScript that we allow import of images, e.g. + * ``` + + + ``` + */ +declare module "*.gif" { + const value: string; + export = value; +} + +declare module "*.jpg" { + const value: string; + export = value; +} + +declare module "*.jpeg" { + const value: string; + export = value; +} + +declare module "*.png" { + const value: string; + export = value; +} + +declare module "*.svg" { + const value: string; + export = value; +} + +declare module "*.webp" { + const value: string; + export = value; +} diff --git a/front/src/iframe_api.ts b/front/src/iframe_api.ts index b8b5ccdf..992f7328 100644 --- a/front/src/iframe_api.ts +++ b/front/src/iframe_api.ts @@ -1,452 +1,178 @@ -import type { ChatEvent } from "./Api/Events/ChatEvent"; -import { IframeEvent, IframeEventMap, isIframeResponseEventWrapper } from "./Api/Events/IframeEvent"; -import { isUserInputChatEvent, UserInputChatEvent } from "./Api/Events/UserInputChatEvent"; -import { Subject } from "rxjs"; -import { EnterLeaveEvent, isEnterLeaveEvent } from "./Api/Events/EnterLeaveEvent"; -import type { OpenPopupEvent } from "./Api/Events/OpenPopupEvent"; -import { isButtonClickedEvent } from "./Api/Events/ButtonClickedEvent"; -import type { ClosePopupEvent } from "./Api/Events/ClosePopupEvent"; -import type { OpenTabEvent } from "./Api/Events/OpenTabEvent"; -import type { GoToPageEvent } from "./Api/Events/GoToPageEvent"; -import type { OpenCoWebSiteEvent } from "./Api/Events/OpenCoWebSiteEvent"; -import type { LayerEvent } from "./Api/Events/LayerEvent"; -import type { SetPropertyEvent } from "./Api/Events/setPropertyEvent"; -import { GameStateEvent, isGameStateEvent } from './Api/Events/GameStateEvent'; -import { HasPlayerMovedEvent, HasPlayerMovedEventCallback, isHasPlayerMovedEvent } from './Api/Events/HasPlayerMovedEvent'; -import { DataLayerEvent, isDataLayerEvent } from "./Api/Events/DataLayerEvent"; -import type { ITiledMap } from "./Phaser/Map/ITiledMap"; -import type { MenuItemRegisterEvent } from "./Api/Events/MenuItemRegisterEvent"; -import { isMenuItemClickedEvent } from "./Api/Events/MenuItemClickedEvent"; -import type { PlaySoundEvent } from "./Api/Events/PlaySoundEvent"; -import type {StopSoundEvent } from "./Api/Events/StopSoundEvent"; -import type { LoadSoundEvent } from "./Api/Events/LoadSoundEvent"; -import SoundConfig = Phaser.Types.Sound.SoundConfig; +import { registeredCallbacks } from "./Api/iframe/registeredCallbacks"; +import { + IframeResponseEvent, + IframeResponseEventMap, + isIframeResponseEventWrapper, + TypedMessageEvent +} from "./Api/Events/IframeEvent"; +import chat from "./Api/iframe/chat"; +import type { IframeCallback } from './Api/iframe/IframeApiContribution'; +import nav from "./Api/iframe/nav"; +import controls from "./Api/iframe/controls"; +import ui from "./Api/iframe/ui"; +import sound from "./Api/iframe/sound"; +import room from "./Api/iframe/room"; +import player from "./Api/iframe/player"; +import type { ButtonDescriptor } from "./Api/iframe/Ui/ButtonDescriptor"; +import type { Popup } from "./Api/iframe/Ui/Popup"; +import type { Sound } from "./Api/iframe/Sound/Sound"; +import {sendToWorkadventure} from "./Api/iframe/IframeApiContribution"; -interface WorkAdventureApi { - sendChatMessage(message: string, author: string): void; - onChatMessage(callback: (message: string) => void): void; - onEnterZone(name: string, callback: () => void): void; - onLeaveZone(name: string, callback: () => void): void; - openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup; - openTab(url: string): void; - goToPage(url: string): void; - openCoWebSite(url: string): void; - closeCoWebSite(): void; - disablePlayerControls() : void; - restorePlayerControls() : void; - displayBubble() : void; - removeBubble() : void; - showLayer(layer: string) : void; - hideLayer(layer: string) : void; - setProperty(layerName: string, propertyName: string, propertyValue: string | number | boolean | undefined): void; - disablePlayerControls(): void; - restorePlayerControls(): void; - displayBubble(): void; - removeBubble(): void; - loadSound(url : string): Sound; - registerMenuCommand(commandDescriptor: string, callback: (commandDescriptor: string) => void): void; - getCurrentUser(): Promise; - getCurrentRoom(): Promise; - changeTile(tiles: TileDescriptor[]): void; - //loadTileset(name: string, imgUrl : string, tilewidth : number, tileheight : number, margin : number, spacing : number): void; +const wa = { + ui, + nav, + controls, + chat, + sound, + room, + player, - onPlayerMove(callback: (playerMovedEvent: HasPlayerMovedEvent) => void): void -} + // All methods below are deprecated and should not be used anymore. + // They are kept here for backward compatibility. -interface User { - id: string | undefined - nickName: string | null - tags: string[] -} + /** + * @deprecated Use WA.chat.sendChatMessage instead + */ + sendChatMessage(message: string, author: string): void { + console.warn('Method WA.sendChatMessage is deprecated. Please use WA.chat.sendChatMessage instead'); + chat.sendChatMessage(message, author); + }, -interface Room { - id: string - mapUrl: string - map: ITiledMap - startLayer: string | null -} + /** + * @deprecated Use WA.chat.disablePlayerControls instead + */ + disablePlayerControls(): void { + console.warn('Method WA.disablePlayerControls is deprecated. Please use WA.controls.disablePlayerControls instead'); + controls.disablePlayerControls(); + }, -interface TileDescriptor { - x: number - y: number - tile: number | string - layer: string -} + /** + * @deprecated Use WA.controls.restorePlayerControls instead + */ + restorePlayerControls(): void { + console.warn('Method WA.restorePlayerControls is deprecated. Please use WA.controls.restorePlayerControls instead'); + controls.restorePlayerControls(); + }, + + /** + * @deprecated Use WA.ui.displayBubble instead + */ + displayBubble(): void { + console.warn('Method WA.displayBubble is deprecated. Please use WA.ui.displayBubble instead'); + ui.displayBubble(); + }, + + /** + * @deprecated Use WA.ui.removeBubble instead + */ + removeBubble(): void { + console.warn('Method WA.removeBubble is deprecated. Please use WA.ui.removeBubble instead'); + ui.removeBubble(); + }, + + /** + * @deprecated Use WA.nav.openTab instead + */ + openTab(url: string): void { + console.warn('Method WA.openTab is deprecated. Please use WA.nav.openTab instead'); + nav.openTab(url); + }, + + /** + * @deprecated Use WA.sound.loadSound instead + */ + loadSound(url: string): Sound { + console.warn('Method WA.loadSound is deprecated. Please use WA.sound.loadSound instead'); + return sound.loadSound(url); + }, + + /** + * @deprecated Use WA.nav.goToPage instead + */ + goToPage(url: string): void { + console.warn('Method WA.goToPage is deprecated. Please use WA.nav.goToPage instead'); + nav.goToPage(url); + }, + + /** + * @deprecated Use WA.nav.goToRoom instead + */ + goToRoom(url: string): void { + console.warn('Method WA.goToRoom is deprecated. Please use WA.nav.goToRoom instead'); + nav.goToRoom(url); + }, + + /** + * @deprecated Use WA.nav.openCoWebSite instead + */ + openCoWebSite(url: string): void { + console.warn('Method WA.openCoWebSite is deprecated. Please use WA.nav.openCoWebSite instead'); + nav.openCoWebSite(url); + }, + + /** + * @deprecated Use WA.nav.closeCoWebSite instead + */ + closeCoWebSite(): void { + console.warn('Method WA.closeCoWebSite is deprecated. Please use WA.nav.closeCoWebSite instead'); + nav.closeCoWebSite(); + }, + + /** + * @deprecated Use WA.controls.restorePlayerControls instead + */ + openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup { + console.warn('Method WA.openPopup is deprecated. Please use WA.ui.openPopup instead'); + return ui.openPopup(targetObject, message, buttons); + }, + /** + * @deprecated Use WA.chat.onChatMessage instead + */ + onChatMessage(callback: (message: string) => void): void { + console.warn('Method WA.onChatMessage is deprecated. Please use WA.chat.onChatMessage instead'); + chat.onChatMessage(callback); + }, + /** + * @deprecated Use WA.room.onEnterZone instead + */ + onEnterZone(name: string, callback: () => void): void { + console.warn('Method WA.onEnterZone is deprecated. Please use WA.room.onEnterZone instead'); + room.onEnterZone(name, callback); + }, + /** + * @deprecated Use WA.room.onLeaveZone instead + */ + onLeaveZone(name: string, callback: () => void): void { + console.warn('Method WA.onLeaveZone is deprecated. Please use WA.room.onLeaveZone instead'); + room.onLeaveZone(name, callback); + }, +}; + +export type WorkAdventureApi = typeof wa; declare global { - // eslint-disable-next-line no-var - var WA: WorkAdventureApi -} - -type ChatMessageCallback = (message: string) => void; -type ButtonClickedCallback = (popup: Popup) => void; - -const userInputChatStream: Subject = new Subject(); -const enterStreams: Map> = new Map>(); -const leaveStreams: Map> = new Map>(); -const popups: Map = new Map(); -const popupCallbacks: Map> = new Map>(); -const menuCallbacks: Map void> = new Map() -let popupId = 0; -interface ButtonDescriptor { - /** - * The label of the button - */ - label: string, - /** - * The type of the button. Can be one of "normal", "primary", "success", "warning", "error", "disabled" - */ - className?: "normal" | "primary" | "success" | "warning" | "error" | "disabled", - /** - * Callback called if the button is pressed - */ - callback: ButtonClickedCallback, -} - -export class Popup { - constructor(private id: number) { - } - - /** - * Closes the popup - */ - public close(): void { - window.parent.postMessage({ - 'type': 'closePopup', - 'data': { - 'popupId': this.id, - } as ClosePopupEvent - }, '*'); + interface Window { + WA: WorkAdventureApi } + let WA: WorkAdventureApi } -export class Sound { - constructor(private url: string) { - window.parent.postMessage({ - "type" : 'loadSound', - "data": { - url: this.url, - } as LoadSoundEvent +window.WA = wa; - },'*'); - } - - public play(config : SoundConfig) { - window.parent.postMessage({ - "type" : 'playSound', - "data": { - url: this.url, - config - } as PlaySoundEvent - - },'*'); - return this.url; - } - public stop() { - window.parent.postMessage({ - "type" : 'stopSound', - "data": { - url: this.url, - } as StopSoundEvent - - },'*'); - return this.url; - } - -} - -function getGameState(): Promise { - if (immutableData) { - return Promise.resolve(immutableData); - } - else { - return new Promise((resolver, thrower) => { - gameStateResolver.push(resolver); - window.parent.postMessage({ - type: "getState" - }, "*") - }) - } -} - -function getDataLayer(): Promise { - return new Promise((resolver, thrower) => { - dataLayerResolver.push(resolver); - postToParent({ - type: "getDataLayer", - data: undefined - }) - }) -} - -const gameStateResolver: Array<(event: GameStateEvent) => void> = [] -const dataLayerResolver: Array<(event: DataLayerEvent) => void> = [] -let immutableData: GameStateEvent; - -//const callbackPlayerMoved: { [type: string]: HasPlayerMovedEventCallback | ((arg?: HasPlayerMovedEvent | never) => void) } = {} -const callbackPlayerMoved: Array<(event: HasPlayerMovedEvent) => void> = [] - -function postToParent(content: IframeEvent) { - window.parent.postMessage(content, "*") -} - -window.WA = { - - onPlayerMove(callback: HasPlayerMovedEventCallback): void { - callbackPlayerMoved.push(callback); - postToParent({ - type: "onPlayerMove", - data: undefined - }) - }, - -/* loadTileset(name: string, imgUrl : string, tilewidth : number, tileheight : number, margin : number, spacing : number): void { - postToParent({ - type: "tilsetEvent", - data: { - name: name, - imgUrl: imgUrl, - tilewidth: tilewidth, - tileheight: tileheight, - margin: margin, - spacing: spacing - } as TilesetEvent - }) - },*/ - - getCurrentUser(): Promise { - return getGameState().then((gameState) => { - return {id: gameState.uuid, nickName: gameState.nickname, tags: gameState.tags}; - }) - }, - - getCurrentRoom(): Promise { - return getGameState().then((gameState) => { - return getDataLayer().then((mapJson) => { - return {id: gameState.roomId, map: mapJson.data as ITiledMap, mapUrl: gameState.mapUrl, startLayer: gameState.startLayerName}; - }) - }) - }, - - changeTile(tiles: TileDescriptor[]) { - postToParent({ - type: 'changeTile', - data: tiles - }) - }, - - /** - * Send a message in the chat. - * Only the local user will receive this message. - */ - sendChatMessage(message: string, author: string) { - window.parent.postMessage({ - 'type': 'chat', - 'data': { - 'message': message, - 'author': author - } as ChatEvent - }, '*'); - }, - - showLayer(layer: string) : void { - window.parent.postMessage({ - 'type' : 'showLayer', - 'data' : { - 'name' : layer - } as LayerEvent - }, '*'); - }, - - hideLayer(layer: string) : void { - window.parent.postMessage({ - 'type' : 'hideLayer', - 'data' : { - 'name' : layer - } as LayerEvent - }, '*'); - }, - - setProperty(layerName: string, propertyName: string, propertyValue: string | number | boolean | undefined): void { - window.parent.postMessage({ - 'type' : 'setProperty', - 'data' : { - 'layerName' : layerName, - 'propertyName' : propertyName, - 'propertyValue' : propertyValue - } as SetPropertyEvent - }, '*'); - }, - - disablePlayerControls(): void { - window.parent.postMessage({ 'type': 'disablePlayerControls' }, '*'); - }, - - restorePlayerControls(): void { - window.parent.postMessage({ 'type': 'restorePlayerControls' }, '*'); - }, - - displayBubble(): void { - window.parent.postMessage({ 'type': 'displayBubble' }, '*'); - }, - - removeBubble(): void { - window.parent.postMessage({ 'type': 'removeBubble' }, '*'); - }, - - openTab(url: string): void { - window.parent.postMessage({ - "type": 'openTab', - "data": { - url - } as OpenTabEvent - }, '*'); - }, - - loadSound(url: string) : Sound { - return new Sound(url); - }, - - goToPage(url : string) : void{ - window.parent.postMessage({ - "type": 'goToPage', - "data": { - url - } as GoToPageEvent - }, '*'); - }, - - openCoWebSite(url: string): void { - window.parent.postMessage({ - "type": 'openCoWebSite', - "data": { - url - } as OpenCoWebSiteEvent - }, '*'); - }, - - closeCoWebSite(): void { - window.parent.postMessage({ - "type": 'closeCoWebSite' - }, '*'); - }, - - openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup { - popupId++; - const popup = new Popup(popupId); - const btnMap = new Map void>(); - popupCallbacks.set(popupId, btnMap); - let id = 0; - for (const button of buttons) { - const callback = button.callback; - if (callback) { - btnMap.set(id, () => { - callback(popup); - }); - } - id++; - } - - - window.parent.postMessage({ - 'type': 'openPopup', - 'data': { - popupId, - targetObject, - message, - buttons: buttons.map((button) => { - return { - label: button.label, - className: button.className - }; - }) - } as OpenPopupEvent - }, '*'); - - popups.set(popupId, popup) - return popup; - }, - - registerMenuCommand(commandDescriptor: string, callback: (commandDescriptor: string) => void) { - menuCallbacks.set(commandDescriptor, callback); - window.parent.postMessage({ - 'type': 'registerMenuCommand', - 'data': { - menutItem: commandDescriptor - } as MenuItemRegisterEvent - }, '*'); - }, - /** - * Listen to messages sent by the local user, in the chat. - */ - onChatMessage(callback: ChatMessageCallback): void { - userInputChatStream.subscribe((userInputChatEvent) => { - callback(userInputChatEvent.message); - }); - }, - onEnterZone(name: string, callback: () => void): void { - let subject = enterStreams.get(name); - if (subject === undefined) { - subject = new Subject(); - enterStreams.set(name, subject); - } - subject.subscribe(callback); - }, - onLeaveZone(name: string, callback: () => void): void { - let subject = leaveStreams.get(name); - if (subject === undefined) { - subject = new Subject(); - leaveStreams.set(name, subject); - } - subject.subscribe(callback); - }, -} - -window.addEventListener('message', message => { +window.addEventListener('message', (message: TypedMessageEvent>) => { if (message.source !== window.parent) { return; // Skip message in this event listener } - const payload = message.data; - console.debug(payload); if (isIframeResponseEventWrapper(payload)) { const payloadData = payload.data; - if (payload.type === 'userInputChat' && isUserInputChatEvent(payloadData)) { - userInputChatStream.next(payloadData); - } else if (payload.type === 'enterEvent' && isEnterLeaveEvent(payloadData)) { - enterStreams.get(payloadData.name)?.next(); - } else if (payload.type === 'leaveEvent' && isEnterLeaveEvent(payloadData)) { - leaveStreams.get(payloadData.name)?.next(); - } else if (payload.type === 'buttonClickedEvent' && isButtonClickedEvent(payloadData)) { - const callback = popupCallbacks.get(payloadData.popupId)?.get(payloadData.buttonId); - const popup = popups.get(payloadData.popupId); - if (popup === undefined) { - throw new Error('Could not find popup with ID "' + payloadData.popupId + '"'); - } - if (callback) { - callback(popup); - } - } else if (payload.type == "gameState" && isGameStateEvent(payloadData)) { - gameStateResolver.forEach(resolver => { - resolver(payloadData); - }) - immutableData = payloadData; - } else if (payload.type == "hasPlayerMoved" && isHasPlayerMovedEvent(payloadData)) { - callbackPlayerMoved.forEach(callback => { - callback(payloadData); - }) - } else if (payload.type == "dataLayer" && isDataLayerEvent(payloadData)) { - dataLayerResolver.forEach(resolver => { - resolver(payloadData); - }) - } else if (payload.type == "menuItemClicked" && isMenuItemClickedEvent(payloadData)) { - const callback = menuCallbacks.get(payloadData.menuItem); - if (callback) { - callback(payloadData.menuItem) - } + + const callback = registeredCallbacks[payload.type] as IframeCallback | undefined + if (callback?.typeChecker(payloadData)) { + callback?.callback(payloadData) } } diff --git a/front/src/index.ts b/front/src/index.ts index 2cdcaa19..59e748b4 100644 --- a/front/src/index.ts +++ b/front/src/index.ts @@ -9,11 +9,11 @@ import {SelectCharacterScene} from "./Phaser/Login/SelectCharacterScene"; import {SelectCompanionScene} from "./Phaser/Login/SelectCompanionScene"; import {EnableCameraScene} from "./Phaser/Login/EnableCameraScene"; import {CustomizeScene} from "./Phaser/Login/CustomizeScene"; -import {ResizableScene} from "./Phaser/Login/ResizableScene"; +import WebFontLoaderPlugin from 'phaser3-rex-plugins/plugins/webfontloader-plugin.js'; +import OutlinePipelinePlugin from 'phaser3-rex-plugins/plugins/outlinepipeline-plugin.js'; import {EntryScene} from "./Phaser/Login/EntryScene"; import {coWebsiteManager} from "./WebRtc/CoWebsiteManager"; import {MenuScene} from "./Phaser/Menu/MenuScene"; -import {HelpCameraSettingsScene} from "./Phaser/Menu/HelpCameraSettingsScene"; import {localUserStore} from "./Connexion/LocalUserStore"; import {ErrorScene} from "./Phaser/Reconnecting/ErrorScene"; import {iframeListener} from "./Api/IframeListener"; @@ -23,6 +23,8 @@ import {waScaleManager} from "./Phaser/Services/WaScaleManager"; import {Game} from "./Phaser/Game/Game"; import App from './Components/App.svelte'; import {HtmlUtils} from "./WebRtc/HtmlUtils"; +import WebGLRenderer = Phaser.Renderer.WebGL.WebGLRenderer; + const {width, height} = coWebsiteManager.getGameSize(); @@ -96,7 +98,7 @@ const config: GameConfig = { ErrorScene, CustomizeScene, MenuScene, - HelpCameraSettingsScene], + ], //resolution: window.devicePixelRatio / 2, fps: fps, dom: { @@ -107,6 +109,13 @@ const config: GameConfig = { roundPixels: true, antialias: false }, + plugins: { + global: [{ + key: 'rexWebFontLoader', + plugin: WebFontLoaderPlugin, + start: true + }] + }, physics: { default: "arcade", arcade: { @@ -117,11 +126,11 @@ const config: GameConfig = { powerPreference: "low-power", callbacks: { postBoot: game => { - // Commented out to try to fix MacOS bug - /*const renderer = game.renderer; + // Install rexOutlinePipeline only if the renderer is WebGL. + const renderer = game.renderer; if (renderer instanceof WebGLRenderer) { - renderer.pipelines.add(OutlinePipeline.KEY, new OutlinePipeline(game)); - }*/ + game.plugins.install('rexOutlinePipeline', OutlinePipelinePlugin, true); + } } } }; @@ -145,7 +154,9 @@ iframeListener.init(); const app = new App({ target: HtmlUtils.getElementByIdOrFail('svelte-overlay'), - props: { }, + props: { + game: game + }, }) export default app diff --git a/front/src/rex-plugins.d.ts b/front/src/rex-plugins.d.ts index d5457702..8c8a9fc1 100644 --- a/front/src/rex-plugins.d.ts +++ b/front/src/rex-plugins.d.ts @@ -1,4 +1,3 @@ - declare module 'phaser3-rex-plugins/plugins/virtualjoystick.js' { const content: any; // eslint-disable-line export default content; @@ -7,6 +6,21 @@ declare module 'phaser3-rex-plugins/plugins/gestures-plugin.js' { const content: any; // eslint-disable-line export default content; } +declare module 'phaser3-rex-plugins/plugins/webfontloader-plugin.js' { + const content: any; // eslint-disable-line + export default content; +} +declare module 'phaser3-rex-plugins/plugins/outlinepipeline-plugin.js' { + import GameObject = Phaser.GameObjects.GameObject; + + class OutlinePipelinePlugin { + add(gameObject: GameObject, config: object); + + remove(gameObject: GameObject, name?: string); + } + + export default OutlinePipelinePlugin; +} declare module 'phaser3-rex-plugins/plugins/gestures.js' { export const Pinch: any; // eslint-disable-line } diff --git a/front/style/cowebsite-mobile.scss b/front/style/cowebsite-mobile.scss index 85b41511..48a6689e 100644 --- a/front/style/cowebsite-mobile.scss +++ b/front/style/cowebsite-mobile.scss @@ -28,6 +28,7 @@ aside { height: 30px; + min-height: 30px; cursor: ns-resize; flex-direction: column; diff --git a/front/style/cowebsite.scss b/front/style/cowebsite.scss index 2ed1ef69..e508b58d 100644 --- a/front/style/cowebsite.scss +++ b/front/style/cowebsite.scss @@ -13,6 +13,8 @@ iframe { width: 100%; height: 100%; + max-width: 100vw; + max-height: 100vh; } } diff --git a/front/style/fonts.scss b/front/style/fonts.scss new file mode 100644 index 00000000..a49d3967 --- /dev/null +++ b/front/style/fonts.scss @@ -0,0 +1,9 @@ +@import "~@fontsource/press-start-2p/index.css"; + +*{ + font-family: PixelFont-7,monospace; +} + +.nes-btn { + font-family: "Press Start 2P"; +} diff --git a/front/style/index.scss b/front/style/index.scss index 67e85c5b..a6afa557 100644 --- a/front/style/index.scss +++ b/front/style/index.scss @@ -1,4 +1,6 @@ @import "cowebsite.scss"; @import "cowebsite-mobile.scss"; -@import "style.css"; -@import "mobile-style.scss"; \ No newline at end of file +@import "style"; +@import "mobile-style.scss"; +@import "fonts.scss"; +@import "svelte-style.scss"; diff --git a/front/style/mobile-style.scss b/front/style/mobile-style.scss index 21753ebd..1b37053a 100644 --- a/front/style/mobile-style.scss +++ b/front/style/mobile-style.scss @@ -1,9 +1,24 @@ +@media (hover: none) { + /** + * If we cannot hover over elements, let's display camera button in full. + */ + .btn-cam-action { + div { + transform: translateY(0px); + } + } +} + @media screen and (max-width: 700px), screen and (max-height: 700px){ - video#myCamVideo { + video.myCamVideo { width: 150px; } + .div-myCamVideo.hide { + right: -160px; + } + .sidebar { width: 20%; min-width: 200px; @@ -22,21 +37,6 @@ } } - .btn-cam-action { - min-width: 150px; - - &:hover{ - transform: translateY(20px); - } - div { - margin: 0 1%; - &:hover { - background-color: #666; - } - margin-bottom: 30px; - } - } - .main-section { position: absolute; width: 100%; diff --git a/front/style/style.css b/front/style/style.scss similarity index 86% rename from front/style/style.css rename to front/style/style.scss index a3bbfa1d..5c958309 100644 --- a/front/style/style.css +++ b/front/style/style.scss @@ -133,11 +133,12 @@ body .message-info.warning{ outline: none; } -.video-container#div-myCamVideo{ +.video-container.div-myCamVideo{ border: none; + background-color: transparent; } -#div-myCamVideo { +.div-myCamVideo { position: absolute; right: 15px; bottom: 30px; @@ -146,11 +147,13 @@ body .message-info.warning{ transition: right 350ms; } -#div-myCamVideo.hide { +.div-myCamVideo.hide { right: -20vw; } -video#myCamVideo{ +video.myCamVideo{ + background-color: #00000099; + max-height: 20vh; width: 15vw; -webkit-transform: scaleX(-1); transform: scaleX(-1); @@ -212,7 +215,9 @@ video#myCamVideo{ /*btn animation*/ .btn-cam-action div{ cursor: url('./images/cursor_pointer.png'), pointer; - /*position: absolute;*/ + display: flex; + align-items: center; + justify-content: center; border: solid 0px black; width: 44px; height: 44px; @@ -267,8 +272,6 @@ video#myCamVideo{ .btn-cam-action div img{ height: 22px; width: 30px; - top: calc(48px - 37px); - left: calc(48px - 41px); position: relative; cursor: url('./images/cursor_pointer.png'), pointer; } @@ -331,48 +334,15 @@ video#myCamVideo{ } } -.webrtcsetup{ - display: none; - position: absolute; - top: 140px; - left: 0; - right: 0; - margin-left: auto; - margin-right: auto; - height: 50%; - width: 50%; - border: white 6px solid; -} -.webrtcsetup .background-img { - position: relative; - display: block; - width: 40%; - height: 60%; - margin-left: auto; - margin-right: auto; - top: 50%; - transform: translateY(-50%); -} -#myCamVideoSetup { - width: 100%; - height: 100%; - -webkit-transform: scaleX(-1); - transform: scaleX(-1); -} -.webrtcsetup.active{ - display: block; -} - - /* New layout */ body { margin: 0; - height: 100vh; - width: 100vw; + height: 100%; + width: 100%; } .main-container { - height: 100vh; - width: 100vw; + height: 100%; + width: 100%; position: absolute; } @@ -397,7 +367,6 @@ body { } #game { - width: 100%; position: relative; /* Position relative is needed for the game-overlay. */ } @@ -618,7 +587,6 @@ input[type=range]:focus::-ms-fill-upper { /* Let's make sure videos are vertically centered if they need to be cropped */ .media-container { display: flex; - justify-content: center; flex-direction: column; overflow: hidden; border-radius: 15px; @@ -659,17 +627,15 @@ input[type=range]:focus::-ms-fill-upper { grid-template-columns: repeat(4, 1fr); } -/*CONSOLE*/ - -.message-container, -.main-console{ +/*GLOBAL MESSAGE*/ +.message-container { position: absolute; width: 80%; height: 80%; min-height: 200px; max-height: 80%; top: -80%; - /*left: 10%;*/ + //left: 10%; left: 250px; background: #333333; z-index: 200; @@ -692,7 +658,6 @@ input[type=range]:focus::-ms-fill-upper { max-height: 400px; } -.main-console div.console, .message-container div.clear { position: absolute; color: white; @@ -707,22 +672,11 @@ input[type=range]:focus::-ms-fill-upper { text-align: center; } -.main-console div.message, -.main-console div.setting{ - display: none; -} - -.main-console div.message.active, -.main-console div.setting.active{ - display: block; -} - .message-container div.clear{ width: 100px; left: calc(50% - 50px); } -.main-console div.console img, .message-container div.clear img{ margin-top: 6px; width: 30px; @@ -733,141 +687,26 @@ input[type=range]:focus::-ms-fill-upper { transform: rotateY(0); opacity: 0.5; } -.main-console div.console img:hover, + .message-container div.clear img:hover{ opacity: 1; } -.main-console div.console img.active, .message-container div.clear img{ transform: rotateY(3.142rad); opacity: 1; } -.main-console div.console p, .message-container div.clear p{ margin-top: 12px; } -.main-console div.console:hover, .message-container div.clear:hover { cursor: url('./images/cursor_pointer.png'), pointer; top: calc(100% + 5px); transform: scale(1.2) translateY(3px); } -.main-console #input-send-text{ - min-height: 200px; -} - -.main-console #input-send-text .ql-editor{ - color: white; - min-height: 200px; - max-height: 300px; -} - -.main-console .ql-toolbar{ - background: white; -} - -.main-console .btn-action{ - margin: 10px; - text-align: center; -} - -.main-console .btn-action .btn{ - border: 1px solid black; - background-color: #00000000; - color: #ffda01; - border-radius: 15px; - padding: 10px 30px; - transition: all .2s ease; -} -.main-console .btn-action .btn:hover{ - cursor: url('./images/cursor_pointer.png'), pointer; - background-color: #ffda01; - color: black; - border: 1px solid black; - transform: scale(1.1); -} - -.main-console .menu { - padding: 20px; - color: #ffffffa6; - text-align: center; -} - -.main-console .menu span { - margin: 20px; - cursor: url('./images/cursor_pointer.png'), pointer; -} - -.main-console .menu span.active { - color: white; - border-bottom: solid 1px white; -} - -.main-console section{ - text-align: center; - display: none; -} - -.main-console section.active{ - display: block; -} - -.main-console section div.upload{ - text-align: center; - border: solid 1px #ffda01; - height: 150px; - margin: 10px 200px; - padding: 20px; - min-height: 200px; -} - -.main-console section div.upload label{ - color: #ffda01; -} -.main-console section div.upload input{ - display: none; -} -.main-console section div.upload label img{ - height: 150px; - cursor: url('./images/cursor_pointer.png'), pointer; -} -.main-console section div.upload label img{ - cursor: url('./images/cursor_pointer.png'), pointer; -} - - - -/*audio html when audio message playing*/ -.main-container .audio-playing { - position: absolute; - width: 200px; - height: 54px; - right: -210px; - top: 40px; - transition: all 0.1s ease-out; - background-color: black; - border-radius: 30px 0 0 30px; - display: inline-flex; -} - -.main-container .audio-playing.active{ - right: 0; -} -.main-container .audio-playing img{ - /*width: 30px;*/ - border-radius: 50%; - background-color: #ffda01; - padding: 10px; -} -.main-container .audio-playing p{ - color: white; - margin-left: 10px; - margin-top: 14px; -} /* VIDEO QUALITY */ .main-console div.setting h1{ @@ -1136,6 +975,10 @@ div.modal-report-user{ color: white; } +.discussion .messages .message p a:visited{ + color: white; +} + .discussion .send-message{ position: absolute; bottom: 45px; @@ -1244,4 +1087,22 @@ div.action.danger p.action-body{ width: 100%; height: 100%; pointer-events: none; + + & > div { + position: relative; + width: 100%; + height: 100%; + + & > div { + position: absolute; + width: 100%; + height: 100%; + } + + & > div.scrollable { + overflow-y: auto; + -webkit-overflow-scrolling: touch; + pointer-events: auto; + } + } } diff --git a/front/style/svelte-style.scss b/front/style/svelte-style.scss new file mode 100644 index 00000000..7881dabb --- /dev/null +++ b/front/style/svelte-style.scss @@ -0,0 +1,60 @@ +//Contains all styles not unique to a svelte component. + +//ConsoleGlobalMessage +div.main-console.nes-container { + pointer-events: auto; + margin-left: auto; + margin-right: auto; + top: 20vh; + width: 50vw; + height: 50vh; + padding: 0; + background-color: #333333; + + .btn-action{ + margin: 10px; + text-align: center; + } + + .main-global-message { + width: 100%; + max-height: 100%; + } + + .main-global-message h2 { + text-align: center; + color: white; + } + + div.global-message { + display: flex; + max-height: 100%; + width: 100%; + } + + div.menu { + flex: auto; + } + + div.menu button { + margin: 7px; + } + + .main-input { + width: 95%; + } + +//InputTextGlobalMessage + .section-input-send-text { + margin: 10px; + } + + .section-input-send-text .input-send-text .ql-editor{ + color: white; + min-height: 200px; + } + + .section-input-send-text .ql-toolbar{ + background: white; + } +} diff --git a/front/tests/Phaser/Connexion/LocalUserTest.ts b/front/tests/Phaser/Connexion/LocalUserTest.ts index 25b54005..4ba20745 100644 --- a/front/tests/Phaser/Connexion/LocalUserTest.ts +++ b/front/tests/Phaser/Connexion/LocalUserTest.ts @@ -19,8 +19,14 @@ describe("isUserNameValid()", () => { it("should not validate spaces", () => { expect(isUserNameValid(' ')).toBe(false); }); - it("should not validate special characters", () => { - expect(isUserNameValid('a&-')).toBe(false); + it("should validate special characters", () => { + expect(isUserNameValid('%&-')).toBe(true); + }); + it("should validate accents", () => { + expect(isUserNameValid('éàëè')).toBe(true); + }); + it("should validate chinese characters", () => { + expect(isUserNameValid('中文鍵盤')).toBe(true); }); }); diff --git a/front/webpack.config.ts b/front/webpack.config.ts index cc87572a..3a69b74a 100644 --- a/front/webpack.config.ts +++ b/front/webpack.config.ts @@ -7,6 +7,7 @@ import MiniCssExtractPlugin from 'mini-css-extract-plugin'; import sveltePreprocess from 'svelte-preprocess'; import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin"; import NodePolyfillPlugin from 'node-polyfill-webpack-plugin'; +import {DISPLAY_TERMS_OF_USE} from "./src/Enum/EnvironmentVariable"; const mode = process.env.NODE_ENV ?? 'development'; const isProduction = mode === 'production'; @@ -88,7 +89,16 @@ module.exports = { preprocess: sveltePreprocess({ scss: true, sass: true, - }) + }), + onwarn: function (warning: { code: string }, handleWarning: (warning: { code: string }) => void) { + // See https://github.com/sveltejs/svelte/issues/4946#issuecomment-662168782 + + if (warning.code === 'a11y-no-onchange') { return } + if (warning.code === 'a11y-autofocus') { return } + + // process as usual + handleWarning(warning); + } } } }, @@ -102,9 +112,17 @@ module.exports = { } }, { - test: /\.(ttf|eot|svg|png|gif|jpg)$/, + test: /\.(eot|svg|png|gif|jpg)$/, exclude: /node_modules/, type: 'asset' + }, + { + test: /\.(woff(2)?|ttf)$/, + type: 'asset', + generator: { + filename: 'fonts/[name][ext]' + } + } ], }, @@ -167,7 +185,8 @@ module.exports = { 'JITSI_PRIVATE_MODE': null, 'START_ROOM_URL': null, 'MAX_USERNAME_LENGTH': 8, - 'MAX_PER_GROUP': 4 + 'MAX_PER_GROUP': 4, + 'DISPLAY_TERMS_OF_USE': false, }) ], diff --git a/front/yarn.lock b/front/yarn.lock index bbdf0e06..f0159242 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -30,6 +30,13 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@babel/runtime@^7.14.0": + version "7.14.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.0.tgz#46794bc20b612c5f75e62dd071e24dfd95f1cbe6" + integrity sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA== + dependencies: + regenerator-runtime "^0.13.4" + "@discoveryjs/json-ext@^0.5.0": version "0.5.3" resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.3.tgz#90420f9f9c6d3987f176a19a7d8e764271a2f55d" @@ -50,6 +57,11 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" +"@fontsource/press-start-2p@^4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@fontsource/press-start-2p/-/press-start-2p-4.3.0.tgz#37124387f7fbfe7792b5fc9a1906b80d9aeda4c6" + integrity sha512-gmS4070EoZp5/6NUJ+tBnvtDiSmFcR+S+ClAOJ8NGFXDWOkO12yMnyGJEJaDCNCAMX0s2TQCcmr6qWKx5ad3RQ== + "@nodelib/fs.scandir@2.1.4": version "2.1.4" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz#d4b3549a5db5de2683e0c1071ab4f140904bbf69" @@ -639,7 +651,7 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" -anymatch@~3.1.1: +anymatch@~3.1.1, anymatch@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== @@ -773,6 +785,14 @@ atob@^2.1.2: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== +automation-events@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/automation-events/-/automation-events-4.0.1.tgz#93acef8a457cbea65f16fdcef8f349fd2fafe298" + integrity sha512-8bQx+PVtNDMD5F2H40cQs7oexZve3Z0xC9fWRQT4fltF65f/WsSpjM4jpulL4K4yLLB71oi4/xVJJCJ5I/Kjbw== + dependencies: + "@babel/runtime" "^7.14.0" + tslib "^2.2.0" + available-typed-arrays@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz#6b098ca9d8039079ee3f77f7b783c4480ba513f5" @@ -1092,7 +1112,7 @@ caniuse-lite@^1.0.30001219: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001228.tgz#bfdc5942cd3326fa51ee0b42fbef4da9d492a7fa" integrity sha512-QQmLOGJ3DEgokHbMSA8cj2a+geXqmnpyOFT0lhQV6P3/YOJvGDEwoedcwxEQ30gJIwIIunHIicunJ2rzK5gB2A== -chalk@^2.0.0: +chalk@^2.0.0, chalk@^2.4.1: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -1143,6 +1163,21 @@ chokidar@^2.1.8: optionalDependencies: fsevents "^1.2.7" +chokidar@^3.4.1: + version "3.5.2" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" + integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + chrome-trace-event@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" @@ -1382,7 +1417,14 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== -cross-spawn@^6.0.0: +cross-env@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" + integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== + dependencies: + cross-spawn "^7.0.1" + +cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== @@ -1393,7 +1435,7 @@ cross-spawn@^6.0.0: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.2, cross-spawn@^7.0.3: +cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -1621,9 +1663,9 @@ dns-equal@^1.0.0: integrity sha1-s55/HabrCnW6nBcySzR1PEfgZU0= dns-packet@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a" - integrity sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg== + version "1.3.4" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.4.tgz#e3455065824a2507ba886c55a89963bb107dec6f" + integrity sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA== dependencies: ip "^1.1.0" safe-buffer "^5.0.1" @@ -2345,7 +2387,7 @@ fsevents@^1.2.7: bindings "^1.5.0" nan "^2.12.1" -fsevents@~2.3.1: +fsevents@~2.3.1, fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -2409,7 +2451,7 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@~5.1.0: +glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@~5.1.0, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -2586,6 +2628,11 @@ hmac-drbg@^1.0.1: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" +hosted-git-info@^2.1.4: + version "2.8.9" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== + hpack.js@^2.1.6: version "2.1.6" resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" @@ -3157,7 +3204,7 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -json-parse-better-errors@^1.0.2: +json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== @@ -3262,6 +3309,16 @@ linked-list-typescript@^1.0.11: resolved "https://registry.yarnpkg.com/linked-list-typescript/-/linked-list-typescript-1.0.15.tgz#faeed93cf9203f102e2158c29edcddda320abe82" integrity sha512-RIyUu9lnJIyIaMe63O7/aFv/T2v3KsMFuXMBbUQCHX+cgtGro86ETDj5ed0a8gQL2+DFjzYYsgVG4I36/cUwgw== +load-json-file@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" + integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs= + dependencies: + graceful-fs "^4.1.2" + parse-json "^4.0.0" + pify "^3.0.0" + strip-bom "^3.0.0" + loader-runner@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384" @@ -3376,6 +3433,11 @@ memory-fs@^0.4.1: errno "^0.1.3" readable-stream "^2.0.1" +memorystream@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" + integrity sha1-htcJCzDORV1j+64S3aUaR93K+bI= + merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" @@ -3509,6 +3571,11 @@ mkdirp@^0.5.1, mkdirp@^0.5.5: dependencies: minimist "^1.2.5" +mri@^1.1.0: + version "1.1.6" + resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.6.tgz#49952e1044db21dbf90f6cd92bc9c9a777d415a6" + integrity sha512-oi1b3MfbyGa7FJMP9GmLTttni5JoICpYBRlq+x5V16fZbLsnL9N3wFqqIm/nIG43FjUFkFh9Epzp/kzUGUnJxQ== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -3637,6 +3704,16 @@ node-releases@^1.1.71: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.72.tgz#14802ab6b1039a79a0c7d662b610a5bbd76eacbe" integrity sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw== +normalize-package-data@^2.3.2: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + normalize-path@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" @@ -3649,6 +3726,21 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +npm-run-all@^4.1.5: + version "4.1.5" + resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.5.tgz#04476202a15ee0e2e214080861bff12a51d98fba" + integrity sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ== + dependencies: + ansi-styles "^3.2.1" + chalk "^2.4.1" + cross-spawn "^6.0.5" + memorystream "^0.3.1" + minimatch "^3.0.4" + pidtree "^0.3.0" + read-pkg "^3.0.0" + shell-quote "^1.6.1" + string.prototype.padend "^3.0.0" + npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -3879,6 +3971,14 @@ parse-asn1@^5.0.0, parse-asn1@^5.1.5: pbkdf2 "^3.0.3" safe-buffer "^5.1.1" +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + parse-json@^5.0.0: version "5.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" @@ -3967,6 +4067,13 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= +path-type@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" + integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== + dependencies: + pify "^3.0.0" + path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" @@ -3991,6 +4098,10 @@ pbkdf2@^3.0.3: safe-buffer "^5.0.1" sha.js "^2.4.8" +phaser-animated-tiles@workadventure/phaser-animated-tiles#da68bbededd605925621dd4f03bd27e69284b254: + version "2.0.2" + resolved "https://codeload.github.com/workadventure/phaser-animated-tiles/tar.gz/da68bbededd605925621dd4f03bd27e69284b254" + phaser3-rex-plugins@^1.1.42: version "1.1.47" resolved "https://registry.yarnpkg.com/phaser3-rex-plugins/-/phaser3-rex-plugins-1.1.47.tgz#89299369437a0032ad31c64e89a26d20fd8a5867" @@ -4014,11 +4125,21 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.3.tgz#465547f359ccc206d3c48e46a1bcb89bf7ee619d" integrity sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg== +pidtree@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.1.tgz#ef09ac2cc0533df1f3250ccf2c4d366b0d12114a" + integrity sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA== + pify@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= + pify@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" @@ -4268,6 +4389,15 @@ raw-body@2.4.0: iconv-lite "0.4.24" unpipe "1.0.0" +read-pkg@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" + integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k= + dependencies: + load-json-file "^4.0.0" + normalize-package-data "^2.3.2" + path-type "^3.0.0" + readable-stream@^2.0.1, readable-stream@^2.0.2: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" @@ -4306,6 +4436,13 @@ readdirp@~3.5.0: dependencies: picomatch "^2.2.1" +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + rechoir@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.0.tgz#32650fd52c21ab252aa5d65b19310441c7e03aca" @@ -4313,6 +4450,11 @@ rechoir@^0.7.0: dependencies: resolve "^1.9.0" +regenerator-runtime@^0.13.4: + version "0.13.7" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" + integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== + regex-not@^1.0.0, regex-not@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" @@ -4419,7 +4561,7 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@^1.9.0: +resolve@^1.10.0, resolve@^1.9.0: version "1.20.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== @@ -4478,6 +4620,13 @@ rxjs@^6.6.3: dependencies: tslib "^1.9.0" +sade@^1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/sade/-/sade-1.7.4.tgz#ea681e0c65d248d2095c90578c03ca0bb1b54691" + integrity sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA== + dependencies: + mri "^1.1.0" + safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -4554,7 +4703,7 @@ selfsigned@^1.10.8: dependencies: node-forge "^0.10.0" -semver@^5.5.0: +"semver@2 || 3 || 4 || 5", semver@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -4689,6 +4838,11 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +shell-quote@^1.6.1: + version "1.7.2" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" + integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg== + signal-exit@^3.0.0, signal-exit@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" @@ -4842,6 +4996,32 @@ source-map@^0.7.3, source-map@~0.7.2: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== +spdx-correct@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" + integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== + +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.9" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.9.tgz#8a595135def9592bda69709474f1cbeea7c2467f" + integrity sha512-Ki212dKK4ogX+xDo4CtOZBVIwhsKBEfsEEcwmJfLQzirgc2jIWdzg40Unxz/HzEUqM1WFzVlQSMF9kZZ2HboLQ== + spdy-transport@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" @@ -4877,6 +5057,15 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= +standardized-audio-context@^25.2.4: + version "25.2.4" + resolved "https://registry.yarnpkg.com/standardized-audio-context/-/standardized-audio-context-25.2.4.tgz#d64dbdd70615171ec90d1b7151a0d945af94cf3d" + integrity sha512-uQKZXRnXrPVO1V6SwZ7PiV3RkQqRY3n7i6Q8nbTXYvoz8NftRNzfOIlwefpuC8LVLUUs9dhbKTpP+WOA82dkBw== + dependencies: + "@babel/runtime" "^7.14.0" + automation-events "^4.0.1" + tslib "^2.2.0" + static-extend@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" @@ -4926,6 +5115,15 @@ string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" +string.prototype.padend@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.1.2.tgz#6858ca4f35c5268ebd5e8615e1327d55f59ee311" + integrity sha512-/AQFLdYvePENU3W5rgurfWSMU6n+Ww8n/3cUt7E+vPBB/D7YDG8x+qjoFs4M/alR2bW7Qg6xMjVwWUOvuQ0XpQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.2" + string.prototype.trimend@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" @@ -5025,6 +5223,21 @@ supports-color@^7.0.0, supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +svelte-check@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/svelte-check/-/svelte-check-2.1.0.tgz#3ee8ad86068256346ebca862bbee8417a757fc53" + integrity sha512-kKLXkFt0XZTn+O1fnilGTQ1SFLsOFF+lXp1YjPfeN9nX+Y3ZpELtZSQCkbuK6HMkWugFvsOM17FCOSa1mfrEFA== + dependencies: + chalk "^4.0.0" + chokidar "^3.4.1" + glob "^7.1.6" + import-fresh "^3.2.1" + minimist "^1.2.5" + sade "^1.7.4" + source-map "^0.7.3" + svelte-preprocess "^4.0.0" + typescript "*" + svelte-dev-helper@^1.1.9: version "1.1.9" resolved "https://registry.yarnpkg.com/svelte-dev-helper/-/svelte-dev-helper-1.1.9.tgz#7d187db5c6cdbbd64d75a32f91b8998bde3273c3" @@ -5044,7 +5257,7 @@ svelte-loader@^3.1.1: svelte-dev-helper "^1.1.9" svelte-hmr "^0.12.3" -svelte-preprocess@^4.7.3: +svelte-preprocess@^4.0.0, svelte-preprocess@^4.7.3: version "4.7.3" resolved "https://registry.yarnpkg.com/svelte-preprocess/-/svelte-preprocess-4.7.3.tgz#454fa059c2400b15e7a3caeca18993cff9df0e96" integrity sha512-Zx1/xLeGOIBlZMGPRCaXtlMe4ZA0faato5Dc3CosEqwu75MIEPuOstdkH6cy+RYTUYynoxzNaDxkPX4DbrPwRA== @@ -5207,7 +5420,7 @@ tslib@^1.8.1, tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.3: +tslib@^2.0.3, tslib@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c" integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w== @@ -5249,6 +5462,11 @@ type-is@~1.6.17, type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" +typescript@*: + version "4.3.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.2.tgz#399ab18aac45802d6f2498de5054fcbbe716a805" + integrity sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw== + typescript@^4.2.4: version "4.2.4" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.4.tgz#8610b59747de028fda898a8aef0e103f156d0961" @@ -5374,6 +5592,14 @@ v8-compile-cache@^2.0.3, v8-compile-cache@^2.2.0: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" @@ -5616,9 +5842,9 @@ wrappy@1: integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= ws@^6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" - integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== + version "6.2.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.2.tgz#dd5cdbd57a9979916097652d78f1cc5faea0c32e" + integrity sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw== dependencies: async-limiter "~1.0.0" diff --git a/maps/tests/Metadata/customMenu.html b/maps/tests/Metadata/customMenu.html index 59f579ba..a80dca08 100644 --- a/maps/tests/Metadata/customMenu.html +++ b/maps/tests/Metadata/customMenu.html @@ -5,11 +5,9 @@ \ No newline at end of file diff --git a/maps/tests/Metadata/getCurrentRoom.html b/maps/tests/Metadata/getCurrentRoom.html index b290c6a4..7429b2a8 100644 --- a/maps/tests/Metadata/getCurrentRoom.html +++ b/maps/tests/Metadata/getCurrentRoom.html @@ -5,7 +5,7 @@ \ No newline at end of file diff --git a/maps/tests/Metadata/setProperty.html b/maps/tests/Metadata/setProperty.html index 06b029da..5259ec0a 100644 --- a/maps/tests/Metadata/setProperty.html +++ b/maps/tests/Metadata/setProperty.html @@ -5,8 +5,8 @@ \ No newline at end of file diff --git a/maps/tests/Metadata/showHideLayer.html b/maps/tests/Metadata/showHideLayer.html index 391ec449..4677f9e5 100644 --- a/maps/tests/Metadata/showHideLayer.html +++ b/maps/tests/Metadata/showHideLayer.html @@ -10,10 +10,10 @@ diff --git a/maps/tests/animated_tiles.json b/maps/tests/animated_tiles.json new file mode 100644 index 00000000..65ac3b97 --- /dev/null +++ b/maps/tests/animated_tiles.json @@ -0,0 +1,120 @@ +{ "compressionlevel":-1, + "height":10, + "infinite":false, + "layers":[ + { + "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], + "height":10, + "id":1, + "name":"floor", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + "height":10, + "id":2, + "name":"start", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "draworder":"topdown", + "id":3, + "name":"floorLayer", + "objects":[ + { + "height":261.73266830836, + "id":3, + "name":"", + "rotation":0, + "text": + { + "color":"#ffffff", + "fontfamily":"Sans Serif", + "pixelsize":11, + "text":"Test:\nOpen this page\n\nResult:\nThe water tileset should be animated", + "wrap":true + }, + "type":"", + "visible":true, + "width":252.4375, + "x":46.5894222943362, + "y":34.2876372135732 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + }], + "nextlayerid":8, + "nextobjectid":5, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"2021.03.23", + "tileheight":32, + "tilesets":[ + { + "columns":8, + "firstgid":1, + "image":"animated_tiles.png", + "imageheight":32, + "imagewidth":256, + "margin":0, + "name":"animated_tiles", + "spacing":0, + "tilecount":8, + "tileheight":32, + "tiles":[ + { + "animation":[ + { + "duration":100, + "tileid":0 + }, + { + "duration":100, + "tileid":1 + }, + { + "duration":100, + "tileid":2 + }, + { + "duration":100, + "tileid":3 + }, + { + "duration":100, + "tileid":4 + }, + { + "duration":100, + "tileid":5 + }, + { + "duration":100, + "tileid":6 + }, + { + "duration":100, + "tileid":7 + }], + "id":0 + }], + "tilewidth":32 + }], + "tilewidth":32, + "type":"map", + "version":1.5, + "width":10 +} \ No newline at end of file diff --git a/maps/tests/animated_tiles.png b/maps/tests/animated_tiles.png new file mode 100644 index 00000000..19ac1e05 Binary files /dev/null and b/maps/tests/animated_tiles.png differ diff --git a/maps/tests/deprecated_functions.json b/maps/tests/deprecated_functions.json new file mode 100644 index 00000000..a4c035f7 --- /dev/null +++ b/maps/tests/deprecated_functions.json @@ -0,0 +1,164 @@ +{ "compressionlevel":-1, + "height":10, + "infinite":false, + "layers":[ + { + "data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + "height":10, + "id":1, + "name":"floor", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 23, 23, 0, 0, 0, 0, 0, 0, 0, 0, 23, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":10, + "id":6, + "name":"triggerZone", + "opacity":1, + "properties":[ + { + "name":"zone", + "type":"string", + "value":"myTrigger" + }], + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 23, 0, 0, 0, 0, 0, 0, 0, 0, 23, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":10, + "id":7, + "name":"popupZone", + "opacity":1, + "properties":[ + { + "name":"zone", + "type":"string", + "value":"popupZone" + }], + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":10, + "id":2, + "name":"start", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "draworder":"topdown", + "id":3, + "name":"floorLayer", + "objects":[ + { + "height":147.135497146101, + "id":1, + "name":"myPopup2", + "rotation":0, + "type":"", + "visible":true, + "width":104.442827410047, + "x":142.817125079855, + "y":147.448134926559 + }, + { + "height":132.434722966794, + "id":2, + "name":"myPopup1", + "rotation":0, + "type":"", + "visible":true, + "width":125.735549178518, + "x":13.649632619596, + "y":50.8502491249093 + }, + { + "height":67, + "id":3, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":11, + "text":"Test:\nWalk on top carpet\nResult:\nA message \"Don't step on my carpet\" is displayed\nIn the console, deprecation warnings MUST be displayed", + "wrap":true + }, + "type":"", + "visible":true, + "width":252.4375, + "x":2.78125, + "y":2.5 + }, + { + "height":67, + "id":4, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":11, + "text":"Test:\nWalk on bottom carpet\nResult:\nA series of 2 popups open. A bubble opens. The player cannot move until popup closes.\nWhen the player leaves the zone, the bubble closes.\nIn the console, deprecation warnings MUST be displayed", + "wrap":true + }, + "type":"", + "visible":true, + "width":252.438, + "x":-1.71899999999999, + "y":163.5 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + }], + "nextlayerid":8, + "nextobjectid":5, + "orientation":"orthogonal", + "properties":[ + { + "name":"script", + "type":"string", + "value":"deprecated_script.js" + }], + "renderorder":"right-down", + "tiledversion":"2021.03.23", + "tileheight":32, + "tilesets":[ + { + "columns":11, + "firstgid":1, + "image":"tileset1.png", + "imageheight":352, + "imagewidth":352, + "margin":0, + "name":"tileset1", + "spacing":0, + "tilecount":121, + "tileheight":32, + "tilewidth":32 + }], + "tilewidth":32, + "type":"map", + "version":1.5, + "width":10 +} \ No newline at end of file diff --git a/maps/tests/deprecated_script.js b/maps/tests/deprecated_script.js new file mode 100644 index 00000000..4a889a8a --- /dev/null +++ b/maps/tests/deprecated_script.js @@ -0,0 +1,79 @@ +console.log('SCRIPT LAUNCHED'); +//WA.sendChatMessage('Hi, my name is Poly and I repeat what you say!', 'Poly Parrot'); +var isFirstTimeTuto = false; +var textFirstPopup = 'Hey ! This is how to open start a discussion with someone ! You can be 4 max in a booble'; +var textSecondPopup = 'You can also use the chat to communicate ! '; +var targetObjectTutoBubble ='myPopup1'; +var targetObjectTutoChat ='myPopup2'; +var popUpExplanation = undefined; +function launchTuto (){ + WA.openPopup(targetObjectTutoBubble, textFirstPopup, [ + { + label: "Next", + className: "popUpElement", + callback: (popup) => { + popup.close(); + + WA.openPopup(targetObjectTutoChat, textSecondPopup, [ + { + label: "Open Chat", + className: "popUpElement", + callback: (popup1) => { + WA.sendChatMessage("Hey you can talk here too ! ", 'WA Guide'); + popup1.close(); + WA.restorePlayerControls(); + } + } + + ]) + } + } + ]); + WA.disablePlayerControls(); + +} +WA.onChatMessage((message => { + console.log('CHAT MESSAGE RECEIVED BY SCRIPT'); + WA.sendChatMessage('Poly Parrot says: "'+message+'"', 'Poly Parrot'); +})); + +WA.onEnterZone('myTrigger', () => { + WA.sendChatMessage("Don't step on my carpet!", 'Poly Parrot'); +}) + +WA.onLeaveZone('popupZone', () => { +}) + +WA.onEnterZone('notExist', () => { + WA.sendChatMessage("YOU SHOULD NEVER SEE THIS", 'Poly Parrot'); +}) + +WA.onEnterZone('popupZone', () => { + WA.displayBubble(); + if (!isFirstTimeTuto) { + isFirstTimeTuto = true; + launchTuto(); + } + else popUpExplanation = WA.openPopup(targetObjectTutoChat,'Do you want to review the explanation ? ', [ + { + label: "No", + className: "popUpElementReviewexplanation", + callback: (popup) => { + popup.close(); + } + }, + { + label: "Yes", + className: "popUpElementReviewexplanation", + callback: (popup) => { + popup.close(); + launchTuto(); + } + } + ]) +}); + +WA.onLeaveZone('popupZone', () => { + if (popUpExplanation !== undefined) popUpExplanation.close(); + WA.removeBubble(); +}) diff --git a/maps/tests/goToPageScript.js b/maps/tests/goToPageScript.js index 2b2cf33b..bccbc5d2 100644 --- a/maps/tests/goToPageScript.js +++ b/maps/tests/goToPageScript.js @@ -1,14 +1,16 @@ +/// var zoneName = "popUpGoToPageZone"; var urlPricing = "https://workadventu.re/pricing"; var urlGettingStarted = "https://workadventu.re/getting-started"; -var isCoWebSiteOpened = false; +var urlRelativeMap = "script_api.json"; +var isCoWebSiteOpened = false; WA.onChatMessage((message => { - WA.sendChatMessage('Poly Parrot says: "'+message+'"', 'Poly Parrot'); + WA.sendChatMessage('Poly Parrot says: "' + message + '"', 'Poly Parrot'); })); WA.onEnterZone(zoneName, () => { - WA.openPopup("popUp","Open Links",[ + WA.openPopup("popUp", "Open Links", [ { label: "Open Tab", className: "popUpElement", @@ -18,27 +20,34 @@ WA.onEnterZone(zoneName, () => { }) }, { - label: "Go To Page", className : "popUpElement", - callback:(popup => { + label: "Go To Page", className: "popUpElement", + callback: (popup => { WA.goToPage(urlPricing); popup.close(); }) - } - , + }, { - label: "openCoWebSite", className : "popUpElement", - callback:(popup => { + label: "openCoWebSite", className: "popUpElement", + callback: (popup => { WA.openCoWebSite(urlPricing); isCoWebSiteOpened = true; popup.close(); }) + }, { + label: "load grouped map", + className: "popUpElement", + callback: (popup => { + WA.goToRoom(urlRelativeMap); + popup.close(); + }) + }]); }) WA.onLeaveZone(zoneName, () => { - if (isCoWebSiteOpened) { + if(isCoWebSiteOpened) { WA.closeCoWebSite(); isCoWebSiteOpened = false; } diff --git a/maps/tests/help_camera_setting.json b/maps/tests/help_camera_setting.json new file mode 100644 index 00000000..2dcdec3a --- /dev/null +++ b/maps/tests/help_camera_setting.json @@ -0,0 +1,164 @@ +{ "compressionlevel":-1, + "height":10, + "infinite":false, + "layers":[ + { + "data":[33, 34, 34, 34, 34, 34, 34, 34, 34, 35, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 49, 50, 50, 50, 50, 50, 50, 50, 50, 51], + "height":10, + "id":3, + "name":"bottom", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":10, + "id":1, + "name":"start", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "draworder":"topdown", + "id":2, + "name":"floorLayer", + "objects":[ + { + "height":254.57168784029, + "id":1, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":12, + "text":"Test 1 : \nBlock permission to camera and\/or microphone access.\n\nResult 1 :\nOrange popup show at the bottom of the screen.\nIf you click on it, the HelpCameraSetting popup open.\n\nTest 2 : \nReload the page and block permission to camera and\/or microphone access on the camera setting page.\n\nResult 2 : \nOrange popup show at the bottom of the screen.\nIf you click on it, the HelpCameraSetting popup open.\n", + "wrap":true + }, + "type":"", + "visible":true, + "width":295.278811252269, + "x":12.2517014519056, + "y":49.3021778584392 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + }], + "nextlayerid":6, + "nextobjectid":2, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"1.4.3", + "tileheight":32, + "tilesets":[ + { + "columns":8, + "firstgid":1, + "image":"Validation\/tileset_dungeon.png", + "imageheight":256, + "imagewidth":256, + "margin":0, + "name":"dungeon", + "spacing":0, + "tilecount":64, + "tileheight":32, + "tiles":[ + { + "id":0, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":1, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":2, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":5, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":8, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":10, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":16, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":17, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":18, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }], + "tilewidth":32 + }], + "tilewidth":32, + "type":"map", + "version":1.4, + "width":10 +} \ No newline at end of file diff --git a/maps/tests/iframe.html b/maps/tests/iframe.html index e0ba05d6..aa8e55ec 100644 --- a/maps/tests/iframe.html +++ b/maps/tests/iframe.html @@ -17,7 +17,6 @@ chatDiv.innerText = message; document.getElementById('chatSent').append(chatDiv); })); - WA.loadTileset('TEST', 'https://gparant.github.io/tcm-client/TCM/paris-map/tileset1.png', 32, 32, 0, 0); diff --git a/maps/tests/index.html b/maps/tests/index.html index a17a3b5d..0929ab83 100644 --- a/maps/tests/index.html +++ b/maps/tests/index.html @@ -42,6 +42,14 @@ Testing scripting API with a script + + + Success Failure Pending + + + Testing goToPage script Api + + Success Failure Pending @@ -50,6 +58,14 @@ Testing scripting API loadSound() function + + + Success Failure Pending + + + Testing scripting API deprecated function + + Success Failure Pending @@ -82,6 +98,70 @@ Test energy consumption + + + Success Failure Pending + + + Test the HelpCameraSettingScene + + + + + Success Failure Pending + + + Testing add a custom menu by scripting API + + + + + Success Failure Pending + + + Testing return current room attributes by Scripting API (Need to test from current user) + + + + + Success Failure Pending + + + Testing return current user attributes by Scripting API + + + + + Success Failure Pending + + + Test listening player movement by Scripting API + + + + + Success Failure Pending + + + Testing set a property on a layer by Scripting API + + + + + Success Failure Pending + + + Testing show or hide a layer by Scripting API + + + + + Success Failure Pending + + + Test animated tiles + +