Merge branch 'develop' of https://github.com/thecodingmachine/workadventure into SoundInMapScript
# Conflicts: # front/src/Api/IframeListener.ts # front/src/Phaser/Game/GameScene.ts # front/src/iframe_api.ts # maps/Tuto/scriptTuto.js # maps/Village/Village.json
This commit is contained in:
commit
0c3b9ccfbf
@ -10,6 +10,12 @@ START_ROOM_URL=/_/global/maps.workadventure.localhost/Floor0/floor0.json
|
||||
# If you are using Coturn, this is the value of the "static-auth-secret" parameter in your coturn config file.
|
||||
# Keep empty if you are sharing hard coded / clear text credentials.
|
||||
TURN_STATIC_AUTH_SECRET=
|
||||
DISABLE_NOTIFICATIONS=true
|
||||
SKIP_RENDER_OPTIMIZATIONS=false
|
||||
|
||||
# The email address used by Let's encrypt to send renewal warnings (compulsory)
|
||||
ACME_EMAIL=
|
||||
|
||||
MAX_PER_GROUP=4
|
||||
MAX_USERNAME_LENGTH=8
|
||||
|
||||
|
37
.github/workflows/build-and-deploy.yml
vendored
37
.github/workflows/build-and-deploy.yml
vendored
@ -1,7 +1,13 @@
|
||||
name: Build, push and deploy Docker image
|
||||
|
||||
on:
|
||||
- push
|
||||
push:
|
||||
branches: [master]
|
||||
release:
|
||||
types: [created]
|
||||
pull_request:
|
||||
types: [ labeled, synchronize ]
|
||||
|
||||
|
||||
# Enables BuildKit
|
||||
env:
|
||||
@ -10,7 +16,7 @@ env:
|
||||
jobs:
|
||||
|
||||
build-front:
|
||||
|
||||
if: ${{ github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy') }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@ -30,11 +36,11 @@ jobs:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
repository: thecodingmachine/workadventure-front
|
||||
tags: ${{ env.GITHUB_REF_SLUG }}
|
||||
tags: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
|
||||
add_git_labels: true
|
||||
|
||||
build-back:
|
||||
|
||||
if: ${{ github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy') }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@ -53,11 +59,11 @@ jobs:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
repository: thecodingmachine/workadventure-back
|
||||
tags: ${{ env.GITHUB_REF_SLUG }}
|
||||
tags: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
|
||||
add_git_labels: true
|
||||
|
||||
build-pusher:
|
||||
|
||||
if: ${{ github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy') }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@ -76,11 +82,11 @@ jobs:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
repository: thecodingmachine/workadventure-pusher
|
||||
tags: ${{ env.GITHUB_REF_SLUG }}
|
||||
tags: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
|
||||
add_git_labels: true
|
||||
|
||||
build-uploader:
|
||||
|
||||
if: ${{ github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy') }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@ -99,11 +105,11 @@ jobs:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
repository: thecodingmachine/workadventure-uploader
|
||||
tags: ${{ env.GITHUB_REF_SLUG }}
|
||||
tags: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
|
||||
add_git_labels: true
|
||||
|
||||
build-maps:
|
||||
|
||||
if: ${{ github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy') }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@ -123,7 +129,7 @@ jobs:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
repository: thecodingmachine/workadventure-maps
|
||||
tags: ${{ env.GITHUB_REF_SLUG }}
|
||||
tags: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
|
||||
add_git_labels: true
|
||||
|
||||
deeploy:
|
||||
@ -134,6 +140,7 @@ jobs:
|
||||
- build-maps
|
||||
- build-uploader
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy') }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
@ -151,14 +158,14 @@ jobs:
|
||||
JITSI_URL: ${{ secrets.JITSI_URL }}
|
||||
SECRET_JITSI_KEY: ${{ secrets.SECRET_JITSI_KEY }}
|
||||
TURN_STATIC_AUTH_SECRET: ${{ secrets.TURN_STATIC_AUTH_SECRET }}
|
||||
DEPLOY_REF: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
|
||||
with:
|
||||
namespace: workadventure-${{ env.GITHUB_REF_SLUG }}
|
||||
namespace: workadventure-${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
|
||||
|
||||
- name: Add a comment in PR
|
||||
uses: unsplash/comment-on-pr@v1.2.0
|
||||
if: ${{ env.GITHUB_REF_SLUG != 'master' }}
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
msg: Environment deployed at https://play.${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com
|
||||
check_for_duplicate_msg: true
|
||||
msg: Environment deployed at https://play.${{ env.GITHUB_HEAD_REF_SLUG }}.test.workadventu.re
|
||||
|
10
.github/workflows/cleanup.yml
vendored
10
.github/workflows/cleanup.yml
vendored
@ -1,7 +1,8 @@
|
||||
name: Cleanup images and environments
|
||||
|
||||
on:
|
||||
- delete
|
||||
pull_request:
|
||||
types: [ closed ]
|
||||
|
||||
# Enables BuildKit
|
||||
env:
|
||||
@ -14,13 +15,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
# Create a slugified value of the branch
|
||||
- uses: rlespinasse/github-slug-action@1.1.0
|
||||
- uses: rlespinasse/github-slug-action@3.1.0
|
||||
|
||||
- name: Cleanup
|
||||
continue-on-error: true
|
||||
uses: thecodingmachine/deeployer-cleanup-action@master
|
||||
env:
|
||||
KUBE_CONFIG_FILE: ${{ secrets.KUBE_CONFIG_FILE }}
|
||||
with:
|
||||
# FIXME: we are not using ${{ env.GITHUB_REF_SLUG }} that resolves to master BUT! we are not using a slugified namespace
|
||||
# so complex namespace names will not be treated correctly
|
||||
namespace: workadventure-${{ github.event.ref }}
|
||||
namespace: workadventure-${{ env.GITHUB_HEAD_REF_SLUG }}
|
||||
|
9
.github/workflows/continuous_integration.yml
vendored
9
.github/workflows/continuous_integration.yml
vendored
@ -3,8 +3,11 @@
|
||||
name: "Continuous Integration"
|
||||
|
||||
on:
|
||||
- "pull_request"
|
||||
- "push"
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
|
||||
@ -46,7 +49,7 @@ jobs:
|
||||
- name: "Build"
|
||||
run: yarn run build
|
||||
env:
|
||||
API_URL: "localhost:8080"
|
||||
PUSHER_URL: "//localhost:8080"
|
||||
working-directory: "front"
|
||||
|
||||
- name: "Lint"
|
||||
|
67
.github/workflows/push-to-npm.yml
vendored
Normal file
67
.github/workflows/push-to-npm.yml
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
name: Push @workadventure/iframe-api-typings to NPM
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
# Setup .npmrc file to publish to npm
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14.x'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Edit tsconfig.json to add declarations
|
||||
run: "sed -i 's/\"declaration\": false/\"declaration\": true/g' tsconfig.json"
|
||||
working-directory: "front"
|
||||
|
||||
- name: Replace version number
|
||||
run: 'sed -i "s#VERSION_PLACEHOLDER#${GITHUB_REF/refs\/tags\//}#g" package.json'
|
||||
working-directory: "front/packages/iframe-api-typings"
|
||||
|
||||
- name: Debug package.json
|
||||
run: cat package.json
|
||||
working-directory: "front/packages/iframe-api-typings"
|
||||
|
||||
- name: Install Protoc
|
||||
uses: arduino/setup-protoc@v1
|
||||
with:
|
||||
version: '3.x'
|
||||
|
||||
- name: "Install dependencies"
|
||||
run: yarn install
|
||||
working-directory: "front"
|
||||
|
||||
- name: "Install messages dependencies"
|
||||
run: yarn install
|
||||
working-directory: "messages"
|
||||
|
||||
- name: "Build proto messages"
|
||||
run: yarn run proto && yarn run copy-to-front
|
||||
working-directory: "messages"
|
||||
|
||||
- name: "Create index.html"
|
||||
run: ./templater.sh
|
||||
working-directory: "front"
|
||||
|
||||
- name: "Build"
|
||||
run: yarn run build
|
||||
env:
|
||||
API_URL: "localhost:8080"
|
||||
working-directory: "front"
|
||||
|
||||
# We build the front to generate the typings of iframe_api, then we copy those typings in a separate package.
|
||||
- name: Copy typings to package dir
|
||||
run: cp front/dist/src/iframe_api.d.ts front/packages/iframe-api-typings/iframe_api.d.ts
|
||||
|
||||
- name: Install dependencies in package
|
||||
run: yarn install
|
||||
working-directory: "front/packages/iframe-api-typings"
|
||||
|
||||
- name: Publish package
|
||||
run: yarn publish
|
||||
working-directory: "front/packages/iframe-api-typings"
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
31
CHANGELOG.md
Normal file
31
CHANGELOG.md
Normal file
@ -0,0 +1,31 @@
|
||||
## Version 1.3.9 - in dev
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
- Scripting API:
|
||||
- Changed function names: `restorePlayerControl` => `restorePlayerControls`, `disablePlayerControl` => `disablePlayerControls`.
|
||||
Please keep in mind that the scripting API is still experimental. Some breaking changes can occur in it until we mark it as stable.
|
||||
|
||||
### Updates
|
||||
|
||||
- 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)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Pinch gesture does no longer move the character
|
||||
|
||||
## Version 1.3.0
|
||||
|
||||
### New Features
|
||||
|
||||
* Maps can now contain "group" layers (layers that contain other layers) - #899 #779 (@Lurkars @moufmouf)
|
||||
|
||||
### Updates
|
||||
|
||||
|
||||
### Bug Fixes
|
@ -20,7 +20,7 @@ Install Docker.
|
||||
Run:
|
||||
|
||||
```
|
||||
docker-compose up
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
The environment will start.
|
||||
|
@ -11,6 +11,7 @@ const HTTP_PORT = parseInt(process.env.HTTP_PORT || '8080') || 8080;
|
||||
const GRPC_PORT = parseInt(process.env.GRPC_PORT || '50051') || 50051;
|
||||
export const SOCKET_IDLE_TIMER = parseInt(process.env.SOCKET_IDLE_TIMER as string) || 30; // maximum time (in second) without activity before a socket is closed
|
||||
export const TURN_STATIC_AUTH_SECRET = process.env.TURN_STATIC_AUTH_SECRET || '';
|
||||
export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || '4');
|
||||
|
||||
export {
|
||||
MINIMUM_DISTANCE,
|
||||
|
@ -105,7 +105,8 @@ export class GameRoom {
|
||||
socket,
|
||||
joinRoomMessage.getTagList(),
|
||||
joinRoomMessage.getName(),
|
||||
ProtobufUtils.toCharacterLayerObjects(joinRoomMessage.getCharacterlayerList())
|
||||
ProtobufUtils.toCharacterLayerObjects(joinRoomMessage.getCharacterlayerList()),
|
||||
joinRoomMessage.getCompanion()
|
||||
);
|
||||
this.nextUserId++;
|
||||
this.users.set(user.id, user);
|
||||
|
@ -4,9 +4,9 @@ import {PositionInterface} from "_Model/PositionInterface";
|
||||
import {Movable} from "_Model/Movable";
|
||||
import {PositionNotifier} from "_Model/PositionNotifier";
|
||||
import {gaugeManager} from "../Services/GaugeManager";
|
||||
import {MAX_PER_GROUP} from "../Enum/EnvironmentVariable";
|
||||
|
||||
export class Group implements Movable {
|
||||
static readonly MAX_PER_GROUP = 4;
|
||||
|
||||
private static nextId: number = 1;
|
||||
|
||||
@ -88,7 +88,7 @@ export class Group implements Movable {
|
||||
}
|
||||
|
||||
isFull(): boolean {
|
||||
return this.users.size >= Group.MAX_PER_GROUP;
|
||||
return this.users.size >= MAX_PER_GROUP;
|
||||
}
|
||||
|
||||
isEmpty(): boolean {
|
||||
|
@ -4,7 +4,7 @@ import {Zone} from "_Model/Zone";
|
||||
import {Movable} from "_Model/Movable";
|
||||
import {PositionNotifier} from "_Model/PositionNotifier";
|
||||
import {ServerDuplexStream} from "grpc";
|
||||
import {BatchMessage, PusherToBackMessage, ServerToClientMessage, SubMessage} from "../Messages/generated/messages_pb";
|
||||
import {BatchMessage, CompanionMessage, PusherToBackMessage, ServerToClientMessage, SubMessage} from "../Messages/generated/messages_pb";
|
||||
import {CharacterLayer} from "_Model/Websocket/CharacterLayer";
|
||||
|
||||
export type UserSocket = ServerDuplexStream<PusherToBackMessage, ServerToClientMessage>;
|
||||
@ -23,7 +23,8 @@ export class User implements Movable {
|
||||
public readonly socket: UserSocket,
|
||||
public readonly tags: string[],
|
||||
public readonly name: string,
|
||||
public readonly characterLayers: CharacterLayer[]
|
||||
public readonly characterLayers: CharacterLayer[],
|
||||
public readonly companion?: CompanionMessage
|
||||
) {
|
||||
this.listenedZones = new Set<Zone>();
|
||||
|
||||
|
@ -296,6 +296,7 @@ export class SocketManager {
|
||||
userJoinedZoneMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers));
|
||||
userJoinedZoneMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition()));
|
||||
userJoinedZoneMessage.setFromzone(this.toProtoZone(fromZone));
|
||||
userJoinedZoneMessage.setCompanion(thing.companion);
|
||||
|
||||
const subMessage = new SubToPusherMessage();
|
||||
subMessage.setUserjoinedzonemessage(userJoinedZoneMessage);
|
||||
@ -509,19 +510,6 @@ export class SocketManager {
|
||||
return this.rooms;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param token
|
||||
*/
|
||||
/*searchClientByUuid(uuid: string): ExSocketInterface | null {
|
||||
for(const socket of this.sockets.values()){
|
||||
if(socket.userUuid === uuid){
|
||||
return socket;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}*/
|
||||
|
||||
|
||||
public handleQueryJitsiJwtMessage(user: User, queryJitsiJwtMessage: QueryJitsiJwtMessage) {
|
||||
const room = queryJitsiJwtMessage.getJitsiroom();
|
||||
@ -605,6 +593,7 @@ export class SocketManager {
|
||||
userJoinedMessage.setName(thing.name);
|
||||
userJoinedMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers));
|
||||
userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition()));
|
||||
userJoinedMessage.setCompanion(thing.companion);
|
||||
|
||||
const subMessage = new SubToPusherMessage();
|
||||
subMessage.setUserjoinedzonemessage(userJoinedMessage);
|
||||
|
@ -1704,9 +1704,9 @@ lodash.once@^4.0.0:
|
||||
integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
|
||||
|
||||
lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19:
|
||||
version "4.17.20"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
|
||||
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
|
||||
long@~3:
|
||||
version "3.2.0"
|
||||
|
@ -1,8 +1,8 @@
|
||||
{
|
||||
local env = std.extVar("env"),
|
||||
local namespace = env.GITHUB_REF_SLUG,
|
||||
local namespace = env.DEPLOY_REF,
|
||||
local tag = namespace,
|
||||
local url = if namespace == "master" then "workadventu.re" else namespace+".workadventure.test.thecodingmachine.com",
|
||||
local url = namespace+".test.workadventu.re",
|
||||
// develop branch does not use admin because of issue with SSL certificate of admin as of now.
|
||||
local adminUrl = if namespace == "master" || namespace == "develop" || std.startsWith(namespace, "admin") then "https://"+url else null,
|
||||
"$schema": "https://raw.githubusercontent.com/thecodingmachine/deeployer/master/deeployer.schema.json",
|
||||
@ -25,10 +25,7 @@
|
||||
"TURN_STATIC_AUTH_SECRET": env.TURN_STATIC_AUTH_SECRET,
|
||||
} + (if adminUrl != null then {
|
||||
"ADMIN_API_URL": adminUrl,
|
||||
} else {}) + if namespace != "master" then {
|
||||
// Absolutely ugly WorkAround to circumvent broken certificates on the K8S test cluster. Don't do this in production kids!
|
||||
"NODE_TLS_REJECT_UNAUTHORIZED": "0"
|
||||
}
|
||||
} else {})
|
||||
},
|
||||
"back2": {
|
||||
"image": "thecodingmachine/workadventure-back:"+tag,
|
||||
@ -47,10 +44,7 @@
|
||||
"TURN_STATIC_AUTH_SECRET": env.TURN_STATIC_AUTH_SECRET,
|
||||
} + (if adminUrl != null then {
|
||||
"ADMIN_API_URL": adminUrl,
|
||||
} else {}) + if namespace != "master" then {
|
||||
// Absolutely ugly WorkAround to circumvent broken certificates on the K8S test cluster. Don't do this in production kids!
|
||||
"NODE_TLS_REJECT_UNAUTHORIZED": "0"
|
||||
}
|
||||
} else {})
|
||||
},
|
||||
"pusher": {
|
||||
"replicas": 2,
|
||||
@ -69,10 +63,7 @@
|
||||
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
|
||||
} + (if adminUrl != null then {
|
||||
"ADMIN_API_URL": adminUrl,
|
||||
} else {}) + if namespace != "master" then {
|
||||
// Absolutely ugly WorkAround to circumvent broken certificates on the K8S test cluster. Don't do this in production kids!
|
||||
"NODE_TLS_REJECT_UNAUTHORIZED": "0"
|
||||
}
|
||||
} else {})
|
||||
},
|
||||
"front": {
|
||||
"image": "thecodingmachine/workadventure-front:"+tag,
|
||||
|
@ -30,8 +30,11 @@ services:
|
||||
UPLOADER_URL: /uploader
|
||||
ADMIN_URL: /admin
|
||||
MAPS_URL: /maps
|
||||
STARTUP_COMMAND_1: yarn install
|
||||
STARTUP_COMMAND_1: ./templater.sh
|
||||
STARTUP_COMMAND_2: yarn install
|
||||
TURN_SERVER: "turn:localhost:3478,turns:localhost:5349"
|
||||
DISABLE_NOTIFICATIONS: "$DISABLE_NOTIFICATIONS"
|
||||
SKIP_RENDER_OPTIMIZATIONS: "$SKIP_RENDER_OPTIMIZATIONS"
|
||||
# Use TURN_USER/TURN_PASSWORD if your Coturn server is secured via hard coded credentials.
|
||||
# Advice: you should instead use Coturn REST API along the TURN_STATIC_AUTH_SECRET in the Back container
|
||||
TURN_USER: ""
|
||||
@ -116,6 +119,7 @@ services:
|
||||
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
|
||||
JITSI_URL: $JITSI_URL
|
||||
JITSI_ISS: $JITSI_ISS
|
||||
MAX_PER_GROUP: "$MAX_PER_GROUP"
|
||||
volumes:
|
||||
- ./back:/usr/src/app
|
||||
labels:
|
||||
@ -151,23 +155,6 @@ services:
|
||||
- "traefik.http.routers.uploader-ssl.tls=true"
|
||||
- "traefik.http.routers.uploader-ssl.service=uploader"
|
||||
|
||||
website:
|
||||
image: thecodingmachine/nodejs:12-apache
|
||||
environment:
|
||||
STARTUP_COMMAND_1: npm install
|
||||
STARTUP_COMMAND_2: npm run watch &
|
||||
APACHE_DOCUMENT_ROOT: dist/
|
||||
volumes:
|
||||
- ./website:/var/www/html
|
||||
labels:
|
||||
- "traefik.http.routers.website.rule=Host(`workadventure.localhost`)"
|
||||
- "traefik.http.routers.website.entryPoints=web"
|
||||
- "traefik.http.services.website.loadbalancer.server.port=80"
|
||||
- "traefik.http.routers.website-ssl.rule=Host(`workadventure.localhost`)"
|
||||
- "traefik.http.routers.website-ssl.entryPoints=websecure"
|
||||
- "traefik.http.routers.website-ssl.tls=true"
|
||||
- "traefik.http.routers.website-ssl.service=website"
|
||||
|
||||
messages:
|
||||
#image: thecodingmachine/nodejs:14
|
||||
image: thecodingmachine/workadventure-back-base:latest
|
||||
|
@ -33,11 +33,15 @@ services:
|
||||
STARTUP_COMMAND_2: yarn install
|
||||
STUN_SERVER: "stun:stun.l.google.com:19302"
|
||||
TURN_SERVER: "turn:coturn.workadventure.localhost:3478,turns:coturn.workadventure.localhost:5349"
|
||||
DISABLE_NOTIFICATIONS: "$DISABLE_NOTIFICATIONS"
|
||||
SKIP_RENDER_OPTIMIZATIONS: "$SKIP_RENDER_OPTIMIZATIONS"
|
||||
# Use TURN_USER/TURN_PASSWORD if your Coturn server is secured via hard coded credentials.
|
||||
# Advice: you should instead use Coturn REST API along the TURN_STATIC_AUTH_SECRET in the Back container
|
||||
TURN_USER: ""
|
||||
TURN_PASSWORD: ""
|
||||
START_ROOM_URL: "$START_ROOM_URL"
|
||||
MAX_PER_GROUP: "$MAX_PER_GROUP"
|
||||
MAX_USERNAME_LENGTH: "$MAX_USERNAME_LENGTH"
|
||||
command: yarn run start
|
||||
volumes:
|
||||
- ./front:/usr/src/app
|
||||
@ -110,6 +114,7 @@ services:
|
||||
JITSI_URL: $JITSI_URL
|
||||
JITSI_ISS: $JITSI_ISS
|
||||
TURN_STATIC_AUTH_SECRET: SomeStaticAuthSecret
|
||||
MAX_PER_GROUP: "MAX_PER_GROUP"
|
||||
volumes:
|
||||
- ./back:/usr/src/app
|
||||
labels:
|
||||
|
237
docs/maps/api-reference.md
Normal file
237
docs/maps/api-reference.md
Normal file
@ -0,0 +1,237 @@
|
||||
{.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.
|
||||
|
||||
<div>
|
||||
<figure class="figure">
|
||||
<img src="https://workadventu.re/img/docs/trigger_event.png" class="figure-img img-fluid rounded" alt="" />
|
||||
<figcaption class="figure-caption">The `zone` property, applied on a layer</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
|
||||
* **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.
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<img src="https://workadventu.re/img/docs/screen_popup_tiled.png" class="figure-img img-fluid rounded" alt="" />
|
||||
</div>
|
||||
<div class="col">
|
||||
<img src="https://workadventu.re/img/docs/screen_popup_in_game.png" class="figure-img img-fluid rounded" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
```
|
||||
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();
|
||||
```
|
117
docs/maps/scripting.md
Normal file
117
docs/maps/scripting.md
Normal file
@ -0,0 +1,117 @@
|
||||
{.alert.alert-danger style="width:80%"}
|
||||
This feature is "_experimental_". We may apply changes in the near future to the way it works when we gather some feedback.
|
||||
|
||||
{.section-title.accent.text-primary}
|
||||
# Scripting WorkAdventure maps
|
||||
|
||||
Do you want to add a bit of intelligence to your map? Scripts allow you to create maps with special features.
|
||||
|
||||
You can for instance:
|
||||
|
||||
* Create FTUE (First Time User Experience) scenarios where a first-time user will be displayed a notification popup.
|
||||
* Create NPC (non playing characters) and interact with those characters using the chat.
|
||||
* Organize interactions between an iframe and your map (for instance, walking on a special zone might add a product in the cart of an eCommerce website...)
|
||||
* etc...
|
||||
|
||||
Please note that scripting in WorkAdventure is at an early stage of development and that more features might be added in the future. You can actually voice your opinion about useful features by adding [an issue on Github](https://github.com/thecodingmachine/workadventure/issues).
|
||||
|
||||
{.alert.alert-warning}
|
||||
**Beware:** Scripts are executed in the browser of the current user only. Generally speaking, scripts cannot be used to trigger a change that will be displayed on other users screen.
|
||||
|
||||
## Scripting language
|
||||
|
||||
Client-side scripting is done in **Javascript** (or any language that transpiles to Javascript like _Typescript_).
|
||||
|
||||
There are 2 ways you can use the scripting language:
|
||||
|
||||
* **In the map**: By directly referring a Javascript file inside your map, in the `script` property of your map.
|
||||
* **In an iFrame**: By placing your Javascript script into an iFrame, your script can communicate with the WorkAdventure game
|
||||
|
||||
## Adding a script in the map
|
||||
|
||||
Create a `script` property in your map.
|
||||
|
||||
In Tiled, in order to access your map properties, you can click on _"Map > Map properties"_.
|
||||
|
||||
<div>
|
||||
<figure class="figure">
|
||||
<img src="https://workadventu.re/img/docs/admin/map_properties.png" class="figure-img img-fluid rounded" alt="" />
|
||||
<figcaption class="figure-caption">The Map properties menu</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
|
||||
Create a `script` property (a "string"), and put the URL of your script.
|
||||
|
||||
You can put relative URLs. If your script file is next to your map, you can simply write the name of the script file here.
|
||||
|
||||
<div>
|
||||
<figure class="figure">
|
||||
<img src="https://workadventu.re/img/docs/script_property.png" class="figure-img img-fluid rounded" alt="" />
|
||||
<figcaption class="figure-caption">The script property</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
|
||||
Start by testing this with a simple message sent to the chat.
|
||||
|
||||
**script.js**
|
||||
```javascript
|
||||
WA.sendChatMessage('Hello world', 'Mr Robot');
|
||||
```
|
||||
|
||||
The `WA` objects contains a number of useful methods enabling you to interact with the WorkAdventure game. For instance, `WA.sendChatMessage` opens the chat and adds a message in it.
|
||||
|
||||
In your browser console, when you open the map, the chat message should be displayed right away.
|
||||
|
||||
## Adding a script in an iFrame
|
||||
|
||||
In WorkAdventure, you can easily [open an iFrame using the `openWebsite` property on a layer](special-zones). However, by default, the iFrame is not allowed to communicate with WorkAdventure.
|
||||
|
||||
This is done to improve security. In order to be able to execute a script that communicates with WorkAdventure inside an iFrame, you have to **explicitly allow the iFrame to use the "iFrame API"**.
|
||||
|
||||
In order to allow communication with WorkAdventure, you need to add an additional property: `openWebsiteAllowApi`. This property must be _boolean_ and you must set it to "true".
|
||||
|
||||
<div>
|
||||
<figure class="figure">
|
||||
<img src="https://workadventu.re/img/docs/open_website_allow_api.png" class="figure-img img-fluid rounded" alt="" />
|
||||
<figcaption class="figure-caption">The `openWebsiteAllowApi` property</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
|
||||
In your iFrame HTML page, you now need to import the _WorkAdventure client API Javascript library_. This library contains the `WA` object that you can use to communicate with WorkAdventure.
|
||||
|
||||
The library is available at `https://play.workadventu.re/iframe_api.js`.
|
||||
|
||||
_Note:_ if you are using a self-hosted version of WorkAdventure, use `https://[front_domain]/iframe_api.js`
|
||||
|
||||
**iframe.html**
|
||||
```html
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script src="https://play.workadventu.re/iframe_api.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
You can now start by testing this with a simple message sent to the chat.
|
||||
|
||||
**iframe.html**
|
||||
```html
|
||||
...
|
||||
<script>
|
||||
WA.sendChatMessage('Hello world', 'Mr Robot');
|
||||
</script>
|
||||
...
|
||||
```
|
||||
|
||||
Let's now review the complete list of methods available in this `WA` object.
|
||||
|
||||
## Using Typescript
|
||||
|
||||
View the dedicated page about [using Typescript with the scripting API](using-typescript).
|
||||
|
||||
## Available features in the client API
|
||||
|
||||
The list of available functions and features is [available in the API Reference page, with examples](api-reference).
|
@ -25,6 +25,15 @@
|
||||
],
|
||||
"rules": {
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-explicit-any": "error"
|
||||
"@typescript-eslint/no-explicit-any": "error",
|
||||
|
||||
// TODO: remove those ignored rules and write a stronger code!
|
||||
"@typescript-eslint/no-floating-promises": "off",
|
||||
"@typescript-eslint/no-unsafe-call": "off",
|
||||
"@typescript-eslint/restrict-plus-operands": "off",
|
||||
"@typescript-eslint/no-unsafe-assignment": "off",
|
||||
"@typescript-eslint/no-unsafe-return": "off",
|
||||
"@typescript-eslint/no-unsafe-member-access": "off",
|
||||
"@typescript-eslint/restrict-template-expressions": "off"
|
||||
}
|
||||
}
|
||||
|
21
front/dist/index.tmpl.html
vendored
21
front/dist/index.tmpl.html
vendored
@ -38,6 +38,8 @@
|
||||
<div class="main-container" id="main-container">
|
||||
<!-- Create the editor container -->
|
||||
<div id="game" class="game">
|
||||
<div id="svelte-overlay">
|
||||
</div>
|
||||
<div id="game-overlay" class="game-overlay">
|
||||
<div id="main-section" class="main-section">
|
||||
</div>
|
||||
@ -48,20 +50,27 @@
|
||||
<div id="activeCam" class="activeCam">
|
||||
<div id="div-myCamVideo" class="video-container">
|
||||
<video id="myCamVideo" autoplay muted></video>
|
||||
<div id="mySoundMeter" class="sound-progress">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-cam-action">
|
||||
<div id="btn-micro" class="btn-micro">
|
||||
<img id="microphone" src="resources/logos/microphone.svg">
|
||||
<img id="microphone-close" src="resources/logos/microphone-close.svg">
|
||||
<div id="btn-monitor" class="btn-monitor">
|
||||
<img id="monitor" src="resources/logos/monitor.svg">
|
||||
<img id="monitor-close" src="resources/logos/monitor-close.svg">
|
||||
</div>
|
||||
<div id="btn-video" class="btn-video">
|
||||
<img id="cinema" src="resources/logos/cinema.svg">
|
||||
<img id="cinema-close" src="resources/logos/cinema-close.svg">
|
||||
</div>
|
||||
<div id="btn-monitor" class="btn-monitor">
|
||||
<img id="monitor" src="resources/logos/monitor.svg">
|
||||
<img id="monitor-close" src="resources/logos/monitor-close.svg">
|
||||
<div id="btn-micro" class="btn-micro">
|
||||
<img id="microphone" src="resources/logos/microphone.svg">
|
||||
<img id="microphone-close" src="resources/logos/microphone-close.svg">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
160
front/dist/resources/html/CustomCharacterScene.html
vendored
Normal file
160
front/dist/resources/html/CustomCharacterScene.html
vendored
Normal file
@ -0,0 +1,160 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#customizeScene {
|
||||
background: #0000;
|
||||
/*border: 1px solid #ebeeee;*/
|
||||
border-radius: 6px;
|
||||
margin: 10px auto 0;
|
||||
color: #ebeeee;
|
||||
width: 42vw;
|
||||
height: 48vh;
|
||||
/*max-width: 300px;
|
||||
max-height: 48vh;*/
|
||||
overflow: hidden;
|
||||
}
|
||||
#customizeScene h1 {
|
||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
||||
border-bottom: 1px solid #a6abaf;
|
||||
border-radius: 6px 6px 0 0;
|
||||
box-sizing: border-box;
|
||||
color: #727678;
|
||||
display: block;
|
||||
height: 43px;
|
||||
padding-top: 10px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
||||
}
|
||||
#customizeScene input {
|
||||
font-size: 70%;
|
||||
background: linear-gradient(top, #d6d7d7, #dee0e0);
|
||||
border: 1px solid #a1a3a3;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px #fff;
|
||||
box-sizing: border-box;
|
||||
color: #696969;
|
||||
height: 30px;
|
||||
transition: box-shadow 0.3s;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
#customizeScene section {
|
||||
margin: 10px;
|
||||
}
|
||||
#customizeScene section.action {
|
||||
text-align: center;
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
top: 100%;
|
||||
}
|
||||
#customizeScene section.action.action-move {
|
||||
top: 55%;
|
||||
}
|
||||
#customizeScene button {
|
||||
margin: 2px 10px;
|
||||
background-color: black;;
|
||||
color: #ebeeee;
|
||||
border-radius: 7px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
#customizeScene button#customizeSceneFormCancel {
|
||||
background-color: #aca6a600;
|
||||
color: #292929;
|
||||
}
|
||||
#customizeScene section h6,
|
||||
#customizeScene section h5{
|
||||
margin: 1px;
|
||||
}
|
||||
#customizeScene section.text-center{
|
||||
text-align: center;
|
||||
}
|
||||
#customizeScene section a{
|
||||
font-size: 14px;
|
||||
text-decoration: underline;
|
||||
color: #ebeeee;
|
||||
}
|
||||
#customizeScene section a:hover{
|
||||
font-weight: 700;
|
||||
}
|
||||
#customizeScene section p{
|
||||
text-align: left;
|
||||
font-size: 8px;
|
||||
margin: 10px 10px;
|
||||
}
|
||||
#customizeScene section p.err{
|
||||
color: red;
|
||||
text-align: center;
|
||||
}
|
||||
#customizeScene section p.info{
|
||||
display: none;
|
||||
text-align: center;
|
||||
}
|
||||
#customizeScene section input#customizeSceneLink{
|
||||
background-color: #a1a3a3;
|
||||
}
|
||||
#customizeScene section button.customizeSceneButton{
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
top: -8vh;
|
||||
font-size: 10px;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
#customizeScene section button.customizeSceneButton#customizeSceneButtonLeft{
|
||||
left: 0vw;
|
||||
}
|
||||
#customizeScene section button.customizeSceneButton#customizeSceneButtonRight{
|
||||
right: 0vw;
|
||||
}
|
||||
#customizeScene section button.customizeSceneButton#customizeSceneButtonUp{
|
||||
left: calc(2vw + 40px);
|
||||
transform: rotate(90deg);
|
||||
margin-top: -20px;
|
||||
}
|
||||
#customizeScene section button.customizeSceneButton#customizeSceneButtonDown{
|
||||
right: calc(2vw + 40px);
|
||||
transform: rotate(90deg);
|
||||
margin-top: 20px;
|
||||
}
|
||||
#customizeScene section.action img{
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
#customizeScene section.action a#customizeSceneFormBack img{
|
||||
margin-top: -2px;
|
||||
}
|
||||
#customizeScene section.action button#customizeSceneFormSubmit img{
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
@media only screen and (max-width: 600px) {
|
||||
#customizeScene {
|
||||
max-width: 160px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}
|
||||
@media only screen and (max-height: 400px) {
|
||||
#customizeScene section.action {
|
||||
top: 92%;
|
||||
}
|
||||
#customizeScene section.action.action-move {
|
||||
top: 80%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<form id="customizeScene" hidden>
|
||||
<section class="text-center">
|
||||
<h5>Custom your WOKA</h3>
|
||||
</section>
|
||||
<section class="action action-move">
|
||||
<button class="customizeSceneButton" id="customizeSceneButtonLeft"> < </button>
|
||||
<button class="customizeSceneButton" id="customizeSceneButtonRight"> > </button>
|
||||
<!--<button class="customizeSceneButton" id="customizeSceneButtonUp"> < </button>
|
||||
<button class="customizeSceneButton" id="customizeSceneButtonDown"> > </button>-->
|
||||
</section>
|
||||
<section class="action">
|
||||
<a type="submit" id="customizeSceneFormBack">Back <img src="resources/objects/arrow_up.png"/></a>
|
||||
<button type="submit" id="customizeSceneFormSubmit">Next <img src="resources/objects/arrow_up.png"/></button>
|
||||
</section>
|
||||
</form>
|
129
front/dist/resources/html/EnableCameraScene.html
vendored
Normal file
129
front/dist/resources/html/EnableCameraScene.html
vendored
Normal file
@ -0,0 +1,129 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#enableCameraScene {
|
||||
background: #000000;
|
||||
/*border: 1px solid #ebeeee;*/
|
||||
border-radius: 6px;
|
||||
margin: 20px auto 0;
|
||||
color: #ebeeee;
|
||||
max-height: 48vh;
|
||||
width: 42vw;
|
||||
max-width: 300px;
|
||||
overflow: hidden;
|
||||
}
|
||||
#enableCameraScene h1 {
|
||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
||||
border-bottom: 1px solid #a6abaf;
|
||||
border-radius: 6px 6px 0 0;
|
||||
box-sizing: border-box;
|
||||
color: #727678;
|
||||
display: block;
|
||||
height: 43px;
|
||||
padding-top: 10px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
||||
}
|
||||
#enableCameraScene input {
|
||||
font-size: 70%;
|
||||
background: linear-gradient(top, #d6d7d7, #dee0e0);
|
||||
border: 1px solid #a1a3a3;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px #fff;
|
||||
box-sizing: border-box;
|
||||
color: #696969;
|
||||
height: 30px;
|
||||
transition: box-shadow 0.3s;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
#enableCameraScene section.title {
|
||||
position: absolute;
|
||||
top: 1vh;
|
||||
width: 100%;
|
||||
}
|
||||
#enableCameraScene section.action{
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
top: 40vh;
|
||||
width: 100%;
|
||||
}
|
||||
#enableCameraScene button {
|
||||
margin: 10px;
|
||||
background-color: black;;
|
||||
color: #ebeeee;
|
||||
border-radius: 7px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
#enableCameraScene button#enableCameraSceneFormCancel {
|
||||
background-color: #c7c7c700;
|
||||
color: #292929;
|
||||
}
|
||||
#enableCameraScene section h6,
|
||||
#enableCameraScene section h5{
|
||||
margin: 1px;
|
||||
}
|
||||
#enableCameraScene section.text-center{
|
||||
text-align: center;
|
||||
}
|
||||
#enableCameraScene section a{
|
||||
font-size: 8px;
|
||||
text-decoration: underline;
|
||||
color: #ebeeee;
|
||||
}
|
||||
#enableCameraScene section a:hover{
|
||||
font-weight: 700;
|
||||
}
|
||||
#enableCameraScene section p{
|
||||
text-align: left;
|
||||
font-size: 8px;
|
||||
margin: 10px 10px;
|
||||
}
|
||||
#enableCameraScene section p.err{
|
||||
color: red;
|
||||
text-align: center;
|
||||
}
|
||||
#enableCameraScene section p.info{
|
||||
display: none;
|
||||
text-align: center;
|
||||
}
|
||||
#enableCameraScene section input#enableCameraSceneLink{
|
||||
background-color: #a1a3a3;
|
||||
}
|
||||
#enableCameraScene section img{
|
||||
width: 160px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
/*@media only screen and (max-width: 800px),
|
||||
only screen and (max-height: 600px) {
|
||||
#enableCameraScene{
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}*/
|
||||
</style>
|
||||
|
||||
<form id="enableCameraScene" hidden>
|
||||
<!-- FIX me -->
|
||||
<section class="title text-center">
|
||||
<h5>Turn on your camera and microphone</h5>
|
||||
</section>
|
||||
<!--<section class="text-center">
|
||||
<video id="myCamVideoSetup" autoplay muted></video>
|
||||
</section>
|
||||
<section class="text-center">
|
||||
<h5>Select your camera</h3>
|
||||
<select id="camera">
|
||||
</select>
|
||||
</section>
|
||||
<section class="text-center">
|
||||
<h5>Select your michrophone</h3>
|
||||
<select id="michrophone">
|
||||
</select>
|
||||
</section>-->
|
||||
<section class="action">
|
||||
<button type="submit" id="enableCameraSceneFormSubmit">Let's go!</button>
|
||||
</section>
|
||||
</form>
|
134
front/dist/resources/html/SelectCompanionScene.html
vendored
Normal file
134
front/dist/resources/html/SelectCompanionScene.html
vendored
Normal file
@ -0,0 +1,134 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#selectCompanionScene {
|
||||
background: #0000;
|
||||
/*border: 1px solid #ebeeee;*/
|
||||
border-radius: 6px;
|
||||
margin: 10px auto 0;
|
||||
color: #ebeeee;
|
||||
max-height: 40vh;
|
||||
max-width: 300px;
|
||||
width: 40vw;
|
||||
overflow: hidden;
|
||||
}
|
||||
#selectCompanionScene h1 {
|
||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
||||
border-bottom: 1px solid #a6abaf;
|
||||
border-radius: 6px 6px 0 0;
|
||||
box-sizing: border-box;
|
||||
color: #727678;
|
||||
display: block;
|
||||
height: 43px;
|
||||
padding-top: 10px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
||||
}
|
||||
#selectCompanionScene input {
|
||||
font-size: 70%;
|
||||
background: linear-gradient(top, #d6d7d7, #dee0e0);
|
||||
border: 1px solid #a1a3a3;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px #fff;
|
||||
box-sizing: border-box;
|
||||
color: #696969;
|
||||
height: 30px;
|
||||
transition: box-shadow 0.3s;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
#selectCompanionScene section {
|
||||
margin: 10px;
|
||||
}
|
||||
#selectCompanionScene section.action {
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
margin-top: 20vh;
|
||||
}
|
||||
#selectCompanionScene button {
|
||||
margin: 10px 4px;
|
||||
background-color: black;;
|
||||
color: #ebeeee;
|
||||
border-radius: 7px;
|
||||
padding-bottom: 4px;
|
||||
width: 100px;
|
||||
}
|
||||
#selectCompanionScene button#selectCompanionSceneFormCancel {
|
||||
background-color: #aca6a600;
|
||||
color: #292929;
|
||||
}
|
||||
#selectCompanionScene section h6,
|
||||
#selectCompanionScene section h5{
|
||||
margin: 1px;
|
||||
}
|
||||
#selectCompanionScene section.text-center{
|
||||
text-align: center;
|
||||
}
|
||||
#selectCompanionScene section a{
|
||||
font-size: 14px;
|
||||
text-decoration: underline;
|
||||
color: #ebeeee;
|
||||
}
|
||||
#selectCompanionScene section a:hover{
|
||||
font-weight: 700;
|
||||
}
|
||||
#selectCompanionScene section p{
|
||||
text-align: left;
|
||||
font-size: 8px;
|
||||
margin: 10px 10px;
|
||||
}
|
||||
#selectCompanionScene section p.err{
|
||||
color: red;
|
||||
text-align: center;
|
||||
}
|
||||
#selectCompanionScene section p.info{
|
||||
display: none;
|
||||
text-align: center;
|
||||
}
|
||||
#selectCompanionScene section input#selectCompanionSceneLink{
|
||||
background-color: #a1a3a3;
|
||||
}
|
||||
#selectCompanionScene section img{
|
||||
width: 160px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
#selectCompanionScene section button.selectCharacterButton{
|
||||
position: absolute;
|
||||
top: 20vh;
|
||||
margin: 0;
|
||||
width: 25px;
|
||||
}
|
||||
#selectCompanionScene section button.selectCharacterButton#selectCharacterButtonLeft{
|
||||
left: 1vw;
|
||||
}
|
||||
#selectCompanionScene section button.selectCharacterButton#selectCharacterButtonRight{
|
||||
right: 1vw;
|
||||
}
|
||||
#selectCompanionScene section button#selectCompanionSceneFormCustomYourOwnSubmit{
|
||||
font-size: 14px;
|
||||
width: auto;
|
||||
margin-top: -2px;
|
||||
background-color: #ffd700;
|
||||
color: black;
|
||||
}
|
||||
@media only screen and (max-width: 800px),
|
||||
only screen and (max-height: 600px) {
|
||||
#selectCompanionScene{
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<form id="selectCompanionScene" hidden>
|
||||
<section class="text-center">
|
||||
<h5>Select your WOKA</h5>
|
||||
<button class="selectCharacterButton" id="selectCharacterButtonLeft"> < </button>
|
||||
<button class="selectCharacterButton" id="selectCharacterButtonRight"> > </button>
|
||||
</section>
|
||||
<section class="action">
|
||||
<a herf="#" id="selectCompanionSceneFormBack">No companion</a>
|
||||
<button type="submit" id="selectCompanionSceneFormSubmit">Continue</button>
|
||||
</section>
|
||||
</form>
|
27
front/dist/resources/html/gameMenu.html
vendored
27
front/dist/resources/html/gameMenu.html
vendored
@ -1,4 +1,10 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#gameMenu main{
|
||||
margin-top: 15px;
|
||||
}
|
||||
#gameMenu button {
|
||||
background-color: black;
|
||||
color: white;
|
||||
@ -16,6 +22,21 @@
|
||||
width: 32px;
|
||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
||||
}
|
||||
@media only screen and (max-height: 700px) {
|
||||
#gameMenu main {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-end;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 0;
|
||||
}
|
||||
#gameMenu section{
|
||||
margin: 2px;
|
||||
}
|
||||
section#socialLinks{
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="gameMenu" hidden>
|
||||
@ -30,9 +51,15 @@
|
||||
<section>
|
||||
<button id="changeSkinButton">Edit skin</button>
|
||||
</section>
|
||||
<section>
|
||||
<button id="changeCompanionButton">Edit companion</button>
|
||||
</section>
|
||||
<section>
|
||||
<button id="editGameSettingsButton">Settings</button>
|
||||
</section>
|
||||
<section>
|
||||
<button id="toggleFullscreen">Toggle fullscreen</button>
|
||||
</section>
|
||||
<section>
|
||||
<button id="sparkButton">Create map</button>
|
||||
</section>
|
||||
|
13
front/dist/resources/html/gameMenuIcon.html
vendored
13
front/dist/resources/html/gameMenuIcon.html
vendored
@ -1,19 +1,26 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#menuIcon button {
|
||||
background-color: black;
|
||||
color: white;
|
||||
border-radius: 7px;
|
||||
height: 28px;
|
||||
width: 34px;
|
||||
padding: 2px 8px;
|
||||
}
|
||||
#menuIcon button img{
|
||||
width: 14px;
|
||||
padding-top: 3px;
|
||||
padding-top: 0;
|
||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
||||
}
|
||||
#menuIcon section {
|
||||
margin: 10px;
|
||||
}
|
||||
@media only screen and (max-height: 700px) {
|
||||
#menuIcon section {
|
||||
margin: 2px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<main id="menuIcon" hidden>
|
||||
<section>
|
||||
|
16
front/dist/resources/html/gameQualityMenu.html
vendored
16
front/dist/resources/html/gameQualityMenu.html
vendored
@ -1,11 +1,14 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#gameQuality {
|
||||
background: #eceeee;
|
||||
border: 1px solid #42464b;
|
||||
border-radius: 6px;
|
||||
height: 257px;
|
||||
margin: 20px auto 0;
|
||||
width: 298px;
|
||||
width: 50vw;
|
||||
max-width: 300px;
|
||||
}
|
||||
#gameQuality .cautiousText {
|
||||
font-size: 50%;
|
||||
@ -33,7 +36,7 @@
|
||||
color: #696969;
|
||||
height: 30px;
|
||||
transition: box-shadow 0.3s;
|
||||
width: 240px;
|
||||
width: 100%;
|
||||
}
|
||||
#gameQuality section {
|
||||
margin: 10px;
|
||||
@ -42,12 +45,11 @@
|
||||
text-align: center;
|
||||
}
|
||||
#gameQuality button {
|
||||
margin-top: 10px;
|
||||
margin: 10px;
|
||||
background-color: black;
|
||||
color: white;
|
||||
border-radius: 7px;
|
||||
padding-bottom: 4px;
|
||||
width: 60px;
|
||||
}
|
||||
#gameQuality button#gameQualityFormCancel {
|
||||
background-color: #c7c7c700;
|
||||
@ -57,7 +59,7 @@
|
||||
|
||||
<form id="gameQuality" hidden>
|
||||
<section>
|
||||
<h3>Game quality</h3>
|
||||
<h5>Game quality</h3>
|
||||
<p class="cautiousText">(Editing these settings will restart the game)</p>
|
||||
<select id="select-game-quality">
|
||||
<option value="120">High video quality (120 fps)</option>
|
||||
@ -67,7 +69,7 @@
|
||||
</select>
|
||||
</section>
|
||||
<section>
|
||||
<h3>Video quality</h3>
|
||||
<h5>Video quality</h3>
|
||||
<select id="select-video-quality">
|
||||
<option value="30">High video quality (30 fps)</option>
|
||||
<option value="20">Medium video quality (20 fps, recommended)</option>
|
||||
|
3
front/dist/resources/html/gameReport.html
vendored
3
front/dist/resources/html/gameReport.html
vendored
@ -1,4 +1,7 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#gameReport {
|
||||
background: #eceeee;
|
||||
border: 1px solid #42464b;
|
||||
|
11
front/dist/resources/html/gameShare.html
vendored
11
front/dist/resources/html/gameShare.html
vendored
@ -1,11 +1,14 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#gameShare {
|
||||
background: #eceeee;
|
||||
border: 1px solid #42464b;
|
||||
border-radius: 6px;
|
||||
margin: 20px auto 0;
|
||||
width: 298px;
|
||||
height: 150px;
|
||||
width: 50vw;
|
||||
max-width: 400px;
|
||||
}
|
||||
#gameShare h1 {
|
||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
||||
@ -40,7 +43,7 @@
|
||||
margin: 0;
|
||||
}
|
||||
#gameShare button {
|
||||
margin-top: 10px;
|
||||
margin: 10px;
|
||||
background-color: black;
|
||||
color: white;
|
||||
border-radius: 7px;
|
||||
@ -66,7 +69,7 @@
|
||||
}
|
||||
#gameShare section p{
|
||||
font-size: 8px;
|
||||
margin: 0px 70px;
|
||||
margin: 0;
|
||||
}
|
||||
#gameShare section p.err{
|
||||
color: red;
|
||||
|
@ -1,11 +1,17 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#helpCameraSettings {
|
||||
background: #eceeee;
|
||||
border: 1px solid #42464b;
|
||||
border-radius: 6px;
|
||||
margin: 10px auto 0;
|
||||
margin: 25px auto 0;
|
||||
width: 400px;
|
||||
height: 370px;
|
||||
max-height: calc(48vh - 50px);
|
||||
max-width: 48vw;
|
||||
overflow: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
#helpCameraSettings h1 {
|
||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
||||
@ -20,18 +26,6 @@
|
||||
text-align: center;
|
||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
||||
}
|
||||
#helpCameraSettings input {
|
||||
font-size: 70%;
|
||||
background: linear-gradient(top, #d6d7d7, #dee0e0);
|
||||
border: 1px solid #a1a3a3;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px #fff;
|
||||
box-sizing: border-box;
|
||||
color: #696969;
|
||||
height: 30px;
|
||||
transition: box-shadow 0.3s;
|
||||
width: 100%;
|
||||
}
|
||||
#helpCameraSettings section {
|
||||
margin: 10px;
|
||||
}
|
||||
@ -40,7 +34,7 @@
|
||||
margin: 0;
|
||||
}
|
||||
#helpCameraSettings button {
|
||||
margin-top: 10px;
|
||||
margin: 10px 4px;
|
||||
background-color: black;
|
||||
color: white;
|
||||
border-radius: 7px;
|
||||
@ -51,9 +45,8 @@
|
||||
color: #292929;
|
||||
}
|
||||
#helpCameraSettings section a{
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
margin: 0 6px;
|
||||
text-decoration: underline;
|
||||
color: black;
|
||||
}
|
||||
#helpCameraSettings section h6,
|
||||
@ -67,6 +60,9 @@
|
||||
font-size: 8px;
|
||||
margin: 0px 20px;
|
||||
}
|
||||
#helpCameraSettings section p a{
|
||||
font-size: 8px;
|
||||
}
|
||||
#helpCameraSettings section p.err{
|
||||
color: #ff0000;
|
||||
}
|
||||
@ -81,6 +77,12 @@
|
||||
width: 200px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
@media only screen and (max-width: 800px),
|
||||
only screen and (max-height: 600px) {
|
||||
#helpCameraSettings{
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<form id="helpCameraSettings" hidden>
|
||||
@ -96,8 +98,12 @@
|
||||
<p>If you prefer to continue without allowing camera and microphone access, click on Continue</p>
|
||||
<p id='browserHelpSetting'></p>
|
||||
</section>
|
||||
<!--<section class="text-center">
|
||||
<p>If your problem persist, please contact us: <a id="mailto" href="mailto:workadventure@thecodingmachine.com?subject=Support camera and microphone settings" target="_blank"> workadventure@thecodingmachine.com</a>.</p>
|
||||
</section>-->
|
||||
</section>
|
||||
<section class="action">
|
||||
<button type="submit" id="helpCameraSettingsFormRefresh">Refresh</button>
|
||||
<a href="#" id="helpCameraSettingsFormRefresh">Refresh</a>
|
||||
<button type="submit" id="helpCameraSettingsFormContinue">Continue</button>
|
||||
</section>
|
||||
</form>
|
||||
|
120
front/dist/resources/html/loginScene.html
vendored
Normal file
120
front/dist/resources/html/loginScene.html
vendored
Normal file
@ -0,0 +1,120 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#loginScene {
|
||||
background: #000000;
|
||||
/*border: 1px solid #ebeeee;*/
|
||||
border-radius: 6px;
|
||||
margin: 20px auto 0;
|
||||
width: 90%;
|
||||
max-width: 200px;
|
||||
color: #ebeeee;
|
||||
max-height: 40vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
#loginScene h1 {
|
||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
||||
border-bottom: 1px solid #a6abaf;
|
||||
border-radius: 6px 6px 0 0;
|
||||
box-sizing: border-box;
|
||||
color: #727678;
|
||||
display: block;
|
||||
height: 43px;
|
||||
padding-top: 10px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
||||
}
|
||||
#loginScene input {
|
||||
font-size: 70%;
|
||||
background: linear-gradient(top, #d6d7d7, #dee0e0);
|
||||
border: 1px solid #a1a3a3;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px #fff;
|
||||
box-sizing: border-box;
|
||||
color: #696969;
|
||||
height: 30px;
|
||||
transition: box-shadow 0.3s;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
#loginScene section {
|
||||
margin: 10px;
|
||||
}
|
||||
#loginScene section.action{
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
#loginScene button {
|
||||
margin: 10px;
|
||||
background-color: black;;
|
||||
color: #ebeeee;
|
||||
border-radius: 7px;
|
||||
padding-bottom: 4px;
|
||||
width: 100px;
|
||||
}
|
||||
#loginScene button#loginSceneFormCancel {
|
||||
background-color: #c7c7c700;
|
||||
color: #292929;
|
||||
}
|
||||
#loginScene section h6,
|
||||
#loginScene section h5{
|
||||
margin: 1px;
|
||||
}
|
||||
#loginScene section.text-center{
|
||||
text-align: center;
|
||||
}
|
||||
#loginScene section a{
|
||||
font-size: 8px;
|
||||
text-decoration: underline;
|
||||
color: #ebeeee;
|
||||
}
|
||||
#loginScene section a:hover{
|
||||
font-weight: 700;
|
||||
}
|
||||
#loginScene section p{
|
||||
text-align: left;
|
||||
font-size: 8px;
|
||||
margin: 10px 10px;
|
||||
}
|
||||
#loginScene section p.err{
|
||||
color: red;
|
||||
text-align: center;
|
||||
}
|
||||
#loginScene section p.info{
|
||||
display: none;
|
||||
text-align: center;
|
||||
}
|
||||
#loginScene section input#loginSceneLink{
|
||||
background-color: #a1a3a3;
|
||||
}
|
||||
#loginScene section img{
|
||||
width: 160px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
@media only screen and (max-width: 800px),
|
||||
only screen and (max-height: 600px) {
|
||||
#loginScene{
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<form id="loginScene" hidden>
|
||||
<section class="text-center">
|
||||
<img src="resources/logos/logo.png">
|
||||
</section>
|
||||
<section class="text-center">
|
||||
<h5>Enter your name</h5>
|
||||
<p class="info">9 chars maximum</p>
|
||||
<p class="err" id="errorLoginScene"></p>
|
||||
</section>
|
||||
<section>
|
||||
<input type="text" name="email" id="loginSceneName">
|
||||
<p>By continuing, you are agreeing our <a href="https://workadventu.re/terms-of-use" target="_blank">terms of use</a>, <a href="https://workadventu.re/privacy-policy" target="_blank">privacy policy</a> and <a href="https://workadventu.re/cookie-policy" target="_blank">cookie policy</a>.</p>
|
||||
</section>
|
||||
<section class="action">
|
||||
<button type="submit" id="loginSceneFormSubmit">Continue</button>
|
||||
</section>
|
||||
</form>
|
142
front/dist/resources/html/selectCharacterScene.html
vendored
Normal file
142
front/dist/resources/html/selectCharacterScene.html
vendored
Normal file
@ -0,0 +1,142 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#selectCharacterScene {
|
||||
background: #0000;
|
||||
/*border: 1px solid #ebeeee;*/
|
||||
border-radius: 6px;
|
||||
margin: 10px auto 0;
|
||||
color: #ebeeee;
|
||||
max-height: 48vh;
|
||||
max-width: 300px;
|
||||
width: 40vw;
|
||||
overflow: hidden;
|
||||
}
|
||||
#selectCharacterScene h1 {
|
||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
||||
border-bottom: 1px solid #a6abaf;
|
||||
border-radius: 6px 6px 0 0;
|
||||
box-sizing: border-box;
|
||||
color: #727678;
|
||||
display: block;
|
||||
height: 43px;
|
||||
padding-top: 10px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
||||
}
|
||||
#selectCharacterScene input {
|
||||
font-size: 70%;
|
||||
background: linear-gradient(top, #d6d7d7, #dee0e0);
|
||||
border: 1px solid #a1a3a3;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px #fff;
|
||||
box-sizing: border-box;
|
||||
color: #696969;
|
||||
height: 30px;
|
||||
transition: box-shadow 0.3s;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
#selectCharacterScene section {
|
||||
margin: 10px;
|
||||
}
|
||||
#selectCharacterScene section.action {
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
margin-top: 28vh;
|
||||
}
|
||||
#selectCharacterScene button {
|
||||
margin: 10px 0px;
|
||||
background-color: black;;
|
||||
color: #ebeeee;
|
||||
border-radius: 7px;
|
||||
padding-bottom: 4px;
|
||||
width: 100px;
|
||||
}
|
||||
#selectCharacterScene button#selectCharacterSceneFormCancel {
|
||||
background-color: #aca6a600;
|
||||
color: #292929;
|
||||
}
|
||||
#selectCharacterScene section h6,
|
||||
#selectCharacterScene section h5{
|
||||
margin: 1px;
|
||||
}
|
||||
#selectCharacterScene section.text-center{
|
||||
text-align: center;
|
||||
}
|
||||
#selectCharacterScene section a{
|
||||
font-size: 8px;
|
||||
text-decoration: underline;
|
||||
color: #ebeeee;
|
||||
}
|
||||
#selectCharacterScene section a:hover{
|
||||
font-weight: 700;
|
||||
}
|
||||
#selectCharacterScene section p{
|
||||
text-align: left;
|
||||
font-size: 8px;
|
||||
margin: 10px 10px;
|
||||
}
|
||||
#selectCharacterScene section p.err{
|
||||
color: red;
|
||||
text-align: center;
|
||||
}
|
||||
#selectCharacterScene section p.info{
|
||||
display: none;
|
||||
text-align: center;
|
||||
}
|
||||
#selectCharacterScene section input#selectCharacterSceneLink{
|
||||
background-color: #a1a3a3;
|
||||
}
|
||||
#selectCharacterScene section img{
|
||||
width: 160px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
#selectCharacterScene section button.selectCharacterButton{
|
||||
position: absolute;
|
||||
top: 20vh;
|
||||
margin: 0;
|
||||
width: 25px;
|
||||
}
|
||||
#selectCharacterScene section button.selectCharacterButton#selectCharacterButtonLeft{
|
||||
display: none;
|
||||
left: 1vw;
|
||||
}
|
||||
#selectCharacterScene section button.selectCharacterButton#selectCharacterButtonRight{
|
||||
display: none;
|
||||
right: 1vw;
|
||||
}
|
||||
#selectCharacterScene section button#selectCharacterSceneFormCustomYourOwnSubmit{
|
||||
font-size: 14px;
|
||||
width: auto;
|
||||
margin-top: -2px;
|
||||
background-color: #ffd700;
|
||||
color: black;
|
||||
}
|
||||
@media only screen and (max-width: 800px),
|
||||
only screen and (max-height: 600px) {
|
||||
#selectCharacterScene{
|
||||
overflow-y: scroll;
|
||||
}
|
||||
#selectCharacterScene section button.selectCharacterButton#selectCharacterButtonRight{
|
||||
display: block;
|
||||
}
|
||||
#selectCharacterScene section button.selectCharacterButton#selectCharacterButtonLeft{
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<form id="selectCharacterScene" hidden>
|
||||
<section class="text-center">
|
||||
<h5>Select your WOKA</h5>
|
||||
<button class="selectCharacterButton" id="selectCharacterButtonLeft"> < </button>
|
||||
<button class="selectCharacterButton" id="selectCharacterButtonRight"> > </button>
|
||||
</section>
|
||||
<section class="action">
|
||||
<button type="submit" id="selectCharacterSceneFormSubmit">Continue</button>
|
||||
<button type="submit" id="selectCharacterSceneFormCustomYourOwnSubmit">Custom your WOKA</button>
|
||||
</section>
|
||||
</form>
|
@ -1,4 +1,7 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#warningMain {
|
||||
border-radius: 5px;
|
||||
height: 100px;
|
||||
|
BIN
front/dist/resources/logos/logo-WA-min.png
vendored
Normal file
BIN
front/dist/resources/logos/logo-WA-min.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
BIN
front/dist/resources/objects/joystickSplitted.png
vendored
Normal file
BIN
front/dist/resources/objects/joystickSplitted.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
BIN
front/dist/resources/objects/play_button.png
vendored
BIN
front/dist/resources/objects/play_button.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 969 B |
BIN
front/dist/resources/objects/smallHandleFilledGrey.png
vendored
Normal file
BIN
front/dist/resources/objects/smallHandleFilledGrey.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
2
front/dist/resources/style/index.scss
vendored
2
front/dist/resources/style/index.scss
vendored
@ -1,2 +0,0 @@
|
||||
@import "cowebsite.scss";
|
||||
@import "style.css";
|
186
front/dist/static/images/favicons/manifest.json
vendored
186
front/dist/static/images/favicons/manifest.json
vendored
@ -1,41 +1,149 @@
|
||||
{
|
||||
"name": "App",
|
||||
"icons": [
|
||||
{
|
||||
"src": "\/android-icon-36x36.png",
|
||||
"sizes": "36x36",
|
||||
"type": "image\/png",
|
||||
"density": "0.75"
|
||||
},
|
||||
{
|
||||
"src": "\/android-icon-48x48.png",
|
||||
"sizes": "48x48",
|
||||
"type": "image\/png",
|
||||
"density": "1.0"
|
||||
},
|
||||
{
|
||||
"src": "\/android-icon-72x72.png",
|
||||
"sizes": "72x72",
|
||||
"type": "image\/png",
|
||||
"density": "1.5"
|
||||
},
|
||||
{
|
||||
"src": "\/android-icon-96x96.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image\/png",
|
||||
"density": "2.0"
|
||||
},
|
||||
{
|
||||
"src": "\/android-icon-144x144.png",
|
||||
"sizes": "144x144",
|
||||
"type": "image\/png",
|
||||
"density": "3.0"
|
||||
},
|
||||
{
|
||||
"src": "\/android-icon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image\/png",
|
||||
"density": "4.0"
|
||||
}
|
||||
]
|
||||
"short_name": "WA",
|
||||
"name": "WorkAdventure",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/static/images/favicons/apple-icon-57x57.png",
|
||||
"sizes": "57x57",
|
||||
"type": "image\/png"
|
||||
},
|
||||
{
|
||||
"src": "/static/images/favicons/apple-icon-60x60.png",
|
||||
"sizes": "60x60",
|
||||
"type": "image\/png"
|
||||
},
|
||||
{
|
||||
"src": "/static/images/favicons/apple-icon-72x72.png",
|
||||
"sizes": "72x72",
|
||||
"type": "image\/png"
|
||||
},
|
||||
{
|
||||
"src": "/static/images/favicons/apple-icon-76x76.png",
|
||||
"sizes": "76x76",
|
||||
"type": "image\/png"
|
||||
},
|
||||
{
|
||||
"src": "/static/images/favicons/apple-icon-114x114.png",
|
||||
"sizes": "114x114",
|
||||
"type": "image\/png"
|
||||
},
|
||||
{
|
||||
"src": "/static/images/favicons/apple-icon-120x120.png",
|
||||
"sizes": "120x120",
|
||||
"type": "image\/png"
|
||||
},
|
||||
{
|
||||
"src": "/static/images/favicons/apple-icon-144x144.png",
|
||||
"sizes": "144x144",
|
||||
"type": "image\/png"
|
||||
},
|
||||
{
|
||||
"src": "/static/images/favicons/apple-icon-152x152.png",
|
||||
"sizes": "152x152",
|
||||
"type": "image\/png"
|
||||
},
|
||||
{
|
||||
"src": "/static/images/favicons/apple-icon-180x180.png",
|
||||
"sizes": "180x180",
|
||||
"type": "image\/png"
|
||||
},
|
||||
{
|
||||
"src": "/static/images/favicons/android-icon-36x36.png",
|
||||
"sizes": "36x36",
|
||||
"type": "image\/png",
|
||||
"density": "0.75"
|
||||
},
|
||||
{
|
||||
"src": "/static/images/favicons/android-icon-48x48.png",
|
||||
"sizes": "48x48",
|
||||
"type": "image\/png",
|
||||
"density": "1.0"
|
||||
},
|
||||
{
|
||||
"src": "/static/images/favicons/android-icon-72x72.png",
|
||||
"sizes": "72x72",
|
||||
"type": "image\/png",
|
||||
"density": "1.5"
|
||||
},
|
||||
|
||||
{
|
||||
"src": "/static/images/favicons/favicon-16x16.png",
|
||||
"sizes": "16x16",
|
||||
"type": "image\/png",
|
||||
"density": "1"
|
||||
},
|
||||
{
|
||||
"src": "/static/images/favicons/favicon-32x32.png",
|
||||
"sizes": "32x32",
|
||||
"type": "image\/png",
|
||||
"density": "1.5"
|
||||
},
|
||||
{
|
||||
"src": "/static/images/favicons/favicon-96x96.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image\/png",
|
||||
"density": "2.0"
|
||||
},
|
||||
|
||||
{
|
||||
"src": "/static/images/favicons/android-icon-36x36.png",
|
||||
"sizes": "36x36",
|
||||
"type": "image\/png",
|
||||
"density": "1"
|
||||
},
|
||||
{
|
||||
"src": "/static/images/favicons/android-icon-48x48.png",
|
||||
"sizes": "48x48",
|
||||
"type": "image\/png",
|
||||
"density": "1"
|
||||
},
|
||||
{
|
||||
"src": "/static/images/favicons/android-icon-72x72.png",
|
||||
"sizes": "72x72",
|
||||
"type": "image\/png",
|
||||
"density": "1.5"
|
||||
},
|
||||
{
|
||||
"src": "/static/images/favicons/android-icon-96x96.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image\/png",
|
||||
"density": "2.0"
|
||||
},
|
||||
{
|
||||
"src": "/static/images/favicons/android-icon-144x144.png",
|
||||
"sizes": "144x144",
|
||||
"type": "image\/png",
|
||||
"density": "3.0"
|
||||
},
|
||||
{
|
||||
"src": "/static/images/favicons/android-icon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image\/png",
|
||||
"density": "4.0"
|
||||
}
|
||||
],
|
||||
"start_url": "/",
|
||||
"background_color": "#000000",
|
||||
"display_override": ["window-control-overlay", "minimal-ui"],
|
||||
"display": "standalone",
|
||||
"scope": "/",
|
||||
"theme_color": "#000000",
|
||||
"shortcuts": [
|
||||
{
|
||||
"name": "WorkAdventures",
|
||||
"short_name": "WA",
|
||||
"description": "WorkAdventure application",
|
||||
"url": "/",
|
||||
"icons": [{ "src": "/static/images/favicons/android-icon-192x192.png", "sizes": "192x192" }]
|
||||
}
|
||||
],
|
||||
"description": "WorkAdventure application",
|
||||
"screenshots": [],
|
||||
"related_applications": [{
|
||||
"platform": "web",
|
||||
"url": "https://workadventu.re"
|
||||
}, {
|
||||
"platform": "play",
|
||||
"url": "https://play.workadventu.re"
|
||||
}]
|
||||
}
|
@ -4,25 +4,34 @@
|
||||
"main": "index.js",
|
||||
"license": "SEE LICENSE IN LICENSE.txt",
|
||||
"devDependencies": {
|
||||
"@tsconfig/svelte": "^1.0.10",
|
||||
"@types/google-protobuf": "^3.7.3",
|
||||
"@types/jasmine": "^3.5.10",
|
||||
"@types/mini-css-extract-plugin": "^1.4.3",
|
||||
"@types/node": "^15.3.0",
|
||||
"@types/quill": "^1.3.7",
|
||||
"@typescript-eslint/eslint-plugin": "^2.26.0",
|
||||
"@typescript-eslint/parser": "^2.26.0",
|
||||
"css-loader": "^5.1.3",
|
||||
"eslint": "^6.8.0",
|
||||
"html-webpack-plugin": "^4.3.0",
|
||||
"@types/webpack-dev-server": "^3.11.4",
|
||||
"@typescript-eslint/eslint-plugin": "^4.23.0",
|
||||
"@typescript-eslint/parser": "^4.23.0",
|
||||
"css-loader": "^5.2.4",
|
||||
"eslint": "^7.26.0",
|
||||
"fork-ts-checker-webpack-plugin": "^6.2.9",
|
||||
"html-webpack-plugin": "^5.3.1",
|
||||
"jasmine": "^3.5.0",
|
||||
"mini-css-extract-plugin": "^1.3.9",
|
||||
"sass": "^1.32.8",
|
||||
"sass-loader": "10.1.1",
|
||||
"ts-loader": "^6.2.2",
|
||||
"ts-node": "^8.10.2",
|
||||
"typescript": "^3.8.3",
|
||||
"webpack": "^4.42.1",
|
||||
"webpack-cli": "^3.3.11",
|
||||
"webpack-dev-server": "^3.10.3",
|
||||
"webpack-merge": "^4.2.2"
|
||||
"mini-css-extract-plugin": "^1.6.0",
|
||||
"node-polyfill-webpack-plugin": "^1.1.2",
|
||||
"sass": "^1.32.12",
|
||||
"sass-loader": "^11.1.0",
|
||||
"svelte": "^3.38.2",
|
||||
"svelte-loader": "^3.1.1",
|
||||
"svelte-preprocess": "^4.7.3",
|
||||
"ts-loader": "^9.1.2",
|
||||
"ts-node": "^9.1.1",
|
||||
"tsconfig-paths": "^3.9.0",
|
||||
"typescript": "^4.2.4",
|
||||
"webpack": "^5.37.0",
|
||||
"webpack-cli": "^4.7.0",
|
||||
"webpack-dev-server": "^3.11.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/simple-peer": "^9.6.0",
|
||||
@ -30,18 +39,18 @@
|
||||
"axios": "^0.21.1",
|
||||
"generic-type-guard": "^3.2.0",
|
||||
"google-protobuf": "^3.13.0",
|
||||
"phaser": "3.24.1",
|
||||
"phaser": "^3.54.0",
|
||||
"phaser3-rex-plugins": "^1.1.42",
|
||||
"queue-typescript": "^1.0.1",
|
||||
"quill": "^1.3.7",
|
||||
"quill": "1.3.6",
|
||||
"rxjs": "^6.6.3",
|
||||
"simple-peer": "^9.6.2",
|
||||
"socket.io-client": "^2.3.0",
|
||||
"webpack-require-http": "^0.4.3"
|
||||
"socket.io-client": "^2.3.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "webpack-dev-server --open",
|
||||
"build": "webpack --config webpack.prod.js",
|
||||
"test": "ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json",
|
||||
"start": "TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" webpack serve --open",
|
||||
"build": "TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" NODE_ENV=production webpack",
|
||||
"test": "TS_NODE_PROJECT=\"tsconfig-for-jasmine.json\" ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json",
|
||||
"lint": "node_modules/.bin/eslint src/ . --ext .ts",
|
||||
"fix": "node_modules/.bin/eslint --fix src/ . --ext .ts"
|
||||
}
|
||||
|
1
front/packages/iframe-api-typings/.gitignore
vendored
Normal file
1
front/packages/iframe-api-typings/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
iframe_api.d.ts
|
0
front/packages/iframe-api-typings/.npmignore
Normal file
0
front/packages/iframe-api-typings/.npmignore
Normal file
27
front/packages/iframe-api-typings/README.md
Normal file
27
front/packages/iframe-api-typings/README.md
Normal file
@ -0,0 +1,27 @@
|
||||
<h1 align="center">WorkAdventure - IFrame API typings for Typescript</h1>
|
||||
|
||||
<p align="center">This package contains Typescript typings for <a href="https://workadventu.re/map-building/scripting">WorkAdventure's map scripting API</a></p>
|
||||
|
||||
<hr/>
|
||||
|
||||
[WorkAdventure](https://workadventu.re) comes with a scripting API. Using this API, you can add some intelligence to your map.
|
||||
You use this API by loading an external script directly from WorkAdventure (at https://play.workadventu.re/iframe_api.js), or this script is loaded
|
||||
for you if you are using the "script" property of a map.
|
||||
|
||||
This project contains Typescript typings for the `WA` object provided by this script.
|
||||
|
||||
## Usage
|
||||
|
||||
This package is only useful if you are using Typescript to script your WorkAdventure maps.
|
||||
|
||||
## Download & Installation
|
||||
|
||||
```shell
|
||||
$ npm install @workadventure/iframe-api-typings
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```shell
|
||||
$ yarn add @workadventure/iframe-api-typings
|
||||
```
|
1
front/packages/iframe-api-typings/iframe_api.js
Normal file
1
front/packages/iframe-api-typings/iframe_api.js
Normal file
@ -0,0 +1 @@
|
||||
// This file is voluntarily empty.
|
13
front/packages/iframe-api-typings/package.json
Normal file
13
front/packages/iframe-api-typings/package.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "@workadventure/iframe-api-typings",
|
||||
"version": "VERSION_PLACEHOLDER",
|
||||
"description": "Typescript typings for WorkAdventure iFrame API",
|
||||
"main": "iframe_api.js",
|
||||
"types": "iframe_api.d.ts",
|
||||
"repository": "https://github.com/thecodingmachine/workadventure/",
|
||||
"author": "David Négrier <d.negrier@thecodingmachine.com>",
|
||||
"license": "MIT",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
@ -1,8 +1,7 @@
|
||||
import {HtmlUtils} from "../WebRtc/HtmlUtils";
|
||||
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
||||
import {RoomConnection} from "../Connexion/RoomConnection";
|
||||
import {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
|
||||
import {ADMIN_URL} from "../Enum/EnvironmentVariable";
|
||||
import type {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
||||
import type {RoomConnection} from "../Connexion/RoomConnection";
|
||||
import type {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
|
||||
import {AdminMessageEventTypes} from "../Connexion/AdminMessagesService";
|
||||
|
||||
export const CLASS_CONSOLE_MESSAGE = 'main-console';
|
||||
@ -162,42 +161,46 @@ export class ConsoleGlobalMessageManager {
|
||||
this.divMessageConsole.appendChild(section);
|
||||
|
||||
(async () => {
|
||||
// Start loading CSS
|
||||
const cssPromise = ConsoleGlobalMessageManager.loadCss();
|
||||
// Import quill
|
||||
const Quill:any = await import("quill"); // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
// Wait for CSS to be loaded
|
||||
await cssPromise;
|
||||
try{
|
||||
// Start loading CSS
|
||||
const cssPromise = ConsoleGlobalMessageManager.loadCss();
|
||||
// Import quill
|
||||
const {default: Quill}:any = await import("quill"); // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
// Wait for CSS to be loaded
|
||||
await cssPromise;
|
||||
|
||||
const toolbarOptions = [
|
||||
['bold', 'italic', 'underline', 'strike'], // toggled buttons
|
||||
['blockquote', 'code-block'],
|
||||
const toolbarOptions = [
|
||||
['bold', 'italic', 'underline', 'strike'], // toggled buttons
|
||||
['blockquote', 'code-block'],
|
||||
|
||||
[{'header': 1}, {'header': 2}], // custom button values
|
||||
[{'list': 'ordered'}, {'list': 'bullet'}],
|
||||
[{'script': 'sub'}, {'script': 'super'}], // superscript/subscript
|
||||
[{'indent': '-1'}, {'indent': '+1'}], // outdent/indent
|
||||
[{'direction': 'rtl'}], // text direction
|
||||
[{'header': 1}, {'header': 2}], // custom button values
|
||||
[{'list': 'ordered'}, {'list': 'bullet'}],
|
||||
[{'script': 'sub'}, {'script': 'super'}], // superscript/subscript
|
||||
[{'indent': '-1'}, {'indent': '+1'}], // outdent/indent
|
||||
[{'direction': 'rtl'}], // text direction
|
||||
|
||||
[{'size': ['small', false, 'large', 'huge']}], // custom dropdown
|
||||
[{'header': [1, 2, 3, 4, 5, 6, false]}],
|
||||
[{'size': ['small', false, 'large', 'huge']}], // custom dropdown
|
||||
[{'header': [1, 2, 3, 4, 5, 6, false]}],
|
||||
|
||||
[{'color': []}, {'background': []}], // dropdown with defaults from theme
|
||||
[{'font': []}],
|
||||
[{'align': []}],
|
||||
[{'color': []}, {'background': []}], // dropdown with defaults from theme
|
||||
[{'font': []}],
|
||||
[{'align': []}],
|
||||
|
||||
['clean'],
|
||||
['clean'],
|
||||
|
||||
['link', 'image', 'video']
|
||||
// remove formatting button
|
||||
];
|
||||
['link', 'image', 'video']
|
||||
// remove formatting button
|
||||
];
|
||||
|
||||
new Quill(`#${INPUT_CONSOLE_MESSAGE}`, {
|
||||
theme: 'snow',
|
||||
modules: {
|
||||
toolbar: toolbarOptions
|
||||
},
|
||||
});
|
||||
new Quill(`#${INPUT_CONSOLE_MESSAGE}`, {
|
||||
theme: 'snow',
|
||||
modules: {
|
||||
toolbar: toolbarOptions
|
||||
},
|
||||
});
|
||||
}catch(err){
|
||||
console.error(err);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import {HtmlUtils} from "./../WebRtc/HtmlUtils";
|
||||
import {AUDIO_TYPE, MESSAGE_TYPE} from "./ConsoleGlobalMessageManager";
|
||||
import {PUSHER_URL, UPLOADER_URL} from "../Enum/EnvironmentVariable";
|
||||
import {RoomConnection} from "../Connexion/RoomConnection";
|
||||
import {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
|
||||
import type {RoomConnection} from "../Connexion/RoomConnection";
|
||||
import type {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
|
||||
|
||||
export class GlobalMessageManager {
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {TypeMessageInterface} from "./UserMessageManager";
|
||||
import type {TypeMessageInterface} from "./UserMessageManager";
|
||||
import {HtmlUtils} from "../WebRtc/HtmlUtils";
|
||||
|
||||
let modalTimeOut : NodeJS.Timeout;
|
||||
|
@ -1,7 +1,55 @@
|
||||
export interface IframeEvent {
|
||||
type: string;
|
||||
data: unknown;
|
||||
|
||||
|
||||
import type { ButtonClickedEvent } from './ButtonClickedEvent';
|
||||
import type { ChatEvent } from './ChatEvent';
|
||||
import type { ClosePopupEvent } from './ClosePopupEvent';
|
||||
import type { EnterLeaveEvent } from './EnterLeaveEvent';
|
||||
import type { GoToPageEvent } from './GoToPageEvent';
|
||||
import type { OpenCoWebSiteEvent } from './OpenCoWebSiteEvent';
|
||||
import type { OpenPopupEvent } from './OpenPopupEvent';
|
||||
import type { OpenTabEvent } from './OpenTabEvent';
|
||||
import type { UserInputChatEvent } from './UserInputChatEvent';
|
||||
|
||||
|
||||
export interface TypedMessageEvent<T> extends MessageEvent {
|
||||
data: T
|
||||
}
|
||||
|
||||
export type IframeEventMap = {
|
||||
//getState: GameStateEvent,
|
||||
// updateTile: UpdateTileEvent
|
||||
chat: ChatEvent,
|
||||
openPopup: OpenPopupEvent
|
||||
closePopup: ClosePopupEvent
|
||||
openTab: OpenTabEvent
|
||||
goToPage: GoToPageEvent
|
||||
openCoWebSite: OpenCoWebSiteEvent
|
||||
closeCoWebSite: null
|
||||
disablePlayerControls: null
|
||||
restorePlayerControls: null
|
||||
displayBubble: null
|
||||
removeBubble: null
|
||||
}
|
||||
export interface IframeEvent<T extends keyof IframeEventMap> {
|
||||
type: T;
|
||||
data: IframeEventMap[T];
|
||||
}
|
||||
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const isIframeEventWrapper = (event: any): event is IframeEvent<keyof IframeEventMap> => typeof event.type === 'string';
|
||||
|
||||
export interface IframeResponseEventMap {
|
||||
userInputChat: UserInputChatEvent
|
||||
enterEvent: EnterLeaveEvent
|
||||
leaveEvent: EnterLeaveEvent
|
||||
buttonClickedEvent: ButtonClickedEvent
|
||||
// gameState: GameStateEvent
|
||||
}
|
||||
export interface IframeResponseEvent<T extends keyof IframeResponseEventMap> {
|
||||
type: T;
|
||||
data: IframeResponseEventMap[T];
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const isIframeEventWrapper = (event: any): event is IframeEvent => typeof event.type === 'string';
|
||||
export const isIframeResponseEventWrapper = (event: { type?: string }): event is IframeResponseEvent<keyof IframeResponseEventMap> => typeof event.type === 'string';
|
||||
|
@ -1,22 +1,20 @@
|
||||
import {Subject} from "rxjs";
|
||||
import {ChatEvent, isChatEvent} from "./Events/ChatEvent";
|
||||
import {IframeEvent, isIframeEventWrapper} from "./Events/IframeEvent";
|
||||
import {UserInputChatEvent} from "./Events/UserInputChatEvent";
|
||||
import * as crypto from "crypto";
|
||||
import {HtmlUtils} from "../WebRtc/HtmlUtils";
|
||||
import {EnterLeaveEvent} from "./Events/EnterLeaveEvent";
|
||||
import {isOpenPopupEvent, OpenPopupEvent} from "./Events/OpenPopupEvent";
|
||||
import {isOpenTabEvent, OpenTabEvent} from "./Events/OpenTabEvent";
|
||||
import {ButtonClickedEvent} from "./Events/ButtonClickedEvent";
|
||||
import {ClosePopupEvent, isClosePopupEvent} from "./Events/ClosePopupEvent";
|
||||
import {scriptUtils} from "./ScriptUtils";
|
||||
import {GoToPageEvent, isGoToPageEvent} from "./Events/GoToPageEvent";
|
||||
import {isOpenCoWebsite, OpenCoWebSiteEvent} from "./Events/OpenCoWebSiteEvent";
|
||||
|
||||
import { Subject } from "rxjs";
|
||||
import { ChatEvent, isChatEvent } from "./Events/ChatEvent";
|
||||
import { HtmlUtils } from "../WebRtc/HtmlUtils";
|
||||
import type { EnterLeaveEvent } from "./Events/EnterLeaveEvent";
|
||||
import { isOpenPopupEvent, OpenPopupEvent } from "./Events/OpenPopupEvent";
|
||||
import { isOpenTabEvent, OpenTabEvent } from "./Events/OpenTabEvent";
|
||||
import type { ButtonClickedEvent } from "./Events/ButtonClickedEvent";
|
||||
import { ClosePopupEvent, isClosePopupEvent } from "./Events/ClosePopupEvent";
|
||||
import { scriptUtils } from "./ScriptUtils";
|
||||
import { GoToPageEvent, isGoToPageEvent } from "./Events/GoToPageEvent";
|
||||
import { isOpenCoWebsite, OpenCoWebSiteEvent } from "./Events/OpenCoWebSiteEvent";
|
||||
import { IframeEventMap, IframeEvent, IframeResponseEvent, IframeResponseEventMap, isIframeEventWrapper, TypedMessageEvent } from "./Events/IframeEvent";
|
||||
import type { UserInputChatEvent } from "./Events/UserInputChatEvent";
|
||||
import {isPlaySoundEvent, PlaySoundEvent} from "./Events/PlaySoundEvent";
|
||||
import {isStopSoundEvent, StopSoundEvent} from "./Events/StopSoundEvent";
|
||||
import {isLoadSoundEvent, LoadSoundEvent} from "./Events/LoadSoundEvent";
|
||||
|
||||
|
||||
/**
|
||||
* Listens to messages from iframes and turn those messages into easy to use observables.
|
||||
* Also allows to send messages to those iframes.
|
||||
@ -68,18 +66,18 @@ class IframeListener {
|
||||
private readonly scripts = new Map<string, HTMLIFrameElement>();
|
||||
|
||||
init() {
|
||||
window.addEventListener("message", (message) => {
|
||||
window.addEventListener("message", (message: TypedMessageEvent<IframeEvent<keyof IframeEventMap>>) => {
|
||||
// Do we trust the sender of this message?
|
||||
// Let's only accept messages from the iframe that are allowed.
|
||||
// Note: maybe we could restrict on the domain too for additional security (in case the iframe goes to another domain).
|
||||
let found = false;
|
||||
let foundSrc: string | null = null;
|
||||
for (const iframe of this.iframes) {
|
||||
if (iframe.contentWindow === message.source) {
|
||||
found = true;
|
||||
foundSrc = iframe.src;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
if (!foundSrc) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -92,37 +90,43 @@ class IframeListener {
|
||||
} else if (payload.type === 'closePopup' && isClosePopupEvent(payload.data)) {
|
||||
this._closePopupStream.next(payload.data);
|
||||
}
|
||||
else if(payload.type === 'openTab' && isOpenTabEvent(payload.data)) {
|
||||
else if (payload.type === 'openTab' && isOpenTabEvent(payload.data)) {
|
||||
scriptUtils.openTab(payload.data.url);
|
||||
}
|
||||
else if(payload.type === 'goToPage' && isGoToPageEvent(payload.data)) {
|
||||
else if (payload.type === 'goToPage' && isGoToPageEvent(payload.data)) {
|
||||
scriptUtils.goToPage(payload.data.url);
|
||||
}
|
||||
else if(payload.type === 'openCoWebSite' && isOpenCoWebsite(payload.data)) {
|
||||
scriptUtils.openCoWebsite(payload.data.url);
|
||||
}
|
||||
else if(payload.type === 'playSound' && isPlaySoundEvent(payload.data)) {
|
||||
else if (payload.type === 'playSound' && isPlaySoundEvent(payload.data)) {
|
||||
this._playSoundStream.next(payload.data);
|
||||
}
|
||||
else if(payload.type === 'stopSound' && isStopSoundEvent(payload.data)) {
|
||||
else if (payload.type === 'stopSound' && isStopSoundEvent(payload.data)) {
|
||||
this._stopSoundStream.next(payload.data);
|
||||
}
|
||||
else if(payload.type === 'loadSound' && isLoadSoundEvent(payload.data)) {
|
||||
else if (payload.type === 'loadSound' && isLoadSoundEvent(payload.data)) {
|
||||
this._loadSoundStream.next(payload.data);
|
||||
}
|
||||
else if(payload.type === 'closeCoWebSite') {
|
||||
else if (payload.type === 'openCoWebSite' && isOpenCoWebsite(payload.data)) {
|
||||
const scriptUrl = [...this.scripts.keys()].find(key => {
|
||||
return this.scripts.get(key)?.contentWindow == message.source
|
||||
})
|
||||
|
||||
scriptUtils.openCoWebsite(payload.data.url, scriptUrl || foundSrc);
|
||||
}
|
||||
|
||||
else if (payload.type === 'closeCoWebSite') {
|
||||
scriptUtils.closeCoWebSite();
|
||||
}
|
||||
else if (payload.type === 'disablePlayerControl'){
|
||||
|
||||
else if (payload.type === 'disablePlayerControls') {
|
||||
this._disablePlayerControlStream.next();
|
||||
}
|
||||
else if (payload.type === 'restorePlayerControl'){
|
||||
else if (payload.type === 'restorePlayerControls') {
|
||||
this._enablePlayerControlStream.next();
|
||||
}
|
||||
else if (payload.type === 'displayBubble'){
|
||||
else if (payload.type === 'displayBubble') {
|
||||
this._displayBubbleStream.next();
|
||||
}
|
||||
else if (payload.type === 'removeBubble'){
|
||||
else if (payload.type === 'removeBubble') {
|
||||
this._removeBubbleStream.next();
|
||||
}
|
||||
}
|
||||
@ -151,7 +155,7 @@ class IframeListener {
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.id = this.getIFrameId(scriptUrl);
|
||||
iframe.style.display = 'none';
|
||||
iframe.src = '/iframe.html?script='+encodeURIComponent(scriptUrl);
|
||||
iframe.src = '/iframe.html?script=' + encodeURIComponent(scriptUrl);
|
||||
|
||||
// We are putting a sandbox on this script because it will run in the same domain as the main website.
|
||||
iframe.sandbox.add('allow-scripts');
|
||||
@ -175,8 +179,8 @@ class IframeListener {
|
||||
'\n' +
|
||||
'<html lang="en">\n' +
|
||||
'<head>\n' +
|
||||
'<script src="'+window.location.protocol+'//'+window.location.host+'/iframe_api.js" ></script>\n' +
|
||||
'<script src="'+scriptUrl+'" ></script>\n' +
|
||||
'<script src="' + window.location.protocol + '//' + window.location.host + '/iframe_api.js" ></script>\n' +
|
||||
'<script src="' + scriptUrl + '" ></script>\n' +
|
||||
'</head>\n' +
|
||||
'</html>\n';
|
||||
|
||||
@ -193,14 +197,14 @@ class IframeListener {
|
||||
}
|
||||
|
||||
private getIFrameId(scriptUrl: string): string {
|
||||
return 'script'+crypto.createHash('md5').update(scriptUrl).digest("hex");
|
||||
return 'script' + btoa(scriptUrl);
|
||||
}
|
||||
|
||||
unregisterScript(scriptUrl: string): void {
|
||||
const iFrameId = this.getIFrameId(scriptUrl);
|
||||
const iframe = HtmlUtils.getElementByIdOrFail<HTMLIFrameElement>(iFrameId);
|
||||
if (!iframe) {
|
||||
throw new Error('Unknown iframe for script "'+scriptUrl+'"');
|
||||
throw new Error('Unknown iframe for script "' + scriptUrl + '"');
|
||||
}
|
||||
this.unregisterIframe(iframe);
|
||||
iframe.remove();
|
||||
@ -248,7 +252,7 @@ class IframeListener {
|
||||
/**
|
||||
* Sends the message... to all allowed iframes.
|
||||
*/
|
||||
private postMessage(message: IframeEvent) {
|
||||
private postMessage(message: IframeResponseEvent<keyof IframeResponseEventMap>) {
|
||||
for (const iframe of this.iframes) {
|
||||
iframe.contentWindow?.postMessage(message, '*');
|
||||
}
|
||||
|
@ -11,8 +11,8 @@ class ScriptUtils {
|
||||
|
||||
}
|
||||
|
||||
public openCoWebsite(url : string){
|
||||
coWebsiteManager.loadCoWebsite(url,url);
|
||||
public openCoWebsite(url: string, base: string) {
|
||||
coWebsiteManager.loadCoWebsite(url, base);
|
||||
}
|
||||
|
||||
public closeCoWebSite(){
|
||||
|
11
front/src/Components/App.svelte
Normal file
11
front/src/Components/App.svelte
Normal file
@ -0,0 +1,11 @@
|
||||
<script lang="typescript">
|
||||
import MenuIcon from "./Menu/MenuIcon.svelte";
|
||||
import {menuIconVisible} from "../Stores/MenuStore";
|
||||
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<!-- {#if $menuIconVisible}
|
||||
<MenuIcon />
|
||||
{/if} -->
|
||||
</div>
|
33
front/src/Components/Menu/MenuIcon.svelte
Normal file
33
front/src/Components/Menu/MenuIcon.svelte
Normal file
@ -0,0 +1,33 @@
|
||||
<script lang="typescript">
|
||||
|
||||
</script>
|
||||
|
||||
<main class="menuIcon">
|
||||
<section>
|
||||
<button>
|
||||
<img src="/static/images/menu.svg" alt="Open menu">
|
||||
</button>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<style lang="scss">
|
||||
.menuIcon button {
|
||||
background-color: black;
|
||||
color: white;
|
||||
border-radius: 7px;
|
||||
padding: 2px 8px;
|
||||
img {
|
||||
width: 14px;
|
||||
padding-top: 0;
|
||||
/*cursor: url('/resources/logos/cursor_pointer.png'), pointer;*/
|
||||
}
|
||||
}
|
||||
.menuIcon section {
|
||||
margin: 10px;
|
||||
}
|
||||
@media only screen and (max-height: 700px) {
|
||||
.menuIcon section {
|
||||
margin: 2px;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,10 +1,11 @@
|
||||
import {Subject} from "rxjs";
|
||||
import {BanUserMessage, SendUserMessage} from "../Messages/generated/messages_pb";
|
||||
import type {BanUserMessage, SendUserMessage} from "../Messages/generated/messages_pb";
|
||||
|
||||
export enum AdminMessageEventTypes {
|
||||
admin = 'message',
|
||||
audio = 'audio',
|
||||
ban = 'ban',
|
||||
banned = 'banned',
|
||||
}
|
||||
|
||||
interface AdminMessageEvent {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Axios from "axios";
|
||||
import {PUSHER_URL, START_ROOM_URL} from "../Enum/EnvironmentVariable";
|
||||
import {RoomConnection} from "./RoomConnection";
|
||||
import {OnConnectInterface, PositionInterface, ViewportInterface} from "./ConnexionModels";
|
||||
import type {OnConnectInterface, PositionInterface, ViewportInterface} from "./ConnexionModels";
|
||||
import {GameConnexionTypes, urlManager} from "../Url/UrlManager";
|
||||
import {localUserStore} from "./LocalUserStore";
|
||||
import {LocalUser} from "./LocalUser";
|
||||
@ -88,9 +88,9 @@ class ConnectionManager {
|
||||
this.localUser = new LocalUser('', 'test', []);
|
||||
}
|
||||
|
||||
public connectToRoomSocket(roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface): Promise<OnConnectInterface> {
|
||||
public connectToRoomSocket(roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface, companion: string|null): Promise<OnConnectInterface> {
|
||||
return new Promise<OnConnectInterface>((resolve, reject) => {
|
||||
const connection = new RoomConnection(this.localUser.jwtToken, roomId, name, characterLayers, position, viewport);
|
||||
const connection = new RoomConnection(this.localUser.jwtToken, roomId, name, characterLayers, position, viewport, companion);
|
||||
connection.onConnectError((error: object) => {
|
||||
console.log('An error occurred while connecting to socket server. Retrying');
|
||||
reject(error);
|
||||
@ -111,7 +111,7 @@ class ConnectionManager {
|
||||
this.reconnectingTimeout = setTimeout(() => {
|
||||
//todo: allow a way to break recursion?
|
||||
//todo: find a way to avoid recursive function. Otherwise, the call stack will grow indefinitely.
|
||||
this.connectToRoomSocket(roomId, name, characterLayers, position, viewport).then((connection) => resolve(connection));
|
||||
this.connectToRoomSocket(roomId, name, characterLayers, position, viewport, companion).then((connection) => resolve(connection));
|
||||
}, 4000 + Math.floor(Math.random() * 2000) );
|
||||
});
|
||||
});
|
||||
|
@ -1,8 +1,8 @@
|
||||
import {PlayerAnimationDirections} from "../Phaser/Player/Animation";
|
||||
import {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
|
||||
import {SignalData} from "simple-peer";
|
||||
import {RoomConnection} from "./RoomConnection";
|
||||
import {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures";
|
||||
import type {SignalData} from "simple-peer";
|
||||
import type {RoomConnection} from "./RoomConnection";
|
||||
import type {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures";
|
||||
|
||||
export enum EventMessage{
|
||||
CONNECT = "connect",
|
||||
@ -47,6 +47,7 @@ export interface MessageUserPositionInterface {
|
||||
name: string;
|
||||
characterLayers: BodyResourceDescriptionInterface[];
|
||||
position: PointInterface;
|
||||
companion: string|null;
|
||||
}
|
||||
|
||||
export interface MessageUserMovedInterface {
|
||||
@ -58,7 +59,8 @@ export interface MessageUserJoined {
|
||||
userId: number;
|
||||
name: string;
|
||||
characterLayers: BodyResourceDescriptionInterface[];
|
||||
position: PointInterface
|
||||
position: PointInterface;
|
||||
companion: string|null;
|
||||
}
|
||||
|
||||
export interface PositionInterface {
|
||||
|
@ -1,3 +1,5 @@
|
||||
import {MAX_USERNAME_LENGTH} from "../Enum/EnvironmentVariable";
|
||||
|
||||
export interface CharacterTexture {
|
||||
id: number,
|
||||
level: number,
|
||||
@ -5,6 +7,23 @@ export interface CharacterTexture {
|
||||
rights: string
|
||||
}
|
||||
|
||||
export const maxUserNameLength: number = MAX_USERNAME_LENGTH;
|
||||
|
||||
export function isUserNameValid(value: string): boolean {
|
||||
const regexp = new RegExp('^[A-Za-z0-9]{1,'+maxUserNameLength+'}$');
|
||||
return regexp.test(value);
|
||||
}
|
||||
|
||||
export function areCharacterLayersValid(value: string[] | null): boolean {
|
||||
if (!value || !value.length) return false;
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
if (/^\w+$/.exec(value[i]) === null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export class LocalUser {
|
||||
constructor(public readonly uuid:string, public readonly jwtToken: string, public readonly textures: CharacterTexture[]) {
|
||||
}
|
||||
|
@ -1,14 +1,16 @@
|
||||
import {LocalUser} from "./LocalUser";
|
||||
import {areCharacterLayersValid, isUserNameValid, LocalUser} from "./LocalUser";
|
||||
|
||||
const playerNameKey = 'playerName';
|
||||
const selectedPlayerKey = 'selectedPlayer';
|
||||
const customCursorPositionKey = 'customCursorPosition';
|
||||
const characterLayersKey = 'characterLayers';
|
||||
const companionKey = 'companion';
|
||||
const gameQualityKey = 'gameQuality';
|
||||
const videoQualityKey = 'videoQuality';
|
||||
const audioPlayerVolumeKey = 'audioVolume';
|
||||
const audioPlayerMuteKey = 'audioMute';
|
||||
const helpCameraSettingsShown = 'helpCameraSettingsShown';
|
||||
const helpCameraSettingsShown = 'helpCameraSettingsShown';
|
||||
const fullscreenKey = 'fullscreen';
|
||||
|
||||
class LocalUserStore {
|
||||
saveUser(localUser: LocalUser) {
|
||||
@ -22,8 +24,9 @@ class LocalUserStore {
|
||||
setName(name:string): void {
|
||||
localStorage.setItem(playerNameKey, name);
|
||||
}
|
||||
getName(): string {
|
||||
return localStorage.getItem(playerNameKey) || '';
|
||||
getName(): string|null {
|
||||
const value = localStorage.getItem(playerNameKey) || '';
|
||||
return isUserNameValid(value) ? value : null;
|
||||
}
|
||||
|
||||
setPlayerCharacterIndex(playerCharacterIndex: number): void {
|
||||
@ -44,7 +47,24 @@ class LocalUserStore {
|
||||
localStorage.setItem(characterLayersKey, JSON.stringify(layers));
|
||||
}
|
||||
getCharacterLayers(): string[]|null {
|
||||
return JSON.parse(localStorage.getItem(characterLayersKey) || "null");
|
||||
const value = JSON.parse(localStorage.getItem(characterLayersKey) || "null");
|
||||
return areCharacterLayersValid(value) ? value : null;
|
||||
}
|
||||
|
||||
setCompanion(companion: string|null): void {
|
||||
return localStorage.setItem(companionKey, JSON.stringify(companion));
|
||||
}
|
||||
getCompanion(): string|null {
|
||||
const companion = JSON.parse(localStorage.getItem(companionKey) || "null");
|
||||
|
||||
if (typeof companion !== "string" || companion === "") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return companion;
|
||||
}
|
||||
wasCompanionSet(): boolean {
|
||||
return localStorage.getItem(companionKey) ? true : false;
|
||||
}
|
||||
|
||||
setGameQualityValue(value: number): void {
|
||||
@ -81,6 +101,13 @@ class LocalUserStore {
|
||||
getHelpCameraSettingsShown(): boolean {
|
||||
return localStorage.getItem(helpCameraSettingsShown) === '1';
|
||||
}
|
||||
|
||||
setFullscreen(value: boolean): void {
|
||||
localStorage.setItem(fullscreenKey, value.toString());
|
||||
}
|
||||
getFullscreen(): boolean {
|
||||
return localStorage.getItem(fullscreenKey) === 'true';
|
||||
}
|
||||
}
|
||||
|
||||
export const localUserStore = new LocalUserStore();
|
||||
|
@ -27,10 +27,11 @@ import {
|
||||
SendJitsiJwtMessage,
|
||||
CharacterLayerMessage,
|
||||
PingMessage,
|
||||
SendUserMessage, BanUserMessage
|
||||
SendUserMessage,
|
||||
BanUserMessage
|
||||
} from "../Messages/generated/messages_pb"
|
||||
|
||||
import {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
|
||||
import type {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
|
||||
import Direction = PositionMessage.Direction;
|
||||
import {ProtobufClientUtils} from "../Network/ProtobufClientUtils";
|
||||
import {
|
||||
@ -41,7 +42,7 @@ import {
|
||||
ViewportInterface, WebRtcDisconnectMessageInterface,
|
||||
WebRtcSignalReceivedMessageInterface,
|
||||
} from "./ConnexionModels";
|
||||
import {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures";
|
||||
import type {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures";
|
||||
import {adminMessagesService} from "./AdminMessagesService";
|
||||
import {worldFullMessageStream} from "./WorldFullMessageStream";
|
||||
import {worldFullWarningStream} from "./WorldFullWarningStream";
|
||||
@ -66,7 +67,7 @@ export class RoomConnection implements RoomConnection {
|
||||
* @param token A JWT token containing the UUID of the user
|
||||
* @param roomId The ID of the room in the form "_/[instance]/[map_url]" or "@/[org]/[event]/[map]"
|
||||
*/
|
||||
public constructor(token: string|null, roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface) {
|
||||
public constructor(token: string|null, roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface, companion: string|null) {
|
||||
let url = new URL(PUSHER_URL, window.location.toString()).toString();
|
||||
url = url.replace('http://', 'ws://').replace('https://', 'wss://');
|
||||
if (!url.endsWith('/')) {
|
||||
@ -86,6 +87,10 @@ export class RoomConnection implements RoomConnection {
|
||||
url += '&left='+Math.floor(viewport.left);
|
||||
url += '&right='+Math.floor(viewport.right);
|
||||
|
||||
if (typeof companion === 'string') {
|
||||
url += '&companion='+encodeURIComponent(companion);
|
||||
}
|
||||
|
||||
if (RoomConnection.websocketFactory) {
|
||||
this.socket = RoomConnection.websocketFactory(url);
|
||||
} else {
|
||||
@ -165,7 +170,10 @@ export class RoomConnection implements RoomConnection {
|
||||
} else if (message.hasWorldfullmessage()) {
|
||||
worldFullMessageStream.onMessage();
|
||||
this.closed = true;
|
||||
} else if (message.hasWebrtcsignaltoclientmessage()) {
|
||||
} 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()) {
|
||||
this.dispatch(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, message.getWebrtcscreensharingsignaltoclientmessage());
|
||||
@ -184,7 +192,7 @@ export class RoomConnection implements RoomConnection {
|
||||
} else if (message.hasSendusermessage()) {
|
||||
adminMessagesService.onSendusermessage(message.getSendusermessage() as SendUserMessage);
|
||||
} else if (message.hasBanusermessage()) {
|
||||
adminMessagesService.onSendusermessage(message.getSendusermessage() as BanUserMessage);
|
||||
adminMessagesService.onSendusermessage(message.getBanusermessage() as BanUserMessage);
|
||||
} else if (message.hasWorldfullwarningmessage()) {
|
||||
worldFullWarningStream.onMessage();
|
||||
} else if (message.hasRefreshroommessage()) {
|
||||
@ -322,11 +330,14 @@ export class RoomConnection implements RoomConnection {
|
||||
}
|
||||
})
|
||||
|
||||
const companion = message.getCompanion();
|
||||
|
||||
return {
|
||||
userId: message.getUserid(),
|
||||
name: message.getName(),
|
||||
characterLayers,
|
||||
position: ProtobufClientUtils.toPointInterface(position)
|
||||
position: ProtobufClientUtils.toPointInterface(position),
|
||||
companion: companion ? companion.getName() : null
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,12 +2,12 @@ import {Subject} from "rxjs";
|
||||
|
||||
class WorldFullMessageStream {
|
||||
|
||||
private _stream:Subject<void> = new Subject();
|
||||
private _stream:Subject<string|null> = new Subject<string|null>();
|
||||
public stream = this._stream.asObservable();
|
||||
|
||||
|
||||
onMessage() {
|
||||
this._stream.next();
|
||||
onMessage(message? :string) {
|
||||
this._stream.next(message);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,28 +1,29 @@
|
||||
const DEBUG_MODE: boolean = process.env.DEBUG_MODE == "true";
|
||||
const START_ROOM_URL : string = process.env.START_ROOM_URL || '/_/global/maps.workadventure.localhost/Floor0/floor0.json';
|
||||
// For compatibility reasons with older versions, API_URL is the old host name of PUSHER_URL
|
||||
const PUSHER_URL = process.env.PUSHER_URL || (process.env.API_URL ? '//'+process.env.API_URL : "//pusher.workadventure.localhost");
|
||||
const PUSHER_URL = process.env.PUSHER_URL || '//pusher.workadventure.localhost';
|
||||
const UPLOADER_URL = process.env.UPLOADER_URL || '//uploader.workadventure.localhost';
|
||||
const ADMIN_URL = process.env.ADMIN_URL || "//workadventure.localhost";
|
||||
const STUN_SERVER: string = process.env.STUN_SERVER || "stun:stun.l.google.com:19302";
|
||||
const TURN_SERVER: string = process.env.TURN_SERVER || "";
|
||||
const SKIP_RENDER_OPTIMIZATIONS: boolean = process.env.SKIP_RENDER_OPTIMIZATIONS == "true";
|
||||
const DISABLE_NOTIFICATIONS: boolean = process.env.DISABLE_NOTIFICATIONS == "true";
|
||||
const TURN_USER: string = process.env.TURN_USER || '';
|
||||
const TURN_PASSWORD: string = process.env.TURN_PASSWORD || '';
|
||||
const JITSI_URL : string|undefined = (process.env.JITSI_URL === '') ? undefined : process.env.JITSI_URL;
|
||||
const JITSI_PRIVATE_MODE : boolean = process.env.JITSI_PRIVATE_MODE == "true";
|
||||
const RESOLUTION = 2;
|
||||
const ZOOM_LEVEL = 1/*3/4*/;
|
||||
const POSITION_DELAY = 200; // Wait 200ms between sending position events
|
||||
const MAX_EXTRAPOLATION_TIME = 100; // Extrapolate a maximum of 250ms if no new movement is sent by the player
|
||||
export const MAX_USERNAME_LENGTH = parseInt(process.env.MAX_USERNAME_LENGTH || '') || 8;
|
||||
export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || '4');
|
||||
|
||||
export const isMobile = ():boolean => ( ( window.innerWidth <= 800 ) || ( window.innerHeight <= 600 ) );
|
||||
|
||||
export {
|
||||
DEBUG_MODE,
|
||||
START_ROOM_URL,
|
||||
SKIP_RENDER_OPTIMIZATIONS,
|
||||
DISABLE_NOTIFICATIONS,
|
||||
PUSHER_URL,
|
||||
UPLOADER_URL,
|
||||
ADMIN_URL,
|
||||
RESOLUTION,
|
||||
ZOOM_LEVEL,
|
||||
POSITION_DELAY,
|
||||
MAX_EXTRAPOLATION_TIME,
|
||||
STUN_SERVER,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {PositionMessage} from "../Messages/generated/messages_pb";
|
||||
import Direction = PositionMessage.Direction;
|
||||
import {PointInterface} from "../Connexion/ConnexionModels";
|
||||
import type {PointInterface} from "../Connexion/ConnexionModels";
|
||||
|
||||
export class ProtobufClientUtils {
|
||||
|
||||
|
220
front/src/Phaser/Companion/Companion.ts
Normal file
220
front/src/Phaser/Companion/Companion.ts
Normal file
@ -0,0 +1,220 @@
|
||||
import Sprite = Phaser.GameObjects.Sprite;
|
||||
import Container = Phaser.GameObjects.Container;
|
||||
import { PlayerAnimationDirections, PlayerAnimationTypes } from "../Player/Animation";
|
||||
|
||||
export interface CompanionStatus {
|
||||
x: number;
|
||||
y: number;
|
||||
name: string;
|
||||
moving: boolean;
|
||||
direction: PlayerAnimationDirections;
|
||||
}
|
||||
|
||||
export class Companion extends Container {
|
||||
public sprites: Map<string, Sprite>;
|
||||
|
||||
private delta: number;
|
||||
private invisible: boolean;
|
||||
private updateListener: Function;
|
||||
private target: { x: number, y: number, direction: PlayerAnimationDirections };
|
||||
|
||||
private companionName: string;
|
||||
private direction: PlayerAnimationDirections;
|
||||
private animationType: PlayerAnimationTypes;
|
||||
|
||||
constructor(scene: Phaser.Scene, x: number, y: number, name: string, texturePromise: Promise<string>) {
|
||||
super(scene, x + 14, y + 4);
|
||||
|
||||
this.sprites = new Map<string, Sprite>();
|
||||
|
||||
this.delta = 0;
|
||||
this.invisible = true;
|
||||
this.target = { x, y, direction: PlayerAnimationDirections.Down };
|
||||
|
||||
this.direction = PlayerAnimationDirections.Down;
|
||||
this.animationType = PlayerAnimationTypes.Idle;
|
||||
|
||||
this.companionName = name;
|
||||
|
||||
texturePromise.then(resource => {
|
||||
this.addResource(resource);
|
||||
this.invisible = false;
|
||||
})
|
||||
|
||||
this.scene.physics.world.enableBody(this);
|
||||
|
||||
this.getBody().setImmovable(true);
|
||||
this.getBody().setCollideWorldBounds(false);
|
||||
this.setSize(16, 16);
|
||||
this.getBody().setSize(16, 16);
|
||||
this.getBody().setOffset(0, 8);
|
||||
|
||||
this.setDepth(-1);
|
||||
|
||||
this.updateListener = this.step.bind(this);
|
||||
this.scene.events.addListener('update', this.updateListener);
|
||||
|
||||
this.scene.add.existing(this);
|
||||
}
|
||||
|
||||
public setTarget(x: number, y: number, direction: PlayerAnimationDirections) {
|
||||
this.target = { x, y: y + 4, direction };
|
||||
}
|
||||
|
||||
public step(time: number, delta: number) {
|
||||
if (typeof this.target === 'undefined') return;
|
||||
|
||||
this.delta += delta;
|
||||
if (this.delta < 128) {
|
||||
return;
|
||||
}
|
||||
this.delta = 0;
|
||||
|
||||
const xDist = this.target.x - this.x;
|
||||
const yDist = this.target.y - this.y;
|
||||
|
||||
const distance = Math.pow(xDist, 2) + Math.pow(yDist, 2);
|
||||
|
||||
if (distance < 650) {
|
||||
this.animationType = PlayerAnimationTypes.Idle;
|
||||
this.direction = this.target.direction;
|
||||
|
||||
this.getBody().stop();
|
||||
} else {
|
||||
this.animationType = PlayerAnimationTypes.Walk;
|
||||
|
||||
const xDir = xDist / Math.max(Math.abs(xDist), 1);
|
||||
const yDir = yDist / Math.max(Math.abs(yDist), 1);
|
||||
|
||||
const speed = 256;
|
||||
this.getBody().setVelocity(Math.min(Math.abs(xDist * 2.5), speed) * xDir, Math.min(Math.abs(yDist * 2.5), speed) * yDir);
|
||||
|
||||
if (Math.abs(xDist) > Math.abs(yDist)) {
|
||||
if (xDist < 0) {
|
||||
this.direction = PlayerAnimationDirections.Left;
|
||||
} else {
|
||||
this.direction = PlayerAnimationDirections.Right;
|
||||
}
|
||||
} else {
|
||||
if (yDist < 0) {
|
||||
this.direction = PlayerAnimationDirections.Up;
|
||||
} else {
|
||||
this.direction = PlayerAnimationDirections.Down;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.setDepth(this.y);
|
||||
this.playAnimation(this.direction, this.animationType);
|
||||
}
|
||||
|
||||
public getStatus(): CompanionStatus {
|
||||
const { x, y, direction, animationType, companionName } = this;
|
||||
|
||||
return {
|
||||
x,
|
||||
y,
|
||||
direction,
|
||||
moving: animationType === PlayerAnimationTypes.Walk,
|
||||
name: companionName
|
||||
}
|
||||
}
|
||||
|
||||
private playAnimation(direction: PlayerAnimationDirections, type: PlayerAnimationTypes): void {
|
||||
if (this.invisible) return;
|
||||
|
||||
for (const [resource, sprite] of this.sprites.entries()) {
|
||||
sprite.play(`${resource}-${direction}-${type}`, true);
|
||||
}
|
||||
}
|
||||
|
||||
private addResource(resource: string, frame?: string | number): void {
|
||||
const sprite = new Sprite(this.scene, 0, 0, resource, frame);
|
||||
|
||||
this.add(sprite);
|
||||
|
||||
this.getAnimations(resource).forEach(animation => {
|
||||
this.scene.anims.create(animation);
|
||||
});
|
||||
|
||||
this.scene.sys.updateList.add(sprite);
|
||||
this.sprites.set(resource, sprite);
|
||||
}
|
||||
|
||||
private getAnimations(resource: string): Phaser.Types.Animations.Animation[] {
|
||||
return [
|
||||
{
|
||||
key: `${resource}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Idle}`,
|
||||
frames: this.scene.anims.generateFrameNumbers(resource, {frames: [1]}),
|
||||
frameRate: 10,
|
||||
repeat: 1
|
||||
},
|
||||
{
|
||||
key: `${resource}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Idle}`,
|
||||
frames: this.scene.anims.generateFrameNumbers(resource, {frames: [4]}),
|
||||
frameRate: 10,
|
||||
repeat: 1
|
||||
},
|
||||
{
|
||||
key: `${resource}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Idle}`,
|
||||
frames: this.scene.anims.generateFrameNumbers(resource, {frames: [7]}),
|
||||
frameRate: 10,
|
||||
repeat: 1
|
||||
},
|
||||
{
|
||||
key: `${resource}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Idle}`,
|
||||
frames: this.scene.anims.generateFrameNumbers(resource, {frames: [10]}),
|
||||
frameRate: 10,
|
||||
repeat: 1
|
||||
},
|
||||
{
|
||||
key: `${resource}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Walk}`,
|
||||
frames: this.scene.anims.generateFrameNumbers(resource, {frames: [0, 1, 2]}),
|
||||
frameRate: 15,
|
||||
repeat: -1
|
||||
},
|
||||
{
|
||||
key: `${resource}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Walk}`,
|
||||
frames: this.scene.anims.generateFrameNumbers(resource, {frames: [3, 4, 5]}),
|
||||
frameRate: 15,
|
||||
repeat: -1
|
||||
},
|
||||
{
|
||||
key: `${resource}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Walk}`,
|
||||
frames: this.scene.anims.generateFrameNumbers(resource, {frames: [6, 7, 8]}),
|
||||
frameRate: 15,
|
||||
repeat: -1
|
||||
},
|
||||
{
|
||||
key: `${resource}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Walk}`,
|
||||
frames: this.scene.anims.generateFrameNumbers(resource, {frames: [9, 10, 11]}),
|
||||
frameRate: 15,
|
||||
repeat: -1
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
private getBody(): Phaser.Physics.Arcade.Body {
|
||||
const body = this.body;
|
||||
|
||||
if (!(body instanceof Phaser.Physics.Arcade.Body)) {
|
||||
throw new Error('Container does not have arcade body');
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
for (const sprite of this.sprites.values()) {
|
||||
if (this.scene) {
|
||||
this.scene.sys.updateList.remove(sprite);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.scene) {
|
||||
this.scene.events.removeListener('update', this.updateListener);
|
||||
}
|
||||
|
||||
super.destroy();
|
||||
}
|
||||
}
|
14
front/src/Phaser/Companion/CompanionTextures.ts
Normal file
14
front/src/Phaser/Companion/CompanionTextures.ts
Normal file
@ -0,0 +1,14 @@
|
||||
export interface CompanionResourceDescriptionInterface {
|
||||
name: string,
|
||||
img: string,
|
||||
behaviour: "dog" | "cat"
|
||||
}
|
||||
|
||||
export const COMPANION_RESOURCES: CompanionResourceDescriptionInterface[] = [
|
||||
{ name: "dog1", img: "resources/characters/pipoya/Dog 01-1.png", behaviour: "dog" },
|
||||
{ name: "dog2", img: "resources/characters/pipoya/Dog 01-2.png", behaviour: "dog" },
|
||||
{ name: "dog3", img: "resources/characters/pipoya/Dog 01-3.png", behaviour: "dog" },
|
||||
{ name: "cat1", img: "resources/characters/pipoya/Cat 01-1.png", behaviour: "cat" },
|
||||
{ name: "cat2", img: "resources/characters/pipoya/Cat 01-2.png", behaviour: "cat" },
|
||||
{ name: "cat3", img: "resources/characters/pipoya/Cat 01-3.png", behaviour: "cat" },
|
||||
]
|
@ -0,0 +1,29 @@
|
||||
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
|
||||
import { COMPANION_RESOURCES, CompanionResourceDescriptionInterface } from "./CompanionTextures";
|
||||
|
||||
export const getAllCompanionResources = (loader: LoaderPlugin): CompanionResourceDescriptionInterface[] => {
|
||||
COMPANION_RESOURCES.forEach((resource: CompanionResourceDescriptionInterface) => {
|
||||
lazyLoadCompanionResource(loader, resource.name);
|
||||
});
|
||||
|
||||
return COMPANION_RESOURCES;
|
||||
}
|
||||
|
||||
export const lazyLoadCompanionResource = (loader: LoaderPlugin, name: string): Promise<string> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const resource = COMPANION_RESOURCES.find(item => item.name === name);
|
||||
|
||||
if (typeof resource === 'undefined') {
|
||||
return reject(`Texture '${name}' not found!`);
|
||||
}
|
||||
|
||||
if (loader.textureManager.exists(resource.name)) {
|
||||
return resolve(resource.name);
|
||||
}
|
||||
|
||||
loader.spritesheet(resource.name, resource.img, { frameWidth: 32, frameHeight: 32, endFrame: 12 });
|
||||
loader.once(`filecomplete-spritesheet-${resource.name}`, () => resolve(resource.name));
|
||||
|
||||
loader.start(); // It's only automatically started during the Scene preload.
|
||||
});
|
||||
}
|
64
front/src/Phaser/Components/MobileJoystick.ts
Normal file
64
front/src/Phaser/Components/MobileJoystick.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import VirtualJoystick from 'phaser3-rex-plugins/plugins/virtualjoystick.js';
|
||||
import {waScaleManager} from "../Services/WaScaleManager";
|
||||
|
||||
//the assets were found here: https://hannemann.itch.io/virtual-joystick-pack-free
|
||||
export const joystickBaseKey = 'joystickBase';
|
||||
export const joystickBaseImg = 'resources/objects/joystickSplitted.png';
|
||||
export const joystickThumbKey = 'joystickThumb';
|
||||
export const joystickThumbImg = 'resources/objects/smallHandleFilledGrey.png';
|
||||
|
||||
const baseSize = 50;
|
||||
const thumbSize = 25;
|
||||
const radius = 17.5;
|
||||
|
||||
export class MobileJoystick extends VirtualJoystick {
|
||||
private resizeCallback: () => void;
|
||||
|
||||
constructor(scene: Phaser.Scene) {
|
||||
super(scene, {
|
||||
x: -1000,
|
||||
y: -1000,
|
||||
radius: radius * window.devicePixelRatio,
|
||||
base: scene.add.image(0, 0, joystickBaseKey).setDisplaySize(baseSize * window.devicePixelRatio, baseSize * window.devicePixelRatio).setDepth(99999),
|
||||
thumb: scene.add.image(0, 0, joystickThumbKey).setDisplaySize(thumbSize * window.devicePixelRatio, thumbSize * window.devicePixelRatio).setDepth(99999),
|
||||
enable: true,
|
||||
dir: "8dir",
|
||||
});
|
||||
this.visible = false;
|
||||
this.enable = false;
|
||||
|
||||
this.scene.input.on('pointerdown', (pointer: { x: number; y: number; wasTouch: boolean; event: TouchEvent }) => {
|
||||
if (!pointer.wasTouch) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Let's only display the joystick if there is one finger on the screen
|
||||
if (pointer.event.touches.length === 1) {
|
||||
this.x = pointer.x;
|
||||
this.y = pointer.y;
|
||||
this.visible = true;
|
||||
this.enable = true;
|
||||
} else {
|
||||
this.visible = false;
|
||||
this.enable = false;
|
||||
}
|
||||
});
|
||||
this.scene.input.on('pointerup', () => {
|
||||
this.visible = false;
|
||||
this.enable = false;
|
||||
});
|
||||
this.resizeCallback = this.resize.bind(this);
|
||||
this.scene.scale.on(Phaser.Scale.Events.RESIZE, this.resizeCallback);
|
||||
}
|
||||
|
||||
private resize() {
|
||||
this.base.setDisplaySize(baseSize / waScaleManager.zoomModifier * window.devicePixelRatio, baseSize / waScaleManager.zoomModifier * window.devicePixelRatio);
|
||||
this.thumb.setDisplaySize(thumbSize / waScaleManager.zoomModifier * window.devicePixelRatio, thumbSize / waScaleManager.zoomModifier * window.devicePixelRatio);
|
||||
this.setRadius(radius / waScaleManager.zoomModifier * window.devicePixelRatio);
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
super.destroy();
|
||||
this.scene.scale.removeListener(Phaser.Scale.Events.RESIZE, this.resizeCallback);
|
||||
}
|
||||
}
|
@ -17,14 +17,12 @@ export class SoundMeter {
|
||||
}
|
||||
|
||||
private init(context: AudioContext) {
|
||||
if (this.context === undefined) {
|
||||
this.context = context;
|
||||
this.analyser = this.context.createAnalyser();
|
||||
this.context = context;
|
||||
this.analyser = this.context.createAnalyser();
|
||||
|
||||
this.analyser.fftSize = 2048;
|
||||
const bufferLength = this.analyser.fftSize;
|
||||
this.dataArray = new Uint8Array(bufferLength);
|
||||
}
|
||||
this.analyser.fftSize = 2048;
|
||||
const bufferLength = this.analyser.fftSize;
|
||||
this.dataArray = new Uint8Array(bufferLength);
|
||||
}
|
||||
|
||||
public connectToSource(stream: MediaStream, context: AudioContext): void
|
||||
|
@ -1,5 +1,5 @@
|
||||
import Container = Phaser.GameObjects.Container;
|
||||
import {Scene} from "phaser";
|
||||
import type {Scene} from "phaser";
|
||||
import GameObject = Phaser.GameObjects.GameObject;
|
||||
import Rectangle = Phaser.GameObjects.Rectangle;
|
||||
|
||||
|
@ -1,46 +1,68 @@
|
||||
|
||||
const IGNORED_KEYS = new Set([
|
||||
'Esc',
|
||||
'Escape',
|
||||
'Alt',
|
||||
'Meta',
|
||||
'Control',
|
||||
'Ctrl',
|
||||
'Space',
|
||||
'Backspace'
|
||||
])
|
||||
|
||||
export class TextInput extends Phaser.GameObjects.BitmapText {
|
||||
private minUnderLineLength = 4;
|
||||
private underLine: Phaser.GameObjects.Text;
|
||||
private domInput = document.createElement('input');
|
||||
|
||||
constructor(scene: Phaser.Scene, x: number, y: number, maxLength: number, text: string, onChange: (text: string) => void) {
|
||||
constructor(scene: Phaser.Scene, x: number, y: number, maxLength: number, text: string,
|
||||
onChange: (text: string) => void) {
|
||||
super(scene, x, y, 'main_font', text, 32);
|
||||
this.setOrigin(0.5).setCenterAlign()
|
||||
this.setOrigin(0.5).setCenterAlign();
|
||||
this.scene.add.existing(this);
|
||||
|
||||
this.underLine = this.scene.add.text(x, y+1, this.getUnderLineBody(text.length), { fontFamily: 'Arial', fontSize: "32px", color: '#ffffff'})
|
||||
this.underLine.setOrigin(0.5)
|
||||
const style = {fontFamily: 'Arial', fontSize: "32px", color: '#ffffff'};
|
||||
this.underLine = this.scene.add.text(x, y+1, this.getUnderLineBody(text.length), style);
|
||||
this.underLine.setOrigin(0.5);
|
||||
|
||||
this.domInput.maxLength = maxLength;
|
||||
this.domInput.style.opacity = "0";
|
||||
if (text) {
|
||||
this.domInput.value = text;
|
||||
}
|
||||
|
||||
this.scene.input.keyboard.on('keydown', (event: KeyboardEvent) => {
|
||||
if (event.keyCode === 8 && this.text.length > 0) {
|
||||
this.deleteLetter();
|
||||
} else if ((event.keyCode === 32 || (event.keyCode >= 48 && event.keyCode <= 90)) && this.text.length < maxLength) {
|
||||
this.addLetter(event.key);
|
||||
this.domInput.addEventListener('keydown', event => {
|
||||
if (IGNORED_KEYS.has(event.key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!/[a-zA-Z0-9:.!&?()+-]/.exec(event.key)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
this.domInput.addEventListener('input', (event) => {
|
||||
if (event.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
this.text = this.domInput.value;
|
||||
this.underLine.text = this.getUnderLineBody(this.text.length);
|
||||
onChange(this.text);
|
||||
});
|
||||
|
||||
document.body.append(this.domInput);
|
||||
this.focus();
|
||||
}
|
||||
|
||||
private getUnderLineBody(textLength:number): string {
|
||||
if (textLength < this.minUnderLineLength) textLength = this.minUnderLineLength;
|
||||
let text = '_______';
|
||||
for (let i = this.minUnderLineLength; i < textLength; i++) {
|
||||
text += '__'
|
||||
text += '__';
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
private deleteLetter() {
|
||||
this.text = this.text.substr(0, this.text.length - 1);
|
||||
}
|
||||
|
||||
|
||||
private addLetter(letter: string) {
|
||||
this.text += letter;
|
||||
}
|
||||
|
||||
getText(): string {
|
||||
return this.text;
|
||||
}
|
||||
@ -56,4 +78,13 @@ export class TextInput extends Phaser.GameObjects.BitmapText {
|
||||
this.underLine.y = y+1;
|
||||
return this;
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.domInput.focus();
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
super.destroy();
|
||||
this.domInput.remove();
|
||||
}
|
||||
}
|
||||
|
51
front/src/Phaser/Components/TextUtils.ts
Normal file
51
front/src/Phaser/Components/TextUtils.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import type {ITiledMapObject} from "../Map/ITiledMap";
|
||||
import type {GameScene} from "../Game/GameScene";
|
||||
|
||||
export class TextUtils {
|
||||
public static createTextFromITiledMapObject(scene: GameScene, object: ITiledMapObject): void {
|
||||
if (object.text === undefined) {
|
||||
throw new Error('This object has not textual representation.');
|
||||
}
|
||||
const options: {
|
||||
fontStyle?: string,
|
||||
fontSize?: string,
|
||||
fontFamily?: string,
|
||||
color?: string,
|
||||
align?: string,
|
||||
wordWrap?: {
|
||||
width: number,
|
||||
useAdvancedWrap?: boolean
|
||||
}
|
||||
} = {};
|
||||
if (object.text.italic) {
|
||||
options.fontStyle = 'italic';
|
||||
}
|
||||
// Note: there is no support for "strikeout" and "underline"
|
||||
let fontSize: number = 16;
|
||||
if (object.text.pixelsize) {
|
||||
fontSize = object.text.pixelsize;
|
||||
}
|
||||
options.fontSize = fontSize + 'px';
|
||||
if (object.text.fontfamily) {
|
||||
options.fontFamily = '"'+object.text.fontfamily+'"';
|
||||
}
|
||||
let color = '#000000';
|
||||
if (object.text.color !== undefined) {
|
||||
color = object.text.color;
|
||||
}
|
||||
options.color = color;
|
||||
if (object.text.wrap === true) {
|
||||
options.wordWrap = {
|
||||
width: object.width,
|
||||
//useAdvancedWrap: true
|
||||
}
|
||||
}
|
||||
if (object.text.halign !== undefined) {
|
||||
options.align = object.text.halign;
|
||||
}
|
||||
|
||||
console.warn(options);
|
||||
const textElem = scene.add.text(object.x, object.y, object.text.text, options);
|
||||
textElem.setAngle(object.rotation);
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ import BitmapText = Phaser.GameObjects.BitmapText;
|
||||
import Container = Phaser.GameObjects.Container;
|
||||
import Sprite = Phaser.GameObjects.Sprite;
|
||||
import {TextureError} from "../../Exception/TextureError";
|
||||
import {Companion} from "../Companion/Companion";
|
||||
|
||||
interface AnimationData {
|
||||
key: string;
|
||||
@ -21,6 +22,7 @@ export abstract class Character extends Container {
|
||||
private lastDirection: PlayerAnimationDirections = PlayerAnimationDirections.Down;
|
||||
//private teleportation: Sprite;
|
||||
private invisible: boolean;
|
||||
public companion?: Companion;
|
||||
|
||||
constructor(scene: Phaser.Scene,
|
||||
x: number,
|
||||
@ -69,6 +71,12 @@ export abstract class Character extends Container {
|
||||
this.playAnimation(direction, moving);
|
||||
}
|
||||
|
||||
public addCompanion(name: string, texturePromise?: Promise<string>): void {
|
||||
if (typeof texturePromise !== 'undefined') {
|
||||
this.companion = new Companion(this.scene, this.x, this.y, name, texturePromise);
|
||||
}
|
||||
}
|
||||
|
||||
public addTextures(textures: string[], frame?: string | number): void {
|
||||
for (const texture of textures) {
|
||||
if(!this.scene.textures.exists(texture)){
|
||||
@ -189,6 +197,10 @@ export abstract class Character extends Container {
|
||||
}
|
||||
|
||||
this.setDepth(this.y);
|
||||
|
||||
if (this.companion) {
|
||||
this.companion.setTarget(this.x, this.y, this.lastDirection);
|
||||
}
|
||||
}
|
||||
|
||||
stop(){
|
||||
|
@ -1,6 +1,5 @@
|
||||
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
|
||||
import TextureManager = Phaser.Textures.TextureManager;
|
||||
import {CharacterTexture} from "../../Connexion/LocalUser";
|
||||
import type {CharacterTexture} from "../../Connexion/LocalUser";
|
||||
import {BodyResourceDescriptionInterface, LAYERS, PLAYER_RESOURCES} from "./PlayerTextures";
|
||||
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {GameScene} from "../Game/GameScene";
|
||||
import {PointInterface} from "../../Connexion/ConnexionModels";
|
||||
import type {GameScene} from "../Game/GameScene";
|
||||
import type {PointInterface} from "../../Connexion/ConnexionModels";
|
||||
import {Character} from "../Entity/Character";
|
||||
import {PlayerAnimationDirections} from "../Player/Animation";
|
||||
import type {PlayerAnimationDirections} from "../Player/Animation";
|
||||
|
||||
/**
|
||||
* Class representing the sprite of a remote player (a player that plays on another computer)
|
||||
@ -17,12 +17,18 @@ export class RemotePlayer extends Character {
|
||||
name: string,
|
||||
texturesPromise: Promise<string[]>,
|
||||
direction: PlayerAnimationDirections,
|
||||
moving: boolean
|
||||
moving: boolean,
|
||||
companion: string|null,
|
||||
companionTexturePromise?: Promise<string>
|
||||
) {
|
||||
super(Scene, x, y, texturesPromise, name, direction, moving, 1);
|
||||
|
||||
//set data
|
||||
this.userId = userId;
|
||||
|
||||
if (typeof companion === 'string') {
|
||||
this.addCompanion(companion, companionTexturePromise);
|
||||
}
|
||||
}
|
||||
|
||||
updatePosition(position: PointInterface): void {
|
||||
@ -31,5 +37,9 @@ export class RemotePlayer extends Character {
|
||||
this.setY(position.y);
|
||||
|
||||
this.setDepth(position.y); //this is to make sure the perspective (player models closer the bottom of the screen will appear in front of models nearer the top of the screen).
|
||||
|
||||
if (this.companion) {
|
||||
this.companion.setTarget(position.x, position.y, position.direction as PlayerAnimationDirections);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import Scene = Phaser.Scene;
|
||||
import {Character} from "./Character";
|
||||
import type {Character} from "./Character";
|
||||
|
||||
//todo: improve this WIP
|
||||
export class SpeechBubble {
|
||||
|
@ -1,9 +1,10 @@
|
||||
import {PointInterface} from "../../Connexion/ConnexionModels";
|
||||
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
||||
import type {PointInterface} from "../../Connexion/ConnexionModels";
|
||||
import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
||||
|
||||
export interface AddPlayerInterface {
|
||||
userId: number;
|
||||
name: string;
|
||||
characterLayers: BodyResourceDescriptionInterface[];
|
||||
position: PointInterface;
|
||||
companion: string|null;
|
||||
}
|
||||
|
53
front/src/Phaser/Game/DirtyScene.ts
Normal file
53
front/src/Phaser/Game/DirtyScene.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import {ResizableScene} from "../Login/ResizableScene";
|
||||
import GameObject = Phaser.GameObjects.GameObject;
|
||||
import Events = Phaser.Scenes.Events;
|
||||
import AnimationEvents = Phaser.Animations.Events;
|
||||
import StructEvents = Phaser.Structs.Events;
|
||||
import {SKIP_RENDER_OPTIMIZATIONS} from "../../Enum/EnvironmentVariable";
|
||||
|
||||
/**
|
||||
* A scene that can track its dirty/pristine state.
|
||||
*/
|
||||
export abstract class DirtyScene extends ResizableScene {
|
||||
private isAlreadyTracking: boolean = false;
|
||||
protected dirty:boolean = true;
|
||||
private objectListChanged:boolean = true;
|
||||
|
||||
/**
|
||||
* Track all objects added to the scene and adds a callback each time an animation is added.
|
||||
* Whenever an object is added, removed, or when an animation is played, the dirty state is set to true.
|
||||
*
|
||||
* Note: this does not work with animations from sprites inside containers.
|
||||
*/
|
||||
protected trackDirtyAnims(): void {
|
||||
if (this.isAlreadyTracking || SKIP_RENDER_OPTIMIZATIONS) {
|
||||
return;
|
||||
}
|
||||
this.isAlreadyTracking = true;
|
||||
const trackAnimationFunction = this.trackAnimation.bind(this);
|
||||
this.sys.updateList.on(StructEvents.PROCESS_QUEUE_ADD, (gameObject: GameObject) => {
|
||||
this.objectListChanged = true;
|
||||
gameObject.on(AnimationEvents.ANIMATION_UPDATE, trackAnimationFunction);
|
||||
});
|
||||
this.sys.updateList.on(StructEvents.PROCESS_QUEUE_REMOVE, (gameObject: GameObject) => {
|
||||
this.objectListChanged = true;
|
||||
gameObject.removeListener(AnimationEvents.ANIMATION_UPDATE, trackAnimationFunction);
|
||||
});
|
||||
|
||||
this.events.on(Events.RENDER, () => {
|
||||
this.objectListChanged = false;
|
||||
});
|
||||
}
|
||||
|
||||
private trackAnimation(): void {
|
||||
this.objectListChanged = true;
|
||||
}
|
||||
|
||||
public isDirty(): boolean {
|
||||
return this.dirty || this.objectListChanged;
|
||||
}
|
||||
|
||||
public onResize(ev: UIEvent): void {
|
||||
this.objectListChanged = true;
|
||||
}
|
||||
}
|
124
front/src/Phaser/Game/Game.ts
Normal file
124
front/src/Phaser/Game/Game.ts
Normal file
@ -0,0 +1,124 @@
|
||||
import {SKIP_RENDER_OPTIMIZATIONS} from "../../Enum/EnvironmentVariable";
|
||||
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
|
||||
import {waScaleManager} from "../Services/WaScaleManager";
|
||||
import {ResizableScene} from "../Login/ResizableScene";
|
||||
|
||||
const Events = Phaser.Core.Events;
|
||||
|
||||
/**
|
||||
* A specialization of the main Phaser Game scene.
|
||||
* It comes with an optimization to skip rendering.
|
||||
*
|
||||
* Beware, the "step" function might vary in future versions of Phaser.
|
||||
*
|
||||
* It also automatically calls "onResize" on any scenes extending ResizableScene.
|
||||
*/
|
||||
export class Game extends Phaser.Game {
|
||||
|
||||
private _isDirty = false;
|
||||
|
||||
|
||||
constructor(GameConfig: Phaser.Types.Core.GameConfig) {
|
||||
super(GameConfig);
|
||||
|
||||
window.addEventListener('resize', (event) => {
|
||||
// Let's trigger the onResize method of any active scene that is a ResizableScene
|
||||
for (const scene of this.scene.getScenes(true)) {
|
||||
if (scene instanceof ResizableScene) {
|
||||
scene.onResize(event);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public step(time: number, delta: number)
|
||||
{
|
||||
// @ts-ignore
|
||||
if (this.pendingDestroy)
|
||||
{
|
||||
// @ts-ignore
|
||||
return this.runDestroy();
|
||||
}
|
||||
|
||||
const eventEmitter = this.events;
|
||||
|
||||
// Global Managers like Input and Sound update in the prestep
|
||||
|
||||
eventEmitter.emit(Events.PRE_STEP, time, delta);
|
||||
|
||||
// This is mostly meant for user-land code and plugins
|
||||
|
||||
eventEmitter.emit(Events.STEP, time, delta);
|
||||
|
||||
// Update the Scene Manager and all active Scenes
|
||||
|
||||
this.scene.update(time, delta);
|
||||
|
||||
// Our final event before rendering starts
|
||||
|
||||
eventEmitter.emit(Events.POST_STEP, time, delta);
|
||||
|
||||
// This "if" is the changed introduced by the new "Game" class to avoid rendering unnecessarily.
|
||||
if (SKIP_RENDER_OPTIMIZATIONS || this.isDirty()) {
|
||||
const renderer = this.renderer;
|
||||
|
||||
// Run the Pre-render (clearing the canvas, setting background colors, etc)
|
||||
|
||||
renderer.preRender();
|
||||
|
||||
eventEmitter.emit(Events.PRE_RENDER, renderer, time, delta);
|
||||
|
||||
// The main render loop. Iterates all Scenes and all Cameras in those scenes, rendering to the renderer instance.
|
||||
|
||||
this.scene.render(renderer);
|
||||
|
||||
// The Post-Render call. Tidies up loose end, takes snapshots, etc.
|
||||
|
||||
renderer.postRender();
|
||||
|
||||
// The final event before the step repeats. Your last chance to do anything to the canvas before it all starts again.
|
||||
|
||||
eventEmitter.emit(Events.POST_RENDER, renderer, time, delta);
|
||||
} else {
|
||||
// @ts-ignore
|
||||
this.scene.isProcessing = false;
|
||||
}
|
||||
}
|
||||
|
||||
private isDirty(): boolean {
|
||||
if (this._isDirty) {
|
||||
this._isDirty = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Loop through the scenes in forward order
|
||||
for (let i = 0; i < this.scene.scenes.length; i++)
|
||||
{
|
||||
const scene = this.scene.scenes[i];
|
||||
const sys = scene.sys;
|
||||
|
||||
if (sys.settings.visible && sys.settings.status >= Phaser.Scenes.LOADING && sys.settings.status < Phaser.Scenes.SLEEPING)
|
||||
{
|
||||
// @ts-ignore
|
||||
if(typeof scene.isDirty === 'function') {
|
||||
// @ts-ignore
|
||||
const isDirty = scene.isDirty() || scene.tweens.getAllTweens().length > 0;
|
||||
if (isDirty) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the game as needing to be redrawn.
|
||||
*/
|
||||
public markDirty(): void {
|
||||
this._isDirty = true;
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import {GameScene} from "./GameScene";
|
||||
import {connectionManager} from "../../Connexion/ConnectionManager";
|
||||
import {Room} from "../../Connexion/Room";
|
||||
import type {Room} from "../../Connexion/Room";
|
||||
import {MenuScene, MenuSceneName} from "../Menu/MenuScene";
|
||||
import {HelpCameraSettingsScene, HelpCameraSettingsSceneName} from "../Menu/HelpCameraSettingsScene";
|
||||
import {LoginSceneName} from "../Login/LoginScene";
|
||||
@ -21,12 +21,14 @@ export interface HasMovedEvent {
|
||||
export class GameManager {
|
||||
private playerName: string|null;
|
||||
private characterLayers: string[]|null;
|
||||
private companion: string|null;
|
||||
private startRoom!:Room;
|
||||
currentGameSceneName: string|null = null;
|
||||
|
||||
constructor() {
|
||||
this.playerName = localUserStore.getName();
|
||||
this.characterLayers = localUserStore.getCharacterLayers();
|
||||
this.companion = localUserStore.getCompanion();
|
||||
}
|
||||
|
||||
public async init(scenePlugin: Phaser.Scenes.ScenePlugin): Promise<string> {
|
||||
@ -35,7 +37,7 @@ export class GameManager {
|
||||
|
||||
if (!this.playerName) {
|
||||
return LoginSceneName;
|
||||
} else if (!this.characterLayers) {
|
||||
} else if (!this.characterLayers || !this.characterLayers.length) {
|
||||
return SelectCharacterSceneName;
|
||||
} else {
|
||||
return EnableCameraSceneName;
|
||||
@ -64,6 +66,14 @@ export class GameManager {
|
||||
}
|
||||
|
||||
|
||||
setCompanion(companion: string|null): void {
|
||||
this.companion = companion;
|
||||
}
|
||||
|
||||
getCompanion(): string|null {
|
||||
return this.companion;
|
||||
}
|
||||
|
||||
public async loadMap(room: Room, scenePlugin: Phaser.Scenes.ScenePlugin): Promise<void> {
|
||||
const roomID = room.id;
|
||||
const mapUrl = await room.getMapUrl();
|
||||
@ -79,10 +89,7 @@ export class GameManager {
|
||||
console.log('starting '+ (this.currentGameSceneName || this.startRoom.id))
|
||||
scenePlugin.start(this.currentGameSceneName || this.startRoom.id);
|
||||
scenePlugin.launch(MenuSceneName);
|
||||
|
||||
if (!localUserStore.getHelpCameraSettingsShown()) {
|
||||
scenePlugin.launch(HelpCameraSettingsSceneName);//700
|
||||
}
|
||||
scenePlugin.launch(HelpCameraSettingsSceneName);//700
|
||||
}
|
||||
|
||||
public gameSceneIsCreated(scene: GameScene) {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import {ITiledMap} from "../Map/ITiledMap";
|
||||
import type {ITiledMap, ITiledMapLayer} from "../Map/ITiledMap";
|
||||
import {LayersIterator} from "../Map/LayersIterator";
|
||||
|
||||
export type PropertyChangeCallback = (newValue: string | number | boolean | undefined, oldValue: string | number | boolean | undefined, allProps: Map<string, string | boolean | number>) => void;
|
||||
|
||||
@ -10,8 +11,10 @@ export class GameMap {
|
||||
private key: number|undefined;
|
||||
private lastProperties = new Map<string, string|boolean|number>();
|
||||
private callbacks = new Map<string, Array<PropertyChangeCallback>>();
|
||||
public readonly layersIterator: LayersIterator;
|
||||
|
||||
public constructor(private map: ITiledMap) {
|
||||
this.layersIterator = new LayersIterator(map);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -55,7 +58,7 @@ export class GameMap {
|
||||
private getProperties(key: number): Map<string, string|boolean|number> {
|
||||
const properties = new Map<string, string|boolean|number>();
|
||||
|
||||
for (const layer of this.map.layers) {
|
||||
for (const layer of this.layersIterator) {
|
||||
if (layer.type !== 'tilelayer') {
|
||||
continue;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {gameManager, HasMovedEvent} from "./GameManager";
|
||||
import {
|
||||
import type {
|
||||
GroupCreatedUpdatedMessageInterface,
|
||||
MessageUserJoined,
|
||||
MessageUserMovedInterface,
|
||||
@ -10,9 +10,22 @@ import {
|
||||
RoomJoinedMessageInterface
|
||||
} from "../../Connexion/ConnexionModels";
|
||||
import {CurrentGamerInterface, hasMovedEventName, Player} from "../Player/Player";
|
||||
import {DEBUG_MODE, JITSI_PRIVATE_MODE, POSITION_DELAY, RESOLUTION, ZOOM_LEVEL} from "../../Enum/EnvironmentVariable";
|
||||
import {ITiledMap, ITiledMapLayer, ITiledMapLayerProperty, ITiledMapObject, ITiledTileSet} from "../Map/ITiledMap";
|
||||
import {AddPlayerInterface} from "./AddPlayerInterface";
|
||||
import {
|
||||
DEBUG_MODE,
|
||||
JITSI_PRIVATE_MODE,
|
||||
MAX_PER_GROUP,
|
||||
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";
|
||||
@ -36,14 +49,14 @@ import {
|
||||
import {GameMap} from "./GameMap";
|
||||
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
|
||||
import {mediaManager} from "../../WebRtc/MediaManager";
|
||||
import {ItemFactoryInterface} from "../Items/ItemFactoryInterface";
|
||||
import {ActionableItem} from "../Items/ActionableItem";
|
||||
import type {ItemFactoryInterface} from "../Items/ItemFactoryInterface";
|
||||
import type {ActionableItem} from "../Items/ActionableItem";
|
||||
import {UserInputManager} from "../UserInput/UserInputManager";
|
||||
import {soundManager} from "./SoundManager";
|
||||
import {UserMovedMessage} from "../../Messages/generated/messages_pb";
|
||||
import type {UserMovedMessage} from "../../Messages/generated/messages_pb";
|
||||
import {ProtobufClientUtils} from "../../Network/ProtobufClientUtils";
|
||||
import {connectionManager} from "../../Connexion/ConnectionManager";
|
||||
import {RoomConnection} from "../../Connexion/RoomConnection";
|
||||
import type {RoomConnection} from "../../Connexion/RoomConnection";
|
||||
import {GlobalMessageManager} from "../../Administration/GlobalMessageManager";
|
||||
import {userMessageManager} from "../../Administration/UserMessageManager";
|
||||
import {ConsoleGlobalMessageManager} from "../../Administration/ConsoleGlobalMessageManager";
|
||||
@ -68,8 +81,17 @@ import CanvasTexture = Phaser.Textures.CanvasTexture;
|
||||
import GameObject = Phaser.GameObjects.GameObject;
|
||||
import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR;
|
||||
import DOMElement = Phaser.GameObjects.DOMElement;
|
||||
import {Subscription} from "rxjs";
|
||||
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 {waScaleManager} from "../Services/WaScaleManager";
|
||||
|
||||
export interface GameSceneInitInterface {
|
||||
initPosition: PointInterface|null,
|
||||
@ -108,13 +130,13 @@ interface DeleteGroupEventInterface {
|
||||
|
||||
const defaultStartLayerName = 'start';
|
||||
|
||||
export class GameScene extends ResizableScene implements CenterListener {
|
||||
export class GameScene extends DirtyScene implements CenterListener {
|
||||
Terrains : Array<Phaser.Tilemaps.Tileset>;
|
||||
CurrentPlayer!: CurrentGamerInterface;
|
||||
MapPlayers!: Phaser.Physics.Arcade.Group;
|
||||
MapPlayersByKey : Map<number, RemotePlayer> = new Map<number, RemotePlayer>();
|
||||
Map!: Phaser.Tilemaps.Tilemap;
|
||||
Layers!: Array<Phaser.Tilemaps.StaticTilemapLayer>;
|
||||
Layers!: Array<Phaser.Tilemaps.TilemapLayer>;
|
||||
Objects!: Array<Phaser.Physics.Arcade.Sprite>;
|
||||
mapFile!: ITiledMap;
|
||||
groups: Map<number, Sprite>;
|
||||
@ -125,12 +147,12 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
pendingEvents: Queue<InitUserPositionEventInterface|AddPlayerEventInterface|RemovePlayerEventInterface|UserMovedEventInterface|GroupCreatedUpdatedEventInterface|DeleteGroupEventInterface> = new Queue<InitUserPositionEventInterface|AddPlayerEventInterface|RemovePlayerEventInterface|UserMovedEventInterface|GroupCreatedUpdatedEventInterface|DeleteGroupEventInterface>();
|
||||
private initPosition: PositionInterface|null = null;
|
||||
private playersPositionInterpolator = new PlayersPositionInterpolator();
|
||||
public connection!: RoomConnection;
|
||||
public connection: RoomConnection|undefined;
|
||||
private simplePeer!: SimplePeer;
|
||||
private GlobalMessageManager!: GlobalMessageManager;
|
||||
public ConsoleGlobalMessageManager!: ConsoleGlobalMessageManager;
|
||||
private connectionAnswerPromise: Promise<RoomJoinedMessageInterface>;
|
||||
private connectionAnswerPromiseResolve!: (value?: RoomJoinedMessageInterface | PromiseLike<RoomJoinedMessageInterface>) => void;
|
||||
private connectionAnswerPromiseResolve!: (value: RoomJoinedMessageInterface | PromiseLike<RoomJoinedMessageInterface>) => void;
|
||||
// A promise that will resolve when the "create" method is called (signaling loading is ended)
|
||||
private createPromise: Promise<void>;
|
||||
private createPromiseResolve!: (value?: void | PromiseLike<void>) => void;
|
||||
@ -160,8 +182,14 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
private openChatIcon!: OpenChatIcon;
|
||||
private playerName!: string;
|
||||
private characterLayers!: string[];
|
||||
private companion!: string|null;
|
||||
private messageSubscription: Subscription|null = null;
|
||||
private popUpElements : Map<number, DOMElement> = new Map<number, Phaser.GameObjects.DOMElement>();
|
||||
private originalMapUrl: string|undefined;
|
||||
private pinchManager: PinchManager|undefined;
|
||||
private physicsEnabled: boolean = true;
|
||||
private mapTransitioning: boolean = false; //used to prevent transitions happenning at the same time.
|
||||
private onVisibilityChangeCallback: () => void;
|
||||
|
||||
constructor(private room: Room, MapUrlFile: string, customKey?: string|undefined) {
|
||||
super({
|
||||
@ -177,10 +205,11 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
|
||||
this.createPromise = new Promise<void>((resolve, reject): void => {
|
||||
this.createPromiseResolve = resolve;
|
||||
})
|
||||
});
|
||||
this.connectionAnswerPromise = new Promise<RoomJoinedMessageInterface>((resolve, reject): void => {
|
||||
this.connectionAnswerPromiseResolve = resolve;
|
||||
})
|
||||
});
|
||||
this.onVisibilityChangeCallback = this.onVisibilityChange.bind(this);
|
||||
}
|
||||
|
||||
//hook preload scene
|
||||
@ -194,9 +223,14 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
}
|
||||
|
||||
this.load.image(openChatIconName, 'resources/objects/talk.png');
|
||||
if (touchScreenManager.supportTouchScreen) {
|
||||
this.load.image(joystickBaseKey, joystickBaseImg);
|
||||
this.load.image(joystickThumbKey, joystickThumbImg);
|
||||
}
|
||||
this.load.on(FILE_LOAD_ERROR, (file: {src: string}) => {
|
||||
// If we happen to be in HTTP and we are trying to load a URL in HTTPS only... (this happens only in dev environments)
|
||||
if (window.location.protocol === 'http:' && file.src === this.MapUrlFile && file.src.startsWith('http:')) {
|
||||
if (window.location.protocol === 'http:' && file.src === this.MapUrlFile && file.src.startsWith('http:') && this.originalMapUrl === undefined) {
|
||||
this.originalMapUrl = this.MapUrlFile;
|
||||
this.MapUrlFile = this.MapUrlFile.replace('http://', 'https://');
|
||||
this.load.tilemapTiledJSON(this.MapUrlFile, this.MapUrlFile);
|
||||
this.load.on('filecomplete-tilemapJSON-'+this.MapUrlFile, (key: string, type: string, data: unknown) => {
|
||||
@ -204,10 +238,25 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 127.0.0.1, localhost and *.localhost are considered secure, even on HTTP.
|
||||
// So if we are in https, we can still try to load a HTTP local resource (can be useful for testing purposes)
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure
|
||||
const url = new URL(file.src);
|
||||
const host = url.host.split(':')[0];
|
||||
if (window.location.protocol === 'https:' && file.src === this.MapUrlFile && (host === '127.0.0.1' || host === 'localhost' || host.endsWith('.localhost')) && this.originalMapUrl === undefined) {
|
||||
this.originalMapUrl = this.MapUrlFile;
|
||||
this.MapUrlFile = this.MapUrlFile.replace('https://', 'http://');
|
||||
this.load.tilemapTiledJSON(this.MapUrlFile, this.MapUrlFile);
|
||||
this.load.on('filecomplete-tilemapJSON-'+this.MapUrlFile, (key: string, type: string, data: unknown) => {
|
||||
this.onMapLoad(data);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.scene.start(ErrorSceneName, {
|
||||
title: 'Network error',
|
||||
subTitle: 'An error occurred while loading resource:',
|
||||
message: file.src
|
||||
message: this.originalMapUrl ?? file.src
|
||||
});
|
||||
});
|
||||
this.load.on('filecomplete-tilemapJSON-'+this.MapUrlFile, (key: string, type: string, data: unknown) => {
|
||||
@ -225,6 +274,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
this.load.spritesheet('layout_modes', 'resources/objects/layout_modes.png', {frameWidth: 32, frameHeight: 32});
|
||||
this.load.bitmapFont('main_font', 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
|
||||
|
||||
//this function must stay at the end of preload function
|
||||
addLoader(this);
|
||||
}
|
||||
|
||||
@ -324,11 +374,17 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
|
||||
//hook create scene
|
||||
create(): void {
|
||||
this.trackDirtyAnims();
|
||||
|
||||
gameManager.gameSceneIsCreated(this);
|
||||
urlManager.pushRoomIdToUrl(this.room);
|
||||
this.startLayerName = urlManager.getStartLayerNameFromUrl();
|
||||
|
||||
this.messageSubscription = worldFullMessageStream.stream.subscribe((message) => this.showWorldFullError())
|
||||
if (touchScreenManager.supportTouchScreen) {
|
||||
this.pinchManager = new PinchManager(this);
|
||||
}
|
||||
|
||||
this.messageSubscription = worldFullMessageStream.stream.subscribe((message) => this.showWorldFullError(message))
|
||||
|
||||
const playerName = gameManager.getPlayerName();
|
||||
if (!playerName) {
|
||||
@ -336,7 +392,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
}
|
||||
this.playerName = playerName;
|
||||
this.characterLayers = gameManager.getCharacterLayers();
|
||||
|
||||
this.companion = gameManager.getCompanion();
|
||||
|
||||
//initalise map
|
||||
this.Map = this.add.tilemap(this.MapUrlFile);
|
||||
@ -350,11 +406,11 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
this.physics.world.setBounds(0, 0, this.Map.widthInPixels, this.Map.heightInPixels);
|
||||
|
||||
//add layer on map
|
||||
this.Layers = new Array<Phaser.Tilemaps.StaticTilemapLayer>();
|
||||
this.Layers = new Array<Phaser.Tilemaps.TilemapLayer>();
|
||||
let depth = -2;
|
||||
for (const layer of this.mapFile.layers) {
|
||||
for (const layer of this.gameMap.layersIterator) {
|
||||
if (layer.type === 'tilelayer') {
|
||||
this.addLayer(this.Map.createStaticLayer(layer.name, this.Terrains, 0, 0).setDepth(depth));
|
||||
this.addLayer(this.Map.createLayer(layer.name, this.Terrains, 0, 0).setDepth(depth));
|
||||
|
||||
const exitSceneUrl = this.getExitSceneUrl(layer);
|
||||
if (exitSceneUrl !== undefined) {
|
||||
@ -368,9 +424,16 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
if (layer.type === 'objectgroup' && layer.name === 'floorLayer') {
|
||||
depth = 10000;
|
||||
}
|
||||
if (layer.type === 'objectgroup') {
|
||||
for (const object of layer.objects) {
|
||||
if (object.text) {
|
||||
TextUtils.createTextFromITiledMapObject(this, object);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (depth === -2) {
|
||||
throw new Error('Your map MUST contain a layer of type "objectgroup" whose name is "floorLayer" that represents the layer characters are drawn at.');
|
||||
throw new Error('Your map MUST contain a layer of type "objectgroup" whose name is "floorLayer" that represents the layer characters are drawn at. This layer cannot be contained in a group.');
|
||||
}
|
||||
|
||||
this.initStartXAndStartY();
|
||||
@ -381,10 +444,15 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
//initialise list of other player
|
||||
this.MapPlayers = this.physics.add.group({immovable: true});
|
||||
|
||||
|
||||
//create input to move
|
||||
this.userInputManager = new UserInputManager(this);
|
||||
mediaManager.setUserInputManager(this.userInputManager);
|
||||
|
||||
if (localUserStore.getFullscreen()) {
|
||||
document.querySelector('body')?.requestFullscreen();
|
||||
}
|
||||
|
||||
//notify game manager can to create currentUser in map
|
||||
this.createCurrentPlayer();
|
||||
this.removeAllRemotePlayers(); //cleanup the list of remote players in case the scene was rebooted
|
||||
@ -424,9 +492,10 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
this.openChatIcon = new OpenChatIcon(this, 2, this.game.renderer.height - 2)
|
||||
|
||||
// FIXME: change this to use the UserInputManager class for input
|
||||
this.input.keyboard.on('keyup-M', () => {
|
||||
// FIXME: Comment this feature because when user write M key in report input, the layout change.
|
||||
/*this.input.keyboard.on('keyup-M', () => {
|
||||
this.switchLayoutMode();
|
||||
});
|
||||
});*/
|
||||
|
||||
this.reposition();
|
||||
|
||||
@ -439,6 +508,8 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
if (!this.room.isDisconnected()) {
|
||||
this.connect();
|
||||
}
|
||||
|
||||
document.addEventListener('visibilitychange', this.onVisibilityChangeCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -460,7 +531,9 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
top: camera.scrollY,
|
||||
right: camera.scrollX + camera.width,
|
||||
bottom: camera.scrollY + camera.height,
|
||||
}).then((onConnect: OnConnectInterface) => {
|
||||
},
|
||||
this.companion
|
||||
).then((onConnect: OnConnectInterface) => {
|
||||
this.connection = onConnect.connection;
|
||||
|
||||
this.connection.onUserJoins((message: MessageUserJoined) => {
|
||||
@ -468,7 +541,8 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
userId: message.userId,
|
||||
characterLayers: message.characterLayers,
|
||||
name: message.name,
|
||||
position: message.position
|
||||
position: message.position,
|
||||
companion: message.companion
|
||||
}
|
||||
this.addPlayer(userMessage);
|
||||
});
|
||||
@ -557,6 +631,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
self.chatModeSprite.setVisible(false);
|
||||
self.openChatIcon.setVisible(false);
|
||||
audioManager.restoreVolume();
|
||||
self.onVisibilityChange();
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -647,7 +722,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
if(openWebsiteTriggerValue && openWebsiteTriggerValue === ON_ACTION_TRIGGER_BUTTON) {
|
||||
let message = allProps.get(WEBSITE_MESSAGE_PROPERTIES);
|
||||
if(message === undefined){
|
||||
message = 'Press on SPACE to open the web site';
|
||||
message = 'Press SPACE or touch here to open web site';
|
||||
}
|
||||
layoutManager.addActionButton('openWebsite', message.toString(), () => {
|
||||
openWebsiteFunction();
|
||||
@ -668,7 +743,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
if (JITSI_PRIVATE_MODE && !jitsiUrl) {
|
||||
const adminTag = allProps.get("jitsiRoomAdminTag") as string|undefined;
|
||||
|
||||
this.connection.emitQueryJitsiJwtMessage(roomName, adminTag);
|
||||
this.connection?.emitQueryJitsiJwtMessage(roomName, adminTag);
|
||||
} else {
|
||||
this.startJitsi(roomName, undefined);
|
||||
}
|
||||
@ -679,7 +754,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
if(jitsiTriggerValue && jitsiTriggerValue === ON_ACTION_TRIGGER_BUTTON) {
|
||||
let message = allProps.get(JITSI_MESSAGE_PROPERTIES);
|
||||
if (message === undefined) {
|
||||
message = 'Press on SPACE to enter in jitsi meet room';
|
||||
message = 'Press SPACE or touch here to enter Jitsi Meet room';
|
||||
}
|
||||
layoutManager.addActionButton('jitsiRoom', message.toString(), () => {
|
||||
openJitsiRoomFunction();
|
||||
@ -691,9 +766,9 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
});
|
||||
this.gameMap.onPropertyChange('silent', (newValue, oldValue) => {
|
||||
if (newValue === undefined || newValue === false || newValue === '') {
|
||||
this.connection.setSilent(false);
|
||||
this.connection?.setSilent(false);
|
||||
} else {
|
||||
this.connection.setSilent(true);
|
||||
this.connection?.setSilent(true);
|
||||
}
|
||||
});
|
||||
this.gameMap.onPropertyChange('playAudio', (newValue, oldValue, allProps) => {
|
||||
@ -834,6 +909,8 @@ ${escapedMessage}
|
||||
}
|
||||
|
||||
private onMapExit(exitKey: string) {
|
||||
if (this.mapTransitioning) return;
|
||||
this.mapTransitioning = true;
|
||||
const {roomId, hash} = Room.getIdFromIdentifier(exitKey, this.MapUrlFile, this.instance);
|
||||
if (!roomId) throw new Error('Could not find the room from its exit key: '+exitKey);
|
||||
urlManager.pushStartLayerNameToUrl(hash);
|
||||
@ -851,6 +928,7 @@ ${escapedMessage}
|
||||
this.initPositionFromLayerName(hash || defaultStartLayerName);
|
||||
this.CurrentPlayer.x = this.startX;
|
||||
this.CurrentPlayer.y = this.startY;
|
||||
setTimeout(() => this.mapTransitioning = false, 500);
|
||||
}
|
||||
}
|
||||
|
||||
@ -867,18 +945,27 @@ ${escapedMessage}
|
||||
audioManager.unloadAudio();
|
||||
// We are completely destroying the current scene to avoid using a half-backed instance when coming back to the same map.
|
||||
this.connection?.closeConnection();
|
||||
this.simplePeer.closeAllConnections();
|
||||
this.simplePeer?.closeAllConnections();
|
||||
this.simplePeer?.unregister();
|
||||
this.messageSubscription?.unsubscribe();
|
||||
this.userInputManager.destroy();
|
||||
this.pinchManager?.destroy();
|
||||
|
||||
for(const iframeEvents of this.iframeSubscriptionList){
|
||||
iframeEvents.unsubscribe();
|
||||
}
|
||||
|
||||
document.removeEventListener('visibilitychange', this.onVisibilityChangeCallback);
|
||||
}
|
||||
|
||||
private removeAllRemotePlayers(): void {
|
||||
this.MapPlayersByKey.forEach((player: RemotePlayer) => {
|
||||
player.destroy();
|
||||
|
||||
if (player.companion) {
|
||||
player.companion.destroy();
|
||||
}
|
||||
|
||||
this.MapPlayers.remove(player);
|
||||
});
|
||||
this.MapPlayersByKey = new Map<number, RemotePlayer>();
|
||||
@ -926,13 +1013,14 @@ ${escapedMessage}
|
||||
}
|
||||
|
||||
private initPositionFromLayerName(layerName: string) {
|
||||
for (const layer of this.mapFile.layers) {
|
||||
if (layerName === layer.name && layer.type === 'tilelayer' && (layerName === defaultStartLayerName || this.isStartLayer(layer))) {
|
||||
for (const layer of this.gameMap.layersIterator) {
|
||||
if ((layerName === layer.name || layer.name.endsWith('/'+layerName)) && layer.type === 'tilelayer' && (layerName === defaultStartLayerName || this.isStartLayer(layer))) {
|
||||
const startPosition = this.startUser(layer);
|
||||
this.startX = startPosition.x + this.mapFile.tilewidth/2;
|
||||
this.startY = startPosition.y + this.mapFile.tileheight/2;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private getExitUrl(layer: ITiledMapLayer): string|undefined {
|
||||
@ -955,7 +1043,7 @@ ${escapedMessage}
|
||||
}
|
||||
|
||||
private getProperty(layer: ITiledMapLayer|ITiledMap, name: string): string|boolean|number|undefined {
|
||||
const properties: ITiledMapLayerProperty[] = layer.properties;
|
||||
const properties: ITiledMapLayerProperty[]|undefined = layer.properties;
|
||||
if (!properties) {
|
||||
return undefined;
|
||||
}
|
||||
@ -967,7 +1055,7 @@ ${escapedMessage}
|
||||
}
|
||||
|
||||
private getProperties(layer: ITiledMapLayer|ITiledMap, name: string): (string|number|boolean|undefined)[] {
|
||||
const properties: ITiledMapLayerProperty[] = layer.properties;
|
||||
const properties: ITiledMapLayerProperty[]|undefined = layer.properties;
|
||||
if (!properties) {
|
||||
return [];
|
||||
}
|
||||
@ -975,13 +1063,13 @@ ${escapedMessage}
|
||||
}
|
||||
|
||||
//todo: push that into the gameManager
|
||||
private async loadNextGame(exitSceneIdentifier: string){
|
||||
private loadNextGame(exitSceneIdentifier: string): void {
|
||||
const {roomId, hash} = Room.getIdFromIdentifier(exitSceneIdentifier, this.MapUrlFile, this.instance);
|
||||
const room = new Room(roomId);
|
||||
await gameManager.loadMap(room, this.scene);
|
||||
gameManager.loadMap(room, this.scene).catch(() => {});
|
||||
}
|
||||
|
||||
private startUser(layer: ITiledMapLayer): PositionInterface {
|
||||
private startUser(layer: ITiledMapTileLayer): PositionInterface {
|
||||
const tiles = layer.data;
|
||||
if (typeof(tiles) === 'string') {
|
||||
throw new Error('The content of a JSON map must be filled as a JSON array, not as a string');
|
||||
@ -1011,17 +1099,19 @@ ${escapedMessage}
|
||||
//todo: in a dedicated class/function?
|
||||
initCamera() {
|
||||
this.cameras.main.setBounds(0,0, this.Map.widthInPixels, this.Map.heightInPixels);
|
||||
this.cameras.main.startFollow(this.CurrentPlayer, true);
|
||||
this.updateCameraOffset();
|
||||
this.cameras.main.setZoom(ZOOM_LEVEL);
|
||||
}
|
||||
|
||||
addLayer(Layer : Phaser.Tilemaps.StaticTilemapLayer){
|
||||
addLayer(Layer : Phaser.Tilemaps.TilemapLayer){
|
||||
this.Layers.push(Layer);
|
||||
}
|
||||
|
||||
createCollisionWithPlayer() {
|
||||
this.physics.disableUpdate();
|
||||
this.physicsEnabled = false;
|
||||
//add collision layer
|
||||
this.Layers.forEach((Layer: Phaser.Tilemaps.StaticTilemapLayer) => {
|
||||
this.Layers.forEach((Layer: Phaser.Tilemaps.TilemapLayer) => {
|
||||
this.physics.add.collider(this.CurrentPlayer, Layer, (object1: GameObject, object2: GameObject) => {
|
||||
//this.CurrentPlayer.say("Collision with layer : "+ (object2 as Tile).layer.name)
|
||||
});
|
||||
@ -1049,7 +1139,9 @@ ${escapedMessage}
|
||||
texturesPromise,
|
||||
PlayerAnimationDirections.Down,
|
||||
false,
|
||||
this.userInputManager
|
||||
this.userInputManager,
|
||||
this.companion,
|
||||
this.companion !== null ? lazyLoadCompanionResource(this.load, this.companion) : undefined
|
||||
);
|
||||
}catch (err){
|
||||
if(err instanceof TextureError) {
|
||||
@ -1135,7 +1227,7 @@ ${escapedMessage}
|
||||
this.lastMoveEventSent = event;
|
||||
this.lastSentTick = this.currentTick;
|
||||
const camera = this.cameras.main;
|
||||
this.connection.sharePosition(event.x, event.y, event.direction, event.moving, {
|
||||
this.connection?.sharePosition(event.x, event.y, event.direction, event.moving, {
|
||||
left: camera.scrollX,
|
||||
top: camera.scrollY,
|
||||
right: camera.scrollX + camera.width,
|
||||
@ -1148,12 +1240,27 @@ ${escapedMessage}
|
||||
* @param delta The delta time in ms since the last frame. This is a smoothed and capped value based on the FPS rate.
|
||||
*/
|
||||
update(time: number, delta: number) : void {
|
||||
mediaManager.setLastUpdateScene();
|
||||
this.dirty = false;
|
||||
mediaManager.updateScene();
|
||||
this.currentTick = time;
|
||||
if (this.CurrentPlayer.isMoving()) {
|
||||
this.dirty = true;
|
||||
}
|
||||
this.CurrentPlayer.moveUser(delta);
|
||||
if (this.CurrentPlayer.isMoving()) {
|
||||
this.dirty = true;
|
||||
if (!this.physicsEnabled) {
|
||||
this.physics.enableUpdate();
|
||||
this.physicsEnabled = true;
|
||||
}
|
||||
} else if (this.physicsEnabled) {
|
||||
this.physics.disableUpdate();
|
||||
this.physicsEnabled = false;
|
||||
}
|
||||
|
||||
// Let's handle all events
|
||||
while (this.pendingEvents.length !== 0) {
|
||||
this.dirty = true;
|
||||
const event = this.pendingEvents.dequeue();
|
||||
switch (event.type) {
|
||||
case "InitUserPositionEvent":
|
||||
@ -1179,6 +1286,7 @@ ${escapedMessage}
|
||||
// Let's move all users
|
||||
const updatedPlayersPositions = this.playersPositionInterpolator.getUpdatedPositions(time);
|
||||
updatedPlayersPositions.forEach((moveEvent: HasMovedEvent, userId: number) => {
|
||||
this.dirty = true;
|
||||
const player: RemotePlayer | undefined = this.MapPlayersByKey.get(userId);
|
||||
if (player === undefined) {
|
||||
throw new Error('Cannot find player with ID "' + userId + '"');
|
||||
@ -1201,7 +1309,7 @@ ${escapedMessage}
|
||||
* Put all the players on the map on map load.
|
||||
*/
|
||||
private doInitUsersPosition(usersPosition: MessageUserPositionInterface[]): void {
|
||||
const currentPlayerId = this.connection.getUserId();
|
||||
const currentPlayerId = this.connection?.getUserId();
|
||||
this.removeAllRemotePlayers();
|
||||
// load map
|
||||
usersPosition.forEach((userPosition : MessageUserPositionInterface) => {
|
||||
@ -1241,7 +1349,9 @@ ${escapedMessage}
|
||||
addPlayerData.name,
|
||||
texturesPromise,
|
||||
addPlayerData.position.direction as PlayerAnimationDirections,
|
||||
addPlayerData.position.moving
|
||||
addPlayerData.position.moving,
|
||||
addPlayerData.companion,
|
||||
addPlayerData.companion !== null ? lazyLoadCompanionResource(this.load, addPlayerData.companion) : undefined
|
||||
);
|
||||
this.MapPlayers.add(player);
|
||||
this.MapPlayersByKey.set(player.userId, player);
|
||||
@ -1264,6 +1374,11 @@ ${escapedMessage}
|
||||
console.error('Cannot find user with id ', userId);
|
||||
} else {
|
||||
player.destroy();
|
||||
|
||||
if (player.companion) {
|
||||
player.companion.destroy();
|
||||
}
|
||||
|
||||
this.MapPlayers.remove(player);
|
||||
}
|
||||
this.MapPlayersByKey.delete(userId);
|
||||
@ -1308,7 +1423,7 @@ ${escapedMessage}
|
||||
this,
|
||||
Math.round(groupPositionMessage.position.x),
|
||||
Math.round(groupPositionMessage.position.y),
|
||||
groupPositionMessage.groupSize === 4 ? 'circleSprite-red' : 'circleSprite-white'
|
||||
groupPositionMessage.groupSize === MAX_PER_GROUP ? 'circleSprite-red' : 'circleSprite-white'
|
||||
);
|
||||
sprite.setDisplayOrigin(48, 48);
|
||||
this.add.existing(sprite);
|
||||
@ -1338,15 +1453,16 @@ ${escapedMessage}
|
||||
* Sends to the server an event emitted by one of the ActionableItems.
|
||||
*/
|
||||
emitActionableEvent(itemId: number, eventName: string, state: unknown, parameters: unknown) {
|
||||
this.connection.emitActionableEvent(itemId, eventName, state, parameters);
|
||||
this.connection?.emitActionableEvent(itemId, eventName, state, parameters);
|
||||
}
|
||||
|
||||
public onResize(): void {
|
||||
public onResize(ev: UIEvent): void {
|
||||
super.onResize(ev);
|
||||
this.reposition();
|
||||
|
||||
// Send new viewport to server
|
||||
const camera = this.cameras.main;
|
||||
this.connection.setViewport({
|
||||
this.connection?.setViewport({
|
||||
left: camera.scrollX,
|
||||
top: camera.scrollY,
|
||||
right: camera.scrollX + camera.width,
|
||||
@ -1376,19 +1492,18 @@ ${escapedMessage}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the offset of the character compared to the center of the screen according to the layout mananger
|
||||
* (tries to put the character in the center of the reamining space if there is a discussion going on.
|
||||
* Updates the offset of the character compared to the center of the screen according to the layout manager
|
||||
* (tries to put the character in the center of the remaining space if there is a discussion going on.
|
||||
*/
|
||||
private updateCameraOffset(): void {
|
||||
const array = layoutManager.findBiggestAvailableArray();
|
||||
let xCenter = (array.xEnd - array.xStart) / 2 + array.xStart;
|
||||
let yCenter = (array.yEnd - array.yStart) / 2 + array.yStart;
|
||||
const xCenter = (array.xEnd - array.xStart) / 2 + array.xStart;
|
||||
const yCenter = (array.yEnd - array.yStart) / 2 + array.yStart;
|
||||
|
||||
const game = HtmlUtils.querySelectorOrFail<HTMLCanvasElement>('#game canvas');
|
||||
// Let's put this in Game coordinates by applying the zoom level:
|
||||
xCenter /= ZOOM_LEVEL * RESOLUTION;
|
||||
yCenter /= ZOOM_LEVEL * RESOLUTION;
|
||||
|
||||
this.cameras.main.startFollow(this.CurrentPlayer, true, 1, 1, xCenter - this.game.renderer.width / 2, yCenter - this.game.renderer.height / 2);
|
||||
this.cameras.main.setFollowOffset((xCenter - game.offsetWidth/2) * window.devicePixelRatio / this.scale.zoom , (yCenter - game.offsetHeight/2) * window.devicePixelRatio / this.scale.zoom);
|
||||
}
|
||||
|
||||
public onCenterChange(): void {
|
||||
@ -1402,13 +1517,15 @@ ${escapedMessage}
|
||||
const jitsiUrl = allProps.get("jitsiUrl") as string|undefined;
|
||||
|
||||
jitsiFactory.start(roomName, this.playerName, jwt, jitsiConfig, jitsiInterfaceConfig, jitsiUrl);
|
||||
this.connection.setSilent(true);
|
||||
this.connection?.setSilent(true);
|
||||
mediaManager.hideGameOverlay();
|
||||
|
||||
//permit to stop jitsi when user close iframe
|
||||
mediaManager.addTriggerCloseJitsiFrameButton('close-jisi',() => {
|
||||
this.stopJitsi();
|
||||
});
|
||||
|
||||
this.onVisibilityChange();
|
||||
}
|
||||
|
||||
public stopJitsi(): void {
|
||||
@ -1417,6 +1534,7 @@ ${escapedMessage}
|
||||
mediaManager.showGameOverlay();
|
||||
|
||||
mediaManager.removeTriggerCloseJitsiFrameButton('close-jisi');
|
||||
this.onVisibilityChange();
|
||||
}
|
||||
|
||||
//todo: put this into an 'orchestrator' scene (EntryScene?)
|
||||
@ -1431,14 +1549,45 @@ ${escapedMessage}
|
||||
}
|
||||
|
||||
//todo: put this into an 'orchestrator' scene (EntryScene?)
|
||||
private showWorldFullError(): void {
|
||||
private showWorldFullError(message: string|null): void {
|
||||
this.cleanupClosingScene();
|
||||
this.scene.stop(ReconnectingSceneName);
|
||||
this.scene.remove(ReconnectingSceneName);
|
||||
this.userInputManager.disableControls();
|
||||
this.scene.start(ErrorSceneName, {
|
||||
title: 'Connection rejected',
|
||||
subTitle: 'The world you are trying to join is full. Try again later.',
|
||||
message: 'If you want more information, you may contact us at: workadventure@thecodingmachine.com'
|
||||
});
|
||||
//FIX ME to use status code
|
||||
if(message == undefined){
|
||||
this.scene.start(ErrorSceneName, {
|
||||
title: 'Connection rejected',
|
||||
subTitle: 'The world you are trying to join is full. Try again later.',
|
||||
message: 'If you want more information, you may contact us at: workadventure@thecodingmachine.com'
|
||||
});
|
||||
}else{
|
||||
this.scene.start(ErrorSceneName, {
|
||||
title: 'Connection rejected',
|
||||
subTitle: 'You cannot join the World. Try again later. \n\r \n\r Error: '+message+'.',
|
||||
message: 'If you want more information, you may contact administrator or contact us at: workadventure@thecodingmachine.com'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
zoomByFactor(zoomFactor: number) {
|
||||
waScaleManager.zoomModifier *= zoomFactor;
|
||||
this.updateCameraOffset();
|
||||
}
|
||||
|
||||
private onVisibilityChange(): void {
|
||||
// If the overlay is not displayed, we are in Jitsi. We don't need the webcam.
|
||||
if (!mediaManager.isGameOverlayVisible()) {
|
||||
mediaManager.blurCamera();
|
||||
return;
|
||||
}
|
||||
|
||||
if (document.visibilityState === 'visible') {
|
||||
mediaManager.focusCamera();
|
||||
} else {
|
||||
if (this.simplePeer.getNbConnections() === 0) {
|
||||
mediaManager.blurCamera();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {HasMovedEvent} from "./GameManager";
|
||||
import type {HasMovedEvent} from "./GameManager";
|
||||
import {MAX_EXTRAPOLATION_TIME} from "../../Enum/EnvironmentVariable";
|
||||
import {PositionInterface} from "../../Connexion/ConnexionModels";
|
||||
import type {PositionInterface} from "../../Connexion/ConnexionModels";
|
||||
|
||||
export class PlayerMovement {
|
||||
public constructor(private startPosition: PositionInterface, private startTick: number, private endPosition: HasMovedEvent, private endTick: number) {
|
||||
|
@ -2,8 +2,8 @@
|
||||
* This class is in charge of computing the position of all players.
|
||||
* Player movement is delayed by 200ms so position depends on ticks.
|
||||
*/
|
||||
import {PlayerMovement} from "./PlayerMovement";
|
||||
import {HasMovedEvent} from "./GameManager";
|
||||
import type {PlayerMovement} from "./PlayerMovement";
|
||||
import type {HasMovedEvent} from "./GameManager";
|
||||
|
||||
export class PlayersPositionInterpolator {
|
||||
playerMovements: Map<number, PlayerMovement> = new Map<number, PlayerMovement>();
|
||||
|
@ -4,7 +4,7 @@
|
||||
*/
|
||||
import Sprite = Phaser.GameObjects.Sprite;
|
||||
import {OutlinePipeline} from "../Shaders/OutlinePipeline";
|
||||
import {GameScene} from "../Game/GameScene";
|
||||
import type {GameScene} from "../Game/GameScene";
|
||||
|
||||
type EventCallback = (state: unknown, parameters: unknown) => void;
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
import * as Phaser from 'phaser';
|
||||
import {Scene} from "phaser";
|
||||
import Sprite = Phaser.GameObjects.Sprite;
|
||||
import {ITiledMapObject} from "../../Map/ITiledMap";
|
||||
import {ItemFactoryInterface} from "../ItemFactoryInterface";
|
||||
import {GameScene} from "../../Game/GameScene";
|
||||
import type {ITiledMapObject} from "../../Map/ITiledMap";
|
||||
import type {ItemFactoryInterface} from "../ItemFactoryInterface";
|
||||
import type {GameScene} from "../../Game/GameScene";
|
||||
import {ActionableItem} from "../ActionableItem";
|
||||
import * as tg from "generic-type-guard";
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import type {GameScene} from "../Game/GameScene";
|
||||
import type {ITiledMapObject} from "../Map/ITiledMap";
|
||||
import type {ActionableItem} from "./ActionableItem";
|
||||
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
|
||||
import {GameScene} from "../Game/GameScene";
|
||||
import {ITiledMapObject} from "../Map/ITiledMap";
|
||||
import {ActionableItem} from "./ActionableItem";
|
||||
|
||||
export interface ItemFactoryInterface {
|
||||
preload: (loader: LoaderPlugin) => void;
|
||||
|
@ -1,8 +1,8 @@
|
||||
import {ResizableScene} from "./ResizableScene";
|
||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
||||
import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
||||
import {loadCustomTexture} from "../Entity/PlayerTexturesLoadingManager";
|
||||
import {CharacterTexture} from "../../Connexion/LocalUser";
|
||||
import type {CharacterTexture} from "../../Connexion/LocalUser";
|
||||
|
||||
export abstract class AbstractCharacterScene extends ResizableScene {
|
||||
|
||||
|
@ -1,46 +1,32 @@
|
||||
import {EnableCameraSceneName} from "./EnableCameraScene";
|
||||
import {TextField} from "../Components/TextField";
|
||||
import Image = Phaser.GameObjects.Image;
|
||||
import Rectangle = Phaser.GameObjects.Rectangle;
|
||||
import {loadAllLayers, loadCustomTexture} from "../Entity/PlayerTexturesLoadingManager";
|
||||
import {loadAllLayers} from "../Entity/PlayerTexturesLoadingManager";
|
||||
import Sprite = Phaser.GameObjects.Sprite;
|
||||
import Container = Phaser.GameObjects.Container;
|
||||
import {gameManager} from "../Game/GameManager";
|
||||
import {ResizableScene} from "./ResizableScene";
|
||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||
import {addLoader} from "../Components/Loader";
|
||||
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
||||
import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
||||
import {AbstractCharacterScene} from "./AbstractCharacterScene";
|
||||
import {areCharacterLayersValid} from "../../Connexion/LocalUser";
|
||||
import { MenuScene } from "../Menu/MenuScene";
|
||||
import { SelectCharacterSceneName } from "./SelectCharacterScene";
|
||||
|
||||
export const CustomizeSceneName = "CustomizeScene";
|
||||
|
||||
enum CustomizeTextures{
|
||||
icon = "icon",
|
||||
arrowRight = "arrow_right",
|
||||
mainFont = "main_font",
|
||||
arrowUp = "arrow_up",
|
||||
}
|
||||
export const CustomizeSceneKey = "CustomizeScene";
|
||||
const customizeSceneKey = 'customizeScene';
|
||||
|
||||
export class CustomizeScene extends AbstractCharacterScene {
|
||||
|
||||
private textField!: TextField;
|
||||
private enterField!: TextField;
|
||||
|
||||
private arrowRight!: Image;
|
||||
private arrowLeft!: Image;
|
||||
|
||||
private arrowDown!: Image;
|
||||
private arrowUp!: Image;
|
||||
|
||||
private Rectangle!: Rectangle;
|
||||
|
||||
private logo!: Image;
|
||||
|
||||
private selectedLayers: number[] = [0];
|
||||
private containersRow: Container[][] = [];
|
||||
private activeRow:number = 0;
|
||||
private layers: BodyResourceDescriptionInterface[][] = [];
|
||||
|
||||
private customizeSceneElement!: Phaser.GameObjects.DOMElement;
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
key: CustomizeSceneName
|
||||
@ -48,52 +34,57 @@ export class CustomizeScene extends AbstractCharacterScene {
|
||||
}
|
||||
|
||||
preload() {
|
||||
addLoader(this);
|
||||
this.load.html(customizeSceneKey, 'resources/html/CustomCharacterScene.html');
|
||||
|
||||
this.layers = loadAllLayers(this.load);
|
||||
this.loadCustomSceneSelectCharacters().then((bodyResourceDescriptions) => {
|
||||
bodyResourceDescriptions.forEach((bodyResourceDescription) => {
|
||||
if(!bodyResourceDescription.level){
|
||||
if(bodyResourceDescription.level == undefined || bodyResourceDescription.level < 0 || bodyResourceDescription.level > 5 ){
|
||||
throw 'Texture level is null';
|
||||
}
|
||||
this.layers[bodyResourceDescription.level].unshift(bodyResourceDescription);
|
||||
});
|
||||
});
|
||||
|
||||
this.load.image(CustomizeTextures.arrowRight, "resources/objects/arrow_right.png");
|
||||
this.load.image(CustomizeTextures.icon, "resources/logos/tcm_full.png");
|
||||
this.load.image(CustomizeTextures.arrowUp, "resources/objects/arrow_up.png");
|
||||
this.load.bitmapFont(CustomizeTextures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
|
||||
//this function must stay at the end of preload function
|
||||
addLoader(this);
|
||||
}
|
||||
|
||||
create() {
|
||||
this.textField = new TextField(this, this.game.renderer.width / 2, 30, 'Customize your own Avatar!');
|
||||
this.customizeSceneElement = this.add.dom(-1000, 0).createFromCache(customizeSceneKey);
|
||||
this.centerXDomElement(this.customizeSceneElement, 150);
|
||||
MenuScene.revealMenusAfterInit(this.customizeSceneElement, customizeSceneKey);
|
||||
|
||||
this.enterField = new TextField(this, this.game.renderer.width / 2, 40, 'you can start the game by pressing SPACE..');
|
||||
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();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.logo = new Image(this, this.game.renderer.width - 30, this.game.renderer.height - 20, CustomizeTextures.icon);
|
||||
this.add.existing(this.logo);
|
||||
|
||||
|
||||
this.arrowRight = new Image(this, this.game.renderer.width*0.9, this.game.renderer.height/2, CustomizeTextures.arrowRight);
|
||||
this.add.existing(this.arrowRight);
|
||||
|
||||
this.arrowLeft = new Image(this, this.game.renderer.width/9, this.game.renderer.height/2, CustomizeTextures.arrowRight);
|
||||
this.arrowLeft.flipX = true;
|
||||
this.add.existing(this.arrowLeft);
|
||||
|
||||
|
||||
this.Rectangle = this.add.rectangle(this.cameras.main.worldView.x + this.cameras.main.width / 2, this.cameras.main.worldView.y + this.cameras.main.height / 2, 32, 33)
|
||||
this.Rectangle = this.add.rectangle(this.cameras.main.worldView.x + this.cameras.main.width / 2, this.cameras.main.worldView.y + this.cameras.main.height / 3, 32, 33)
|
||||
this.Rectangle.setStrokeStyle(2, 0xFFFFFF);
|
||||
this.add.existing(this.Rectangle);
|
||||
|
||||
this.arrowDown = new Image(this, this.game.renderer.width - 30, 100, CustomizeTextures.arrowUp);
|
||||
this.arrowDown.flipY = true;
|
||||
this.add.existing(this.arrowDown);
|
||||
|
||||
this.arrowUp = new Image(this, this.game.renderer.width - 30, 60, CustomizeTextures.arrowUp);
|
||||
this.add.existing(this.arrowUp);
|
||||
|
||||
this.createCustomizeLayer(0, 0, 0);
|
||||
this.createCustomizeLayer(0, 0, 1);
|
||||
this.createCustomizeLayer(0, 0, 2);
|
||||
@ -103,19 +94,10 @@ export class CustomizeScene extends AbstractCharacterScene {
|
||||
|
||||
this.moveLayers();
|
||||
this.input.keyboard.on('keyup-ENTER', () => {
|
||||
const layers: string[] = [];
|
||||
let i = 0;
|
||||
for (const layerItem of this.selectedLayers) {
|
||||
if (layerItem !== undefined) {
|
||||
layers.push(this.layers[i][layerItem].name);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
gameManager.setCharacterLayers(layers);
|
||||
|
||||
this.scene.sleep(CustomizeSceneName);
|
||||
gameManager.tryResumingGame(this, EnableCameraSceneName);
|
||||
this.nextSceneToCamera();
|
||||
});
|
||||
this.input.keyboard.on('keyup-BACKSPACE', () => {
|
||||
this.backToPreviousScene();
|
||||
});
|
||||
|
||||
this.input.keyboard.on('keyup-RIGHT', () => this.moveCursorHorizontally(1));
|
||||
@ -130,6 +112,8 @@ export class CustomizeScene extends AbstractCharacterScene {
|
||||
this.moveLayers();
|
||||
this.updateSelectedLayer();
|
||||
}
|
||||
|
||||
this.onResize();
|
||||
}
|
||||
|
||||
private moveCursorHorizontally(index: number): void {
|
||||
@ -145,6 +129,27 @@ export class CustomizeScene extends AbstractCharacterScene {
|
||||
}
|
||||
|
||||
private moveCursorVertically(index:number): void {
|
||||
|
||||
if(index === -1 && this.activeRow === 5){
|
||||
const button = this.customizeSceneElement.getChildByID('customizeSceneFormSubmit') as HTMLButtonElement;
|
||||
button.innerHTML = `Next <img src="resources/objects/arrow_up.png"/>`;
|
||||
}
|
||||
|
||||
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 <img src="resources/objects/arrow_up.png"/>`;
|
||||
}
|
||||
|
||||
this.activeRow += index;
|
||||
if (this.activeRow < 0) {
|
||||
this.activeRow = 0
|
||||
@ -159,11 +164,6 @@ export class CustomizeScene extends AbstractCharacterScene {
|
||||
localUserStore.setCustomCursorPosition(this.activeRow, this.selectedLayers);
|
||||
}
|
||||
|
||||
update(time: number, delta: number): void {
|
||||
super.update(time, delta);
|
||||
this.enterField.setVisible(!!(Math.floor(time / 500) % 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param x, the layer's vertical position
|
||||
* @param y, the layer's horizontal position
|
||||
@ -221,7 +221,7 @@ export class CustomizeScene extends AbstractCharacterScene {
|
||||
*/
|
||||
private moveLayers(): void {
|
||||
const screenCenterX = this.cameras.main.worldView.x + this.cameras.main.width / 2;
|
||||
const screenCenterY = this.cameras.main.worldView.y + this.cameras.main.height / 2;
|
||||
const screenCenterY = this.cameras.main.worldView.y + this.cameras.main.height / 3;
|
||||
const screenWidth = this.game.renderer.width;
|
||||
const screenHeight = this.game.renderer.height;
|
||||
for (let i = 0; i < this.containersRow.length; i++) {
|
||||
@ -258,29 +258,42 @@ export class CustomizeScene extends AbstractCharacterScene {
|
||||
this.containersRow[i][j].add(children);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update(time: number, delta: number): void {
|
||||
|
||||
}
|
||||
|
||||
public onResize(): void {
|
||||
this.moveLayers();
|
||||
|
||||
this.Rectangle.x = this.cameras.main.worldView.x + this.cameras.main.width / 2;
|
||||
this.Rectangle.y = this.cameras.main.worldView.y + this.cameras.main.height / 2;
|
||||
this.Rectangle.y = this.cameras.main.worldView.y + this.cameras.main.height / 3;
|
||||
|
||||
this.textField.x = this.game.renderer.width/2;
|
||||
|
||||
this.logo.x = this.game.renderer.width - 30;
|
||||
this.logo.y = this.game.renderer.height - 20;
|
||||
|
||||
this.arrowUp.x = this.game.renderer.width - 30;
|
||||
this.arrowUp.y = 60;
|
||||
|
||||
this.arrowDown.x = this.game.renderer.width - 30;
|
||||
this.arrowDown.y = 100;
|
||||
|
||||
this.arrowLeft.x = this.game.renderer.width/9;
|
||||
this.arrowLeft.y = this.game.renderer.height/2;
|
||||
|
||||
this.arrowRight.x = this.game.renderer.width*0.9;
|
||||
this.arrowRight.y = this.game.renderer.height/2;
|
||||
this.centerXDomElement(this.customizeSceneElement, 150);
|
||||
}
|
||||
|
||||
private nextSceneToCamera(){
|
||||
const layers: string[] = [];
|
||||
let i = 0;
|
||||
for (const layerItem of this.selectedLayers) {
|
||||
if (layerItem !== undefined) {
|
||||
layers.push(this.layers[i][layerItem].name);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (!areCharacterLayersValid(layers)) {
|
||||
return;
|
||||
}
|
||||
|
||||
gameManager.setCharacterLayers(layers);
|
||||
this.scene.sleep(CustomizeSceneName);
|
||||
this.scene.remove(SelectCharacterSceneName);
|
||||
gameManager.tryResumingGame(this, EnableCameraSceneName);
|
||||
}
|
||||
|
||||
private backToPreviousScene(){
|
||||
this.scene.sleep(CustomizeSceneName);
|
||||
this.scene.run(SelectCharacterSceneName);
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,14 @@ import {gameManager} from "../Game/GameManager";
|
||||
import {TextField} from "../Components/TextField";
|
||||
import Image = Phaser.GameObjects.Image;
|
||||
import {mediaManager} from "../../WebRtc/MediaManager";
|
||||
import {RESOLUTION} from "../../Enum/EnvironmentVariable";
|
||||
import {SoundMeter} from "../Components/SoundMeter";
|
||||
import {SoundMeterSprite} from "../Components/SoundMeterSprite";
|
||||
import {HtmlUtils} from "../../WebRtc/HtmlUtils";
|
||||
import {touchScreenManager} from "../../Touch/TouchScreenManager";
|
||||
import {PinchManager} from "../UserInput/PinchManager";
|
||||
import Zone = Phaser.GameObjects.Zone;
|
||||
import { MenuScene } from "../Menu/MenuScene";
|
||||
import {ResizableScene} from "./ResizableScene";
|
||||
|
||||
export const EnableCameraSceneName = "EnableCameraScene";
|
||||
enum LoginTextures {
|
||||
@ -16,12 +20,11 @@ enum LoginTextures {
|
||||
arrowUp = "arrow_up"
|
||||
}
|
||||
|
||||
const enableCameraSceneKey = 'enableCameraScene';
|
||||
|
||||
export class EnableCameraScene extends Phaser.Scene {
|
||||
export class EnableCameraScene extends ResizableScene {
|
||||
private textField!: TextField;
|
||||
private pressReturnField!: TextField;
|
||||
private cameraNameField!: TextField;
|
||||
private logo!: Image;
|
||||
private arrowLeft!: Image;
|
||||
private arrowRight!: Image;
|
||||
private arrowDown!: Image;
|
||||
@ -33,7 +36,10 @@ export class EnableCameraScene extends Phaser.Scene {
|
||||
private soundMeter: SoundMeter;
|
||||
private soundMeterSprite!: SoundMeterSprite;
|
||||
private microphoneNameField!: TextField;
|
||||
private repositionCallback!: (this: Window, ev: UIEvent) => void;
|
||||
|
||||
private enableCameraSceneElement!: Phaser.GameObjects.DOMElement;
|
||||
|
||||
private mobileTapZone!: Zone;
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
@ -43,8 +49,10 @@ export class EnableCameraScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
preload() {
|
||||
|
||||
this.load.html(enableCameraSceneKey, 'resources/html/EnableCameraScene.html');
|
||||
|
||||
this.load.image(LoginTextures.playButton, "resources/objects/play_button.png");
|
||||
this.load.image(LoginTextures.icon, "resources/logos/tcm_full.png");
|
||||
this.load.image(LoginTextures.arrowRight, "resources/objects/arrow_right.png");
|
||||
this.load.image(LoginTextures.arrowUp, "resources/objects/arrow_up.png");
|
||||
// Note: arcade.png from the Phaser 3 examples at: https://github.com/photonstorm/phaser3-examples/tree/master/public/assets/fonts/bitmap
|
||||
@ -52,9 +60,32 @@ export class EnableCameraScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
create() {
|
||||
this.textField = new TextField(this, this.game.renderer.width / 2, 20, 'Turn on your camera and microphone');
|
||||
|
||||
this.pressReturnField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height - 30, 'Press enter to start');
|
||||
this.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, '');
|
||||
|
||||
@ -82,9 +113,6 @@ export class EnableCameraScene extends Phaser.Scene {
|
||||
this.arrowDown.setInteractive().on('pointerdown', this.nextMic.bind(this));
|
||||
this.add.existing(this.arrowDown);
|
||||
|
||||
this.logo = new Image(this, this.game.renderer.width - 30, this.game.renderer.height - 20, LoginTextures.icon);
|
||||
this.add.existing(this.logo);
|
||||
|
||||
this.input.keyboard.on('keyup-ENTER', () => {
|
||||
this.login();
|
||||
});
|
||||
@ -104,8 +132,7 @@ export class EnableCameraScene extends Phaser.Scene {
|
||||
this.soundMeterSprite.setVisible(false);
|
||||
this.add.existing(this.soundMeterSprite);
|
||||
|
||||
this.repositionCallback = this.reposition.bind(this);
|
||||
window.addEventListener('resize', this.repositionCallback);
|
||||
this.onResize();
|
||||
}
|
||||
|
||||
private previousCam(): void {
|
||||
@ -183,10 +210,9 @@ export class EnableCameraScene extends Phaser.Scene {
|
||||
this.arrowUp.setVisible(this.microphoneSelected > 0);
|
||||
|
||||
}
|
||||
this.reposition();
|
||||
}
|
||||
|
||||
private reposition(): void {
|
||||
public onResize(): void {
|
||||
let div = HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('myCamVideoSetup');
|
||||
let bounds = div.getBoundingClientRect();
|
||||
if (!div.srcObject) {
|
||||
@ -195,23 +221,22 @@ export class EnableCameraScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
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.pressReturnField.x = this.game.renderer.width / 2;
|
||||
this.pressReturnField.x = this.game.renderer.width / 2;
|
||||
|
||||
this.cameraNameField.y = bounds.top / RESOLUTION - 8;
|
||||
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 / RESOLUTION + 16;
|
||||
this.soundMeterSprite.y = bounds.bottom / this.scale.zoom + 16;
|
||||
|
||||
this.microphoneNameField.y = this.soundMeterSprite.y + 22;
|
||||
|
||||
this.arrowRight.x = bounds.right / RESOLUTION + 16;
|
||||
this.arrowRight.y = (bounds.top + bounds.height / 2) / RESOLUTION;
|
||||
this.arrowRight.x = bounds.right / this.scale.zoom + 16;
|
||||
this.arrowRight.y = (bounds.top + bounds.height / 2) / this.scale.zoom;
|
||||
|
||||
this.arrowLeft.x = bounds.left / RESOLUTION - 16;
|
||||
this.arrowLeft.y = (bounds.top + bounds.height / 2) / RESOLUTION;
|
||||
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;
|
||||
@ -219,23 +244,21 @@ export class EnableCameraScene extends Phaser.Scene {
|
||||
this.arrowUp.x = this.microphoneNameField.x - this.microphoneNameField.width / 2 - 16;
|
||||
this.arrowUp.y = this.microphoneNameField.y;
|
||||
|
||||
this.pressReturnField.y = Math.max(this.game.renderer.height - 30, this.microphoneNameField.y + 20);
|
||||
this.logo.x = this.game.renderer.width - 30;
|
||||
this.logo.y = Math.max(this.game.renderer.height - 20, this.microphoneNameField.y + 30);
|
||||
const actionBtn = document.querySelector<HTMLDivElement>('#enableCameraScene .action');
|
||||
if (actionBtn !== null) {
|
||||
actionBtn.style.top = (this.scale.height - 65) + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
update(time: number, delta: number): void {
|
||||
this.pressReturnField.setVisible(!!(Math.floor(time / 500) % 2));
|
||||
|
||||
this.soundMeterSprite.setVolume(this.soundMeter.getVolume());
|
||||
|
||||
mediaManager.setLastUpdateScene();
|
||||
this.centerXDomElement(this.enableCameraSceneElement, 300);
|
||||
}
|
||||
|
||||
private login(): void {
|
||||
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('webRtcSetup').style.display = 'none';
|
||||
this.soundMeter.stop();
|
||||
window.removeEventListener('resize', this.repositionCallback);
|
||||
|
||||
mediaManager.stopCamera();
|
||||
mediaManager.stopMicrophone();
|
||||
|
@ -2,6 +2,7 @@ import {gameManager} from "../Game/GameManager";
|
||||
import {Scene} from "phaser";
|
||||
import {ErrorScene} from "../Reconnecting/ErrorScene";
|
||||
import {WAError} from "../Reconnecting/WAError";
|
||||
import {waScaleManager} from "../Services/WaScaleManager";
|
||||
|
||||
export const EntrySceneName = "EntryScene";
|
||||
|
||||
@ -17,11 +18,18 @@ export class EntryScene extends Scene {
|
||||
}
|
||||
|
||||
create() {
|
||||
|
||||
gameManager.init(this.scene).then((nextSceneName) => {
|
||||
// Let's rescale before starting the game
|
||||
// We can do it at this stage.
|
||||
waScaleManager.applyNewSize();
|
||||
this.scene.start(nextSceneName);
|
||||
}).catch((err) => {
|
||||
if (err.response && err.response.status == 404) {
|
||||
ErrorScene.showError(new WAError('Page Not Found', 'Could not find map', window.location.pathname), this.scene);
|
||||
ErrorScene.showError(new WAError(
|
||||
'Access link incorrect',
|
||||
'Could not find map. Please check your access link.',
|
||||
'If you want more information, you may contact administrator or contact us at: workadventure@thecodingmachine.com'), this.scene);
|
||||
} else {
|
||||
ErrorScene.showError(err, this.scene);
|
||||
}
|
||||
|
@ -1,23 +1,17 @@
|
||||
import {gameManager} from "../Game/GameManager";
|
||||
import {TextField} from "../Components/TextField";
|
||||
import {TextInput} from "../Components/TextInput";
|
||||
import Image = Phaser.GameObjects.Image;
|
||||
import {SelectCharacterSceneName} from "./SelectCharacterScene";
|
||||
import {ResizableScene} from "./ResizableScene";
|
||||
import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||
import {MenuScene} from "../Menu/MenuScene";
|
||||
import { isUserNameValid } from "../../Connexion/LocalUser";
|
||||
|
||||
//todo: put this constants in a dedicated file
|
||||
export const LoginSceneName = "LoginScene";
|
||||
enum LoginTextures {
|
||||
icon = "icon",
|
||||
mainFont = "main_font"
|
||||
}
|
||||
|
||||
const loginSceneKey = 'loginScene';
|
||||
|
||||
export class LoginScene extends ResizableScene {
|
||||
private nameInput!: TextInput;
|
||||
private textField!: TextField;
|
||||
private infoTextField!: TextField;
|
||||
private pressReturnField!: TextField;
|
||||
private logo!: Image;
|
||||
|
||||
private loginSceneElement!: Phaser.GameObjects.DOMElement;
|
||||
private name: string = '';
|
||||
|
||||
constructor() {
|
||||
@ -28,58 +22,65 @@ export class LoginScene extends ResizableScene {
|
||||
}
|
||||
|
||||
preload() {
|
||||
//this.load.image(LoginTextures.playButton, "resources/objects/play_button.png");
|
||||
this.load.image(LoginTextures.icon, "resources/logos/tcm_full.png");
|
||||
// Note: arcade.png from the Phaser 3 examples at: https://github.com/photonstorm/phaser3-examples/tree/master/public/assets/fonts/bitmap
|
||||
this.load.bitmapFont(LoginTextures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
|
||||
this.load.html(loginSceneKey, 'resources/html/loginScene.html');
|
||||
}
|
||||
|
||||
create() {
|
||||
this.loginSceneElement = this.add.dom(-1000, 0).createFromCache(loginSceneKey);
|
||||
this.centerXDomElement(this.loginSceneElement, 200);
|
||||
MenuScene.revealMenusAfterInit(this.loginSceneElement, loginSceneKey);
|
||||
|
||||
this.textField = new TextField(this, this.game.renderer.width / 2, 50, 'Enter your name:');
|
||||
this.nameInput = new TextInput(this, this.game.renderer.width / 2, 70, 8, this.name,(text: string) => {
|
||||
this.name = text;
|
||||
});
|
||||
|
||||
this.pressReturnField = new TextField(this, this.game.renderer.width / 2, 130, 'Press enter to start');
|
||||
|
||||
this.logo = new Image(this, this.game.renderer.width - 30, this.game.renderer.height - 20, LoginTextures.icon);
|
||||
this.add.existing(this.logo);
|
||||
|
||||
const infoText = "Commands: \n - Arrows or Z,Q,S,D to move\n - SHIFT to run";
|
||||
this.infoTextField = new TextField(this, 10, this.game.renderer.height - 35, infoText, false);
|
||||
|
||||
this.input.keyboard.on('keyup-ENTER', () => {
|
||||
if (this.name === '') {
|
||||
return
|
||||
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;
|
||||
}
|
||||
this.login(this.name);
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
update(time: number, delta: number): void {
|
||||
if (this.name == '') {
|
||||
this.pressReturnField?.setVisible(false);
|
||||
} else {
|
||||
this.pressReturnField?.setVisible(!!(Math.floor(time / 500) % 2));
|
||||
private login(inputElement: HTMLInputElement): void {
|
||||
const pErrorElement = this.loginSceneElement.getChildByID('errorLoginScene') as HTMLInputElement;
|
||||
this.name = inputElement.value;
|
||||
if (this.name === '') {
|
||||
pErrorElement.innerHTML = 'The name is empty';
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
private login(name: string): void {
|
||||
gameManager.setPlayerName(name);
|
||||
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);
|
||||
|
||||
this.scene.stop(LoginSceneName)
|
||||
gameManager.tryResumingGame(this, SelectCharacterSceneName);
|
||||
this.scene.remove(LoginSceneName)
|
||||
}
|
||||
|
||||
public onResize(ev: UIEvent): void {
|
||||
this.textField.x = this.game.renderer.width / 2;
|
||||
this.nameInput.setX(this.game.renderer.width / 2 - 64);
|
||||
this.pressReturnField.x = this.game.renderer.width / 2;
|
||||
this.logo.x = this.game.renderer.width - 30;
|
||||
this.logo.y = this.game.renderer.height - 20;
|
||||
this.infoTextField.y = this.game.renderer.height - 35;
|
||||
update(time: number, delta: number): void {
|
||||
|
||||
}
|
||||
|
||||
public onResize(ev: UIEvent): void {
|
||||
this.centerXDomElement(this.loginSceneElement, 200);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,23 @@
|
||||
import {Scene} from "phaser";
|
||||
import DOMElement = Phaser.GameObjects.DOMElement;
|
||||
|
||||
export abstract class ResizableScene extends Scene {
|
||||
public abstract onResize(ev: UIEvent): void;
|
||||
|
||||
/**
|
||||
* Centers the DOM element on the X axis.
|
||||
*
|
||||
* @param object
|
||||
* @param defaultWidth The width of the DOM element. We try to compute it but it may not be available if called from "create".
|
||||
*/
|
||||
public centerXDomElement(object: DOMElement, defaultWidth: number): void {
|
||||
object.x = (this.scale.width / 2) -
|
||||
(
|
||||
object
|
||||
&& object.node
|
||||
&& object.node.getBoundingClientRect().width > 0
|
||||
? (object.node.getBoundingClientRect().width / 2 / this.scale.zoom)
|
||||
: (300 / this.scale.zoom)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
62
front/src/Phaser/Login/SelectCharacterMobileScene.ts
Normal file
62
front/src/Phaser/Login/SelectCharacterMobileScene.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { SelectCharacterScene } from "./SelectCharacterScene";
|
||||
|
||||
export class SelectCharacterMobileScene extends SelectCharacterScene {
|
||||
|
||||
create(){
|
||||
super.create();
|
||||
this.selectedRectangle.destroy();
|
||||
}
|
||||
|
||||
protected defineSetupPlayer(numero: number){
|
||||
const deltaX = 30;
|
||||
const deltaY = 2;
|
||||
let [playerX, playerY] = this.getCharacterPosition();
|
||||
let playerVisible = true;
|
||||
let playerScale = 1.5;
|
||||
let playserOpactity = 1;
|
||||
|
||||
if( this.currentSelectUser !== numero ){
|
||||
playerVisible = false;
|
||||
}
|
||||
if( numero === (this.currentSelectUser + 1) ){
|
||||
playerY -= deltaY;
|
||||
playerX += deltaX;
|
||||
playerScale = 0.8;
|
||||
playserOpactity = 0.6;
|
||||
playerVisible = true;
|
||||
}
|
||||
if( numero === (this.currentSelectUser + 2) ){
|
||||
playerY -= deltaY;
|
||||
playerX += (deltaX * 2);
|
||||
playerScale = 0.8;
|
||||
playserOpactity = 0.6;
|
||||
playerVisible = true;
|
||||
}
|
||||
if( numero === (this.currentSelectUser - 1) ){
|
||||
playerY -= deltaY;
|
||||
playerX -= deltaX;
|
||||
playerScale = 0.8;
|
||||
playserOpactity = 0.6;
|
||||
playerVisible = true;
|
||||
}
|
||||
if( numero === (this.currentSelectUser - 2) ){
|
||||
playerY -= deltaY;
|
||||
playerX -= (deltaX * 2);
|
||||
playerScale = 0.8;
|
||||
playserOpactity = 0.6;
|
||||
playerVisible = true;
|
||||
}
|
||||
return {playerX, playerY, playerScale, playserOpactity, playerVisible}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns pixel position by on column and row number
|
||||
*/
|
||||
protected getCharacterPosition(): [number, number] {
|
||||
return [
|
||||
this.game.renderer.width / 2,
|
||||
this.game.renderer.height / 3
|
||||
];
|
||||
}
|
||||
|
||||
}
|
@ -1,254 +1,249 @@
|
||||
import {gameManager} from "../Game/GameManager";
|
||||
import {TextField} from "../Components/TextField";
|
||||
import Image = Phaser.GameObjects.Image;
|
||||
import Rectangle = Phaser.GameObjects.Rectangle;
|
||||
import {EnableCameraSceneName} from "./EnableCameraScene";
|
||||
import {CustomizeSceneName} from "./CustomizeScene";
|
||||
import {ResizableScene} from "./ResizableScene";
|
||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||
import {loadAllDefaultModels, loadCustomTexture} from "../Entity/PlayerTexturesLoadingManager";
|
||||
import {loadAllDefaultModels} from "../Entity/PlayerTexturesLoadingManager";
|
||||
import {addLoader} from "../Components/Loader";
|
||||
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
||||
import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
||||
import {AbstractCharacterScene} from "./AbstractCharacterScene";
|
||||
|
||||
import {areCharacterLayersValid} from "../../Connexion/LocalUser";
|
||||
import {touchScreenManager} from "../../Touch/TouchScreenManager";
|
||||
import {PinchManager} from "../UserInput/PinchManager";
|
||||
import {MenuScene} from "../Menu/MenuScene";
|
||||
|
||||
//todo: put this constants in a dedicated file
|
||||
export const SelectCharacterSceneName = "SelectCharacterScene";
|
||||
enum LoginTextures {
|
||||
playButton = "play_button",
|
||||
icon = "icon",
|
||||
mainFont = "main_font",
|
||||
customizeButton = "customize_button",
|
||||
customizeButtonSelected = "customize_button_selected"
|
||||
}
|
||||
|
||||
const selectCharacterKey = 'selectCharacterScene';
|
||||
|
||||
export class SelectCharacterScene extends AbstractCharacterScene {
|
||||
private readonly nbCharactersPerRow = 6;
|
||||
private textField!: TextField;
|
||||
private pressReturnField!: TextField;
|
||||
private logo!: Image;
|
||||
private customizeButton!: Image;
|
||||
private customizeButtonSelected!: Image;
|
||||
protected readonly nbCharactersPerRow = 6;
|
||||
protected selectedPlayer!: Phaser.Physics.Arcade.Sprite|null; // null if we are selecting the "customize" option
|
||||
protected players: Array<Phaser.Physics.Arcade.Sprite> = new Array<Phaser.Physics.Arcade.Sprite>();
|
||||
protected playerModels!: BodyResourceDescriptionInterface[];
|
||||
|
||||
private selectedRectangle!: Rectangle;
|
||||
private selectedRectangleXPos = 0; // Number of the character selected in the rows
|
||||
private selectedRectangleYPos = 0; // Number of the character selected in the columns
|
||||
private selectedPlayer!: Phaser.Physics.Arcade.Sprite|null; // null if we are selecting the "customize" option
|
||||
private players: Array<Phaser.Physics.Arcade.Sprite> = new Array<Phaser.Physics.Arcade.Sprite>();
|
||||
private playerModels!: BodyResourceDescriptionInterface[];
|
||||
protected selectedRectangle!: Rectangle;
|
||||
|
||||
protected selectCharacterSceneElement!: Phaser.GameObjects.DOMElement;
|
||||
protected currentSelectUser = 0;
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
key: SelectCharacterSceneName
|
||||
key: SelectCharacterSceneName,
|
||||
});
|
||||
}
|
||||
|
||||
preload() {
|
||||
addLoader(this);
|
||||
this.load.html(selectCharacterKey, 'resources/html/selectCharacterScene.html');
|
||||
|
||||
this.loadSelectSceneCharacters().then((bodyResourceDescriptions) => {
|
||||
bodyResourceDescriptions.forEach((bodyResourceDescription) => {
|
||||
this.playerModels.push(bodyResourceDescription);
|
||||
});
|
||||
})
|
||||
|
||||
this.load.image(LoginTextures.playButton, "resources/objects/play_button.png");
|
||||
this.load.image(LoginTextures.icon, "resources/logos/tcm_full.png");
|
||||
// Note: arcade.png from the Phaser 3 examples at: https://github.com/photonstorm/phaser3-examples/tree/master/public/assets/fonts/bitmap
|
||||
this.load.bitmapFont(LoginTextures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
|
||||
this.playerModels = loadAllDefaultModels(this.load);
|
||||
this.load.image(LoginTextures.customizeButton, 'resources/objects/customize.png');
|
||||
this.load.image(LoginTextures.customizeButtonSelected, 'resources/objects/customize_selected.png');
|
||||
|
||||
//this function must stay at the end of preload function
|
||||
addLoader(this);
|
||||
}
|
||||
|
||||
create() {
|
||||
this.textField = new TextField(this, this.game.renderer.width / 2, 50, 'Select your character');
|
||||
this.pressReturnField = new TextField(
|
||||
this,
|
||||
this.game.renderer.width / 2,
|
||||
90 + 32 * Math.ceil( this.playerModels.length / this.nbCharactersPerRow) + 40,
|
||||
'Press enter to start');
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
if (touchScreenManager.supportTouchScreen) {
|
||||
new PinchManager(this);
|
||||
}
|
||||
|
||||
const rectangleXStart = this.game.renderer.width / 2 - (this.nbCharactersPerRow / 2) * 32 + 16;
|
||||
|
||||
this.selectedRectangle = this.add.rectangle(rectangleXStart, 90, 32, 32).setStrokeStyle(2, 0xFFFFFF);
|
||||
|
||||
this.logo = new Image(this, this.game.renderer.width - 30, this.game.renderer.height - 20, LoginTextures.icon);
|
||||
this.add.existing(this.logo);
|
||||
|
||||
this.input.keyboard.on('keyup-ENTER', () => {
|
||||
return this.nextScene();
|
||||
});
|
||||
|
||||
this.input.keyboard.on('keydown-RIGHT', () => {
|
||||
if(this.selectedRectangleYPos * this.nbCharactersPerRow + (this.selectedRectangleXPos + 2))
|
||||
if (
|
||||
this.selectedRectangleXPos < this.nbCharactersPerRow - 1
|
||||
&& ((this.selectedRectangleYPos * this.nbCharactersPerRow) + (this.selectedRectangleXPos + 1) + 1) <= this.playerModels.length
|
||||
) {
|
||||
this.selectedRectangleXPos++;
|
||||
}
|
||||
this.updateSelectedPlayer();
|
||||
});
|
||||
this.input.keyboard.on('keydown-LEFT', () => {
|
||||
if (
|
||||
this.selectedRectangleXPos > 0
|
||||
&& ((this.selectedRectangleYPos * this.nbCharactersPerRow) + (this.selectedRectangleXPos - 1) + 1) <= this.playerModels.length
|
||||
) {
|
||||
this.selectedRectangleXPos--;
|
||||
}
|
||||
this.updateSelectedPlayer();
|
||||
});
|
||||
this.input.keyboard.on('keydown-DOWN', () => {
|
||||
if (
|
||||
this.selectedRectangleYPos < Math.ceil(this.playerModels.length / this.nbCharactersPerRow)
|
||||
&& (
|
||||
(((this.selectedRectangleYPos + 1) * this.nbCharactersPerRow) + this.selectedRectangleXPos + 1) <= this.playerModels.length // check if player isn't empty
|
||||
|| (this.selectedRectangleYPos + 1) === Math.ceil(this.playerModels.length / this.nbCharactersPerRow) // check if is custom rectangle
|
||||
)
|
||||
) {
|
||||
this.selectedRectangleYPos++;
|
||||
}
|
||||
this.updateSelectedPlayer();
|
||||
});
|
||||
this.input.keyboard.on('keydown-UP', () => {
|
||||
if (
|
||||
this.selectedRectangleYPos > 0
|
||||
&& (((this.selectedRectangleYPos - 1) * this.nbCharactersPerRow) + this.selectedRectangleXPos + 1) <= this.playerModels.length
|
||||
) {
|
||||
this.selectedRectangleYPos--;
|
||||
}
|
||||
this.updateSelectedPlayer();
|
||||
});
|
||||
this.selectedRectangle.setDepth(2);
|
||||
|
||||
/*create user*/
|
||||
this.createCurrentPlayer();
|
||||
|
||||
const playerNumber = localUserStore.getPlayerCharacterIndex();
|
||||
if (playerNumber && playerNumber !== -1) {
|
||||
this.selectedRectangleXPos = playerNumber % this.nbCharactersPerRow;
|
||||
this.selectedRectangleYPos = Math.floor(playerNumber / this.nbCharactersPerRow);
|
||||
this.updateSelectedPlayer();
|
||||
} else if (playerNumber === -1) {
|
||||
this.selectedRectangleYPos = Math.ceil(this.playerModels.length / this.nbCharactersPerRow);
|
||||
this.updateSelectedPlayer();
|
||||
|
||||
this.input.keyboard.on('keyup-ENTER', () => {
|
||||
return this.nextSceneToCameraScene();
|
||||
});
|
||||
|
||||
this.input.keyboard.on('keydown-RIGHT', () => {
|
||||
this.moveToRight();
|
||||
});
|
||||
this.input.keyboard.on('keydown-LEFT', () => {
|
||||
this.moveToLeft();
|
||||
});
|
||||
this.input.keyboard.on('keydown-UP', () => {
|
||||
this.moveToUp();
|
||||
});
|
||||
this.input.keyboard.on('keydown-DOWN', () => {
|
||||
this.moveToDown();
|
||||
});
|
||||
}
|
||||
|
||||
protected nextSceneToCameraScene(): void {
|
||||
if (this.selectedPlayer !== null && !areCharacterLayersValid([this.selectedPlayer.texture.key])) {
|
||||
return;
|
||||
}
|
||||
if(!this.selectedPlayer){
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
update(time: number, delta: number): void {
|
||||
this.pressReturnField.setVisible(!!(Math.floor(time / 500) % 2));
|
||||
}
|
||||
|
||||
private nextScene(): void {
|
||||
this.scene.stop(SelectCharacterSceneName);
|
||||
if (this.selectedPlayer !== null) {
|
||||
gameManager.setCharacterLayers([this.selectedPlayer.texture.key]);
|
||||
gameManager.tryResumingGame(this, EnableCameraSceneName);
|
||||
} else {
|
||||
this.scene.run(CustomizeSceneName);
|
||||
}
|
||||
gameManager.setCharacterLayers([this.selectedPlayer.texture.key]);
|
||||
gameManager.tryResumingGame(this, EnableCameraSceneName);
|
||||
this.scene.remove(SelectCharacterSceneName);
|
||||
}
|
||||
|
||||
protected nextSceneToCustomizeScene(): void {
|
||||
if (this.selectedPlayer !== null && !areCharacterLayersValid([this.selectedPlayer.texture.key])) {
|
||||
return;
|
||||
}
|
||||
this.scene.sleep(SelectCharacterSceneName);
|
||||
this.scene.run(CustomizeSceneName);
|
||||
}
|
||||
|
||||
createCurrentPlayer(): void {
|
||||
for (let i = 0; i <this.playerModels.length; i++) {
|
||||
const playerResource = this.playerModels[i];
|
||||
|
||||
const col = i % this.nbCharactersPerRow;
|
||||
const row = Math.floor(i / this.nbCharactersPerRow);
|
||||
|
||||
const [x, y] = this.getCharacterPosition(col, row);
|
||||
const player = this.physics.add.sprite(x, y, playerResource.name, 0);
|
||||
player.setBounce(0.2);
|
||||
player.setCollideWorldBounds(true);
|
||||
const [middleX, middleY] = this.getCharacterPosition();
|
||||
const player = this.physics.add.sprite(middleX, middleY, playerResource.name, 0);
|
||||
this.setUpPlayer(player, i);
|
||||
this.anims.create({
|
||||
key: playerResource.name,
|
||||
frames: this.anims.generateFrameNumbers(playerResource.name, {start: 0, end: 2,}),
|
||||
frameRate: 10,
|
||||
frames: this.anims.generateFrameNumbers(playerResource.name, {start: 0, end: 11}),
|
||||
frameRate: 8,
|
||||
repeat: -1
|
||||
});
|
||||
player.setInteractive().on("pointerdown", () => {
|
||||
this.selectedRectangleXPos = col;
|
||||
this.selectedRectangleYPos = row;
|
||||
this.updateSelectedPlayer();
|
||||
if(this.currentSelectUser === i){
|
||||
return;
|
||||
}
|
||||
this.currentSelectUser = i;
|
||||
this.moveUser();
|
||||
});
|
||||
this.players.push(player);
|
||||
}
|
||||
|
||||
const maxRow = Math.ceil( this.playerModels.length / this.nbCharactersPerRow);
|
||||
this.customizeButton = new Image(this, this.game.renderer.width / 2, 90 + 32 * maxRow + 6, LoginTextures.customizeButton);
|
||||
this.customizeButton.setOrigin(0.5, 0.5);
|
||||
this.add.existing(this.customizeButton);
|
||||
this.customizeButtonSelected = new Image(this, this.game.renderer.width / 2, 90 + 32 * maxRow + 6, LoginTextures.customizeButtonSelected);
|
||||
this.customizeButtonSelected.setOrigin(0.5, 0.5);
|
||||
this.customizeButtonSelected.setVisible(false);
|
||||
this.add.existing(this.customizeButtonSelected);
|
||||
this.selectedPlayer = this.players[this.currentSelectUser];
|
||||
this.selectedPlayer.play(this.playerModels[this.currentSelectUser].name);
|
||||
}
|
||||
|
||||
this.customizeButton.setInteractive().on("pointerdown", () => {
|
||||
this.selectedRectangleYPos = Math.ceil(this.playerModels.length / this.nbCharactersPerRow);
|
||||
this.updateSelectedPlayer();
|
||||
});
|
||||
protected moveUser(){
|
||||
for(let i = 0; i < this.players.length; i++){
|
||||
const player = this.players[i];
|
||||
this.setUpPlayer(player, i);
|
||||
}
|
||||
this.updateSelectedPlayer();
|
||||
}
|
||||
|
||||
this.selectedPlayer = this.players[0];
|
||||
this.selectedPlayer.play(this.playerModels[0].name);
|
||||
protected moveToLeft(){
|
||||
if(this.currentSelectUser === 0){
|
||||
return;
|
||||
}
|
||||
this.currentSelectUser -= 1;
|
||||
this.moveUser();
|
||||
}
|
||||
|
||||
protected moveToRight(){
|
||||
if(this.currentSelectUser === (this.players.length - 1)){
|
||||
return;
|
||||
}
|
||||
this.currentSelectUser += 1;
|
||||
this.moveUser();
|
||||
}
|
||||
|
||||
protected moveToUp(){
|
||||
if(this.currentSelectUser < this.nbCharactersPerRow){
|
||||
return;
|
||||
}
|
||||
this.currentSelectUser -= this.nbCharactersPerRow;
|
||||
this.moveUser();
|
||||
}
|
||||
|
||||
protected moveToDown(){
|
||||
if((this.currentSelectUser + this.nbCharactersPerRow) > (this.players.length - 1)){
|
||||
return;
|
||||
}
|
||||
this.currentSelectUser += this.nbCharactersPerRow;
|
||||
this.moveUser();
|
||||
}
|
||||
|
||||
protected defineSetupPlayer(numero: 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
|
||||
|
||||
const playerVisible = true;
|
||||
const playerScale = 1;
|
||||
const playserOpactity = 1;
|
||||
|
||||
// if selected
|
||||
if( numero === this.currentSelectUser ){
|
||||
this.selectedRectangle.setX(playerX);
|
||||
this.selectedRectangle.setY(playerY);
|
||||
}
|
||||
|
||||
return {playerX, playerY, playerScale, playserOpactity, playerVisible}
|
||||
}
|
||||
|
||||
protected setUpPlayer(player: Phaser.Physics.Arcade.Sprite, numero: number){
|
||||
|
||||
const {playerX, playerY, playerScale, playserOpactity, playerVisible} = this.defineSetupPlayer(numero);
|
||||
player.setBounce(0.2);
|
||||
player.setCollideWorldBounds(true);
|
||||
player.setVisible( playerVisible );
|
||||
player.setScale(playerScale, playerScale);
|
||||
player.setAlpha(playserOpactity);
|
||||
player.setX(playerX);
|
||||
player.setY(playerY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns pixel position by on column and row number
|
||||
*/
|
||||
private getCharacterPosition(x: number, y: number): [number, number] {
|
||||
protected getCharacterPosition(): [number, number] {
|
||||
return [
|
||||
this.game.renderer.width / 2 + 16 + (x - this.nbCharactersPerRow / 2) * 32,
|
||||
y * 32 + 90
|
||||
this.game.renderer.width / 2,
|
||||
this.game.renderer.height / 2.5
|
||||
];
|
||||
}
|
||||
|
||||
private updateSelectedPlayer(): void {
|
||||
this.selectedPlayer?.anims.pause();
|
||||
// If we selected the customize button
|
||||
if (this.selectedRectangleYPos === Math.ceil(this.playerModels.length / this.nbCharactersPerRow)) {
|
||||
this.selectedPlayer = null;
|
||||
this.selectedRectangle.setVisible(false);
|
||||
this.customizeButtonSelected.setVisible(true);
|
||||
this.customizeButton.setVisible(false);
|
||||
localUserStore.setPlayerCharacterIndex(-1);
|
||||
return;
|
||||
}
|
||||
this.customizeButtonSelected.setVisible(false);
|
||||
this.customizeButton.setVisible(true);
|
||||
const [x, y] = this.getCharacterPosition(this.selectedRectangleXPos, this.selectedRectangleYPos);
|
||||
this.selectedRectangle.setVisible(true);
|
||||
this.selectedRectangle.setX(x);
|
||||
this.selectedRectangle.setY(y);
|
||||
this.selectedRectangle.setSize(32, 32);
|
||||
const playerNumber = this.selectedRectangleXPos + this.selectedRectangleYPos * this.nbCharactersPerRow;
|
||||
const player = this.players[playerNumber];
|
||||
player.play(this.playerModels[playerNumber].name);
|
||||
protected updateSelectedPlayer(): void {
|
||||
this.selectedPlayer?.anims.pause(this.selectedPlayer?.anims.currentAnim.frames[0]);
|
||||
const player = this.players[this.currentSelectUser];
|
||||
player.play(this.playerModels[this.currentSelectUser].name);
|
||||
this.selectedPlayer = player;
|
||||
localUserStore.setPlayerCharacterIndex(playerNumber);
|
||||
localUserStore.setPlayerCharacterIndex(this.currentSelectUser);
|
||||
}
|
||||
|
||||
update(time: number, delta: number): void {
|
||||
}
|
||||
|
||||
public onResize(ev: UIEvent): void {
|
||||
this.textField.x = this.game.renderer.width / 2;
|
||||
this.pressReturnField.x = this.game.renderer.width / 2;
|
||||
this.logo.x = this.game.renderer.width - 30;
|
||||
this.logo.y = this.game.renderer.height - 20;
|
||||
this.customizeButton.x = this.game.renderer.width / 2;
|
||||
this.customizeButtonSelected.x = this.game.renderer.width / 2;
|
||||
//move position of user
|
||||
this.moveUser();
|
||||
|
||||
for (let i = 0; i <this.playerModels.length; i++) {
|
||||
const player = this.players[i];
|
||||
|
||||
const col = i % this.nbCharactersPerRow;
|
||||
const row = Math.floor(i / this.nbCharactersPerRow);
|
||||
|
||||
const [x, y] = this.getCharacterPosition(col, row);
|
||||
player.x = x;
|
||||
player.y = y;
|
||||
}
|
||||
this.updateSelectedPlayer();
|
||||
this.centerXDomElement(this.selectCharacterSceneElement, 150);
|
||||
}
|
||||
|
||||
}
|
||||
|
228
front/src/Phaser/Login/SelectCompanionScene.ts
Normal file
228
front/src/Phaser/Login/SelectCompanionScene.ts
Normal file
@ -0,0 +1,228 @@
|
||||
import Image = Phaser.GameObjects.Image;
|
||||
import Rectangle = Phaser.GameObjects.Rectangle;
|
||||
import { addLoader } from "../Components/Loader";
|
||||
import { gameManager} from "../Game/GameManager";
|
||||
import { ResizableScene } from "./ResizableScene";
|
||||
import { EnableCameraSceneName } from "./EnableCameraScene";
|
||||
import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||
import type { CompanionResourceDescriptionInterface } from "../Companion/CompanionTextures";
|
||||
import { getAllCompanionResources } from "../Companion/CompanionTexturesLoadingManager";
|
||||
import {touchScreenManager} from "../../Touch/TouchScreenManager";
|
||||
import {PinchManager} from "../UserInput/PinchManager";
|
||||
import { MenuScene } from "../Menu/MenuScene";
|
||||
|
||||
export const SelectCompanionSceneName = "SelectCompanionScene";
|
||||
|
||||
const selectCompanionSceneKey = 'selectCompanionScene';
|
||||
|
||||
export class SelectCompanionScene extends ResizableScene {
|
||||
private selectedCompanion!: Phaser.Physics.Arcade.Sprite;
|
||||
private companions: Array<Phaser.Physics.Arcade.Sprite> = new Array<Phaser.Physics.Arcade.Sprite>();
|
||||
private companionModels: Array<CompanionResourceDescriptionInterface> = [];
|
||||
|
||||
private selectCompanionSceneElement!: Phaser.GameObjects.DOMElement;
|
||||
private currentCompanion = 0;
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
key: SelectCompanionSceneName
|
||||
});
|
||||
}
|
||||
|
||||
preload() {
|
||||
this.load.html(selectCompanionSceneKey, 'resources/html/SelectCompanionScene.html');
|
||||
|
||||
getAllCompanionResources(this.load).forEach(model => {
|
||||
this.companionModels.push(model);
|
||||
});
|
||||
|
||||
//this function must stay at the end of preload function
|
||||
addLoader(this);
|
||||
}
|
||||
|
||||
create() {
|
||||
|
||||
this.selectCompanionSceneElement = this.add.dom(-1000, 0).createFromCache(selectCompanionSceneKey);
|
||||
this.centerXDomElement(this.selectCompanionSceneElement, 150);
|
||||
MenuScene.revealMenusAfterInit(this.selectCompanionSceneElement, selectCompanionSceneKey);
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
if (touchScreenManager.supportTouchScreen) {
|
||||
new PinchManager(this);
|
||||
}
|
||||
|
||||
// input events
|
||||
this.input.keyboard.on('keyup-ENTER', this.nextScene.bind(this));
|
||||
|
||||
this.input.keyboard.on('keydown-RIGHT', this.moveToRight.bind(this));
|
||||
this.input.keyboard.on('keydown-LEFT', this.moveToLeft.bind(this));
|
||||
|
||||
if(localUserStore.getCompanion()){
|
||||
const companionIndex = this.companionModels.findIndex((companion) => companion.name === localUserStore.getCompanion());
|
||||
if(companionIndex > -1 || companionIndex < this.companions.length){
|
||||
this.currentCompanion = companionIndex;
|
||||
this.selectedCompanion = this.companions[companionIndex];
|
||||
}
|
||||
}
|
||||
|
||||
localUserStore.setCompanion(null);
|
||||
gameManager.setCompanion(null);
|
||||
|
||||
this.createCurrentCompanion();
|
||||
this.updateSelectedCompanion();
|
||||
}
|
||||
|
||||
update(time: number, delta: number): void {
|
||||
|
||||
}
|
||||
|
||||
private nextScene(): void {
|
||||
localUserStore.setCompanion(this.companionModels[this.currentCompanion].name);
|
||||
gameManager.setCompanion(this.companionModels[this.currentCompanion].name);
|
||||
|
||||
this._nextScene();
|
||||
}
|
||||
|
||||
private _nextScene(){
|
||||
// next scene
|
||||
this.scene.stop(SelectCompanionSceneName);
|
||||
gameManager.tryResumingGame(this, EnableCameraSceneName);
|
||||
this.scene.remove(SelectCompanionSceneName);
|
||||
}
|
||||
|
||||
private createCurrentCompanion(): void {
|
||||
for (let i = 0; i < this.companionModels.length; i++) {
|
||||
const companionResource = this.companionModels[i]
|
||||
const [middleX, middleY] = this.getCompanionPosition();
|
||||
const companion = this.physics.add.sprite(middleX, middleY, companionResource.name, 0);
|
||||
this.setUpCompanion(companion, i);
|
||||
this.anims.create({
|
||||
key: companionResource.name,
|
||||
frames: this.anims.generateFrameNumbers(companionResource.name, {start: 0, end: 2,}),
|
||||
frameRate: 10,
|
||||
repeat: -1
|
||||
});
|
||||
|
||||
companion.setInteractive().on("pointerdown", () => {
|
||||
this.currentCompanion = i;
|
||||
this.moveCompanion();
|
||||
});
|
||||
|
||||
this.companions.push(companion);
|
||||
}
|
||||
this.selectedCompanion = this.companions[this.currentCompanion];
|
||||
}
|
||||
|
||||
public onResize(ev: UIEvent): void {
|
||||
this.moveCompanion();
|
||||
|
||||
this.centerXDomElement(this.selectCompanionSceneElement, 150);
|
||||
}
|
||||
|
||||
private updateSelectedCompanion(): void {
|
||||
this.selectedCompanion?.anims.pause();
|
||||
const companion = this.companions[this.currentCompanion];
|
||||
companion.play(this.companionModels[this.currentCompanion].name);
|
||||
this.selectedCompanion = companion;
|
||||
}
|
||||
|
||||
private moveCompanion(){
|
||||
for(let i = 0; i < this.companions.length; i++){
|
||||
const companion = this.companions[i];
|
||||
this.setUpCompanion(companion, i);
|
||||
}
|
||||
this.updateSelectedCompanion();
|
||||
}
|
||||
|
||||
private moveToLeft(){
|
||||
if(this.currentCompanion === 0){
|
||||
return;
|
||||
}
|
||||
this.currentCompanion -= 1;
|
||||
this.moveCompanion();
|
||||
}
|
||||
|
||||
private moveToRight(){
|
||||
if(this.currentCompanion === (this.companions.length - 1)){
|
||||
return;
|
||||
}
|
||||
this.currentCompanion += 1;
|
||||
this.moveCompanion();
|
||||
}
|
||||
|
||||
private defineSetupCompanion(numero: 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 ){
|
||||
companionVisible = false;
|
||||
}
|
||||
if( numero === (this.currentCompanion + 1) ){
|
||||
companionY -= deltaY;
|
||||
companionX += deltaX;
|
||||
companionScale = 0.8;
|
||||
companionOpactity = 0.6;
|
||||
companionVisible = true;
|
||||
}
|
||||
if( numero === (this.currentCompanion + 2) ){
|
||||
companionY -= deltaY;
|
||||
companionX += (deltaX * 2);
|
||||
companionScale = 0.8;
|
||||
companionOpactity = 0.6;
|
||||
companionVisible = true;
|
||||
}
|
||||
if( numero === (this.currentCompanion - 1) ){
|
||||
companionY -= deltaY;
|
||||
companionX -= deltaX;
|
||||
companionScale = 0.8;
|
||||
companionOpactity = 0.6;
|
||||
companionVisible = true;
|
||||
}
|
||||
if( numero === (this.currentCompanion - 2) ){
|
||||
companionY -= deltaY;
|
||||
companionX -= (deltaX * 2);
|
||||
companionScale = 0.8;
|
||||
companionOpactity = 0.6;
|
||||
companionVisible = true;
|
||||
}
|
||||
return {companionX, companionY, companionScale, companionOpactity, companionVisible}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns pixel position by on column and row number
|
||||
*/
|
||||
private getCompanionPosition(): [number, number] {
|
||||
return [
|
||||
this.game.renderer.width / 2,
|
||||
this.game.renderer.height / 3
|
||||
];
|
||||
}
|
||||
|
||||
private setUpCompanion(companion: Phaser.Physics.Arcade.Sprite, numero: number){
|
||||
|
||||
const {companionX, companionY, companionScale, companionOpactity, companionVisible} = this.defineSetupCompanion(numero);
|
||||
companion.setBounce(0.2);
|
||||
companion.setCollideWorldBounds(true);
|
||||
companion.setVisible( companionVisible );
|
||||
companion.setScale(companionScale, companionScale);
|
||||
companion.setAlpha(companionOpactity);
|
||||
companion.setX(companionX);
|
||||
companion.setY(companionY);
|
||||
}
|
||||
}
|
@ -14,7 +14,7 @@ export interface ITiledMap {
|
||||
* Map orientation (orthogonal)
|
||||
*/
|
||||
orientation: string;
|
||||
properties: ITiledMapLayerProperty[];
|
||||
properties?: ITiledMapLayerProperty[];
|
||||
|
||||
/**
|
||||
* Render order (right-down)
|
||||
@ -24,6 +24,11 @@ export interface ITiledMap {
|
||||
tilewidth: number;
|
||||
tilesets: ITiledTileSet[];
|
||||
version: number;
|
||||
compressionlevel?: number;
|
||||
infinite?: boolean;
|
||||
nextlayerid?: number;
|
||||
tiledversion?: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
export interface ITiledMapLayerProperty {
|
||||
@ -38,19 +43,35 @@ export interface ITiledMapLayerProperty {
|
||||
value: boolean
|
||||
}*/
|
||||
|
||||
export interface ITiledMapLayer {
|
||||
export type ITiledMapLayer = ITiledMapGroupLayer | ITiledMapObjectLayer | ITiledMapTileLayer;
|
||||
|
||||
export interface ITiledMapGroupLayer {
|
||||
id?: number,
|
||||
name: string;
|
||||
opacity: number;
|
||||
properties?: ITiledMapLayerProperty[];
|
||||
|
||||
type: "group";
|
||||
visible: boolean;
|
||||
x: number;
|
||||
y: number;
|
||||
/**
|
||||
* Layers for group layer
|
||||
*/
|
||||
layers: ITiledMapLayer[];
|
||||
}
|
||||
|
||||
export interface ITiledMapTileLayer {
|
||||
id?: number,
|
||||
data: number[]|string;
|
||||
height: number;
|
||||
name: string;
|
||||
opacity: number;
|
||||
properties: ITiledMapLayerProperty[];
|
||||
encoding: string;
|
||||
properties?: ITiledMapLayerProperty[];
|
||||
encoding?: string;
|
||||
compression?: string;
|
||||
|
||||
/**
|
||||
* Type of layer (tilelayer, objectgroup)
|
||||
*/
|
||||
type: string;
|
||||
type: "tilelayer";
|
||||
visible: boolean;
|
||||
width: number;
|
||||
x: number;
|
||||
@ -59,7 +80,28 @@ export interface ITiledMapLayer {
|
||||
/**
|
||||
* Draw order (topdown (default), index)
|
||||
*/
|
||||
draworder: string;
|
||||
draworder?: string;
|
||||
}
|
||||
|
||||
export interface ITiledMapObjectLayer {
|
||||
id?: number,
|
||||
height: number;
|
||||
name: string;
|
||||
opacity: number;
|
||||
properties?: ITiledMapLayerProperty[];
|
||||
encoding?: string;
|
||||
compression?: string;
|
||||
|
||||
type: "objectgroup";
|
||||
visible: boolean;
|
||||
width: number;
|
||||
x: number;
|
||||
y: number;
|
||||
|
||||
/**
|
||||
* Draw order (topdown (default), index)
|
||||
*/
|
||||
draworder?: string;
|
||||
objects: ITiledMapObject[];
|
||||
}
|
||||
|
||||
@ -94,6 +136,20 @@ export interface ITiledMapObject {
|
||||
* Polyline points
|
||||
*/
|
||||
polyline: {x: number, y: number}[];
|
||||
|
||||
text?: ITiledText
|
||||
}
|
||||
|
||||
export interface ITiledText {
|
||||
text: string,
|
||||
wrap?: boolean,
|
||||
fontfamily?: string,
|
||||
pixelsize?: number,
|
||||
color?: string,
|
||||
underline?: boolean,
|
||||
italic?: boolean,
|
||||
strikeout?: boolean,
|
||||
halign?: "center"|"right"|"justify"|"left"
|
||||
}
|
||||
|
||||
export interface ITiledTileSet {
|
||||
|
44
front/src/Phaser/Map/LayersIterator.ts
Normal file
44
front/src/Phaser/Map/LayersIterator.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import type {ITiledMap, ITiledMapLayer} from "./ITiledMap";
|
||||
|
||||
/**
|
||||
* Iterates over the layers of a map, flattening the grouped layers
|
||||
*/
|
||||
export class LayersIterator implements IterableIterator<ITiledMapLayer> {
|
||||
|
||||
private layers: ITiledMapLayer[] = [];
|
||||
private pointer: number = 0;
|
||||
|
||||
constructor(private map: ITiledMap) {
|
||||
this.initLayersList(map.layers, '');
|
||||
}
|
||||
|
||||
private initLayersList(layers : ITiledMapLayer[], prefix : string) {
|
||||
for (const layer of layers) {
|
||||
if (layer.type === 'group') {
|
||||
this.initLayersList(layer.layers, prefix + layer.name + '/');
|
||||
} else {
|
||||
const layerWithNewName = { ...layer };
|
||||
layerWithNewName.name = prefix+layerWithNewName.name;
|
||||
this.layers.push(layerWithNewName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public next(): IteratorResult<ITiledMapLayer> {
|
||||
if (this.pointer < this.layers.length) {
|
||||
return {
|
||||
done: false,
|
||||
value: this.layers[this.pointer++]
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
done: true,
|
||||
value: null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Symbol.iterator](): IterableIterator<ITiledMapLayer> {
|
||||
return new LayersIterator(this.map);
|
||||
}
|
||||
}
|
@ -1,13 +1,14 @@
|
||||
import {mediaManager} from "../../WebRtc/MediaManager";
|
||||
import {HtmlUtils} from "../../WebRtc/HtmlUtils";
|
||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||
import {DirtyScene} from "../Game/DirtyScene";
|
||||
|
||||
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 Phaser.Scene {
|
||||
export class HelpCameraSettingsScene extends DirtyScene {
|
||||
private helpCameraSettingsElement!: Phaser.GameObjects.DOMElement;
|
||||
private helpCameraSettingsOpened: boolean = false;
|
||||
|
||||
@ -20,16 +21,18 @@ export class HelpCameraSettingsScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
create(){
|
||||
localUserStore.setHelpCameraSettingsShown();
|
||||
this.createHelpCameraSettings();
|
||||
}
|
||||
|
||||
private createHelpCameraSettings() : void {
|
||||
const middleX = (window.innerWidth / 3) - (370*0.85);
|
||||
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();
|
||||
@ -38,27 +41,30 @@ export class HelpCameraSettingsScene extends Phaser.Scene {
|
||||
}
|
||||
});
|
||||
|
||||
if(!mediaManager.constraintsMedia.audio || !mediaManager.constraintsMedia.video){
|
||||
if(!localUserStore.getHelpCameraSettingsShown() && (!mediaManager.constraintsMedia.audio || !mediaManager.constraintsMedia.video)){
|
||||
this.openHelpCameraSettingsOpened();
|
||||
localUserStore.setHelpCameraSettingsShown();
|
||||
}
|
||||
|
||||
mediaManager.setHelpCameraSettingsCallBack(() => {
|
||||
this.openHelpCameraSettingsOpened();
|
||||
});
|
||||
}
|
||||
|
||||
private openHelpCameraSettingsOpened(): void{
|
||||
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('webRtcSetup').style.display = 'none';
|
||||
this.helpCameraSettingsOpened = true;
|
||||
let middleY = (window.innerHeight / 3) - (495);
|
||||
if(middleY < 0){
|
||||
middleY = 0;
|
||||
}
|
||||
let middleX = (window.innerWidth / 3) - (370*0.85);
|
||||
if(middleX < 0){
|
||||
middleX = 0;
|
||||
}
|
||||
if(window.navigator.userAgent.includes('Firefox')){
|
||||
HtmlUtils.getElementByIdOrFail<HTMLParagraphElement>('browserHelpSetting').innerHTML ='<img src="/resources/objects/help-setting-camera-permission-firefox.png"/>';
|
||||
}else if(window.navigator.userAgent.includes('Chrome')){
|
||||
HtmlUtils.getElementByIdOrFail<HTMLParagraphElement>('browserHelpSetting').innerHTML ='<img src="/resources/objects/help-setting-camera-permission-chrome.png"/>';
|
||||
try{
|
||||
if(window.navigator.userAgent.includes('Firefox')){
|
||||
HtmlUtils.getElementByIdOrFail<HTMLParagraphElement>('browserHelpSetting').innerHTML ='<img src="/resources/objects/help-setting-camera-permission-firefox.png"/>';
|
||||
}else if(window.navigator.userAgent.includes('Chrome')){
|
||||
HtmlUtils.getElementByIdOrFail<HTMLParagraphElement>('browserHelpSetting').innerHTML ='<img src="/resources/objects/help-setting-camera-permission-chrome.png"/>';
|
||||
}
|
||||
}catch(err) {
|
||||
console.error('openHelpCameraSettingsOpened => getElementByIdOrFail => error', err);
|
||||
}
|
||||
const middleY = this.getMiddleY();
|
||||
const middleX = this.getMiddleX();
|
||||
this.tweens.add({
|
||||
targets: this.helpCameraSettingsElement,
|
||||
y: middleY,
|
||||
@ -67,20 +73,26 @@ export class HelpCameraSettingsScene extends Phaser.Scene {
|
||||
ease: 'Power3',
|
||||
overflow: 'scroll'
|
||||
});
|
||||
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
private closeHelpCameraSettingsOpened(): void{
|
||||
const helpCameraSettingsInfo = this.helpCameraSettingsElement.getChildByID('helpCameraSettings') as HTMLParagraphElement;
|
||||
const middleX = this.getMiddleX();
|
||||
/*const helpCameraSettingsInfo = this.helpCameraSettingsElement.getChildByID('helpCameraSettings') as HTMLParagraphElement;
|
||||
helpCameraSettingsInfo.innerText = '';
|
||||
helpCameraSettingsInfo.style.display = 'none';
|
||||
helpCameraSettingsInfo.style.display = 'none';*/
|
||||
this.helpCameraSettingsOpened = false;
|
||||
this.tweens.add({
|
||||
targets: this.helpCameraSettingsElement,
|
||||
y: -400,
|
||||
y: -1000,
|
||||
x: middleX,
|
||||
duration: 1000,
|
||||
ease: 'Power3',
|
||||
overflow: 'scroll'
|
||||
});
|
||||
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
private revealMenusAfterInit(menuElement: Phaser.GameObjects.DOMElement, rootDomId: string) {
|
||||
@ -91,5 +103,48 @@ export class HelpCameraSettingsScene extends Phaser.Scene {
|
||||
}, 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import {LoginScene, LoginSceneName} from "../Login/LoginScene";
|
||||
import {SelectCharacterScene, SelectCharacterSceneName} from "../Login/SelectCharacterScene";
|
||||
import {SelectCompanionScene, SelectCompanionSceneName} from "../Login/SelectCompanionScene";
|
||||
import {gameManager} from "../Game/GameManager";
|
||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||
import {mediaManager} from "../../WebRtc/MediaManager";
|
||||
@ -8,6 +9,7 @@ import {connectionManager} from "../../Connexion/ConnectionManager";
|
||||
import {GameConnexionTypes} from "../../Url/UrlManager";
|
||||
import {WarningContainer, warningContainerHtml, warningContainerKey} from "../Components/WarningContainer";
|
||||
import {worldFullWarningStream} from "../../Connexion/WorldFullWarningStream";
|
||||
import {menuIconVisible} from "../../Stores/MenuStore";
|
||||
|
||||
export const MenuSceneName = 'MenuScene';
|
||||
const gameMenuKey = 'gameMenu';
|
||||
@ -15,7 +17,7 @@ const gameMenuIconKey = 'gameMenuIcon';
|
||||
const gameSettingsMenuKey = 'gameSettingsMenu';
|
||||
const gameShare = 'gameShare';
|
||||
|
||||
const closedSideMenuX = -200;
|
||||
const closedSideMenuX = -1000;
|
||||
const openedSideMenuX = 0;
|
||||
|
||||
/**
|
||||
@ -52,6 +54,7 @@ export class MenuScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
create() {
|
||||
menuIconVisible.set(true);
|
||||
this.menuElement = this.add.dom(closedSideMenuX, 30).createFromCache(gameMenuKey);
|
||||
this.menuElement.setOrigin(0);
|
||||
MenuScene.revealMenusAfterInit(this.menuElement, 'gameMenu');
|
||||
@ -104,7 +107,12 @@ export class MenuScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
public revealMenuIcon(): void {
|
||||
(this.menuButton.getChildByID('menuIcon') as HTMLElement).hidden = false
|
||||
//TODO fix me: add try catch because at the same time, 'this.menuButton' variable doesn't exist and there is error on 'getChildByID' function
|
||||
try {
|
||||
(this.menuButton.getChildByID('menuIcon') as HTMLElement).hidden = false;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
openSideMenu() {
|
||||
@ -112,7 +120,8 @@ export class MenuScene extends Phaser.Scene {
|
||||
this.closeAll();
|
||||
this.sideMenuOpened = true;
|
||||
this.menuButton.getChildByID('openMenuButton').innerHTML = 'X';
|
||||
if (gameManager.getCurrentGameScene(this).connection && gameManager.getCurrentGameScene(this).connection.isAdmin()) {
|
||||
const connection = gameManager.getCurrentGameScene(this).connection;
|
||||
if (connection && connection.isAdmin()) {
|
||||
const adminSection = this.menuElement.getChildByID('adminConsoleSection') as HTMLElement;
|
||||
adminSection.hidden = false;
|
||||
}
|
||||
@ -185,11 +194,11 @@ export class MenuScene extends Phaser.Scene {
|
||||
}
|
||||
});
|
||||
|
||||
let middleY = (window.innerHeight / 3) - (257);
|
||||
let middleY = this.scale.height / 2 - 392/2;
|
||||
if(middleY < 0){
|
||||
middleY = 0;
|
||||
}
|
||||
let middleX = (window.innerWidth / 3) - 298;
|
||||
let middleX = this.scale.width / 2 - 457/2;
|
||||
if(middleX < 0){
|
||||
middleX = 0;
|
||||
}
|
||||
@ -229,11 +238,11 @@ export class MenuScene extends Phaser.Scene {
|
||||
|
||||
this.gameShareOpened = true;
|
||||
|
||||
let middleY = (window.innerHeight / 3) - (257);
|
||||
let middleY = this.scale.height / 2 - 85;
|
||||
if(middleY < 0){
|
||||
middleY = 0;
|
||||
}
|
||||
let middleX = (window.innerWidth / 3) - 298;
|
||||
let middleX = this.scale.width / 2 - 200;
|
||||
if(middleX < 0){
|
||||
middleX = 0;
|
||||
}
|
||||
@ -277,6 +286,10 @@ export class MenuScene extends Phaser.Scene {
|
||||
this.closeSideMenu();
|
||||
gameManager.leaveGame(this, SelectCharacterSceneName, new SelectCharacterScene());
|
||||
break;
|
||||
case 'changeCompanionButton':
|
||||
this.closeSideMenu();
|
||||
gameManager.leaveGame(this, SelectCompanionSceneName, new SelectCompanionScene());
|
||||
break;
|
||||
case 'closeButton':
|
||||
this.closeSideMenu();
|
||||
break;
|
||||
@ -286,6 +299,9 @@ export class MenuScene extends Phaser.Scene {
|
||||
case 'editGameSettingsButton':
|
||||
this.openGameSettingsMenu();
|
||||
break;
|
||||
case 'toggleFullscreen':
|
||||
this.toggleFullscreen();
|
||||
break;
|
||||
case 'adminConsoleButton':
|
||||
gameManager.getCurrentGameScene(this).ConsoleGlobalMessageManager.activeMessageConsole();
|
||||
break;
|
||||
@ -314,7 +330,9 @@ export class MenuScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
private gotToCreateMapPage() {
|
||||
const sparkHost = 'https://'+window.location.host.replace('play.', '')+'/choose-map.html';
|
||||
//const sparkHost = 'https://'+window.location.host.replace('play.', '')+'/choose-map.html';
|
||||
//TODO fix me: this button can to send us on WorkAdventure BO.
|
||||
const sparkHost = 'https://workadventu.re/getting-started';
|
||||
window.open(sparkHost, '_blank');
|
||||
}
|
||||
|
||||
@ -323,4 +341,19 @@ export class MenuScene extends Phaser.Scene {
|
||||
this.closeGameShare();
|
||||
this.gameReportElement.close();
|
||||
}
|
||||
|
||||
private toggleFullscreen() {
|
||||
const body = document.querySelector('body')
|
||||
if (body) {
|
||||
if (document.fullscreenElement ?? document.fullscreen) {
|
||||
document.exitFullscreen()
|
||||
} else {
|
||||
body.requestFullscreen();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public isDirty(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -72,8 +72,8 @@ export class ReportMenu extends Phaser.GameObjects.DOMElement {
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
gameManager.getCurrentGameScene(this.scene).userInputManager.restoreControls();
|
||||
this.opened = false;
|
||||
gameManager.getCurrentGameScene(this.scene).userInputManager.initKeyBoardEvent();
|
||||
const mainEl = this.getChildByID('gameReport') as HTMLElement;
|
||||
this.scene.tweens.add({
|
||||
targets: this,
|
||||
@ -109,7 +109,7 @@ export class ReportMenu extends Phaser.GameObjects.DOMElement {
|
||||
gamePError.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
gameManager.getCurrentGameScene(this.scene).connection.emitReportPlayerMessage(
|
||||
gameManager.getCurrentGameScene(this.scene).connection?.emitReportPlayerMessage(
|
||||
this.userId,
|
||||
gameTextArea.value
|
||||
);
|
||||
|
@ -1,13 +1,13 @@
|
||||
import {PlayerAnimationDirections} from "./Animation";
|
||||
import {GameScene} from "../Game/GameScene";
|
||||
import type {GameScene} from "../Game/GameScene";
|
||||
import {UserInputEvent, UserInputManager} from "../UserInput/UserInputManager";
|
||||
import {Character} from "../Entity/Character";
|
||||
|
||||
|
||||
export const hasMovedEventName = "hasMoved";
|
||||
export interface CurrentGamerInterface extends Character{
|
||||
moveUser(delta: number) : void;
|
||||
say(text : string) : void;
|
||||
isMoving(): boolean;
|
||||
}
|
||||
|
||||
export class Player extends Character implements CurrentGamerInterface {
|
||||
@ -22,12 +22,18 @@ export class Player extends Character implements CurrentGamerInterface {
|
||||
texturesPromise: Promise<string[]>,
|
||||
direction: PlayerAnimationDirections,
|
||||
moving: boolean,
|
||||
private userInputManager: UserInputManager
|
||||
private userInputManager: UserInputManager,
|
||||
companion: string|null,
|
||||
companionTexturePromise?: Promise<string>
|
||||
) {
|
||||
super(Scene, x, y, texturesPromise, name, direction, moving, 1);
|
||||
|
||||
//the current player model should be push away by other players to prevent conflict
|
||||
this.getBody().setImmovable(false);
|
||||
|
||||
if (typeof companion === 'string') {
|
||||
this.addCompanion(companion, companionTexturePromise);
|
||||
}
|
||||
}
|
||||
|
||||
moveUser(delta: number): void {
|
||||
@ -42,7 +48,7 @@ export class Player extends Character implements CurrentGamerInterface {
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
if (activeEvents.get(UserInputEvent.MoveUp)) {
|
||||
y = - moveAmount;
|
||||
y = -moveAmount;
|
||||
direction = PlayerAnimationDirections.Up;
|
||||
moving = true;
|
||||
} else if (activeEvents.get(UserInputEvent.MoveDown)) {
|
||||
@ -59,15 +65,18 @@ export class Player extends Character implements CurrentGamerInterface {
|
||||
direction = PlayerAnimationDirections.Right;
|
||||
moving = true;
|
||||
}
|
||||
moving = moving || activeEvents.get(UserInputEvent.JoystickMove);
|
||||
|
||||
if (x !== 0 || y !== 0) {
|
||||
this.move(x, y);
|
||||
this.emit(hasMovedEventName, {moving, direction, x: this.x, y: this.y});
|
||||
} else {
|
||||
if (this.wasMoving) {
|
||||
//direction = PlayerAnimationNames.None;
|
||||
this.stop();
|
||||
this.emit(hasMovedEventName, {moving, direction: this.previousDirection, x: this.x, y: this.y});
|
||||
}
|
||||
} else if (this.wasMoving && moving) {
|
||||
// slow joystick movement
|
||||
this.move(0, 0);
|
||||
this.emit(hasMovedEventName, {moving, direction: this.previousDirection, x: this.x, y: this.y});
|
||||
} else if (this.wasMoving && !moving) {
|
||||
this.stop();
|
||||
this.emit(hasMovedEventName, {moving, direction: this.previousDirection, x: this.x, y: this.y});
|
||||
}
|
||||
|
||||
if (direction !== null) {
|
||||
@ -75,4 +84,8 @@ export class Player extends Character implements CurrentGamerInterface {
|
||||
}
|
||||
this.wasMoving = moving;
|
||||
}
|
||||
|
||||
public isMoving(): boolean {
|
||||
return this.wasMoving;
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ export class ErrorScene extends Phaser.Scene {
|
||||
|
||||
this.subTitleField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height / 2 + 24, this.subTitle);
|
||||
|
||||
this.messageField = this.add.text(this.game.renderer.width / 2, this.game.renderer.height / 2 + 38, this.message, {
|
||||
this.messageField = this.add.text(this.game.renderer.width / 2, this.game.renderer.height / 2 + 48, this.message, {
|
||||
fontFamily: 'Georgia, "Goudy Bookletter 1911", Times, serif',
|
||||
fontSize: '10px'
|
||||
});
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user