Merge remote-tracking branch 'remotes/workadventure-main/develop' into gamestate-api-read

This commit is contained in:
jonny 2021-05-10 10:01:53 +02:00
commit ce0c7ea3eb
146 changed files with 7765 additions and 1891 deletions

View File

@ -1,7 +1,11 @@
name: Build, push and deploy Docker image
on:
- push
release:
types: [created]
pull_request:
types: [ labeled, synchronize ]
# Enables BuildKit
env:
@ -10,7 +14,7 @@ env:
jobs:
build-front:
if: ${{ github.event.release || contains(github.event.pull_request.labels.*.name, 'deploy') }}
runs-on: ubuntu-latest
steps:
@ -30,11 +34,11 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
repository: thecodingmachine/workadventure-front
tags: ${{ env.GITHUB_REF_SLUG }}
tags: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
add_git_labels: true
build-back:
if: ${{ github.event.release || contains(github.event.pull_request.labels.*.name, 'deploy') }}
runs-on: ubuntu-latest
steps:
@ -53,11 +57,11 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
repository: thecodingmachine/workadventure-back
tags: ${{ env.GITHUB_REF_SLUG }}
tags: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
add_git_labels: true
build-pusher:
if: ${{ github.event.release || contains(github.event.pull_request.labels.*.name, 'deploy') }}
runs-on: ubuntu-latest
steps:
@ -76,11 +80,11 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
repository: thecodingmachine/workadventure-pusher
tags: ${{ env.GITHUB_REF_SLUG }}
tags: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
add_git_labels: true
build-uploader:
if: ${{ github.event.release || contains(github.event.pull_request.labels.*.name, 'deploy') }}
runs-on: ubuntu-latest
steps:
@ -99,11 +103,11 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
repository: thecodingmachine/workadventure-uploader
tags: ${{ env.GITHUB_REF_SLUG }}
tags: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
add_git_labels: true
build-maps:
if: ${{ github.event.release || contains(github.event.pull_request.labels.*.name, 'deploy') }}
runs-on: ubuntu-latest
steps:
@ -123,7 +127,7 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
repository: thecodingmachine/workadventure-maps
tags: ${{ env.GITHUB_REF_SLUG }}
tags: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
add_git_labels: true
deeploy:
@ -134,6 +138,7 @@ jobs:
- build-maps
- build-uploader
runs-on: ubuntu-latest
if: ${{ contains(github.event.pull_request.labels.*.name, 'deploy') }}
steps:
- name: Checkout
@ -151,14 +156,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: ${{ env.GITHUB_HEAD_REF_SLUG }}
with:
namespace: workadventure-${{ env.GITHUB_REF_SLUG }}
namespace: workadventure-${{ env.GITHUB_HEAD_REF_SLUG }}
- name: Add a comment in PR
uses: unsplash/comment-on-pr@v1.2.0
if: ${{ env.GITHUB_REF_SLUG != 'master' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
msg: Environment deployed at https://play.${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com
msg: Environment deployed at https://play.${{ env.GITHUB_HEAD_REF_SLUG }}.test.workadventu.re
check_for_duplicate_msg: true

View File

@ -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 }}

View File

@ -3,8 +3,11 @@
name: "Continuous Integration"
on:
- "pull_request"
- "push"
push:
branches:
- master
- develop
pull_request:
jobs:

24
CHANGELOG.md Normal file
View File

@ -0,0 +1,24 @@
## Version 1.3.9 - in dev
### 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

View File

@ -20,7 +20,7 @@ Install Docker.
Run:
```
docker-compose up
docker-compose up -d
```
The environment will start.

View File

@ -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 = if namespace == "master" then "workadventu.re" else 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",

View File

@ -30,7 +30,8 @@ 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"
# 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
@ -152,23 +153,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

View File

@ -48,20 +48,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>

View 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>

View 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>

View 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>

View File

@ -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>

View File

@ -1,10 +1,12 @@
<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;
@ -14,6 +16,11 @@
#menuIcon section {
margin: 10px;
}
@media only screen and (max-height: 700px) {
#menuIcon section {
margin: 2px;
}
}
</style>
<main id="menuIcon" hidden>
<section>

View File

@ -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>

View File

@ -1,4 +1,7 @@
<style>
*{
font-family: PixelFont-7,monospace!important;
}
#gameReport {
background: #eceeee;
border: 1px solid #42464b;

View File

@ -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: 160px;
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;

View File

@ -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>

View 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>

View 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>

View File

@ -1,4 +1,7 @@
<style>
*{
font-family: PixelFont-7,monospace!important;
}
#warningMain {
border-radius: 5px;
height: 100px;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 969 B

View File

@ -0,0 +1,49 @@
@media (max-aspect-ratio: 1/1) {
#main-container {
display: flex;
flex-direction: column-reverse;
}
#cowebsite {
left: 0;
top: 0;
width: 100%;
height: 50%;
display: flex;
flex-direction: column-reverse;
&.loading {
transform: translateY(-90%);
}
&.hidden {
transform: translateY(-100%);
}
main {
height: 100%;
}
aside {
height: 30px;
cursor: ns-resize;
flex-direction: column;
img {
cursor: ns-resize;
}
}
.top-right-btn {
&#cowebsite-close {
right: 0;
}
&#cowebsite-fullscreen {
left: 0;
}
}
}
}

View File

@ -87,51 +87,3 @@
}
}
}
@media (max-aspect-ratio: 1/1) {
#main-container {
display: flex;
flex-direction: column-reverse;
}
#cowebsite {
left: 0;
top: 0;
width: 100%;
height: 50%;
display: flex;
flex-direction: column-reverse;
&.loading {
transform: translateY(-90%);
}
&.hidden {
transform: translateY(-100%);
}
main {
height: 100%;
}
aside {
height: 30px;
cursor: ns-resize;
flex-direction: column;
img {
cursor: ns-resize;
}
}
.top-right-btn {
&#cowebsite-close {
right: 0px;
}
&#cowebsite-fullscreen {
right: 25px;
}
}
}
}

View File

@ -1,2 +1,4 @@
@import "cowebsite.scss";
@import "style.css";
@import "cowebsite-mobile.scss";
@import "style.css";
@import "mobile-style.scss";

View File

@ -0,0 +1,49 @@
@media screen and (max-width: 700px),
screen and (max-height: 700px){
video#myCamVideo {
width: 150px;
}
.sidebar {
width: 20%;
min-width: 200px;
position: absolute;
display: block;
right: 0;
height: 80%;
&> div {
max-height: 120px;
min-width: 200px;
}
.video-container{
min-width: 200px;
}
}
.btn-cam-action {
min-width: 150px;
&:hover{
transform: translateY(20px);
}
div {
margin: 0 1%;
&:hover {
background-color: #666;
}
margin-bottom: 30px;
}
}
.main-section {
position: absolute;
width: 100%;
min-width: 400px;
& > div {
z-index: 2;
}
}
}

View File

@ -53,6 +53,7 @@ body .message-info.warning{
padding-top: 32px;
font-size: 28px;
color: white;
overflow: hidden;
}
.video-container img{
@ -97,7 +98,7 @@ body .message-info.warning{
}
.video-container button.report:hover {
width: 150px;
width: 160px;
}
.video-container button.report img{
@ -125,6 +126,7 @@ body .message-info.warning{
.video-container video{
height: 100%;
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
}
.video-container video:focus{
@ -140,6 +142,7 @@ body .message-info.warning{
right: 15px;
bottom: 30px;
border-radius: 15px 15px 15px 15px;
max-height: 20%;
}
video#myCamVideo{
@ -151,19 +154,60 @@ video#myCamVideo{
/*height: 113px;*/
}
.sound-progress{
display: none;
position: absolute;
right: 14px;
top: calc(50% - 5px);
}
.sound-progress.active{
display: table-column;
}
.sound-progress span{
position: absolute;
color: black;
background-color: #00000020;
width: 5px;
height: 5px;
border-radius: 50%;
}
.sound-progress span.active{
background-color: #00c3ff66
}
.sound-progress span:nth-child(1){
top: calc(50% + 20px);
}
.sound-progress span:nth-child(2){
top: calc(50% + 10px);
}
.sound-progress span:nth-child(3){
top: calc(50% - 0px);
}
.sound-progress span:nth-child(4){
top: calc(50% - 10px);
}
.sound-progress span:nth-child(5){
top: calc(50% - 20px);
}
.btn-cam-action {
pointer-events: all;
position: absolute;
bottom: 0px;
right: 0px;
width: 450px;
height: 150px;
display: inline-flex;
bottom: 10px;
right: 15px;
width: 15vw;
height: 40px;
text-align: center;
align-content: center;
align-items: center;
justify-content: center;
justify-items: center;
}
/*btn animation*/
.btn-cam-action div{
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
position: absolute;
/*position: absolute;*/
border: solid 0px black;
width: 44px;
height: 44px;
@ -172,7 +216,8 @@ video#myCamVideo{
border-radius: 48px;
transform: translateY(20px);
transition-timing-function: ease-in-out;
bottom: 20px;
margin-bottom: 20px;
margin: 0 4%;
}
.btn-cam-action div.disabled {
background: #d75555;
@ -191,17 +236,17 @@ video#myCamVideo{
.btn-micro{
pointer-events: auto;
transition: all .3s;
right: 44px;
/*right: 44px;*/
}
.btn-video{
pointer-events: auto;
transition: all .25s;
right: 134px;
/*right: 134px;*/
}
.btn-monitor{
pointer-events: auto;
transition: all .2s;
right: 224px;
/*right: 224px;*/
}
.btn-copy{
pointer-events: auto;
@ -336,25 +381,7 @@ body {
max-height: 25%;
}
}
@media (max-aspect-ratio: 1/1) {
.game-overlay {
flex-direction: column;
}
.sidebar {
flex-direction: row;
align-items: flex-end;
}
.sidebar > div {
max-width: 21%;
}
.sidebar > div:hover {
max-width: 25%;
}
}
#game {
@ -517,12 +544,20 @@ input[type=range]:focus::-ms-fill-upper {
/* TODO: DO WE NEED FLEX HERE???? WE WANT A SIDEBAR OF EXACTLY 25% (note: flex useful for direction!!!) */
}
.game-overlay + div {
pointer-events: none;
}
.game-overlay + div > div {
pointer-events: auto;
}
.game-overlay.active {
display: flex;
}
.game-overlay video {
width: 100%
width: 100%;
}
.main-section {
@ -538,6 +573,7 @@ input[type=range]:focus::-ms-fill-upper {
flex-basis: 96%;
transition: margin-left 0.2s, margin-right 0.2s, margin-bottom 0.2s, margin-top 0.2s, flex-basis 0.2s;
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
pointer-events: auto;
/*flex-shrink: 2;*/
}
@ -549,7 +585,6 @@ input[type=range]:focus::-ms-fill-upper {
.sidebar {
flex: 0 0 25%;
display: flex;
pointer-events: none;
}
.sidebar > div {
@ -557,12 +592,17 @@ input[type=range]:focus::-ms-fill-upper {
transition: margin-left 0.2s, margin-right 0.2s, margin-bottom 0.2s, margin-top 0.2s, max-height 0.2s, max-width 0.2s;
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
border-radius: 15px 15px 15px 15px;
pointer-events: auto;
}
.sidebar > div:hover {
margin: 0%;
}
.sidebar > div video {
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
}
/* Let's make sure videos are vertically centered if they need to be cropped */
.media-container {
display: flex;
@ -1046,7 +1086,7 @@ div.modal-report-user{
.discussion .messages{
position: absolute;
height: calc(100% - 360px);
height: calc(100% - 390px);
overflow-x: hidden;
overflow-y: auto;
max-width: calc(100% - 40px);
@ -1127,17 +1167,34 @@ div.action{
animation-iteration-count: infinite;
animation-timing-function: ease-in-out;
}
div.action.info,
div.action.warning,
div.action.danger{
transition: all 1s ease;
animation: mymove 1s;
animation-iteration-count: infinite;
animation-timing-function: ease-in-out;
}
div.action p.action-body{
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
padding: 10px;
background-color: #2d2d2dba;
color: #fff;
font-size: 14px;
font-weight: 500;
text-align: center;
max-width: 250px;
margin-left: calc(50% - 125px);
max-width: 350px;
margin-left: calc(50% - 175px);
border-radius: 15px;
}
div.action.warning p.action-body{
background-color: #ff9800eb;
color: #000;
}
div.action.danger p.action-body{
background-color: #da0000e3;
color: #000;
}
.popUpElement{
font-family: 'Press Start 2P';
text-align: left;

View File

@ -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"
}]
}

View File

@ -30,10 +30,10 @@
"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",

View File

@ -2,7 +2,6 @@ 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 {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);
}
})();
}

View File

@ -1,7 +1,56 @@
export interface IframeEvent {
type: string;
data: unknown;
import { GameStateEvent } from './ApiGameStateEvent';
import { ButtonClickedEvent } from './ButtonClickedEvent';
import { ChatEvent } from './ChatEvent';
import { ClosePopupEvent } from './ClosePopupEvent';
import { EnterLeaveEvent } from './EnterLeaveEvent';
import { GoToPageEvent } from './GoToPageEvent';
import { OpenCoWebSiteEvent } from './OpenCoWebSiteEvent';
import { OpenPopupEvent } from './OpenPopupEvent';
import { OpenTabEvent } from './OpenTabEvent';
import { 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
disablePlayerControl: null
restorePlayerControl: 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';

View File

@ -1,17 +1,17 @@
import {Subject} from "rxjs";
import {ChatEvent, isChatEvent} from "./Events/ChatEvent";
import {IframeEvent, isIframeEventWrapper} from "./Events/IframeEvent";
import {UserInputChatEvent} from "./Events/UserInputChatEvent";
import { Subject } from "rxjs";
import { ChatEvent, isChatEvent } from "./Events/ChatEvent";
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 { 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 { IframeEventMap, IframeEvent, IframeResponseEvent, IframeResponseEventMap, isIframeEventWrapper, TypedMessageEvent } from "./Events/IframeEvent";
import { UserInputChatEvent } from "./Events/UserInputChatEvent";
import { GameStateEvent } from './Events/ApiGameStateEvent';
import { deepFreezeClone as deepFreezeClone } from '../utility';
@ -62,18 +62,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;
}
@ -86,28 +86,32 @@ 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 === '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') {
else if (payload.type === 'closeCoWebSite') {
scriptUtils.closeCoWebSite();
}
else if (payload.type === 'disablePlayerControl'){
else if (payload.type === 'disablePlayerControl') {
this._disablePlayerControlStream.next();
}
else if (payload.type === 'restorePlayerControl'){
else if (payload.type === 'restorePlayerControl') {
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();
}else if(payload.type=="getState"){
this._gameStateStream.next();
@ -146,7 +150,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');
@ -170,8 +174,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';
@ -188,14 +192,14 @@ class IframeListener {
}
private getIFrameId(scriptUrl: string): string {
return 'script'+crypto.createHash('md5').update(scriptUrl).digest("hex");
return 'script' + crypto.createHash('md5').update(scriptUrl).digest("hex");
}
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();
@ -243,7 +247,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, '*');
}

View File

@ -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(){

View File

@ -10,7 +10,7 @@ export interface CharacterTexture {
export const maxUserNameLength: number = MAX_USERNAME_LENGTH;
export function isUserNameValid(value: string): boolean {
const regexp = new RegExp('^[A-Za-z]{1,'+maxUserNameLength+'}$');
const regexp = new RegExp('^[A-Za-z0-9]{1,'+maxUserNameLength+'}$');
return regexp.test(value);
}

View File

@ -27,7 +27,8 @@ import {
SendJitsiJwtMessage,
CharacterLayerMessage,
PingMessage,
SendUserMessage, BanUserMessage
SendUserMessage,
BanUserMessage
} from "../Messages/generated/messages_pb"
import {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
@ -169,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());

View File

@ -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);
}
}

View File

@ -10,21 +10,19 @@ 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,
PUSHER_URL,
UPLOADER_URL,
ADMIN_URL,
RESOLUTION,
ZOOM_LEVEL,
POSITION_DELAY,
MAX_EXTRAPOLATION_TIME,
STUN_SERVER,

View File

@ -1,8 +1,6 @@
import VirtualJoystick from 'phaser3-rex-plugins/plugins/virtualjoystick.js';
const outOfScreenX = -1000;
const outOfScreenY = -1000;
import ScaleManager = Phaser.Scale.ScaleManager;
import {waScaleManager} from "../Services/WaScaleManager";
//the assets were found here: https://hannemann.itch.io/virtual-joystick-pack-free
export const joystickBaseKey = 'joystickBase';
@ -10,26 +8,58 @@ 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: outOfScreenX,
y: outOfScreenY,
radius: 20,
base: scene.add.image(0, 0, joystickBaseKey).setDisplaySize(60, 60).setDepth(99999),
thumb: scene.add.image(0, 0, joystickThumbKey).setDisplaySize(30, 30).setDepth(99999),
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; }) => {
this.x = pointer.x;
this.y = pointer.y;
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.x = outOfScreenX;
this.y = outOfScreenY;
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);
}
}

View File

@ -0,0 +1,53 @@
import {ITiledMapObject} from "../Map/ITiledMap";
import Text = Phaser.GameObjects.Text;
import {GameScene} from "../Game/GameScene";
import TextStyle = Phaser.GameObjects.TextStyle;
export class TextUtils {
public static createTextFromITiledMapObject(scene: GameScene, object: ITiledMapObject): void {
if (object.text === undefined) {
throw new Error('This object has not textual representation.');
}
const options: {
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);
}
}

View File

@ -62,7 +62,7 @@ export const getRessourceDescriptor = (textureKey: string|BodyResourceDescriptio
const textureName:string = typeof textureKey === 'string' ? textureKey : textureKey.name;
const playerResource = PLAYER_RESOURCES[textureName];
if (playerResource !== undefined) return playerResource;
for (let i=0; i<LAYERS.length;i++) {
const playerResource = LAYERS[i][textureName];
if (playerResource !== undefined) return playerResource;
@ -81,4 +81,4 @@ const createLoadingPromise = (loadPlugin: LoaderPlugin, playerResourceDescriptor
});
loadPlugin.once('filecomplete-spritesheet-' + playerResourceDescriptor.name, () => res(playerResourceDescriptor));
});
}
}

View File

@ -0,0 +1,52 @@
import {ResizableScene} from "../Login/ResizableScene";
import GameObject = Phaser.GameObjects.GameObject;
import Events = Phaser.Scenes.Events;
import AnimationEvents = Phaser.Animations.Events;
/**
* 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) {
return;
}
this.isAlreadyTracking = true;
const trackAnimationFunction = this.trackAnimation.bind(this);
this.events.on(Events.ADDED_TO_SCENE, (gameObject: GameObject) => {
this.objectListChanged = true;
gameObject.on(AnimationEvents.ANIMATION_UPDATE, trackAnimationFunction);
});
this.events.on(Events.REMOVED_FROM_SCENE, (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;
}
}

View File

@ -0,0 +1,88 @@
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.
*/
export class Game extends Phaser.Game {
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 (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 {
// 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;
}
}

View File

@ -89,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) {

View File

@ -1,4 +1,5 @@
import {ITiledMap} from "../Map/ITiledMap";
import {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;
}

View File

@ -15,10 +15,16 @@ import {
JITSI_PRIVATE_MODE,
MAX_PER_GROUP,
POSITION_DELAY,
RESOLUTION,
ZOOM_LEVEL
} from "../../Enum/EnvironmentVariable";
import {ITiledMap, ITiledMapLayer, ITiledMapLayerProperty, ITiledMapObject, ITiledTileSet} from "../Map/ITiledMap";
import {
ITiledMap,
ITiledMapLayer,
ITiledMapLayerProperty,
ITiledMapObject,
ITiledText,
ITiledMapTileLayer,
ITiledTileSet
} from "../Map/ITiledMap";
import {AddPlayerInterface} from "./AddPlayerInterface";
import {PlayerAnimationDirections} from "../Player/Animation";
import {PlayerMovement} from "./PlayerMovement";
@ -77,10 +83,15 @@ import DOMElement = Phaser.GameObjects.DOMElement;
import {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 { PlayerStateObject } from '../../Api/Events/ApiGameStateEvent';
import {waScaleManager} from "../Services/WaScaleManager";
export interface GameSceneInitInterface {
initPosition: PointInterface|null,
@ -119,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>;
@ -136,7 +147,7 @@ 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;
@ -175,6 +186,7 @@ export class GameScene extends ResizableScene implements CenterListener {
private messageSubscription: Subscription|null = null;
private popUpElements : Map<number, DOMElement> = new Map<number, Phaser.GameObjects.DOMElement>();
private originalMapUrl: string|undefined;
private pinchManager: PinchManager|undefined;
constructor(private room: Room, MapUrlFile: string, customKey?: string|undefined) {
super({
@ -193,12 +205,11 @@ export class GameScene extends ResizableScene implements CenterListener {
})
this.connectionAnswerPromise = new Promise<RoomJoinedMessageInterface>((resolve, reject): void => {
this.connectionAnswerPromiseResolve = resolve;
})
});
}
//hook preload scene
preload(): void {
addLoader(this);
const localUser = localUserStore.getLocalUser();
const textures = localUser?.textures;
if (textures) {
@ -258,6 +269,9 @@ 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);
}
// FIXME: we need to put a "unknown" instead of a "any" and validate the structure of the JSON we are receiving.
@ -356,15 +370,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();
if (touchScreenManager.supportTouchScreen) {
new PinchManager(this);
this.pinchManager = new PinchManager(this);
}
this.messageSubscription = worldFullMessageStream.stream.subscribe((message) => this.showWorldFullError())
this.messageSubscription = worldFullMessageStream.stream.subscribe((message) => this.showWorldFullError(message))
const playerName = gameManager.getPlayerName();
if (!playerName) {
@ -386,11 +402,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) {
@ -404,9 +420,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();
@ -417,10 +440,10 @@ export class GameScene extends ResizableScene implements CenterListener {
//initialise list of other player
this.MapPlayers = this.physics.add.group({immovable: true});
//create input to move
mediaManager.setUserInputManager(this.userInputManager);
this.userInputManager = new UserInputManager(this);
mediaManager.setUserInputManager(this.userInputManager);
if (localUserStore.getFullscreen()) {
document.querySelector('body')?.requestFullscreen();
@ -713,7 +736,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);
}
@ -736,9 +759,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) => {
@ -925,9 +948,11 @@ ${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();
@ -989,13 +1014,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 {
@ -1018,7 +1044,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;
}
@ -1030,7 +1056,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 [];
}
@ -1044,7 +1070,7 @@ ${escapedMessage}
await gameManager.loadMap(room, this.scene);
}
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');
@ -1074,17 +1100,18 @@ ${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();
//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)
});
@ -1200,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,
@ -1213,12 +1240,24 @@ ${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;
this.physics.enableUpdate();
} else {
this.physics.disableUpdate();
}
// Let's handle all events
while (this.pendingEvents.length !== 0) {
this.dirty = true;
const event = this.pendingEvents.dequeue();
switch (event.type) {
case "InitUserPositionEvent":
@ -1244,6 +1283,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 + '"');
@ -1266,7 +1306,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) => {
@ -1410,15 +1450,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,
@ -1448,19 +1489,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 {
@ -1474,7 +1514,7 @@ ${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
@ -1503,14 +1543,29 @@ ${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();
}
}

View File

@ -1,54 +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 {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 mobileTapUP!: Rectangle;
private mobileTapDOWN!: Rectangle;
private mobileTapLEFT!: Rectangle;
private mobileTapRIGHT!: Rectangle;
private mobileTapENTER!: 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
@ -56,117 +34,56 @@ 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, 60, 'Start the game by pressing ENTER\n\n or touching the center rectangle');
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.mobileTapRIGHT = this.add
.rectangle(
this.game.renderer.width*0.9,
this.game.renderer.height/2,
32,
32,
)
.setInteractive()
.on("pointerdown", () => {
this.moveCursorHorizontally(1);
});
this.arrowLeft = new Image(this, this.game.renderer.width/9, this.game.renderer.height/2, CustomizeTextures.arrowRight);
this.arrowLeft.flipX = true;
this.add.existing(this.arrowLeft);
this.mobileTapLEFT = this.add
.rectangle(
this.game.renderer.width/9,
this.game.renderer.height/2,
32,
32,
)
.setInteractive()
.on("pointerdown", () => {
this.moveCursorHorizontally(-1);
});
this.Rectangle = this.add.rectangle(this.cameras.main.worldView.x + this.cameras.main.width / 2, this.cameras.main.worldView.y + this.cameras.main.height / 2, 32, 33)
this.Rectangle = 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.mobileTapENTER = this.add
.rectangle(
this.cameras.main.worldView.x + this.cameras.main.width / 2,
this.cameras.main.worldView.y + this.cameras.main.height / 2,
32,
32,
)
.setInteractive()
.on("pointerdown", () => {
const layers: string[] = [];
let i = 0;
for (const layerItem of this.selectedLayers) {
if (layerItem !== undefined) {
layers.push(this.layers[i][layerItem].name);
}
i++;
}
gameManager.setCharacterLayers(layers);
this.scene.sleep(CustomizeSceneName);
gameManager.tryResumingGame(this, EnableCameraSceneName);
});
this.arrowDown = new Image(this, this.game.renderer.width - 30, 100, CustomizeTextures.arrowUp);
this.arrowDown.flipY = true;
this.add.existing(this.arrowDown);
this.mobileTapDOWN = this.add
.rectangle(
this.game.renderer.width - 30,
100,
32,
32,
)
.setInteractive()
.on("pointerdown", () => {
this.moveCursorVertically(1);
});
this.arrowUp = new Image(this, this.game.renderer.width - 30, 60, CustomizeTextures.arrowUp);
this.add.existing(this.arrowUp);
this.mobileTapUP = this.add
.rectangle(
this.game.renderer.width - 30,
60,
32,
32,
)
.setInteractive()
.on("pointerdown", () => {
this.moveCursorVertically(-1);
});
this.createCustomizeLayer(0, 0, 0);
this.createCustomizeLayer(0, 0, 1);
@ -177,22 +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++;
}
if (!areCharacterLayersValid(layers)) {
return;
}
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));
@ -207,6 +112,8 @@ export class CustomizeScene extends AbstractCharacterScene {
this.moveLayers();
this.updateSelectedLayer();
}
this.onResize();
}
private moveCursorHorizontally(index: number): void {
@ -222,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
@ -236,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
@ -298,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++) {
@ -335,41 +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.mobileTapENTER.x = this.cameras.main.worldView.x + this.cameras.main.width / 2;
this.Rectangle.y = this.cameras.main.worldView.y + this.cameras.main.height / 2;
this.mobileTapENTER.y = this.cameras.main.worldView.y + this.cameras.main.height / 2;
this.textField.x = this.game.renderer.width/2;
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.mobileTapUP.x = this.game.renderer.width - 30;
this.arrowUp.y = 60;
this.mobileTapUP.y = 60;
this.arrowDown.x = this.game.renderer.width - 30;
this.mobileTapDOWN.x = this.game.renderer.width - 30;
this.arrowDown.y = 100;
this.mobileTapDOWN.y = 100;
this.arrowLeft.x = this.game.renderer.width/9;
this.mobileTapLEFT.x = this.game.renderer.width/9;
this.arrowLeft.y = this.game.renderer.height/2;
this.mobileTapLEFT.y = this.game.renderer.height/2;
this.arrowRight.x = this.game.renderer.width*0.9;
this.mobileTapRIGHT.x = this.game.renderer.width*0.9;
this.arrowRight.y = this.game.renderer.height/2;
this.mobileTapRIGHT.y = this.game.renderer.height/2;
this.Rectangle.y = this.cameras.main.worldView.y + this.cameras.main.height / 3;
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);
}
}

View File

@ -1,14 +1,15 @@
import {gameManager} from "../Game/GameManager";
import {TextField} from "../Components/TextField";
import Image = Phaser.GameObjects.Image;
import Rectangle = Phaser.GameObjects.Rectangle;
import {mediaManager} from "../../WebRtc/MediaManager";
import {RESOLUTION} from "../../Enum/EnvironmentVariable";
import {SoundMeter} from "../Components/SoundMeter";
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 {
@ -19,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;
@ -36,9 +36,11 @@ export class EnableCameraScene extends Phaser.Scene {
private soundMeter: SoundMeter;
private soundMeterSprite!: SoundMeterSprite;
private microphoneNameField!: TextField;
private repositionCallback!: (this: Window, ev: UIEvent) => void;
private mobileTapRectangle!: Rectangle;
private enableCameraSceneElement!: Phaser.GameObjects.DOMElement;
private mobileTapZone!: Zone;
constructor() {
super({
key: EnableCameraSceneName
@ -47,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
@ -56,23 +60,30 @@ export class EnableCameraScene extends Phaser.Scene {
}
create() {
this.enableCameraSceneElement = this.add.dom(-1000, 0).createFromCache(enableCameraSceneKey);
this.centerXDomElement(this.enableCameraSceneElement, 300);
MenuScene.revealMenusAfterInit(this.enableCameraSceneElement, enableCameraSceneKey);
const continuingButton = this.enableCameraSceneElement.getChildByID('enableCameraSceneFormSubmit') as HTMLButtonElement;
continuingButton.addEventListener('click', (e) => {
e.preventDefault();
this.login();
});
if (touchScreenManager.supportTouchScreen) {
new PinchManager(this);
}
this.textField = new TextField(this, this.game.renderer.width / 2, 20, 'Turn on your camera and microphone');
//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, '');
this.pressReturnField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height - 30, 'Touch here\n\n or \n\nPress enter to start');
// For mobile purposes - we need a big enough touchable area.
this.mobileTapRectangle = this.add
.rectangle(
this.game.renderer.width / 2,
this.game.renderer.height - 30,
200,
50,
)
.setInteractive()
.on("pointerdown", () => {
this.mobileTapZone = this.add.zone(this.scale.width / 2,this.scale.height - 30,200,50)
.setInteractive().on("pointerdown", () => {
this.login();
});
@ -102,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();
});
@ -124,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 {
@ -203,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) {
@ -215,24 +221,22 @@ export class EnableCameraScene extends Phaser.Scene {
}
this.textField.x = this.game.renderer.width / 2;
this.mobileTapRectangle.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;
@ -240,23 +244,22 @@ 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.updateScene();
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();

View File

@ -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);
}

View File

@ -1,30 +1,18 @@
import {gameManager} from "../Game/GameManager";
import {TextField} from "../Components/TextField";
import {TextInput} from "../Components/TextInput";
import Image = Phaser.GameObjects.Image;
import {SelectCharacterSceneName} from "./SelectCharacterScene";
import {ResizableScene} from "./ResizableScene";
import {isUserNameValid, maxUserNameLength} from "../../Connexion/LocalUser";
import { localUserStore } from "../../Connexion/LocalUserStore";
import Rectangle = Phaser.GameObjects.Rectangle;
import {touchScreenManager} from "../../Touch/TouchScreenManager";
import {PinchManager} from "../UserInput/PinchManager";
import {MenuScene} from "../Menu/MenuScene";
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 = '';
private mobileTapRectangle!: Rectangle;
constructor() {
super({
@ -34,81 +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() {
if (touchScreenManager.supportTouchScreen) {
new PinchManager(this);
}
this.loginSceneElement = this.add.dom(-1000, 0).createFromCache(loginSceneKey);
this.centerXDomElement(this.loginSceneElement, 200);
MenuScene.revealMenusAfterInit(this.loginSceneElement, loginSceneKey);
this.nameInput = new TextInput(this, this.game.renderer.width / 2, 70, maxUserNameLength, this.name,(text: string) => {
this.name = text;
localUserStore.setName(text);
})
.setInteractive()
.on('pointerdown', () => {
this.nameInput.focus();
})
this.textField = new TextField(this, this.game.renderer.width / 2, 50, 'Enter your name:')
.setInteractive()
.on('pointerdown', () => {
this.nameInput.focus();
})
// For mobile purposes - we need a big enough touchable area.
this.mobileTapRectangle = this.add.rectangle(
this.game.renderer.width / 2,
130,
this.game.renderer.width / 2,
60,
).setInteractive()
.on('pointerdown', () => {
this.login(this.name)
})
this.pressReturnField = new TextField(this, this.game.renderer.width / 2, 130, 'Touch here\n\n or \n\nPress enter to start')
this.logo = new Image(this, this.game.renderer.width - 30, this.game.renderer.height - 20, LoginTextures.icon);
this.add.existing(this.logo);
const infoText = "Commands: \n - Arrows or W, A, S, D to move\n - SHIFT to run";
this.infoTextField = new TextField(this, 10, this.game.renderer.height - 35, infoText, false);
this.input.keyboard.on('keyup-ENTER', () => {
if (isUserNameValid(this.name)) {
this.login(this.name);
const pErrorElement = this.loginSceneElement.getChildByID('errorLoginScene') as HTMLInputElement;
const inputElement = this.loginSceneElement.getChildByID('loginSceneName') as HTMLInputElement;
inputElement.value = localUserStore.getName() ?? '';
inputElement.focus();
inputElement.addEventListener('keypress', (event: KeyboardEvent) => {
if(inputElement.value.length > 7){
event.preventDefault();
return;
}
pErrorElement.innerHTML = '';
if(inputElement.value && !isUserNameValid(inputElement.value)){
pErrorElement.innerHTML = 'Invalid user name: only letters and numbers are allowed. No spaces.';
}
if (event.key === 'Enter') {
event.preventDefault();
this.login(inputElement);
return;
}
});
const continuingButton = this.loginSceneElement.getChildByID('loginSceneFormSubmit') as HTMLButtonElement;
continuingButton.addEventListener('click', (e) => {
e.preventDefault();
this.login(inputElement);
});
}
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
}
if(!isUserNameValid(this.name)){
pErrorElement.innerHTML = 'Invalid user name: only letters and numbers are allowed. No spaces.';
return
}
}
private login(name: string): void {
if (this.name === '') return
gameManager.setPlayerName(name);
gameManager.setPlayerName(this.name);
this.scene.stop(LoginSceneName)
gameManager.tryResumingGame(this, SelectCharacterSceneName);
this.scene.remove(LoginSceneName)
}
public onResize(ev: UIEvent): void {
this.textField.x = this.game.renderer.width / 2;
this.nameInput.setX(this.game.renderer.width / 2 - 64);
this.pressReturnField.x = this.game.renderer.width / 2;
this.mobileTapRectangle.x = this.game.renderer.width / 2;
this.logo.x = this.game.renderer.width - 30;
this.logo.y = this.game.renderer.height - 20;
this.infoTextField.y = this.game.renderer.height - 35;
update(time: number, delta: number): void {
}
public onResize(ev: UIEvent): void {
this.centerXDomElement(this.loginSceneElement, 200);
}
}

View File

@ -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)
);
}
}

View File

@ -0,0 +1,76 @@
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 {localUserStore} from "../../Connexion/LocalUserStore";
import {loadAllDefaultModels} from "../Entity/PlayerTexturesLoadingManager";
import {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";
import { SelectCharacterScene, SelectCharacterSceneName } 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
];
}
}

View File

@ -1,5 +1,4 @@
import {gameManager} from "../Game/GameManager";
import {TextField} from "../Components/TextField";
import Image = Phaser.GameObjects.Image;
import Rectangle = Phaser.GameObjects.Rectangle;
import {EnableCameraSceneName} from "./EnableCameraScene";
@ -12,268 +11,241 @@ import {AbstractCharacterScene} from "./AbstractCharacterScene";
import {areCharacterLayersValid} from "../../Connexion/LocalUser";
import {touchScreenManager} from "../../Touch/TouchScreenManager";
import {PinchManager} from "../UserInput/PinchManager";
import {MenuScene} from "../Menu/MenuScene";
import { SelectCharacterMobileScene } from "./SelectCharacterMobileScene";
//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 mobileTapRectangle!: Rectangle;
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.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);
}
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) + 60,
'Touch here\n\n or \n\nPress enter to start');
// For mobile purposes - we need a big enough touchable area.
this.mobileTapRectangle = this.add
.rectangle(
this.game.renderer.width / 2,
275,
this.game.renderer.width / 2,
50,
)
.setInteractive()
.on("pointerdown", () => {
this.nextScene();
});
const rectangleXStart = this.game.renderer.width / 2 - (this.nbCharactersPerRow / 2) * 32 + 16;
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();
});
}
update(time: number, delta: number): void {
this.pressReturnField.setVisible(!!(Math.floor(time / 500) % 2));
}
private nextScene(): void {
protected nextSceneToCameraScene(): void {
if (this.selectedPlayer !== null && !areCharacterLayersValid([this.selectedPlayer.texture.key])) {
return;
}
this.scene.stop(SelectCharacterSceneName);
if (this.selectedPlayer !== null) {
gameManager.setCharacterLayers([this.selectedPlayer.texture.key]);
gameManager.tryResumingGame(this, EnableCameraSceneName);
} else {
this.scene.run(CustomizeSceneName);
if(!this.selectedPlayer){
return;
}
this.scene.stop(SelectCharacterSceneName);
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();
this.nextScene();
});
this.customizeButtonSelected.setInteractive().on("pointerdown", () => {
this.nextScene();
});
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);
}
}

View File

@ -3,35 +3,25 @@ import Rectangle = Phaser.GameObjects.Rectangle;
import { addLoader } from "../Components/Loader";
import { gameManager} from "../Game/GameManager";
import { ResizableScene } from "./ResizableScene";
import { TextField } from "../Components/TextField";
import { EnableCameraSceneName } from "./EnableCameraScene";
import { localUserStore } from "../../Connexion/LocalUserStore";
import { CompanionResourceDescriptionInterface } from "../Companion/CompanionTextures";
import { getAllCompanionResources } from "../Companion/CompanionTexturesLoadingManager";
import {touchScreenManager} from "../../Touch/TouchScreenManager";
import {PinchManager} from "../UserInput/PinchManager";
import { MenuScene } from "../Menu/MenuScene";
export const SelectCompanionSceneName = "SelectCompanionScene";
enum LoginTextures {
playButton = "play_button",
icon = "icon",
mainFont = "main_font"
}
const selectCompanionSceneKey = 'selectCompanionScene';
export class SelectCompanionScene extends ResizableScene {
private logo!: Image;
private textField!: TextField;
private pressReturnField!: TextField;
private readonly nbCharactersPerRow = 7;
private selectedRectangle!: Rectangle;
private selectedCompanion!: Phaser.Physics.Arcade.Sprite;
private companions: Array<Phaser.Physics.Arcade.Sprite> = new Array<Phaser.Physics.Arcade.Sprite>();
private companionModels: Array<CompanionResourceDescriptionInterface|null> = [null];
private companionModels: Array<CompanionResourceDescriptionInterface> = [];
private confirmTouchArea!: Rectangle;
private selectCompanionSceneElement!: Phaser.GameObjects.DOMElement;
private currentCompanion = 0;
constructor() {
super({
@ -40,210 +30,199 @@ export class SelectCompanionScene extends ResizableScene {
}
preload() {
addLoader(this);
this.load.html(selectCompanionSceneKey, 'resources/html/SelectCompanionScene.html');
getAllCompanionResources(this.load).forEach(model => {
this.companionModels.push(model);
});
this.load.image(LoginTextures.icon, "resources/logos/tcm_full.png");
this.load.image(LoginTextures.playButton, "resources/objects/play_button.png");
// Note: arcade.png from the Phaser 3 examples at: https://github.com/photonstorm/phaser3-examples/tree/master/public/assets/fonts/bitmap
this.load.bitmapFont(LoginTextures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
//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);
}
this.textField = new TextField(this, this.game.renderer.width / 2, 50, 'Select your companion');
const confirmTouchAreaY = 115 + 32 * Math.ceil(this.companionModels.length / this.nbCharactersPerRow);
this.pressReturnField = new TextField(
this,
this.game.renderer.width / 2,
confirmTouchAreaY,
'Touch here\n\n or \n\n press enter to start'
);
this.confirmTouchArea = this.add
.rectangle(this.game.renderer.width / 2, confirmTouchAreaY, 200, 50)
.setInteractive()
.on("pointerdown", this.nextScene.bind(this));
const rectangleXStart = this.game.renderer.width / 2 - (this.nbCharactersPerRow / 2) * 32 + 16;
this.selectedRectangle = this.add.rectangle(rectangleXStart, 90, 32, 32).setStrokeStyle(2, 0xFFFFFF);
this.logo = new Image(this, this.game.renderer.width - 30, this.game.renderer.height - 20, LoginTextures.icon);
this.add.existing(this.logo);
// input events
this.input.keyboard.on('keyup-ENTER', this.nextScene.bind(this));
this.input.keyboard.on('keydown-RIGHT', this.selectNext.bind(this));
this.input.keyboard.on('keydown-LEFT', this.selectPrevious.bind(this));
this.input.keyboard.on('keydown-DOWN', this.jumpToNextRow.bind(this));
this.input.keyboard.on('keydown-UP', this.jumpToPreviousRow.bind(this));
this.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.selectCompanion(this.getCompanionIndex());
this.updateSelectedCompanion();
}
update(time: number, delta: number): void {
this.pressReturnField.setVisible(!!(Math.floor(time / 500) % 2));
}
private jumpToPreviousRow(): void {
const index = this.companions.indexOf(this.selectedCompanion) - this.nbCharactersPerRow;
if (index >= 0) {
this.selectCompanion(index);
}
}
private jumpToNextRow(): void {
const index = this.companions.indexOf(this.selectedCompanion) + this.nbCharactersPerRow;
if (index < this.companions.length) {
this.selectCompanion(index);
}
}
private selectPrevious(): void {
const index = this.companions.indexOf(this.selectedCompanion);
this.selectCompanion(index - 1);
}
private selectNext(): void {
const index = this.companions.indexOf(this.selectedCompanion);
this.selectCompanion(index + 1);
}
private selectCompanion(index?: number): void {
if (typeof index === 'undefined') {
index = this.companions.indexOf(this.selectedCompanion);
}
// make sure index is inside possible range
index = Math.min(this.companions.length - 1, Math.max(0, index));
if (this.selectedCompanion === this.companions[index]) {
return;
}
this.selectedCompanion.anims.pause();
this.selectedCompanion = this.companions[index];
this.redrawSelectedRectangle();
const model = this.companionModels[index];
if (model !== null) {
this.selectedCompanion.anims.play(model.name);
}
}
private redrawSelectedRectangle(): void {
this.selectedRectangle.setVisible(true);
this.selectedRectangle.setX(this.selectedCompanion.x);
this.selectedRectangle.setY(this.selectedCompanion.y);
this.selectedRectangle.setSize(32, 32);
}
private storeCompanionSelection(): string|null {
const index = this.companions.indexOf(this.selectedCompanion);
const model = this.companionModels[index];
const companion = model === null ? null : model.name;
localUserStore.setCompanion(companion);
return companion;
}
private nextScene(): void {
const companion = this.storeCompanionSelection();
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.setCompanion(companion);
gameManager.tryResumingGame(this, EnableCameraSceneName);
this.scene.remove(SelectCompanionSceneName);
}
private createCurrentCompanion(): void {
for (let i = 0; i < this.companionModels.length; i++) {
const companionResource = this.companionModels[i];
const col = i % this.nbCharactersPerRow;
const row = Math.floor(i / this.nbCharactersPerRow);
const [x, y] = this.getCharacterPosition(col, row);
let name = "null";
if (companionResource !== null) {
name = companionResource.name;
}
const companion = this.physics.add.sprite(x, y, name, 0);
companion.setBounce(0.2);
companion.setCollideWorldBounds(true);
if (companionResource !== null) {
this.anims.create({
key: name,
frames: this.anims.generateFrameNumbers(name, {start: 0, end: 2,}),
frameRate: 10,
repeat: -1
});
}
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.selectCompanion(i);
this.currentCompanion = i;
this.moveCompanion();
});
this.companions.push(companion);
}
this.selectedCompanion = this.companions[0];
}
private getCharacterPosition(x: number, y: number): [number, number] {
return [
this.game.renderer.width / 2 + 16 + (x - this.nbCharactersPerRow / 2) * 32,
y * 32 + 90
];
this.selectedCompanion = this.companions[this.currentCompanion];
}
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.moveCompanion();
for (let i = 0; i < this.companionModels.length; i++) {
const companion = this.companions[i];
const col = i % this.nbCharactersPerRow;
const row = Math.floor(i / this.nbCharactersPerRow);
const [x, y] = this.getCharacterPosition(col, row);
companion.x = x;
companion.y = y;
}
this.redrawSelectedRectangle();
this.centerXDomElement(this.selectCompanionSceneElement, 150);
}
private getCompanionIndex(): number {
const companion = localUserStore.getCompanion();
private updateSelectedCompanion(): void {
this.selectedCompanion?.anims.pause();
const companion = this.companions[this.currentCompanion];
companion.play(this.companionModels[this.currentCompanion].name);
this.selectedCompanion = companion;
}
if (companion === null) {
return 0;
private moveCompanion(){
for(let i = 0; i < this.companions.length; i++){
const companion = this.companions[i];
this.setUpCompanion(companion, i);
}
this.updateSelectedCompanion();
}
return this.companionModels.findIndex(model => model !== null && model.name === companion);
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);
}
}

View File

@ -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 {

View File

@ -0,0 +1,44 @@
import {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);
}
}

View File

@ -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,45 @@ export class HelpCameraSettingsScene extends Phaser.Scene {
}, 250);
}
update(time: number, delta: number): void {
this.dirty = false;
}
public onResize(ev: UIEvent): void {
super.onResize(ev);
const middleX = this.getMiddleX();
const middleY = this.getMiddleY();
this.tweens.add({
targets: this.helpCameraSettingsElement,
x: middleX,
y: middleY,
duration: 1000,
ease: 'Power3'
});
}
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;
}
}

View File

@ -16,7 +16,7 @@ const gameMenuIconKey = 'gameMenuIcon';
const gameSettingsMenuKey = 'gameSettingsMenu';
const gameShare = 'gameShare';
const closedSideMenuX = -200;
const closedSideMenuX = -1000;
const openedSideMenuX = 0;
/**
@ -91,7 +91,7 @@ export class MenuScene extends Phaser.Scene {
this.menuElement.addListener('click');
this.menuElement.on('click', this.onMenuClick.bind(this));
worldFullWarningStream.stream.subscribe(() => this.showWorldCapacityWarning());
}
@ -118,7 +118,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;
}
@ -134,7 +135,7 @@ export class MenuScene extends Phaser.Scene {
ease: 'Power3'
});
}
private showWorldCapacityWarning() {
if (!this.warningContainer) {
this.warningContainer = new WarningContainer(this);
@ -147,7 +148,7 @@ export class MenuScene extends Phaser.Scene {
this.warningContainer = null
this.warningContainerTimeout = null
}, 120000);
}
private closeSideMenu(): void {
@ -191,11 +192,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;
}
@ -235,11 +236,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;
}
@ -349,4 +350,8 @@ export class MenuScene extends Phaser.Scene {
}
}
}
public isDirty(): boolean {
return false;
}
}

View File

@ -7,11 +7,11 @@ export const gameReportRessource = 'resources/html/gameReport.html';
export class ReportMenu extends Phaser.GameObjects.DOMElement {
private opened: boolean = false;
private userId!: number;
private userName!: string|undefined;
private anonymous: boolean;
constructor(scene: Phaser.Scene, anonymous: boolean) {
super(scene, -2000, -2000);
this.anonymous = anonymous;
@ -23,7 +23,7 @@ export class ReportMenu extends Phaser.GameObjects.DOMElement {
const textToHide = this.getChildByID('askActionP') as HTMLElement;
textToHide.hidden = true;
}
scene.add.existing(this);
MenuScene.revealMenusAfterInit(this, gameReportKey);
@ -45,10 +45,10 @@ export class ReportMenu extends Phaser.GameObjects.DOMElement {
this.close();
return;
}
this.userId = userId;
this.userName = userName;
const mainEl = this.getChildByID('gameReport') as HTMLElement;
this.x = this.getCenteredX(mainEl);
this.y = this.getHiddenY(mainEl);
@ -82,7 +82,7 @@ export class ReportMenu extends Phaser.GameObjects.DOMElement {
ease: 'Power3'
});
}
//todo: into a parent class?
private getCenteredX(mainEl: HTMLElement): number {
return window.innerWidth / 4 - mainEl.clientWidth / 2;
@ -93,7 +93,7 @@ export class ReportMenu extends Phaser.GameObjects.DOMElement {
private getCenteredY(mainEl: HTMLElement): number {
return window.innerHeight / 4 - mainEl.clientHeight / 2;
}
private toggleBlock(): void {
!blackListManager.isBlackListed(this.userId) ? blackListManager.blackList(this.userId) : blackListManager.cancelBlackList(this.userId);
this.close();
@ -109,10 +109,10 @@ 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
);
this.close();
}
}
}

View File

@ -7,6 +7,7 @@ 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 {
@ -83,4 +84,8 @@ export class Player extends Character implements CurrentGamerInterface {
}
this.wasMoving = moving;
}
public isMoving(): boolean {
return this.wasMoving;
}
}

View File

@ -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'
});

View File

@ -0,0 +1,105 @@
import ScaleManager = Phaser.Scale.ScaleManager;
interface Size {
width: number;
height: number;
}
export class HdpiManager {
private _zoomModifier: number = 1;
/**
*
* @param minRecommendedGamePixelsNumber The minimum number of pixels we want to display "by default" to the user
* @param absoluteMinPixelNumber The very minimum of game pixels to display. Below, we forbid zooming more
*/
public constructor(private minRecommendedGamePixelsNumber: number, private absoluteMinPixelNumber: number) {
}
/**
* Returns the optimal size in "game pixels" based on the screen size in "real pixels".
*
* Note: the function is returning the optimal size in "game pixels" in the "game" property,
* but also recommends resizing the "real" pixel screen size of the canvas.
* The proposed new real size is a few pixels bigger than the real size available (if the size is not a multiple of the pixel size) and should overflow.
*
* @param realPixelScreenSize
*/
public getOptimalGameSize(realPixelScreenSize: Size): { game: Size, real: Size } {
const realPixelNumber = realPixelScreenSize.width * realPixelScreenSize.height;
// If the screen has not a definition small enough to match the minimum number of pixels we want to display,
// let's make the canvas the size of the screen (in real pixels)
if (realPixelNumber <= this.minRecommendedGamePixelsNumber) {
return {
game: realPixelScreenSize,
real: realPixelScreenSize
};
}
let i = 1;
while (realPixelNumber > this.minRecommendedGamePixelsNumber * i * i) {
i++;
}
// Has the canvas more pixels than the screen? This is forbidden
if ((i - 1) * this._zoomModifier < 1) {
// Let's reset the zoom modifier (WARNING this is a SIDE EFFECT in a getter)
this._zoomModifier = 1 / (i - 1);
return {
game: {
width: realPixelScreenSize.width,
height: realPixelScreenSize.height,
},
real: {
width: realPixelScreenSize.width,
height: realPixelScreenSize.height,
}
}
}
const gameWidth = Math.ceil(realPixelScreenSize.width / (i - 1) / this._zoomModifier);
const gameHeight = Math.ceil(realPixelScreenSize.height / (i - 1) / this._zoomModifier);
// Let's ensure we display a minimum of pixels, even if crazily zoomed in.
if (gameWidth * gameHeight < this.absoluteMinPixelNumber) {
const minGameHeight = Math.sqrt(this.absoluteMinPixelNumber * realPixelScreenSize.height / realPixelScreenSize.width);
const minGameWidth = Math.sqrt(this.absoluteMinPixelNumber * realPixelScreenSize.width / realPixelScreenSize.height);
// Let's reset the zoom modifier (WARNING this is a SIDE EFFECT in a getter)
this._zoomModifier = realPixelScreenSize.width / minGameWidth / (i - 1);
return {
game: {
width: minGameWidth,
height: minGameHeight,
},
real: {
width: realPixelScreenSize.width,
height: realPixelScreenSize.height,
}
}
}
return {
game: {
width: gameWidth,
height: gameHeight,
},
real: {
width: Math.ceil(realPixelScreenSize.width / (i - 1)) * (i - 1),
height: Math.ceil(realPixelScreenSize.height / (i - 1)) * (i - 1),
}
}
}
public get zoomModifier(): number {
return this._zoomModifier;
}
public set zoomModifier(zoomModifier: number) {
this._zoomModifier = zoomModifier;
}
}

View File

@ -0,0 +1,47 @@
import {HdpiManager} from "./HdpiManager";
import ScaleManager = Phaser.Scale.ScaleManager;
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
class WaScaleManager {
private hdpiManager: HdpiManager;
private scaleManager!: ScaleManager;
public constructor(private minGamePixelsNumber: number, private absoluteMinPixelNumber: number) {
this.hdpiManager = new HdpiManager(minGamePixelsNumber, absoluteMinPixelNumber);
}
public setScaleManager(scaleManager: ScaleManager) {
this.scaleManager = scaleManager;
}
public applyNewSize() {
const {width, height} = coWebsiteManager.getGameSize();
let devicePixelRatio = 1;
if (window.devicePixelRatio) {
devicePixelRatio = window.devicePixelRatio;
}
const { game: gameSize, real: realSize } = this.hdpiManager.getOptimalGameSize({width: width * devicePixelRatio, height: height * devicePixelRatio});
this.scaleManager.setZoom(realSize.width / gameSize.width / devicePixelRatio);
this.scaleManager.resize(gameSize.width, gameSize.height);
// Override bug in canvas resizing in Phaser. Let's resize the canvas ourselves
const style = this.scaleManager.canvas.style;
style.width = Math.ceil(realSize.width / devicePixelRatio) + 'px';
style.height = Math.ceil(realSize.height / devicePixelRatio) + 'px';
}
public get zoomModifier(): number {
return this.hdpiManager.zoomModifier;
}
public set zoomModifier(zoomModifier: number) {
this.hdpiManager.zoomModifier = zoomModifier;
this.applyNewSize();
}
}
export const waScaleManager = new WaScaleManager(640*480, 196*196);

View File

@ -1,4 +1,4 @@
export class OutlinePipeline extends Phaser.Renderer.WebGL.Pipelines.TextureTintPipeline {
export class OutlinePipeline extends Phaser.Renderer.WebGL.Pipelines.MultiPipeline {
// the unique id of this pipeline
public static readonly KEY = 'Outline';

View File

@ -1,22 +1,41 @@
import {Pinch} from "phaser3-rex-plugins/plugins/gestures.js";
import {waScaleManager} from "../Services/WaScaleManager";
import {GameScene} from "../Game/GameScene";
export class PinchManager {
private scene: Phaser.Scene;
private pinch!: any; // eslint-disable-line
constructor(scene: Phaser.Scene) {
this.scene = scene;
this.pinch = new Pinch(scene);
this.pinch.setDragThreshold(10);
// The "pinch.scaleFactor" value is very sensitive and causes the screen to flicker.
// We are smoothing its value with previous values to prevent the flicking.
let smoothPinch = 1;
this.pinch.on('pinchstart', () => {
smoothPinch = 1;
});
this.pinch.on('pinch', (pinch:any) => { // eslint-disable-line
let newZoom = this.scene.cameras.main.zoom * pinch.scaleFactor;
if (newZoom < 0.25) {
newZoom = 0.25;
} else if(newZoom > 2) {
newZoom = 2;
if (pinch.scaleFactor > 1.2 || pinch.scaleFactor < 0.8) {
// Pinch too fast! Probably a bad measure.
return;
}
smoothPinch = 3/5*smoothPinch + 2/5*pinch.scaleFactor;
if (this.scene instanceof GameScene) {
this.scene.zoomByFactor(smoothPinch);
} else {
waScaleManager.zoomModifier *= smoothPinch;
}
this.scene.cameras.main.setZoom(newZoom);
});
}
}
destroy() {
this.pinch.removeAllListeners();
}
}

View File

@ -54,11 +54,12 @@ export class UserInputManager {
this.Scene = Scene;
this.isInputDisabled = false;
this.initKeyBoardEvent();
this.initMouseWheel();
if (touchScreenManager.supportTouchScreen) {
this.initVirtualJoystick();
}
}
initVirtualJoystick() {
this.joystick = new MobileJoystick(this.Scene);
this.joystick.on("update", () => {
@ -170,4 +171,14 @@ export class UserInputManager {
removeSpaceEventListner(callback : Function){
this.Scene.input.keyboard.removeListener('keyup-SPACE', callback);
}
destroy(): void {
this.joystick.destroy();
}
private initMouseWheel() {
this.Scene.input.on('wheel', (pointer: unknown, gameObjects: unknown, deltaX: number, deltaY: number, deltaZ: number) => {
this.Scene.zoomByFactor(1 - deltaY / 53 * 0.1);
});
}
}

View File

@ -346,7 +346,7 @@ class LayoutManager {
userInputManager.addSpaceEventListner(callBack);
}
public removeActionButton(id: string, userInputManager: UserInputManager){
public removeActionButton(id: string, userInputManager?: UserInputManager){
//delete previous element
const previousDiv = this.actionButtonInformation.get(id);
if(previousDiv){
@ -354,10 +354,45 @@ class LayoutManager {
this.actionButtonInformation.delete(id);
}
const previousEventCallback = this.actionButtonTrigger.get(id);
if(previousEventCallback){
if(previousEventCallback && userInputManager){
userInputManager.removeSpaceEventListner(previousEventCallback);
}
}
public addInformation(id: string, text: string, callBack?: Function, userInputManager?: UserInputManager){
//delete previous element
for ( const [key, value] of this.actionButtonInformation ) {
this.removeActionButton(key, userInputManager);
}
//create div and text html component
const p = document.createElement('p');
p.classList.add('action-body');
p.innerText = text;
const div = document.createElement('div');
div.classList.add('action');
div.classList.add(id);
div.id = id;
div.appendChild(p);
this.actionButtonInformation.set(id, div);
const mainContainer = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('main-container');
mainContainer.appendChild(div);
//add trigger action
if(callBack){
div.onpointerdown = () => {
callBack();
this.removeActionButton(id, userInputManager);
};
}
//remove it after 10 sec
setTimeout(() => {
this.removeActionButton(id, userInputManager);
}, 10000)
}
}
const layoutManager = new LayoutManager();

View File

@ -4,6 +4,8 @@ import {discussionManager, SendMessageCallback} from "./DiscussionManager";
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
import {localUserStore} from "../Connexion/LocalUserStore";
import {UserSimplePeerInterface} from "./SimplePeer";
import {SoundMeter} from "../Phaser/Components/SoundMeter";
declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any
let videoConstraint: boolean|MediaTrackConstraints = {
@ -26,6 +28,7 @@ export type StartScreenSharingCallback = (media: MediaStream) => void;
export type StopScreenSharingCallback = (media: MediaStream) => void;
export type ReportCallback = (message: string) => void;
export type ShowReportCallBack = (userId: string, userName: string|undefined) => void;
export type HelpCameraSettingsCallBack = () => void;
// TODO: Split MediaManager in 2 classes: MediaManagerUI (in charge of HTML) and MediaManager (singleton in charge of the camera only)
export class MediaManager {
@ -40,6 +43,7 @@ export class MediaManager {
microphoneClose: HTMLImageElement;
microphone: HTMLImageElement;
webrtcInAudio: HTMLAudioElement;
mySoundMeterElement: HTMLDivElement;
private webrtcOutAudio: HTMLAudioElement;
constraintsMedia : MediaStreamConstraints = {
audio: audioConstraint,
@ -49,6 +53,8 @@ export class MediaManager {
startScreenSharingCallBacks : Set<StartScreenSharingCallback> = new Set<StartScreenSharingCallback>();
stopScreenSharingCallBacks : Set<StopScreenSharingCallback> = new Set<StopScreenSharingCallback>();
showReportModalCallBacks : Set<ShowReportCallBack> = new Set<ShowReportCallBack>();
helpCameraSettingsCallBacks : Set<HelpCameraSettingsCallBack> = new Set<HelpCameraSettingsCallBack>();
private microphoneBtn: HTMLDivElement;
private cinemaBtn: HTMLDivElement;
private monitorBtn: HTMLDivElement;
@ -63,6 +69,12 @@ export class MediaManager {
private triggerCloseJistiFrame : Map<String, Function> = new Map<String, Function>();
private userInputManager?: UserInputManager;
private mySoundMeter?: SoundMeter|null;
private soundMeters: Map<string, SoundMeter> = new Map<string, SoundMeter>();
private soundMeterElements: Map<string, HTMLDivElement> = new Map<string, HTMLDivElement>();
constructor() {
this.myCamVideo = HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('myCamVideo');
@ -121,10 +133,16 @@ export class MediaManager {
this.pingCameraStatus();
this.checkActiveUser(); //todo: desactivated in case of bug
this.mySoundMeterElement = (HtmlUtils.getElementByIdOrFail('mySoundMeter'));
this.mySoundMeterElement.childNodes.forEach((value: ChildNode, index) => {
this.mySoundMeterElement.children.item(index)?.classList.remove('active');
});
}
public setLastUpdateScene(){
public updateScene(){
this.lastUpdateScene = new Date();
this.updateSoudMeter();
}
public blurCamera() {
@ -225,6 +243,10 @@ export class MediaManager {
}).catch((err) => {
console.error(err);
this.disableCameraStyle();
layoutManager.addInformation('warning', 'Camera access denied. Click here and check navigators permissions.', () => {
this.showHelpCameraSettingsCallBack();
}, this.userInputManager);
});
}
@ -253,6 +275,10 @@ export class MediaManager {
}).catch((err) => {
console.error(err);
this.disableMicrophoneStyle();
layoutManager.addInformation('warning', 'Microphone access denied. Click here and check navigators permissions.', () => {
this.showHelpCameraSettingsCallBack();
}, this.userInputManager);
});
}
@ -324,6 +350,10 @@ export class MediaManager {
this.monitorClose.style.display = "block";
this.monitor.style.display = "none";
this.monitorBtn.classList.remove("enabled");
layoutManager.addInformation('warning', 'Screen sharing access denied. Click here and check navigators permissions.', () => {
this.showHelpCameraSettingsCallBack();
}, this.userInputManager);
});
}
@ -402,13 +432,14 @@ export class MediaManager {
}
}
return this.getLocalStream().catch(() => {
console.info('Error get camera, trying with video option at null');
return this.getLocalStream().catch((err) => {
console.info('Error get camera, trying with video option at null =>', err);
this.disableCameraStyle();
return this.getLocalStream().then((stream : MediaStream) => {
this.hasCamera = false;
return stream;
}).catch((err) => {
this.disableMicrophoneStyle();
console.info("error get media ", this.constraintsMedia.video, this.constraintsMedia.audio, err);
throw err;
});
@ -425,6 +456,13 @@ export class MediaManager {
return navigator.mediaDevices.getUserMedia(this.constraintsMedia).then((stream : MediaStream) => {
this.localStream = stream;
this.myCamVideo.srcObject = this.localStream;
//init sound meter
this.mySoundMeter = null;
if(this.constraintsMedia.audio){
this.mySoundMeter = new SoundMeter();
this.mySoundMeter.connectToSource(stream, new AudioContext());
}
return stream;
}).catch((err: Error) => {
throw err;
@ -451,6 +489,7 @@ export class MediaManager {
track.stop();
}
}
this.mySoundMeter?.stop();
}
setCamera(id: string): Promise<MediaStream> {
@ -496,6 +535,13 @@ export class MediaManager {
</button>
<video id="${userId}" autoplay></video>
<img src="resources/logos/blockSign.svg" id="blocking-${userId}" class="block-logo">
<div id="soundMeter-${userId}" class="sound-progress">
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
</div>
</div>
`;
@ -585,6 +631,12 @@ export class MediaManager {
throw `Unable to find video for ${userId}`;
}
remoteVideo.srcObject = stream;
//sound metter
const soundMeter = new SoundMeter();
soundMeter.connectToSource(stream, new AudioContext());
this.soundMeters.set(userId, soundMeter);
this.soundMeterElements.set(userId, HtmlUtils.getElementByIdOrFail<HTMLImageElement>('soundMeter-'+userId));
}
addStreamRemoteScreenSharing(userId: string, stream : MediaStream){
// In the case of screen sharing (going both ways), we may need to create the HTML element if it does not exist yet
@ -600,6 +652,10 @@ export class MediaManager {
layoutManager.remove(userId);
this.remoteVideo.delete(userId);
this.soundMeters.get(userId)?.stop();
this.soundMeters.delete(userId);
this.soundMeterElements.delete(userId);
//permit to remove user in discussion part
this.removeParticipant(userId);
}
@ -717,6 +773,7 @@ export class MediaManager {
}
public setUserInputManager(userInputManager : UserInputManager){
this.userInputManager = userInputManager;
discussionManager.setUserInputManager(userInputManager);
}
//check if user is active
@ -739,6 +796,57 @@ export class MediaManager {
public setShowReportModalCallBacks(callback: ShowReportCallBack){
this.showReportModalCallBacks.add(callback);
}
public setHelpCameraSettingsCallBack(callback: HelpCameraSettingsCallBack){
this.helpCameraSettingsCallBacks.add(callback);
}
private showHelpCameraSettingsCallBack(){
for(const callBack of this.helpCameraSettingsCallBacks){
callBack();
}
}
updateSoudMeter(){
try{
const volume = parseInt(((this.mySoundMeter ? this.mySoundMeter.getVolume() : 0) / 10).toFixed(0));
this.setVolumeSoundMeter(volume, this.mySoundMeterElement);
for(const indexUserId of this.soundMeters.keys()){
const soundMeter = this.soundMeters.get(indexUserId);
const soundMeterElement = this.soundMeterElements.get(indexUserId);
if(!soundMeter || !soundMeterElement){
return;
}
const volumeByUser = parseInt((soundMeter.getVolume() / 10).toFixed(0));
this.setVolumeSoundMeter(volumeByUser, soundMeterElement);
}
}catch(err){
//console.error(err);
}
}
private setVolumeSoundMeter(volume: number, element: HTMLDivElement){
if(volume <= 0 && !element.classList.contains('active')){
return;
}
element.classList.remove('active');
if(volume <= 0){
return;
}
element.classList.add('active');
element.childNodes.forEach((value: ChildNode, index) => {
const elementChildre = element.children.item(index);
if(!elementChildre){
return;
}
elementChildre.classList.remove('active');
if((index +1) > volume){
return;
}
elementChildre.classList.add('active');
});
}
}
export const mediaManager = new MediaManager();

View File

@ -22,7 +22,7 @@ export class ScreenSharingPeer extends Peer {
constructor(user: UserSimplePeerInterface, initiator: boolean, private connection: RoomConnection) {
super({
initiator: initiator ? initiator : false,
reconnectTimer: 10000,
//reconnectTimer: 10000,
config: {
iceServers: [
{

View File

@ -82,15 +82,11 @@ export class SimplePeer {
});
mediaManager.showGameOverlay();
mediaManager.getCamera().then(() => {
mediaManager.getCamera().finally(() => {
//receive message start
this.Connection.receiveWebrtcStart((message: UserSimplePeerInterface) => {
this.receiveWebrtcStart(message);
});
}).catch((err) => {
console.error("err", err);
});
this.Connection.disconnectMessage((data: WebRtcDisconnectMessageInterface): void => {

View File

@ -28,7 +28,7 @@ export class VideoPeer extends Peer {
constructor(public user: UserSimplePeerInterface, initiator: boolean, private connection: RoomConnection) {
super({
initiator: initiator ? initiator : false,
reconnectTimer: 10000,
//reconnectTimer: 10000,
config: {
iceServers: [
{

View File

@ -1,14 +1,14 @@
import {ChatEvent, isChatEvent} from "./Api/Events/ChatEvent";
import {isIframeEventWrapper} from "./Api/Events/IframeEvent";
import {isUserInputChatEvent, UserInputChatEvent} from "./Api/Events/UserInputChatEvent";
import {Subject} from "rxjs";
import {EnterLeaveEvent, isEnterLeaveEvent} from "./Api/Events/EnterLeaveEvent";
import {OpenPopupEvent} from "./Api/Events/OpenPopupEvent";
import {isButtonClickedEvent} from "./Api/Events/ButtonClickedEvent";
import {ClosePopupEvent} from "./Api/Events/ClosePopupEvent";
import {OpenTabEvent} from "./Api/Events/OpenTabEvent";
import {GoToPageEvent} from "./Api/Events/GoToPageEvent";
import {OpenCoWebSiteEvent} from "./Api/Events/OpenCoWebSiteEvent";
import { ChatEvent } from "./Api/Events/ChatEvent";
import { isIframeResponseEventWrapper } from "./Api/Events/IframeEvent";
import { isUserInputChatEvent, UserInputChatEvent } from "./Api/Events/UserInputChatEvent";
import { Subject } from "rxjs";
import { EnterLeaveEvent, isEnterLeaveEvent } from "./Api/Events/EnterLeaveEvent";
import { OpenPopupEvent } from "./Api/Events/OpenPopupEvent";
import { isButtonClickedEvent } from "./Api/Events/ButtonClickedEvent";
import { ClosePopupEvent } from "./Api/Events/ClosePopupEvent";
import { OpenTabEvent } from "./Api/Events/OpenTabEvent";
import { GoToPageEvent } from "./Api/Events/GoToPageEvent";
import { OpenCoWebSiteEvent } from "./Api/Events/OpenCoWebSiteEvent";
import { GameStateEvent, isGameStateEvent } from './Api/Events/ApiGameStateEvent';
interface WorkAdventureApi {
@ -21,10 +21,10 @@ interface WorkAdventureApi {
goToPage(url : string): void;
openCoWebSite(url : string): void;
closeCoWebSite(): void;
disablePlayerControl() : void;
restorePlayerControl() : void;
displayBubble() : void;
removeBubble() : void;
disablePlayerControl(): void;
restorePlayerControl(): void;
displayBubble(): void;
removeBubble(): void;
getGameState():Promise<unknown>
}
@ -52,7 +52,7 @@ interface ButtonDescriptor {
/**
* The type of the button. Can be one of "normal", "primary", "success", "warning", "error", "disabled"
*/
className?: "normal"|"primary"|"success"|"warning"|"error"|"disabled",
className?: "normal" | "primary" | "success" | "warning" | "error" | "disabled",
/**
* Callback called if the button is pressed
*/
@ -106,38 +106,38 @@ window.WA = {
} as ChatEvent
}, '*');
},
disablePlayerControl() : void {
window.parent.postMessage({'type' : 'disablePlayerControl'},'*');
disablePlayerControl(): void {
window.parent.postMessage({ 'type': 'disablePlayerControl' }, '*');
},
restorePlayerControl() : void {
window.parent.postMessage({'type' : 'restorePlayerControl'},'*');
restorePlayerControl(): void {
window.parent.postMessage({ 'type': 'restorePlayerControl' }, '*');
},
displayBubble() : void {
window.parent.postMessage({'type' : 'displayBubble'},'*');
displayBubble(): void {
window.parent.postMessage({ 'type': 'displayBubble' }, '*');
},
removeBubble() : void {
window.parent.postMessage({'type' : 'removeBubble'},'*');
removeBubble(): void {
window.parent.postMessage({ 'type': 'removeBubble' }, '*');
},
openTab(url : string) : void{
openTab(url: string): void {
window.parent.postMessage({
"type" : 'openTab',
"data" : {
"type": 'openTab',
"data": {
url
} as OpenTabEvent
},'*');
}, '*');
},
goToPage(url : string) : void{
goToPage(url: string): void {
window.parent.postMessage({
"type" : 'goToPage',
"data" : {
"type": 'goToPage',
"data": {
url
} as GoToPageEvent
},'*');
}, '*');
},
openCoWebSite(url : string) : void{
@ -146,13 +146,13 @@ window.WA = {
"data" : {
url
} as OpenCoWebSiteEvent
},'*');
}, '*');
},
closeCoWebSite() : void{
closeCoWebSite(): void {
window.parent.postMessage({
"type" : 'closeCoWebSite'
},'*');
"type": 'closeCoWebSite'
}, '*');
},
openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup {
@ -223,9 +223,9 @@ window.addEventListener('message', message => {
const payload = message.data;
console.log(payload);
console.debug(payload);
if (isIframeEventWrapper(payload)) {
if (isIframeResponseEventWrapper(payload)) {
const payloadData = payload.data;
if (payload.type === 'userInputChat' && isUserInputChatEvent(payloadData)) {
userInputChatStream.next(payloadData);
@ -237,7 +237,7 @@ window.addEventListener('message', message => {
const callback = popupCallbacks.get(payloadData.popupId)?.get(payloadData.buttonId);
const popup = popups.get(payloadData.popupId);
if (popup === undefined) {
throw new Error('Could not find popup with ID "'+payloadData.popupId+'"');
throw new Error('Could not find popup with ID "' + payloadData.popupId + '"');
}
if (callback) {
callback(popup);

View File

@ -2,7 +2,7 @@ import 'phaser';
import GameConfig = Phaser.Types.Core.GameConfig;
import "../dist/resources/style/index.scss";
import {DEBUG_MODE, RESOLUTION} from "./Enum/EnvironmentVariable";
import {DEBUG_MODE, isMobile} from "./Enum/EnvironmentVariable";
import {LoginScene} from "./Phaser/Login/LoginScene";
import {ReconnectingScene} from "./Phaser/Reconnecting/ReconnectingScene";
import {SelectCharacterScene} from "./Phaser/Login/SelectCharacterScene";
@ -17,6 +17,10 @@ import {HelpCameraSettingsScene} from "./Phaser/Menu/HelpCameraSettingsScene";
import {localUserStore} from "./Connexion/LocalUserStore";
import {ErrorScene} from "./Phaser/Reconnecting/ErrorScene";
import {iframeListener} from "./Api/IframeListener";
import { SelectCharacterMobileScene } from './Phaser/Login/SelectCharacterMobileScene';
import {HdpiManager} from "./Phaser/Services/HdpiManager";
import {waScaleManager} from "./Phaser/Services/WaScaleManager";
import {Game} from "./Phaser/Game/Game";
const {width, height} = coWebsiteManager.getGameSize();
@ -67,15 +71,31 @@ switch (phaserMode) {
throw new Error('phaserMode parameter must be one of "auto", "canvas" or "webgl"');
}
const hdpiManager = new HdpiManager(640*480, 196*196);
const { game: gameSize, real: realSize } = hdpiManager.getOptimalGameSize({width, height});
const config: GameConfig = {
type: mode,
title: "WorkAdventure",
width: width / RESOLUTION,
height: height / RESOLUTION,
parent: "game",
scene: [EntryScene, LoginScene, SelectCharacterScene, SelectCompanionScene, EnableCameraScene, ReconnectingScene, ErrorScene, CustomizeScene, MenuScene, HelpCameraSettingsScene],
zoom: RESOLUTION,
scale: {
parent: "game",
width: gameSize.width,
height: gameSize.height,
zoom: realSize.width / gameSize.width,
autoRound: true,
resizeInterval: 999999999999
},
scene: [EntryScene,
LoginScene,
isMobile() ? SelectCharacterMobileScene : SelectCharacterScene,
SelectCompanionScene,
EnableCameraScene,
ReconnectingScene,
ErrorScene,
CustomizeScene,
MenuScene,
HelpCameraSettingsScene],
//resolution: window.devicePixelRatio / 2,
fps: fps,
dom: {
createContainer: true
@ -91,6 +111,8 @@ const config: GameConfig = {
debug: DEBUG_MODE,
}
},
// Instruct systems with 2 GPU to choose the low power one. We don't need that extra power and we want to save battery
powerPreference: "low-power",
callbacks: {
postBoot: game => {
// Commented out to try to fix MacOS bug
@ -102,12 +124,15 @@ const config: GameConfig = {
}
};
const game = new Phaser.Game(config);
//const game = new Phaser.Game(config);
const game = new Game(config);
waScaleManager.setScaleManager(game.scale);
window.addEventListener('resize', function (event) {
coWebsiteManager.resetStyle();
const {width, height} = coWebsiteManager.getGameSize();
game.scale.resize(width / RESOLUTION, height / RESOLUTION);
waScaleManager.applyNewSize();
// Let's trigger the onResize method of any active scene that is a ResizableScene
for (const scene of game.scene.getScenes(true)) {
@ -118,8 +143,7 @@ window.addEventListener('resize', function (event) {
});
coWebsiteManager.onResize.subscribe(() => {
const {width, height} = coWebsiteManager.getGameSize();
game.scale.resize(width / RESOLUTION, height / RESOLUTION);
waScaleManager.applyNewSize();
});
iframeListener.init();

View File

@ -4,9 +4,9 @@ declare module 'phaser3-rex-plugins/plugins/virtualjoystick.js' {
export default content;
}
declare module 'phaser3-rex-plugins/plugins/gestures-plugin.js' {
const content: any; // eslint-disable-line
const content: any; // eslint-disable-line
export default content;
}
declare module 'phaser3-rex-plugins/plugins/gestures.js' {
export const Pinch: any; // eslint-disable-line
}
}

View File

@ -19,9 +19,6 @@ describe("isUserNameValid()", () => {
it("should not validate spaces", () => {
expect(isUserNameValid(' ')).toBe(false);
});
it("should not validate numbers", () => {
expect(isUserNameValid('a12')).toBe(false);
});
it("should not validate special characters", () => {
expect(isUserNameValid('a&-')).toBe(false);
});
@ -42,4 +39,4 @@ describe("areCharacterLayersValid()", () => {
it("should not validate empty strings", () => {
expect(areCharacterLayersValid(['', 'male1'])).toBe(false);
});
});
});

View File

@ -0,0 +1,147 @@
import "jasmine";
import {Room} from "../../../src/Connexion/Room";
import {LayersIterator} from "../../../src/Phaser/Map/LayersIterator";
describe("Layers iterator", () => {
it("should iterate maps with no group", () => {
const layersIterator = new LayersIterator({
"compressionlevel":-1,
"height":2,
"infinite":false,
"layers":[
{
"data":[0, 0, 0, 0],
"height":2,
"id":1,
"name":"Tile Layer 1",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":2,
"x":0,
"y":0
},
{
"data":[0, 0, 0, 0],
"height":2,
"id":1,
"name":"Tile Layer 2",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":2,
"x":0,
"y":0
}],
"nextlayerid":2,
"nextobjectid":1,
"orientation":"orthogonal",
"renderorder":"right-down",
"tiledversion":"2021.03.23",
"tileheight":32,
"tilesets":[],
"tilewidth":32,
"type":"map",
"version":1.5,
"width":2
})
const layers = [];
for (const layer of layersIterator) {
layers.push(layer.name);
}
expect(layers).toEqual(['Tile Layer 1', 'Tile Layer 2']);
});
it("should iterate maps with recursive groups", () => {
const layersIterator = new LayersIterator({
"compressionlevel":-1,
"height":2,
"infinite":false,
"layers":[
{
"id":6,
"layers":[
{
"id":5,
"layers":[
{
"data":[0, 0, 0, 0],
"height":2,
"id":10,
"name":"Tile3",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":2,
"x":0,
"y":0
},
{
"data":[0, 0, 0, 0],
"height":2,
"id":9,
"name":"Tile2",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":2,
"x":0,
"y":0
}],
"name":"Group 3",
"opacity":1,
"type":"group",
"visible":true,
"x":0,
"y":0
},
{
"id":7,
"layers":[
{
"data":[0, 0, 0, 0],
"height":2,
"id":8,
"name":"Tile1",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":2,
"x":0,
"y":0
}],
"name":"Group 2",
"opacity":1,
"type":"group",
"visible":true,
"x":0,
"y":0
}],
"name":"Group 1",
"opacity":1,
"type":"group",
"visible":true,
"x":0,
"y":0
}],
"nextlayerid":11,
"nextobjectid":1,
"orientation":"orthogonal",
"renderorder":"right-down",
"tiledversion":"2021.03.23",
"tileheight":32,
"tilesets":[],
"tilewidth":32,
"type":"map",
"version":1.5,
"width":2
})
const layers = [];
for (const layer of layersIterator) {
layers.push(layer.name);
}
expect(layers).toEqual(['Group 1/Group 3/Tile3', 'Group 1/Group 3/Tile2', 'Group 1/Group 2/Tile1']);
});
});

View File

@ -0,0 +1,55 @@
import "jasmine";
import {HdpiManager} from "../../../src/Phaser/Services/HdpiManager";
describe("Test HdpiManager", () => {
it("should match screen size if size is too small.", () => {
const hdpiManager = new HdpiManager(640*480, 64*64);
const result = hdpiManager.getOptimalGameSize({ width: 320, height: 200 });
expect(result.game.width).toEqual(320);
expect(result.game.height).toEqual(200);
expect(result.real.width).toEqual(320);
expect(result.real.height).toEqual(200);
});
it("should match multiple just above.", () => {
const hdpiManager = new HdpiManager(640*480, 64*64);
let result = hdpiManager.getOptimalGameSize({ width: 960, height: 600 });
expect(result.game.width).toEqual(960);
expect(result.game.height).toEqual(600);
result = hdpiManager.getOptimalGameSize({ width: 640 * 2 + 50, height: 480 * 2 + 50 });
expect(result.game.width).toEqual(Math.ceil((640 * 2 + 50) / 2));
expect(result.game.height).toEqual((480 * 2 + 50) / 2);
result = hdpiManager.getOptimalGameSize({ width: 640 * 3 + 50, height: 480 * 3 + 50 });
expect(result.game.width).toEqual(Math.ceil((640 * 3 + 50) / 3));
expect(result.game.height).toEqual(Math.ceil((480 * 3 + 50) / 3));
expect(result.real.width).toEqual(result.game.width * 3);
expect(result.real.height).toEqual(result.game.height * 3);
});
it("should not zoom in too much.", () => {
const hdpiManager = new HdpiManager(640*480, 64*64);
hdpiManager.zoomModifier = 11;
const result = hdpiManager.getOptimalGameSize({ width: 640, height: 640 });
expect(result.game.width).toEqual(64);
expect(result.game.height).toEqual(64);
expect(hdpiManager.zoomModifier).toEqual(10);
});
it("should not zoom out too much.", () => {
const hdpiManager = new HdpiManager(640*480, 64*64);
hdpiManager.zoomModifier = 1/10;
const result = hdpiManager.getOptimalGameSize({ width: 1280, height: 768 });
expect(result.game.width).toEqual(1280);
expect(result.game.height).toEqual(768);
expect(hdpiManager.zoomModifier).toEqual(1);
});
});

File diff suppressed because it is too large Load Diff

View File

@ -24,7 +24,7 @@
{
"name":"exitUrl",
"type":"string",
"value":"\/@\/tcm\/workadventure\/floor0"
"value":"\/@\/tcm\/workadventure\/wa-village"
}],
"type":"tilelayer",
"visible":true,
@ -260,15 +260,15 @@
"name":"floorLayer",
"objects":[
{
"height":199.667,
"height":140,
"id":1,
"name":"Tutobubble",
"rotation":0,
"type":"",
"visible":true,
"width":245.667,
"x":1170,
"y":350
"width":185,
"x":1022,
"y":308
},
{
"height":86.9996666666667,
@ -277,9 +277,9 @@
"rotation":0,
"type":"",
"visible":true,
"width":245.667,
"x":1171,
"y":412
"width":187,
"x":1019,
"y":361
},
{
"height":84.6667,
@ -288,9 +288,9 @@
"rotation":0,
"type":"",
"visible":true,
"width":210.667,
"x":1030.5,
"y":318.66665
"width":184.5,
"x":1023,
"y":319
},
{
"height":99.6667,
@ -299,9 +299,9 @@
"rotation":0,
"type":"",
"visible":true,
"width":210.667,
"x":1174,
"y":400
"width":185,
"x":1021,
"y":348
}],
"opacity":1,
"type":"objectgroup",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

File diff suppressed because one or more lines are too long

BIN
maps/Village/logo-WA.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Some files were not shown because too many files have changed in this diff Show More