Merge branch 'develop' of github.com:thecodingmachine/workadventure into fix/deploy-cleanup

# Conflicts:
#	front/Dockerfile
This commit is contained in:
David Négrier 2021-02-01 14:00:07 +01:00
commit 686427f6fe
222 changed files with 1355 additions and 12017 deletions

View File

@ -5,3 +5,4 @@ JITSI_PRIVATE_MODE=false
JITSI_ISS=
SECRET_JITSI_KEY=
ADMIN_API_TOKEN=123
START_ROOM_URL=/_/global/maps.workadventure.localhost/Floor0/floor0.json

View File

@ -102,29 +102,6 @@ jobs:
tags: ${{ env.GITHUB_REF_SLUG }}
add_git_labels: true
build-website:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
# Create a slugified value of the branch
- uses: rlespinasse/github-slug-action@3.1.0
- name: "Build and push back image"
uses: docker/build-push-action@v1
with:
dockerfile: website/Dockerfile
path: website/
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
repository: thecodingmachine/workadventure-website
tags: ${{ env.GITHUB_REF_SLUG }}
add_git_labels: true
build-maps:
runs-on: ubuntu-latest
@ -156,7 +133,6 @@ jobs:
- build-pusher
- build-maps
- build-uploader
- build-website
runs-on: ubuntu-latest
steps:
@ -185,32 +161,3 @@ jobs:
with:
msg: Environment deployed at https://${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com
check_for_duplicate_msg: true
- name: Run Cypress tests
uses: cypress-io/github-action@v2
if: ${{ env.GITHUB_REF_SLUG != 'master' }}
env:
CYPRESS_BASE_URL: https://play.${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com
with:
env: host=play.${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com,port=80
spec: cypress/integration/spec.js
wait-on: https://play.${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com
working-directory: e2e
- name: Run Cypress tests in prod
uses: cypress-io/github-action@v2
if: ${{ env.GITHUB_REF_SLUG == 'master' }}
env:
CYPRESS_BASE_URL: https://play.workadventu.re
with:
env: host=play.workadventu.re
spec: cypress/integration/spec.js
wait-on: https://workadventu.re
working-directory: e2e
- name: "Upload the screenshot on test failure"
uses: actions/upload-artifact@v1
if: failure()
with:
name: "screenshot"
path: "./e2e/cypress/screenshots/spec.js/WorkAdventureGame -- loads (failed).png"

View File

@ -39,6 +39,10 @@ jobs:
run: yarn run proto && yarn run copy-to-front
working-directory: "messages"
- name: "Create index.html"
run: ./templater.sh
working-directory: "front"
- name: "Build"
run: yarn run build
env:
@ -53,6 +57,49 @@ jobs:
run: yarn test
working-directory: "front"
continuous-integration-pusher:
name: "Continuous Integration Pusher"
runs-on: "ubuntu-latest"
steps:
- name: "Checkout"
uses: "actions/checkout@v2.0.0"
- name: "Setup NodeJS"
uses: actions/setup-node@v1
with:
node-version: '12.x'
- name: Install Protoc
uses: arduino/setup-protoc@v1
with:
version: '3.x'
- name: "Install dependencies"
run: yarn install
working-directory: "pusher"
- name: "Install messages dependencies"
run: yarn install
working-directory: "messages"
- name: "Build proto messages"
run: yarn run proto && yarn run copy-to-pusher
working-directory: "messages"
- name: "Build"
run: yarn run tsc
working-directory: "pusher"
- name: "Lint"
run: yarn run lint
working-directory: "pusher"
- name: "Jasmine"
run: yarn test
working-directory: "pusher"
continuous-integration-back:
name: "Continuous Integration Back"

View File

@ -1,5 +1,4 @@
const SECRET_KEY = process.env.SECRET_KEY || "THECODINGMACHINE_SECRET_KEY";
const URL_ROOM_STARTED = "/Floor0/floor0.json";
const MINIMUM_DISTANCE = process.env.MINIMUM_DISTANCE ? Number(process.env.MINIMUM_DISTANCE) : 64;
const GROUP_RADIUS = process.env.GROUP_RADIUS ? Number(process.env.GROUP_RADIUS) : 48;
const ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLERY == 'true' : false;
@ -16,7 +15,6 @@ export const SOCKET_IDLE_TIMER = parseInt(process.env.SOCKET_IDLE_TIMER as strin
export {
SECRET_KEY,
URL_ROOM_STARTED,
MINIMUM_DISTANCE,
ADMIN_API_URL,
ADMIN_API_TOKEN,

View File

@ -9,7 +9,7 @@ import {
PusherToBackMessage,
ServerToAdminClientMessage,
ServerToClientMessage,
SubMessage
SubMessage, UserJoinedRoomMessage, UserLeftRoomMessage
} from "../Messages/generated/messages_pb";
import {CharacterLayer} from "_Model/Websocket/CharacterLayer";
import {AdminSocket} from "../RoomManager";
@ -21,16 +21,26 @@ export class Admin {
) {
}
public sendUserJoin(uuid: string): void {
public sendUserJoin(uuid: string, name: string, ip: string): void {
const serverToAdminClientMessage = new ServerToAdminClientMessage();
serverToAdminClientMessage.setUseruuidjoinedroom(uuid);
const userJoinedRoomMessage = new UserJoinedRoomMessage();
userJoinedRoomMessage.setUuid(uuid);
userJoinedRoomMessage.setName(name);
userJoinedRoomMessage.setIpaddress(ip);
serverToAdminClientMessage.setUserjoinedroom(userJoinedRoomMessage);
this.socket.write(serverToAdminClientMessage);
}
public sendUserLeft(uuid: string): void {
public sendUserLeft(uuid: string/*, name: string, ip: string*/): void {
const serverToAdminClientMessage = new ServerToAdminClientMessage();
serverToAdminClientMessage.setUseruuidleftroom(uuid);
const userLeftRoomMessage = new UserLeftRoomMessage();
userLeftRoomMessage.setUuid(uuid);
serverToAdminClientMessage.setUserleftroom(userLeftRoomMessage);
this.socket.write(serverToAdminClientMessage);
}

View File

@ -102,7 +102,17 @@ export class GameRoom {
}
const position = ProtobufUtils.toPointInterface(positionMessage);
const user = new User(this.nextUserId, joinRoomMessage.getUseruuid(), position, false, this.positionNotifier, socket, joinRoomMessage.getTagList(), joinRoomMessage.getName(), ProtobufUtils.toCharacterLayerObjects(joinRoomMessage.getCharacterlayerList()));
const user = new User(this.nextUserId,
joinRoomMessage.getUseruuid(),
joinRoomMessage.getIpaddress(),
position,
false,
this.positionNotifier,
socket,
joinRoomMessage.getTagList(),
joinRoomMessage.getName(),
ProtobufUtils.toCharacterLayerObjects(joinRoomMessage.getCharacterlayerList())
);
this.nextUserId++;
this.users.set(user.id, user);
this.usersByUuid.set(user.uuid, user);
@ -112,7 +122,7 @@ export class GameRoom {
// Notify admins
for (const admin of this.admins) {
admin.sendUserJoin(user.uuid);
admin.sendUserJoin(user.uuid, user.name, user.IPAddress);
}
return user;
@ -135,7 +145,7 @@ export class GameRoom {
// Notify admins
for (const admin of this.admins) {
admin.sendUserLeft(user.uuid);
admin.sendUserLeft(user.uuid/*, user.name, user.IPAddress*/);
}
}
@ -318,7 +328,7 @@ export class GameRoom {
// Let's send all connected users
for (const user of this.users.values()) {
admin.sendUserJoin(user.uuid);
admin.sendUserJoin(user.uuid, user.name, user.IPAddress);
}
}

View File

@ -16,6 +16,7 @@ export class User implements Movable {
public constructor(
public id: number,
public readonly uuid: string,
public readonly IPAddress: string,
private position: PointInterface,
public silent: boolean,
private positionNotifier: PositionNotifier,

View File

@ -2,25 +2,22 @@ import {IRoomManagerServer} from "./Messages/generated/messages_grpc_pb";
import {
AdminGlobalMessage,
AdminMessage,
AdminPusherToBackMessage, BanMessage,
ClientToServerMessage, EmptyMessage,
AdminPusherToBackMessage,
BanMessage,
EmptyMessage,
ItemEventMessage,
JoinRoomMessage,
PlayGlobalMessage,
PusherToBackMessage,
QueryJitsiJwtMessage,
ReportPlayerMessage,
RoomJoinedMessage,
ServerToAdminClientMessage,
ServerToClientMessage,
SilentMessage,
UserMovesMessage,
ViewportMessage,
WebRtcSignalToServerMessage,
ZoneMessage
} from "./Messages/generated/messages_pb";
import grpc, {sendUnaryData, ServerDuplexStream, ServerUnaryCall, ServerWritableStream} from "grpc";
import {Empty} from "google-protobuf/google/protobuf/empty_pb";
import {sendUnaryData, ServerDuplexStream, ServerUnaryCall, ServerWritableStream} from "grpc";
import {socketManager} from "./Services/SocketManager";
import {emitError} from "./Services/MessageHelpers";
import {User, UserSocket} from "./Model/User";
@ -74,6 +71,16 @@ const roomManager: IRoomManagerServer = {
socketManager.handleReportMessage(client, message.getReportplayermessage() as ReportPlayerMessage);*/
} else if (message.hasQueryjitsijwtmessage()){
socketManager.handleQueryJitsiJwtMessage(user, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage);
}else if (message.hasSendusermessage()) {
const sendUserMessage = message.getSendusermessage();
if(sendUserMessage !== undefined) {
socketManager.handlerSendUserMessage(user, sendUserMessage);
}
}else if (message.hasBanusermessage()) {
const banUserMessage = message.getBanusermessage();
if(banUserMessage !== undefined) {
socketManager.handlerBanUserMessage(room, user, banUserMessage);
}
} else {
throw new Error('Unhandled message type');
}
@ -196,8 +203,8 @@ const roomManager: IRoomManagerServer = {
callback(null, new EmptyMessage());
},
ban(call: ServerUnaryCall<BanMessage>, callback: sendUnaryData<EmptyMessage>): void {
socketManager.banUser(call.request.getRoomid(), call.request.getRecipientuuid());
// FIXME Work in progress
socketManager.banUser(call.request.getRoomid(), call.request.getRecipientuuid(), 'foo bar TODO change this');
callback(null, new EmptyMessage());
},

View File

@ -1,24 +1,16 @@
import {GameRoom} from "../Model/GameRoom";
import {CharacterLayer} from "_Model/Websocket/CharacterLayer";
import {
GroupDeleteMessage,
GroupUpdateMessage,
ItemEventMessage,
ItemStateMessage,
PlayGlobalMessage,
PointMessage,
PositionMessage,
RoomJoinedMessage,
ServerToClientMessage,
SetPlayerDetailsMessage,
SilentMessage,
SubMessage,
ReportPlayerMessage,
UserJoinedMessage,
UserLeftMessage,
UserMovedMessage,
UserMovesMessage,
ViewportMessage,
WebRtcDisconnectMessage,
WebRtcSignalToClientMessage,
WebRtcSignalToServerMessage,
@ -28,24 +20,23 @@ import {
SendUserMessage,
JoinRoomMessage,
Zone as ProtoZone,
BatchMessage,
BatchToPusherMessage,
SubToPusherMessage,
UserJoinedZoneMessage, GroupUpdateZoneMessage, GroupLeftZoneMessage, UserLeftZoneMessage, AdminMessage, BanMessage
UserJoinedZoneMessage, GroupUpdateZoneMessage, GroupLeftZoneMessage, UserLeftZoneMessage, BanUserMessage
} from "../Messages/generated/messages_pb";
import {User, UserSocket} from "../Model/User";
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
import {Group} from "../Model/Group";
import {cpuTracker} from "./CpuTracker";
import {ADMIN_API_URL, GROUP_RADIUS, JITSI_ISS, MINIMUM_DISTANCE, SECRET_JITSI_KEY} from "../Enum/EnvironmentVariable";
import {GROUP_RADIUS, JITSI_ISS, MINIMUM_DISTANCE, SECRET_JITSI_KEY} from "../Enum/EnvironmentVariable";
import {Movable} from "../Model/Movable";
import {PositionInterface} from "../Model/PositionInterface";
import {adminApi, CharacterTexture, FetchMemberDataByUuidResponse} from "./AdminApi";
import {adminApi, CharacterTexture} from "./AdminApi";
import Jwt from "jsonwebtoken";
import {JITSI_URL} from "../Enum/EnvironmentVariable";
import {clientEventsEmitter} from "./ClientEventsEmitter";
import {gaugeManager} from "./GaugeManager";
import {AdminSocket, ZoneSocket} from "../RoomManager";
import {ZoneSocket} from "../RoomManager";
import {Zone} from "_Model/Zone";
import Debug from "debug";
import {Admin} from "_Model/Admin";
@ -119,7 +110,7 @@ export class SocketManager {
//const things = room.setViewport(client, viewport);
const roomJoinedMessage = new RoomJoinedMessage();
roomJoinedMessage.setTagList(joinRoomMessage.getTagList());
/*for (const thing of things) {
if (thing instanceof User) {
const player: ExSocketInterface|undefined = this.sockets.get(thing.id);
@ -626,6 +617,33 @@ export class SocketManager {
user.socket.write(serverToClientMessage);
}
public handlerSendUserMessage(user: User, sendUserMessageToSend: SendUserMessage){
const sendUserMessage = new SendUserMessage();
sendUserMessage.setMessage(sendUserMessageToSend.getMessage());
sendUserMessage.setType(sendUserMessageToSend.getType());
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setSendusermessage(sendUserMessage);
user.socket.write(serverToClientMessage);
}
public handlerBanUserMessage(room: GameRoom, user: User, banUserMessageToSend: BanUserMessage){
const banUserMessage = new BanUserMessage();
banUserMessage.setMessage(banUserMessageToSend.getMessage());
banUserMessage.setType(banUserMessageToSend.getType());
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setSendusermessage(banUserMessage);
user.socket.write(serverToClientMessage);
setTimeout(() => {
// Let's leave the room now.
room.leave(user);
// Let's close the connection when the user is banned.
user.socket.end();
}, 10000);
}
/**
* Merges the characterLayers received from the front (as an array of string) with the custom textures from the back.
*/
@ -748,7 +766,7 @@ export class SocketManager {
recipient.socket.write(subToPusherMessage);
}
public banUser(roomId: string, recipientUuid: string): void {
public banUser(roomId: string, recipientUuid: string, message: string): void {
const room = this.rooms.get(roomId);
if (!room) {
console.error("In banUser, could not find room with id '" + roomId + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?");
@ -765,6 +783,7 @@ export class SocketManager {
room.leave(recipient);
const sendUserMessage = new SendUserMessage();
sendUserMessage.setMessage(message);
sendUserMessage.setType('banned');
const subToPusherMessage = new SubToPusherMessage();

View File

@ -26,6 +26,7 @@ function createJoinRoomMessage(uuid: string, x: number, y: number): JoinRoomMess
positionMessage.setMoving(false);
const joinRoomMessage = new JoinRoomMessage();
joinRoomMessage.setUseruuid('1');
joinRoomMessage.setIpaddress('10.0.0.2');
joinRoomMessage.setName('foo');
joinRoomMessage.setRoomid('_/global/test.json');
joinRoomMessage.setPositionmessage(positionMessage);

View File

@ -25,14 +25,14 @@ describe("PositionNotifier", () => {
leaveTriggered = true;
});
const user1 = new User(1, 'test', {
const user1 = new User(1, 'test', '10.0.0.2', {
x: 500,
y: 500,
moving: false,
direction: 'down'
}, false, positionNotifier, {} as UserSocket, [], 'foo', []);
const user2 = new User(2, 'test', {
const user2 = new User(2, 'test', '10.0.0.2', {
x: -9999,
y: -9999,
moving: false,
@ -100,14 +100,14 @@ describe("PositionNotifier", () => {
leaveTriggered = true;
});
const user1 = new User(1, 'test', {
const user1 = new User(1, 'test', '10.0.0.2', {
x: 500,
y: 500,
moving: false,
direction: 'down'
}, false, positionNotifier, {} as UserSocket, [], 'foo', []);
const user2 = new User(2, 'test', {
const user2 = new User(2, 'test', '10.0.0.2', {
x: 0,
y: 0,
moving: false,

View File

@ -3,7 +3,8 @@
local namespace = env.GITHUB_REF_SLUG,
local tag = namespace,
local url = if namespace == "master" then "workadventu.re" else namespace+".workadventure.test.thecodingmachine.com",
local adminUrl = if namespace == "master" || namespace == "develop" || std.startsWith(namespace, "admin") then "https://admin."+url else null,
// 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",
"version": "1.0",
"containers": {
@ -72,13 +73,15 @@
"env": {
"API_URL": "pusher."+url,
"UPLOADER_URL": "uploader."+url,
"ADMIN_URL": "admin."+url,
"ADMIN_URL": url,
"JITSI_URL": env.JITSI_URL,
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
"TURN_SERVER": "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443",
"TURN_USER": "workadventure",
"TURN_PASSWORD": "WorkAdventure123",
"JITSI_PRIVATE_MODE": if env.SECRET_JITSI_KEY != '' then "true" else "false"
"JITSI_PRIVATE_MODE": if env.SECRET_JITSI_KEY != '' then "true" else "false",
"START_ROOM_URL": "/_/global/maps."+url+"/Floor0/floor0.json"
//"GA_TRACKING_ID": "UA-10196481-11"
}
},
"uploader": {
@ -100,17 +103,6 @@
},
"ports": [80]
},
"website": {
"image": "thecodingmachine/workadventure-website:"+tag,
"host": {
"url": url,
"https": "enable"
},
"ports": [80],
"env": {
"GAME_URL": "https://play."+url
}
}
},
"config": {
"https": {

View File

@ -1,20 +0,0 @@
version: '3'
services:
wait_app:
image: dadarek/wait-for-dependencies
depends_on:
- reverse-proxy
command: front:8080
cypress:
# the Docker image to use from https://github.com/cypress-io/cypress-docker-images
image: "cypress/included:3.8.3"
depends_on:
- reverse-proxy
environment:
# pass base url to test pointing at the web application
- CYPRESS_baseUrl=http://front:8080
working_dir: /e2e
volumes:
- ./e2e/:/e2e

View File

@ -28,11 +28,13 @@ services:
NODE_ENV: development
API_URL: pusher.workadventure.localhost
UPLOADER_URL: uploader.workadventure.localhost
ADMIN_URL: admin.workadventure.localhost
STARTUP_COMMAND_1: yarn install
ADMIN_URL: workadventure.localhost
STARTUP_COMMAND_1: ./templater.sh
STARTUP_COMMAND_2: yarn install
TURN_SERVER: "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443"
TURN_USER: workadventure
TURN_PASSWORD: WorkAdventure123
START_ROOM_URL: "$START_ROOM_URL"
command: yarn run start
volumes:
- ./front:/usr/src/app
@ -135,23 +137,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

3
e2e/.gitignore vendored
View File

@ -1,3 +0,0 @@
screenshots/
videos/
node_modules/

View File

@ -1,36 +0,0 @@
# Testing with cypress
This project use [cypress](https://www.cypress.io/) to do functional testing of the website.
Unfortunately we cannot integrate it with docker-compose for the moment, so you will need to install some packages locally on your pc.
## Getting Started
You will need to install theses dependancies on linux (don't know about mac):
```bash
sudo apt update
sudo apt install libgtk2.0-0 libgtk-3-0 libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb
```
Cypress can be installed locally in the e2e directory
```bash
cd e2e
npm install
```
How to use:
```bash
npm run cy:run
npm run cy:open
```
The first command will run all tests in the terminal, while the second will open the interactive task runner which allow you to easily manage the test workflow
[More details here](https://docs.cypress.io/guides/getting-started/testing-your-app.html#Step-1-Start-your-server)
## How to test a game
Cypress cannot "see" and so cannot directly manipulate the canva created by Phaser.
This means we have to do workarounds such as exposing core objects in the window so that cypress can manipulate them or doing console that cypress can catch.

View File

@ -1,7 +0,0 @@
{
"baseUrl": "http://workadventure.localhost",
"video": false,
"defaultCommandTimeout": 20000,
"pluginsFile": false,
"supportFile": false
}

View File

@ -1,25 +0,0 @@
Cypress.on('window:before:load', (win) => {
// because this is called before any scripts
// have loaded - the ga function is undefined
// so we need to create it.
win.cypressAsserter = cy.stub().as('ca')
})
describe('WorkAdventureGame', () => {
beforeEach(() => {
cy.visit('/', {
onBeforeLoad (win) {
cy.spy(win.console, 'log').as('console.log')
},
})
});
it('loads', () => {
cy.get('@console.log').should('be.calledWith', 'Started the game')
cy.get('@console.log').should('be.calledWith', 'Preloading')
cy.get('@console.log').should('be.calledWith', 'Preloading done')
cy.get('@console.log').should('be.calledWith', 'startInit')
cy.get('@console.log').should('be.calledWith', 'startInit done')
});
});

1406
e2e/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +0,0 @@
{
"dependencies": {
"cypress": "^3.8.3"
},
"scripts": {
"cy:run": "cypress run",
"cy:open": "cypress open"
}
}

1
front/.gitignore vendored
View File

@ -6,3 +6,4 @@
/dist/webpack.config.js.map
/dist/src
*.sh
!templater.sh

View File

@ -25,4 +25,5 @@ RUN API_URL=$API_URL UPLOADER_URL=$UPLOADER_URL ADMIN_URL=$ADMIN_URL \
# final production image
FROM nginx:1.19.6-alpine@sha256:01747306a7247dbe928db991eab42e4002118bf636dd85b4ffea05dd907e5b66
COPY front/nginx-vhost.conf /etc/nginx/conf.d/default.conf
COPY front/templater.sh /docker-entrypoint.d/templater.sh
COPY --from=builder2 /usr/src/dist /usr/share/nginx/html

1
front/dist/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
index.html

9
front/dist/ga.html.tmpl vendored Normal file
View File

@ -0,0 +1,9 @@
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=<!-- TRACKING NUMBER -->"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '<!-- TRACKING NUMBER -->');
</script>

View File

@ -6,15 +6,8 @@
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-10196481-11"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-10196481-11');
</script>
<!-- TRACK CODE -->
<!-- END TRACK CODE -->
<link rel="apple-touch-icon" sizes="57x57" href="static/images/favicons/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="static/images/favicons/apple-icon-60x60.png">

View File

@ -1072,17 +1072,22 @@ div.modal-report-user{
}
.discussion .messages .message p.body{
color: white;
font-size: 16px;
overflow: hidden;
white-space: pre-wrap;
word-wrap: break-word;
}
.discussion .messages .message p.a{
color: white;
}
.discussion .send-message{
position: absolute;
bottom: 45px;
width: 220px;
height: 26px;
margin-bottom: 10px;
}
.discussion .send-message input{

View File

@ -26,7 +26,7 @@
"axios": "^0.21.1",
"generic-type-guard": "^3.2.0",
"google-protobuf": "^3.13.0",
"phaser": "^3.22.0",
"phaser": "3.24.1",
"queue-typescript": "^1.0.1",
"quill": "^1.3.7",
"simple-peer": "^9.6.2",

View File

@ -1,5 +1,8 @@
import {RoomConnection} from "../Connexion/RoomConnection";
import * as TypeMessages from "./TypeMessage";
import List = Phaser.Structs.List;
import {UpdatedLocalStreamCallback} from "../WebRtc/MediaManager";
import {Banned} from "./TypeMessage";
export interface TypeMessageInterface {
showMessage(message: string): void;
@ -8,6 +11,7 @@ export interface TypeMessageInterface {
export class UserMessageManager {
typeMessages: Map<string, TypeMessageInterface> = new Map<string, TypeMessageInterface>();
receiveBannedMessageListener: Set<Function> = new Set<UpdatedLocalStreamCallback>();
constructor(private Connection: RoomConnection) {
const valueTypeMessageTab = Object.values(TypeMessages);
@ -21,7 +25,14 @@ export class UserMessageManager {
initialise() {
//receive signal to show message
this.Connection.receiveUserMessage((type: string, message: string) => {
this.showMessage(type, message);
const typeMessage = this.showMessage(type, message);
//listener on banned receive message
if(typeMessage instanceof Banned) {
for (const callback of this.receiveBannedMessageListener) {
callback();
}
}
});
}
@ -32,5 +43,10 @@ export class UserMessageManager {
return;
}
classTypeMessage.showMessage(message);
return classTypeMessage;
}
setReceiveBanListener(callback: Function){
this.receiveBannedMessageListener.add(callback);
}
}

View File

@ -1,5 +1,5 @@
import Axios from "axios";
import {API_URL} from "../Enum/EnvironmentVariable";
import {API_URL, START_ROOM_URL} from "../Enum/EnvironmentVariable";
import {RoomConnection} from "./RoomConnection";
import {OnConnectInterface, PositionInterface, ViewportInterface} from "./ConnexionModels";
import {GameConnexionTypes, urlManager} from "../Url/UrlManager";
@ -7,8 +7,6 @@ import {localUserStore} from "./LocalUserStore";
import {LocalUser} from "./LocalUser";
import {Room} from "./Room";
const URL_ROOM_STARTED = '/Floor0/floor0.json';
class ConnectionManager {
private localUser!:LocalUser;
@ -29,9 +27,9 @@ class ConnectionManager {
const organizationSlug = data.organizationSlug;
const worldSlug = data.worldSlug;
const roomSlug = data.roomSlug;
urlManager.editUrlForRoom(roomSlug, organizationSlug, worldSlug);
const room = new Room(window.location.pathname + window.location.hash);
const room = new Room('/@/'+organizationSlug+'/'+worldSlug+'/'+roomSlug + window.location.hash);
urlManager.pushRoomIdToUrl(room);
return Promise.resolve(room);
} else if (connexionType === GameConnexionTypes.organization || connexionType === GameConnexionTypes.anonymous || connexionType === GameConnexionTypes.empty) {
const localUser = localUserStore.getLocalUser();
@ -50,8 +48,7 @@ class ConnectionManager {
}
let roomId: string
if (connexionType === GameConnexionTypes.empty) {
const defaultMapUrl = window.location.host.replace('play.', 'maps.') + URL_ROOM_STARTED;
roomId = urlManager.editUrlForRoom(defaultMapUrl, null, null);
roomId = START_ROOM_URL;
} else {
roomId = window.location.pathname + window.location.hash;
}

View File

@ -1,8 +1,8 @@
import {PlayerAnimationNames} from "../Phaser/Player/Animation";
import {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
import {SignalData} from "simple-peer";
import {BodyResourceDescriptionInterface} from "../Phaser/Entity/body_character";
import {RoomConnection} from "./RoomConnection";
import {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures";
export enum EventMessage{
CONNECT = "connect",

View File

@ -72,8 +72,9 @@ export class Room {
console.log('Map ', this.id, ' resolves to URL ', data.mapUrl);
resolve(data.mapUrl);
return;
}).catch((reason) => {
reject(reason);
});
}
});
}

View File

@ -41,7 +41,7 @@ import {
ViewportInterface, WebRtcDisconnectMessageInterface,
WebRtcSignalReceivedMessageInterface,
} from "./ConnexionModels";
import {BodyResourceDescriptionInterface} from "../Phaser/Entity/body_character";
import {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures";
const manualPingDelay = 20000;

View File

@ -1,36 +0,0 @@
declare let window:WindowWithCypressAsserter;
interface WindowWithCypressAsserter extends Window {
cypressAsserter: CypressAsserter;
}
//this class is used to communicate with cypress, our e2e testing client
//Since cypress cannot manipulate canvas, we notified it with console logs
class CypressAsserter {
constructor() {
window.cypressAsserter = this
}
gameStarted() {
console.log('Started the game')
}
preloadStarted() {
console.log('Preloading')
}
preloadFinished() {
console.log('Preloading done')
}
initStarted() {
console.log('startInit')
}
initFinished() {
console.log('startInit done')
}
}
export const cypressAsserter = new CypressAsserter()

View File

@ -1,7 +1,8 @@
const DEBUG_MODE: boolean = process.env.DEBUG_MODE == "true";
const START_ROOM_URL : string = process.env.START_ROOM_URL || '/_/global/maps.workadventure.localhost/Floor0/floor0.json';
const API_URL = (process.env.API_PROTOCOL || (typeof(window) !== 'undefined' ? window.location.protocol : 'http:')) + '//' + (process.env.API_URL || "pusher.workadventure.localhost");
const UPLOADER_URL = (process.env.API_PROTOCOL || (typeof(window) !== 'undefined' ? window.location.protocol : 'http:')) + '//' + (process.env.UPLOADER_URL || 'uploader.workadventure.localhost');
const ADMIN_URL = (process.env.API_PROTOCOL || (typeof(window) !== 'undefined' ? window.location.protocol : 'http:')) + '//' + (process.env.ADMIN_URL || "admin.workadventure.localhost");
const ADMIN_URL = (process.env.API_PROTOCOL || (typeof(window) !== 'undefined' ? window.location.protocol : 'http:')) + '//' + (process.env.ADMIN_URL || "workadventure.localhost");
const TURN_SERVER: string = process.env.TURN_SERVER || "turn:numb.viagenie.ca";
const TURN_USER: string = process.env.TURN_USER || 'g.parant@thecodingmachine.com';
const TURN_PASSWORD: string = process.env.TURN_PASSWORD || 'itcugcOHxle9Acqi$';
@ -14,6 +15,7 @@ const MAX_EXTRAPOLATION_TIME = 100; // Extrapolate a maximum of 250ms if no new
export {
DEBUG_MODE,
START_ROOM_URL,
API_URL,
UPLOADER_URL,
ADMIN_URL,

View File

@ -0,0 +1,14 @@
export const addLoader = (scene:Phaser.Scene): void => {
const loadingText = scene.add.text(scene.game.renderer.width / 2, 200, 'Loading');
const progress = scene.add.graphics();
scene.load.on('progress', (value: number) => {
progress.clear();
progress.fillStyle(0xffffff, 1);
progress.fillRect(0, 270, 800 * value, 60);
});
scene.load.on('complete', () => {
loadingText.destroy();
progress.destroy();
});
}

View File

@ -5,33 +5,6 @@ import Container = Phaser.GameObjects.Container;
import Sprite = Phaser.GameObjects.Sprite;
import {TextureError} from "../../Exception/TextureError";
export interface PlayerResourceDescriptionInterface {
name: string,
img: string
}
export const PLAYER_RESOURCES: Array<PlayerResourceDescriptionInterface> = [
{name: "male1", img: "resources/characters/pipoya/Male 01-1.png" /*, x: 32, y: 32*/},
{name: "male2", img: "resources/characters/pipoya/Male 02-2.png"/*, x: 64, y: 32*/},
{name: "male3", img: "resources/characters/pipoya/Male 03-4.png"/*, x: 96, y: 32*/},
{name: "male4", img: "resources/characters/pipoya/Male 09-1.png"/*, x: 128, y: 32*/},
{name: "male5", img: "resources/characters/pipoya/Male 10-3.png"/*, x: 32, y: 64*/},
{name: "male6", img: "resources/characters/pipoya/Male 17-2.png"/*, x: 64, y: 64*/},
{name: "male7", img: "resources/characters/pipoya/Male 18-1.png"/*, x: 96, y: 64*/},
{name: "male8", img: "resources/characters/pipoya/Male 16-4.png"/*, x: 128, y: 64*/},
{name: "Female1", img: "resources/characters/pipoya/Female 01-1.png"/*, x: 32, y: 96*/},
{name: "Female2", img: "resources/characters/pipoya/Female 02-2.png"/*, x: 64, y: 96*/},
{name: "Female3", img: "resources/characters/pipoya/Female 03-4.png"/*, x: 96, y: 96*/},
{name: "Female4", img: "resources/characters/pipoya/Female 09-1.png"/*, x: 128, y: 96*/},
{name: "Female5", img: "resources/characters/pipoya/Female 10-3.png"/*, x: 32, y: 128*/},
{name: "Female6", img: "resources/characters/pipoya/Female 17-2.png"/*, x: 64, y: 128*/},
{name: "Female7", img: "resources/characters/pipoya/Female 18-1.png"/*, x: 96, y: 128*/},
{name: "Female8", img: "resources/characters/pipoya/Female 16-4.png"/*, x: 128, y: 128*/}
];
interface AnimationData {
key: string;
frameRate: number;
@ -48,11 +21,12 @@ export abstract class Character extends Container {
public sprites: Map<string, Sprite>;
private lastDirection: string = PlayerAnimationNames.WalkDown;
//private teleportation: Sprite;
private invisible: boolean;
constructor(scene: Phaser.Scene,
x: number,
y: number,
textures: string[],
texturesPromise: Promise<string[]>,
name: string,
direction: string,
moving: boolean,
@ -60,10 +34,15 @@ export abstract class Character extends Container {
) {
super(scene, x, y/*, texture, frame*/);
this.PlayerValue = name;
this.invisible = true
this.sprites = new Map<string, Sprite>();
this.addTextures(textures, frame);
//textures are inside a Promise in case they need to be lazyloaded before use.
texturesPromise.then((textures) => {
this.addTextures(textures, frame);
this.invisible = false
})
/*this.teleportation = new Sprite(scene, -20, -10, 'teleportation', 3);
this.teleportation.setInteractive();
@ -148,6 +127,7 @@ export abstract class Character extends Container {
}
protected playAnimation(direction : string, moving: boolean): void {
if (this.invisible) return;
for (const [texture, sprite] of this.sprites.entries()) {
if (!sprite.anims) {
console.error('ANIMS IS NOT DEFINED!!!');
@ -190,7 +170,6 @@ export abstract class Character extends Container {
this.playAnimation(PlayerAnimationNames.WalkLeft, true);
}
//update depth user
this.setDepth(this.y);
}

View File

@ -0,0 +1,344 @@
//The list of all the player textures, both the default models and the partial textures used for customization
export interface BodyResourceDescriptionListInterface {
[key: string]: BodyResourceDescriptionInterface
}
export interface BodyResourceDescriptionInterface {
name: string,
img: string,
level?: number
}
export const PLAYER_RESOURCES: BodyResourceDescriptionListInterface = {
"male1": {name: "male1", img: "resources/characters/pipoya/Male 01-1.png"},
"male2": {name: "male2", img: "resources/characters/pipoya/Male 02-2.png"},
"male3": {name: "male3", img: "resources/characters/pipoya/Male 03-4.png"},
"male4": {name: "male4", img: "resources/characters/pipoya/Male 09-1.png"},
"male5": {name: "male5", img: "resources/characters/pipoya/Male 10-3.png"},
"male6": {name: "male6", img: "resources/characters/pipoya/Male 17-2.png"},
"male7": {name: "male7", img: "resources/characters/pipoya/Male 18-1.png"},
"male8": {name: "male8", img: "resources/characters/pipoya/Male 16-4.png"},
"male9": {name: "male9", img: "resources/characters/pipoya/Male 07-2.png"},
"male10": {name: "male10", img: "resources/characters/pipoya/Male 05-3.png"},
"male11": {name: "male11", img: "resources/characters/pipoya/Teacher male 02.png"},
"male12": {name: "male12", img: "resources/characters/pipoya/su4 Student male 12.png"},
"Female1": {name: "Female1", img: "resources/characters/pipoya/Female 01-1.png"},
"Female2": {name: "Female2", img: "resources/characters/pipoya/Female 02-2.png"},
"Female3": {name: "Female3", img: "resources/characters/pipoya/Female 03-4.png"},
"Female4": {name: "Female4", img: "resources/characters/pipoya/Female 09-1.png"},
"Female5": {name: "Female5", img: "resources/characters/pipoya/Female 10-3.png"},
"Female6": {name: "Female6", img: "resources/characters/pipoya/Female 17-2.png"},
"Female7": {name: "Female7", img: "resources/characters/pipoya/Female 18-1.png"},
"Female8": {name: "Female8", img: "resources/characters/pipoya/Female 16-4.png"},
"Female9": {name: "Female9", img: "resources/characters/pipoya/Female 07-2.png"},
"Female10": {name: "Female10", img: "resources/characters/pipoya/Female 05-3.png"},
"Female11": {name: "Female11", img: "resources/characters/pipoya/Teacher fmale 02.png"},
"Female12": {name: "Female12", img: "resources/characters/pipoya/su4 Student fmale 12.png"},
};
export const COLOR_RESOURCES: BodyResourceDescriptionListInterface = {
"color_1": {name: "color_1", img: "resources/customisation/character_color/character_color0.png"},
"color_2": {name: "color_2", img: "resources/customisation/character_color/character_color1.png"},
"color_3": {name: "color_3", img: "resources/customisation/character_color/character_color2.png"},
"color_4": {name: "color_4", img: "resources/customisation/character_color/character_color3.png"},
"color_5": {name: "color_5", img: "resources/customisation/character_color/character_color4.png"},
"color_6": {name: "color_6", img: "resources/customisation/character_color/character_color5.png"},
"color_7": {name: "color_7", img: "resources/customisation/character_color/character_color6.png"},
"color_8": {name: "color_8", img: "resources/customisation/character_color/character_color7.png"},
"color_9": {name: "color_9", img: "resources/customisation/character_color/character_color8.png"},
"color_10": {name: "color_10", img: "resources/customisation/character_color/character_color9.png"},
"color_11": {name: "color_11", img: "resources/customisation/character_color/character_color10.png"},
"color_12": {name: "color_12", img: "resources/customisation/character_color/character_color11.png"},
"color_13": {name: "color_13", img: "resources/customisation/character_color/character_color12.png"},
"color_14": {name: "color_14", img: "resources/customisation/character_color/character_color13.png"},
"color_15": {name: "color_15", img: "resources/customisation/character_color/character_color14.png"},
"color_16": {name: "color_16", img: "resources/customisation/character_color/character_color15.png"},
"color_17": {name: "color_17", img: "resources/customisation/character_color/character_color16.png"},
"color_18": {name: "color_18", img: "resources/customisation/character_color/character_color17.png"},
"color_19": {name: "color_19", img: "resources/customisation/character_color/character_color18.png"},
"color_20": {name: "color_20", img: "resources/customisation/character_color/character_color19.png"},
"color_21": {name: "color_21", img: "resources/customisation/character_color/character_color20.png"},
"color_22": {name: "color_22", img: "resources/customisation/character_color/character_color21.png"},
"color_23": {name: "color_23", img: "resources/customisation/character_color/character_color22.png"},
"color_24": {name: "color_24", img: "resources/customisation/character_color/character_color23.png"},
"color_25": {name: "color_25", img: "resources/customisation/character_color/character_color24.png"},
"color_26": {name: "color_26", img: "resources/customisation/character_color/character_color25.png"},
"color_27": {name: "color_27", img: "resources/customisation/character_color/character_color26.png"},
"color_28": {name: "color_28", img: "resources/customisation/character_color/character_color27.png"},
"color_29": {name: "color_29", img: "resources/customisation/character_color/character_color28.png"},
"color_30": {name: "color_30", img: "resources/customisation/character_color/character_color29.png"},
"color_31": {name: "color_31", img: "resources/customisation/character_color/character_color30.png"},
"color_32": {name: "color_32", img: "resources/customisation/character_color/character_color31.png"},
"color_33": {name: "color_33", img: "resources/customisation/character_color/character_color32.png"}
};
export const EYES_RESOURCES: BodyResourceDescriptionListInterface = {
"eyes_1": {name: "eyes_1", img: "resources/customisation/character_eyes/character_eyes1.png"},
"eyes_2": {name: "eyes_2", img: "resources/customisation/character_eyes/character_eyes2.png"},
"eyes_3": {name: "eyes_3", img: "resources/customisation/character_eyes/character_eyes3.png"},
"eyes_4": {name: "eyes_4", img: "resources/customisation/character_eyes/character_eyes4.png"},
"eyes_5": {name: "eyes_5", img: "resources/customisation/character_eyes/character_eyes5.png"},
"eyes_6": {name: "eyes_6", img: "resources/customisation/character_eyes/character_eyes6.png"},
"eyes_7": {name: "eyes_7", img: "resources/customisation/character_eyes/character_eyes7.png"},
"eyes_8": {name: "eyes_8", img: "resources/customisation/character_eyes/character_eyes8.png"},
"eyes_9": {name: "eyes_9", img: "resources/customisation/character_eyes/character_eyes9.png"},
"eyes_10": {name: "eyes_10", img: "resources/customisation/character_eyes/character_eyes10.png"},
"eyes_11": {name: "eyes_11", img: "resources/customisation/character_eyes/character_eyes11.png"},
"eyes_12": {name: "eyes_12", img: "resources/customisation/character_eyes/character_eyes12.png"},
"eyes_13": {name: "eyes_13", img: "resources/customisation/character_eyes/character_eyes13.png"},
"eyes_14": {name: "eyes_14", img: "resources/customisation/character_eyes/character_eyes14.png"},
"eyes_15": {name: "eyes_15", img: "resources/customisation/character_eyes/character_eyes15.png"},
"eyes_16": {name: "eyes_16", img: "resources/customisation/character_eyes/character_eyes16.png"},
"eyes_17": {name: "eyes_17", img: "resources/customisation/character_eyes/character_eyes17.png"},
"eyes_18": {name: "eyes_18", img: "resources/customisation/character_eyes/character_eyes18.png"},
"eyes_19": {name: "eyes_19", img: "resources/customisation/character_eyes/character_eyes19.png"},
"eyes_20": {name: "eyes_20", img: "resources/customisation/character_eyes/character_eyes20.png"},
"eyes_21": {name: "eyes_21", img: "resources/customisation/character_eyes/character_eyes21.png"},
"eyes_22": {name: "eyes_22", img: "resources/customisation/character_eyes/character_eyes22.png"},
"eyes_23": {name: "eyes_23", img: "resources/customisation/character_eyes/character_eyes23.png"},
"eyes_24": {name: "eyes_24", img: "resources/customisation/character_eyes/character_eyes24.png"},
"eyes_25": {name: "eyes_25", img: "resources/customisation/character_eyes/character_eyes25.png"},
"eyes_26": {name: "eyes_26", img: "resources/customisation/character_eyes/character_eyes26.png"},
"eyes_27": {name: "eyes_27", img: "resources/customisation/character_eyes/character_eyes27.png"},
"eyes_28": {name: "eyes_28", img: "resources/customisation/character_eyes/character_eyes28.png"},
"eyes_29": {name: "eyes_29", img: "resources/customisation/character_eyes/character_eyes29.png"},
"eyes_30": {name: "eyes_30", img: "resources/customisation/character_eyes/character_eyes30.png"}
};
export const HAIR_RESOURCES: BodyResourceDescriptionListInterface = {
"hair_1": {name:"hair_1", img: "resources/customisation/character_hairs/character_hairs0.png"},
"hair_2": {name:"hair_2", img: "resources/customisation/character_hairs/character_hairs1.png"},
"hair_3": {name:"hair_3", img: "resources/customisation/character_hairs/character_hairs2.png"},
"hair_4": {name:"hair_4", img: "resources/customisation/character_hairs/character_hairs3.png"},
"hair_5": {name:"hair_5", img: "resources/customisation/character_hairs/character_hairs4.png"},
"hair_6": {name:"hair_6", img: "resources/customisation/character_hairs/character_hairs5.png"},
"hair_7": {name:"hair_7", img: "resources/customisation/character_hairs/character_hairs6.png"},
"hair_8": {name:"hair_8", img: "resources/customisation/character_hairs/character_hairs7.png"},
"hair_9": {name:"hair_9", img: "resources/customisation/character_hairs/character_hairs8.png"},
"hair_10": {name:"hair_10",img: "resources/customisation/character_hairs/character_hairs9.png"},
"hair_11": {name:"hair_11",img: "resources/customisation/character_hairs/character_hairs10.png"},
"hair_12": {name:"hair_12",img: "resources/customisation/character_hairs/character_hairs11.png"},
"hair_13": {name:"hair_13",img: "resources/customisation/character_hairs/character_hairs12.png"},
"hair_14": {name:"hair_14",img: "resources/customisation/character_hairs/character_hairs13.png"},
"hair_15": {name:"hair_15",img: "resources/customisation/character_hairs/character_hairs14.png"},
"hair_16": {name:"hair_16",img: "resources/customisation/character_hairs/character_hairs15.png"},
"hair_17": {name:"hair_17",img: "resources/customisation/character_hairs/character_hairs16.png"},
"hair_18": {name:"hair_18",img: "resources/customisation/character_hairs/character_hairs17.png"},
"hair_19": {name:"hair_19",img: "resources/customisation/character_hairs/character_hairs18.png"},
"hair_20": {name:"hair_20",img: "resources/customisation/character_hairs/character_hairs19.png"},
"hair_21": {name:"hair_21",img: "resources/customisation/character_hairs/character_hairs20.png"},
"hair_22": {name:"hair_22",img: "resources/customisation/character_hairs/character_hairs21.png"},
"hair_23": {name:"hair_23",img: "resources/customisation/character_hairs/character_hairs22.png"},
"hair_24": {name:"hair_24",img: "resources/customisation/character_hairs/character_hairs23.png"},
"hair_25": {name:"hair_25",img: "resources/customisation/character_hairs/character_hairs24.png"},
"hair_26": {name:"hair_26",img: "resources/customisation/character_hairs/character_hairs25.png"},
"hair_27": {name:"hair_27",img: "resources/customisation/character_hairs/character_hairs26.png"},
"hair_28": {name:"hair_28",img: "resources/customisation/character_hairs/character_hairs27.png"},
"hair_29": {name:"hair_29",img: "resources/customisation/character_hairs/character_hairs28.png"},
"hair_30": {name:"hair_30",img: "resources/customisation/character_hairs/character_hairs29.png"},
"hair_31": {name:"hair_31",img: "resources/customisation/character_hairs/character_hairs30.png"},
"hair_32": {name:"hair_32",img: "resources/customisation/character_hairs/character_hairs31.png"},
"hair_33": {name:"hair_33",img: "resources/customisation/character_hairs/character_hairs32.png"},
"hair_34": {name:"hair_34",img: "resources/customisation/character_hairs/character_hairs33.png"},
"hair_35": {name:"hair_35",img: "resources/customisation/character_hairs/character_hairs34.png"},
"hair_36": {name:"hair_36",img: "resources/customisation/character_hairs/character_hairs35.png"},
"hair_37": {name:"hair_37",img: "resources/customisation/character_hairs/character_hairs36.png"},
"hair_38": {name:"hair_38",img: "resources/customisation/character_hairs/character_hairs37.png"},
"hair_39": {name:"hair_39",img: "resources/customisation/character_hairs/character_hairs38.png"},
"hair_40": {name:"hair_40",img: "resources/customisation/character_hairs/character_hairs39.png"},
"hair_41": {name:"hair_41",img: "resources/customisation/character_hairs/character_hairs40.png"},
"hair_42": {name:"hair_42",img: "resources/customisation/character_hairs/character_hairs41.png"},
"hair_43": {name:"hair_43",img: "resources/customisation/character_hairs/character_hairs42.png"},
"hair_44": {name:"hair_44",img: "resources/customisation/character_hairs/character_hairs43.png"},
"hair_45": {name:"hair_45",img: "resources/customisation/character_hairs/character_hairs44.png"},
"hair_46": {name:"hair_46",img: "resources/customisation/character_hairs/character_hairs45.png"},
"hair_47": {name:"hair_47",img: "resources/customisation/character_hairs/character_hairs46.png"},
"hair_48": {name:"hair_48",img: "resources/customisation/character_hairs/character_hairs47.png"},
"hair_49": {name:"hair_49",img: "resources/customisation/character_hairs/character_hairs48.png"},
"hair_50": {name:"hair_50",img: "resources/customisation/character_hairs/character_hairs49.png"},
"hair_51": {name:"hair_51",img: "resources/customisation/character_hairs/character_hairs50.png"},
"hair_52": {name:"hair_52",img: "resources/customisation/character_hairs/character_hairs51.png"},
"hair_53": {name:"hair_53",img: "resources/customisation/character_hairs/character_hairs52.png"},
"hair_54": {name:"hair_54",img: "resources/customisation/character_hairs/character_hairs53.png"},
"hair_55": {name:"hair_55",img: "resources/customisation/character_hairs/character_hairs54.png"},
"hair_56": {name:"hair_56",img: "resources/customisation/character_hairs/character_hairs55.png"},
"hair_57": {name:"hair_57",img: "resources/customisation/character_hairs/character_hairs56.png"},
"hair_58": {name:"hair_58",img: "resources/customisation/character_hairs/character_hairs57.png"},
"hair_59": {name:"hair_59",img: "resources/customisation/character_hairs/character_hairs58.png"},
"hair_60": {name:"hair_60",img: "resources/customisation/character_hairs/character_hairs59.png"},
"hair_61": {name:"hair_61",img: "resources/customisation/character_hairs/character_hairs60.png"},
"hair_62": {name:"hair_62",img: "resources/customisation/character_hairs/character_hairs61.png"},
"hair_63": {name:"hair_63",img: "resources/customisation/character_hairs/character_hairs62.png"},
"hair_64": {name:"hair_64",img: "resources/customisation/character_hairs/character_hairs63.png"},
"hair_65": {name:"hair_65",img: "resources/customisation/character_hairs/character_hairs64.png"},
"hair_66": {name:"hair_66",img: "resources/customisation/character_hairs/character_hairs65.png"},
"hair_67": {name:"hair_67",img: "resources/customisation/character_hairs/character_hairs66.png"},
"hair_68": {name:"hair_68",img: "resources/customisation/character_hairs/character_hairs67.png"},
"hair_69": {name:"hair_69",img: "resources/customisation/character_hairs/character_hairs68.png"},
"hair_70": {name:"hair_70",img: "resources/customisation/character_hairs/character_hairs69.png"},
"hair_71": {name:"hair_71",img: "resources/customisation/character_hairs/character_hairs70.png"},
"hair_72": {name:"hair_72",img: "resources/customisation/character_hairs/character_hairs71.png"},
"hair_73": {name:"hair_73",img: "resources/customisation/character_hairs/character_hairs72.png"},
"hair_74": {name:"hair_74",img: "resources/customisation/character_hairs/character_hairs73.png"}
};
export const CLOTHES_RESOURCES: BodyResourceDescriptionListInterface = {
"clothes_1": {name:"clothes_1", img: "resources/customisation/character_clothes/character_clothes0.png"},
"clothes_2": {name:"clothes_2", img: "resources/customisation/character_clothes/character_clothes1.png"},
"clothes_3": {name:"clothes_3", img: "resources/customisation/character_clothes/character_clothes2.png"},
"clothes_4": {name:"clothes_4", img: "resources/customisation/character_clothes/character_clothes3.png"},
"clothes_5": {name:"clothes_5", img: "resources/customisation/character_clothes/character_clothes4.png"},
"clothes_6": {name:"clothes_6", img: "resources/customisation/character_clothes/character_clothes5.png"},
"clothes_7": {name:"clothes_7", img: "resources/customisation/character_clothes/character_clothes6.png"},
"clothes_8": {name:"clothes_8", img: "resources/customisation/character_clothes/character_clothes7.png"},
"clothes_9": {name:"clothes_9", img: "resources/customisation/character_clothes/character_clothes8.png"},
"clothes_10": {name:"clothes_10",img: "resources/customisation/character_clothes/character_clothes9.png"},
"clothes_11": {name:"clothes_11",img: "resources/customisation/character_clothes/character_clothes10.png"},
"clothes_12": {name:"clothes_12",img: "resources/customisation/character_clothes/character_clothes11.png"},
"clothes_13": {name:"clothes_13",img: "resources/customisation/character_clothes/character_clothes12.png"},
"clothes_14": {name:"clothes_14",img: "resources/customisation/character_clothes/character_clothes13.png"},
"clothes_15": {name:"clothes_15",img: "resources/customisation/character_clothes/character_clothes14.png"},
"clothes_16": {name:"clothes_16",img: "resources/customisation/character_clothes/character_clothes15.png"},
"clothes_17": {name:"clothes_17",img: "resources/customisation/character_clothes/character_clothes16.png"},
"clothes_18": {name:"clothes_18",img: "resources/customisation/character_clothes/character_clothes17.png"},
"clothes_19": {name:"clothes_19",img: "resources/customisation/character_clothes/character_clothes18.png"},
"clothes_20": {name:"clothes_20",img: "resources/customisation/character_clothes/character_clothes19.png"},
"clothes_21": {name:"clothes_21",img: "resources/customisation/character_clothes/character_clothes20.png"},
"clothes_22": {name:"clothes_22",img: "resources/customisation/character_clothes/character_clothes21.png"},
"clothes_23": {name:"clothes_23",img: "resources/customisation/character_clothes/character_clothes22.png"},
"clothes_24": {name:"clothes_24",img: "resources/customisation/character_clothes/character_clothes23.png"},
"clothes_25": {name:"clothes_25",img: "resources/customisation/character_clothes/character_clothes24.png"},
"clothes_26": {name:"clothes_26",img: "resources/customisation/character_clothes/character_clothes25.png"},
"clothes_27": {name:"clothes_27",img: "resources/customisation/character_clothes/character_clothes26.png"},
"clothes_28": {name:"clothes_28",img: "resources/customisation/character_clothes/character_clothes27.png"},
"clothes_29": {name:"clothes_29",img: "resources/customisation/character_clothes/character_clothes28.png"},
"clothes_30": {name:"clothes_30",img: "resources/customisation/character_clothes/character_clothes29.png"},
"clothes_31": {name:"clothes_31",img: "resources/customisation/character_clothes/character_clothes30.png"},
"clothes_32": {name:"clothes_32",img: "resources/customisation/character_clothes/character_clothes31.png"},
"clothes_33": {name:"clothes_33",img: "resources/customisation/character_clothes/character_clothes32.png"},
"clothes_34": {name:"clothes_34",img: "resources/customisation/character_clothes/character_clothes33.png"},
"clothes_35": {name:"clothes_35",img: "resources/customisation/character_clothes/character_clothes34.png"},
"clothes_36": {name:"clothes_36",img: "resources/customisation/character_clothes/character_clothes35.png"},
"clothes_37": {name:"clothes_37",img: "resources/customisation/character_clothes/character_clothes36.png"},
"clothes_38": {name:"clothes_38",img: "resources/customisation/character_clothes/character_clothes37.png"},
"clothes_39": {name:"clothes_39",img: "resources/customisation/character_clothes/character_clothes38.png"},
"clothes_40": {name:"clothes_40",img: "resources/customisation/character_clothes/character_clothes39.png"},
"clothes_41": {name:"clothes_41",img: "resources/customisation/character_clothes/character_clothes40.png"},
"clothes_42": {name:"clothes_42",img: "resources/customisation/character_clothes/character_clothes41.png"},
"clothes_43": {name:"clothes_43",img: "resources/customisation/character_clothes/character_clothes42.png"},
"clothes_44": {name:"clothes_44",img: "resources/customisation/character_clothes/character_clothes43.png"},
"clothes_45": {name:"clothes_45",img: "resources/customisation/character_clothes/character_clothes44.png"},
"clothes_46": {name:"clothes_46",img: "resources/customisation/character_clothes/character_clothes45.png"},
"clothes_47": {name:"clothes_47",img: "resources/customisation/character_clothes/character_clothes46.png"},
"clothes_48": {name:"clothes_48",img: "resources/customisation/character_clothes/character_clothes47.png"},
"clothes_49": {name:"clothes_49",img: "resources/customisation/character_clothes/character_clothes48.png"},
"clothes_50": {name:"clothes_50",img: "resources/customisation/character_clothes/character_clothes49.png"},
"clothes_51": {name:"clothes_51",img: "resources/customisation/character_clothes/character_clothes50.png"},
"clothes_52": {name:"clothes_52",img: "resources/customisation/character_clothes/character_clothes51.png"},
"clothes_53": {name:"clothes_53",img: "resources/customisation/character_clothes/character_clothes52.png"},
"clothes_54": {name:"clothes_54",img: "resources/customisation/character_clothes/character_clothes53.png"},
"clothes_55": {name:"clothes_55",img: "resources/customisation/character_clothes/character_clothes54.png"},
"clothes_56": {name:"clothes_56",img: "resources/customisation/character_clothes/character_clothes55.png"},
"clothes_57": {name:"clothes_57",img: "resources/customisation/character_clothes/character_clothes56.png"},
"clothes_58": {name:"clothes_58",img: "resources/customisation/character_clothes/character_clothes57.png"},
"clothes_59": {name:"clothes_59",img: "resources/customisation/character_clothes/character_clothes58.png"},
"clothes_60": {name:"clothes_60",img: "resources/customisation/character_clothes/character_clothes59.png"},
"clothes_61": {name:"clothes_61",img: "resources/customisation/character_clothes/character_clothes60.png"},
"clothes_62": {name:"clothes_62",img: "resources/customisation/character_clothes/character_clothes61.png"},
"clothes_63": {name:"clothes_63",img: "resources/customisation/character_clothes/character_clothes62.png"},
"clothes_64": {name:"clothes_64",img: "resources/customisation/character_clothes/character_clothes63.png"},
"clothes_65": {name:"clothes_65",img: "resources/customisation/character_clothes/character_clothes64.png"},
"clothes_66": {name:"clothes_66",img: "resources/customisation/character_clothes/character_clothes65.png"},
"clothes_67": {name:"clothes_67",img: "resources/customisation/character_clothes/character_clothes66.png"},
"clothes_68": {name:"clothes_68",img: "resources/customisation/character_clothes/character_clothes67.png"},
"clothes_69": {name:"clothes_69",img: "resources/customisation/character_clothes/character_clothes68.png"},
"clothes_70": {name:"clothes_70",img: "resources/customisation/character_clothes/character_clothes69.png"},
"clothes_pride_shirt": {name:"clothes_pride_shirt",img: "resources/customisation/character_clothes/pride_shirt.png"},
"clothes_black_hoodie": {name:"clothes_black_hoodie",img: "resources/customisation/character_clothes/black_hoodie.png"},
"clothes_white_hoodie": {name:"clothes_white_hoodie",img: "resources/customisation/character_clothes/white_hoodie.png"},
"clothes_engelbert": {name:"clothes_engelbert",img: "resources/customisation/character_clothes/engelbert.png"}
};
export const HATS_RESOURCES: BodyResourceDescriptionListInterface = {
"hats_1": {name: "hats_1", img: "resources/customisation/character_hats/character_hats1.png"},
"hats_2": {name: "hats_2", img: "resources/customisation/character_hats/character_hats2.png"},
"hats_3": {name: "hats_3", img: "resources/customisation/character_hats/character_hats3.png"},
"hats_4": {name: "hats_4", img: "resources/customisation/character_hats/character_hats4.png"},
"hats_5": {name: "hats_5", img: "resources/customisation/character_hats/character_hats5.png"},
"hats_6": {name: "hats_6", img: "resources/customisation/character_hats/character_hats6.png"},
"hats_7": {name: "hats_7", img: "resources/customisation/character_hats/character_hats7.png"},
"hats_8": {name: "hats_8", img: "resources/customisation/character_hats/character_hats8.png"},
"hats_9": {name: "hats_9", img: "resources/customisation/character_hats/character_hats9.png"},
"hats_10": {name: "hats_10", img: "resources/customisation/character_hats/character_hats10.png"},
"hats_11": {name: "hats_11", img: "resources/customisation/character_hats/character_hats11.png"},
"hats_12": {name: "hats_12", img: "resources/customisation/character_hats/character_hats12.png"},
"hats_13": {name: "hats_13", img: "resources/customisation/character_hats/character_hats13.png"},
"hats_14": {name: "hats_14", img: "resources/customisation/character_hats/character_hats14.png"},
"hats_15": {name: "hats_15", img: "resources/customisation/character_hats/character_hats15.png"},
"hats_16": {name: "hats_16", img: "resources/customisation/character_hats/character_hats16.png"},
"hats_17": {name: "hats_17", img: "resources/customisation/character_hats/character_hats17.png"},
"hats_18": {name: "hats_18", img: "resources/customisation/character_hats/character_hats18.png"},
"hats_19": {name: "hats_19", img: "resources/customisation/character_hats/character_hats19.png"},
"hats_20": {name: "hats_20", img: "resources/customisation/character_hats/character_hats20.png"},
"hats_21": {name: "hats_21", img: "resources/customisation/character_hats/character_hats21.png"},
"hats_22": {name: "hats_22", img: "resources/customisation/character_hats/character_hats22.png"},
"hats_23": {name: "hats_23", img: "resources/customisation/character_hats/character_hats23.png"},
"hats_24": {name: "hats_24", img: "resources/customisation/character_hats/character_hats24.png"},
"hats_25": {name: "hats_25", img: "resources/customisation/character_hats/character_hats25.png"},
"hats_26": {name: "hats_26", img: "resources/customisation/character_hats/character_hats26.png"},
"tinfoil_hat1": {name: "tinfoil_hat1", img: "resources/customisation/character_hats/tinfoil_hat1.png"}
};
export const ACCESSORIES_RESOURCES: BodyResourceDescriptionListInterface = {
"accessory_1": {name: "accessory_1", img: "resources/customisation/character_accessories/character_accessories1.png"},
"accessory_2": {name: "accessory_2", img: "resources/customisation/character_accessories/character_accessories2.png"},
"accessory_3": {name: "accessory_3", img: "resources/customisation/character_accessories/character_accessories3.png"},
"accessory_4": {name: "accessory_4", img: "resources/customisation/character_accessories/character_accessories4.png"},
"accessory_5": {name: "accessory_5", img: "resources/customisation/character_accessories/character_accessories5.png"},
"accessory_6": {name: "accessory_6", img: "resources/customisation/character_accessories/character_accessories6.png"},
"accessory_7": {name: "accessory_7", img: "resources/customisation/character_accessories/character_accessories7.png"},
"accessory_8": {name: "accessory_8", img: "resources/customisation/character_accessories/character_accessories8.png"},
"accessory_9": {name: "accessory_9", img: "resources/customisation/character_accessories/character_accessories9.png"},
"accessory_10": {name: "accessory_10", img: "resources/customisation/character_accessories/character_accessories10.png"},
"accessory_11": {name: "accessory_11", img: "resources/customisation/character_accessories/character_accessories11.png"},
"accessory_12": {name: "accessory_12", img: "resources/customisation/character_accessories/character_accessories12.png"},
"accessory_13": {name: "accessory_13", img: "resources/customisation/character_accessories/character_accessories13.png"},
"accessory_14": {name: "accessory_14", img: "resources/customisation/character_accessories/character_accessories14.png"},
"accessory_15": {name: "accessory_15", img: "resources/customisation/character_accessories/character_accessories15.png"},
"accessory_16": {name: "accessory_16", img: "resources/customisation/character_accessories/character_accessories16.png"},
"accessory_17": {name: "accessory_17", img: "resources/customisation/character_accessories/character_accessories17.png"},
"accessory_18": {name: "accessory_18", img: "resources/customisation/character_accessories/character_accessories18.png"},
"accessory_19": {name: "accessory_19", img: "resources/customisation/character_accessories/character_accessories19.png"},
"accessory_20": {name: "accessory_20", img: "resources/customisation/character_accessories/character_accessories20.png"},
"accessory_21": {name: "accessory_21", img: "resources/customisation/character_accessories/character_accessories21.png"},
"accessory_22": {name: "accessory_22", img: "resources/customisation/character_accessories/character_accessories22.png"},
"accessory_23": {name: "accessory_23", img: "resources/customisation/character_accessories/character_accessories23.png"},
"accessory_24": {name: "accessory_24", img: "resources/customisation/character_accessories/character_accessories24.png"},
"accessory_25": {name: "accessory_25", img: "resources/customisation/character_accessories/character_accessories25.png"},
"accessory_26": {name: "accessory_26", img: "resources/customisation/character_accessories/character_accessories26.png"},
"accessory_27": {name: "accessory_27", img: "resources/customisation/character_accessories/character_accessories27.png"},
"accessory_28": {name: "accessory_28", img: "resources/customisation/character_accessories/character_accessories28.png"},
"accessory_29": {name: "accessory_29", img: "resources/customisation/character_accessories/character_accessories29.png"},
"accessory_30": {name: "accessory_30", img: "resources/customisation/character_accessories/character_accessories30.png"},
"accessory_31": {name: "accessory_31", img: "resources/customisation/character_accessories/character_accessories31.png"},
"accessory_32": {name: "accessory_32", img: "resources/customisation/character_accessories/character_accessories32.png"},
"accessory_mate_bottle": {name: "accessory_mate_bottle", img: "resources/customisation/character_accessories/mate_bottle1.png"},
"accessory_mask": {name: "accessory_mask", img: "resources/customisation/character_accessories/mask.png"}
};
export const LAYERS: BodyResourceDescriptionListInterface[] = [
COLOR_RESOURCES,
EYES_RESOURCES,
HAIR_RESOURCES,
CLOTHES_RESOURCES,
HATS_RESOURCES,
ACCESSORIES_RESOURCES
];
export const OBJECTS: BodyResourceDescriptionInterface[] = [
{name:'teleportation', img:'resources/objects/teleportation.png'},
];

View File

@ -0,0 +1,84 @@
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
import TextureManager = Phaser.Textures.TextureManager;
import {CharacterTexture} from "../../Connexion/LocalUser";
import {BodyResourceDescriptionInterface, LAYERS, PLAYER_RESOURCES} from "./PlayerTextures";
export const loadAllLayers = (load: LoaderPlugin): BodyResourceDescriptionInterface[][] => {
const returnArray:BodyResourceDescriptionInterface[][] = [];
LAYERS.forEach(layer => {
const layerArray:BodyResourceDescriptionInterface[] = [];
Object.values(layer).forEach((textureDescriptor) => {
layerArray.push(textureDescriptor);
load.spritesheet(textureDescriptor.name,textureDescriptor.img,{frameWidth: 32, frameHeight: 32});
})
returnArray.push(layerArray)
});
return returnArray;
}
export const loadAllDefaultModels = (load: LoaderPlugin): BodyResourceDescriptionInterface[] => {
const returnArray = Object.values(PLAYER_RESOURCES);
returnArray.forEach((playerResource: BodyResourceDescriptionInterface) => {
load.spritesheet(playerResource.name, playerResource.img, {frameWidth: 32, frameHeight: 32});
});
return returnArray;
}
export const loadCustomTexture = (loaderPlugin: LoaderPlugin, texture: CharacterTexture) : Promise<BodyResourceDescriptionInterface> => {
const name = 'customCharacterTexture'+texture.id;
const playerResourceDescriptor: BodyResourceDescriptionInterface = {name, img: texture.url, level: texture.level}
return createLoadingPromise(loaderPlugin, playerResourceDescriptor);
}
export const lazyLoadPlayerCharacterTextures = (loadPlugin: LoaderPlugin, texturekeys:Array<string|BodyResourceDescriptionInterface>): Promise<string[]> => {
const promisesList:Promise<unknown>[] = [];
texturekeys.forEach((textureKey: string|BodyResourceDescriptionInterface) => {
try {
//TODO refactor
const playerResourceDescriptor = getRessourceDescriptor(textureKey);
if (playerResourceDescriptor && !loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
promisesList.push(createLoadingPromise(loadPlugin, playerResourceDescriptor));
}
}catch (err){
console.error(err);
}
});
let returnPromise:Promise<Array<string|BodyResourceDescriptionInterface>>;
if (promisesList.length > 0) {
loadPlugin.start();
returnPromise = Promise.all(promisesList).then(() => texturekeys);
} else {
returnPromise = Promise.resolve(texturekeys);
}
return returnPromise.then((keys) => keys.map((key) => {
return typeof key !== 'string' ? key.name : key;
}))
}
export const getRessourceDescriptor = (textureKey: string|BodyResourceDescriptionInterface): BodyResourceDescriptionInterface => {
if (typeof textureKey !== 'string' && textureKey.img) {
return textureKey;
}
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;
}
throw 'Could not find a data for texture '+textureName;
}
const createLoadingPromise = (loadPlugin: LoaderPlugin, playerResourceDescriptor: BodyResourceDescriptionInterface) => {
return new Promise<BodyResourceDescriptionInterface>((res) => {
if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
return res(playerResourceDescriptor);
}
loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, {
frameWidth: 32,
frameHeight: 32
});
loadPlugin.once('filecomplete-spritesheet-' + playerResourceDescriptor.name, () => res(playerResourceDescriptor));
});
}

View File

@ -15,11 +15,11 @@ export class RemotePlayer extends Character {
x: number,
y: number,
name: string,
PlayerTextures: string[],
texturesPromise: Promise<string[]>,
direction: string,
moving: boolean
) {
super(Scene, x, y, PlayerTextures, name, direction, moving, 1);
super(Scene, x, y, texturesPromise, name, direction, moving, 1);
//set data
this.userId = userId;

View File

@ -1,355 +0,0 @@
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
import {PLAYER_RESOURCES, PlayerResourceDescriptionInterface} from "./Character";
import {CharacterTexture} from "../../Connexion/LocalUser";
export interface BodyResourceDescriptionInterface {
name: string,
img: string
}
export const COLOR_RESOURCES: Array<BodyResourceDescriptionInterface> = [
{name:"color_1", img: "resources/customisation/character_color/character_color0.png"},
{name:"color_2", img: "resources/customisation/character_color/character_color1.png"},
{name:"color_3", img: "resources/customisation/character_color/character_color2.png"},
{name:"color_4", img: "resources/customisation/character_color/character_color3.png"},
{name:"color_5", img: "resources/customisation/character_color/character_color4.png"},
{name:"color_6", img: "resources/customisation/character_color/character_color5.png"},
{name:"color_7", img: "resources/customisation/character_color/character_color6.png"},
{name:"color_8", img: "resources/customisation/character_color/character_color7.png"},
{name:"color_9", img: "resources/customisation/character_color/character_color8.png"},
{name:"color_10",img: "resources/customisation/character_color/character_color9.png"},
{name:"color_11",img: "resources/customisation/character_color/character_color10.png"},
{name:"color_12",img: "resources/customisation/character_color/character_color11.png"},
{name:"color_13",img: "resources/customisation/character_color/character_color12.png"},
{name:"color_14",img: "resources/customisation/character_color/character_color13.png"},
{name:"color_15",img: "resources/customisation/character_color/character_color14.png"},
{name:"color_16",img: "resources/customisation/character_color/character_color15.png"},
{name:"color_17",img: "resources/customisation/character_color/character_color16.png"},
{name:"color_18",img: "resources/customisation/character_color/character_color17.png"},
{name:"color_19",img: "resources/customisation/character_color/character_color18.png"},
{name:"color_20",img: "resources/customisation/character_color/character_color19.png"},
{name:"color_21",img: "resources/customisation/character_color/character_color20.png"},
{name:"color_22",img: "resources/customisation/character_color/character_color21.png"},
{name:"color_23",img: "resources/customisation/character_color/character_color22.png"},
{name:"color_24",img: "resources/customisation/character_color/character_color23.png"},
{name:"color_25",img: "resources/customisation/character_color/character_color24.png"},
{name:"color_26",img: "resources/customisation/character_color/character_color25.png"},
{name:"color_27",img: "resources/customisation/character_color/character_color26.png"},
{name:"color_28",img: "resources/customisation/character_color/character_color27.png"},
{name:"color_29",img: "resources/customisation/character_color/character_color28.png"},
{name:"color_30",img: "resources/customisation/character_color/character_color29.png"},
{name:"color_31",img: "resources/customisation/character_color/character_color30.png"},
{name:"color_32",img: "resources/customisation/character_color/character_color31.png"},
{name:"color_33",img: "resources/customisation/character_color/character_color32.png"}
];
export const EYES_RESOURCES: Array<BodyResourceDescriptionInterface> = [
{name: "eyes_1", img: "resources/customisation/character_eyes/character_eyes1.png"},
{name: "eyes_2", img: "resources/customisation/character_eyes/character_eyes2.png"},
{name: "eyes_3", img: "resources/customisation/character_eyes/character_eyes3.png"},
{name: "eyes_4", img: "resources/customisation/character_eyes/character_eyes4.png"},
{name: "eyes_5", img: "resources/customisation/character_eyes/character_eyes5.png"},
{name: "eyes_6", img: "resources/customisation/character_eyes/character_eyes6.png"},
{name: "eyes_7", img: "resources/customisation/character_eyes/character_eyes7.png"},
{name: "eyes_8", img: "resources/customisation/character_eyes/character_eyes8.png"},
{name: "eyes_9", img: "resources/customisation/character_eyes/character_eyes9.png"},
{name: "eyes_10", img: "resources/customisation/character_eyes/character_eyes10.png"},
{name: "eyes_11", img: "resources/customisation/character_eyes/character_eyes11.png"},
{name: "eyes_12", img: "resources/customisation/character_eyes/character_eyes12.png"},
{name: "eyes_13", img: "resources/customisation/character_eyes/character_eyes13.png"},
{name: "eyes_14", img: "resources/customisation/character_eyes/character_eyes14.png"},
{name: "eyes_15", img: "resources/customisation/character_eyes/character_eyes15.png"},
{name: "eyes_16", img: "resources/customisation/character_eyes/character_eyes16.png"},
{name: "eyes_17", img: "resources/customisation/character_eyes/character_eyes17.png"},
{name: "eyes_18", img: "resources/customisation/character_eyes/character_eyes18.png"},
{name: "eyes_19", img: "resources/customisation/character_eyes/character_eyes19.png"},
{name: "eyes_20", img: "resources/customisation/character_eyes/character_eyes20.png"},
{name: "eyes_21", img: "resources/customisation/character_eyes/character_eyes21.png"},
{name: "eyes_22", img: "resources/customisation/character_eyes/character_eyes22.png"},
{name: "eyes_23", img: "resources/customisation/character_eyes/character_eyes23.png"},
{name: "eyes_24", img: "resources/customisation/character_eyes/character_eyes24.png"},
{name: "eyes_25", img: "resources/customisation/character_eyes/character_eyes25.png"},
{name: "eyes_26", img: "resources/customisation/character_eyes/character_eyes26.png"},
{name: "eyes_27", img: "resources/customisation/character_eyes/character_eyes27.png"},
{name: "eyes_28", img: "resources/customisation/character_eyes/character_eyes28.png"},
{name: "eyes_29", img: "resources/customisation/character_eyes/character_eyes29.png"},
{name: "eyes_30", img: "resources/customisation/character_eyes/character_eyes30.png"}
]
export const HAIR_RESOURCES: Array<BodyResourceDescriptionInterface> = [
{name:"hair_1", img: "resources/customisation/character_hairs/character_hairs0.png"},
{name:"hair_2", img: "resources/customisation/character_hairs/character_hairs1.png"},
{name:"hair_3", img: "resources/customisation/character_hairs/character_hairs2.png"},
{name:"hair_4", img: "resources/customisation/character_hairs/character_hairs3.png"},
{name:"hair_5", img: "resources/customisation/character_hairs/character_hairs4.png"},
{name:"hair_6", img: "resources/customisation/character_hairs/character_hairs5.png"},
{name:"hair_7", img: "resources/customisation/character_hairs/character_hairs6.png"},
{name:"hair_8", img: "resources/customisation/character_hairs/character_hairs7.png"},
{name:"hair_9", img: "resources/customisation/character_hairs/character_hairs8.png"},
{name:"hair_10",img: "resources/customisation/character_hairs/character_hairs9.png"},
{name:"hair_11",img: "resources/customisation/character_hairs/character_hairs10.png"},
{name:"hair_12",img: "resources/customisation/character_hairs/character_hairs11.png"},
{name:"hair_13",img: "resources/customisation/character_hairs/character_hairs12.png"},
{name:"hair_14",img: "resources/customisation/character_hairs/character_hairs13.png"},
{name:"hair_15",img: "resources/customisation/character_hairs/character_hairs14.png"},
{name:"hair_16",img: "resources/customisation/character_hairs/character_hairs15.png"},
{name:"hair_17",img: "resources/customisation/character_hairs/character_hairs16.png"},
{name:"hair_18",img: "resources/customisation/character_hairs/character_hairs17.png"},
{name:"hair_19",img: "resources/customisation/character_hairs/character_hairs18.png"},
{name:"hair_20",img: "resources/customisation/character_hairs/character_hairs19.png"},
{name:"hair_21",img: "resources/customisation/character_hairs/character_hairs20.png"},
{name:"hair_22",img: "resources/customisation/character_hairs/character_hairs21.png"},
{name:"hair_23",img: "resources/customisation/character_hairs/character_hairs22.png"},
{name:"hair_24",img: "resources/customisation/character_hairs/character_hairs23.png"},
{name:"hair_25",img: "resources/customisation/character_hairs/character_hairs24.png"},
{name:"hair_26",img: "resources/customisation/character_hairs/character_hairs25.png"},
{name:"hair_27",img: "resources/customisation/character_hairs/character_hairs26.png"},
{name:"hair_28",img: "resources/customisation/character_hairs/character_hairs27.png"},
{name:"hair_29",img: "resources/customisation/character_hairs/character_hairs28.png"},
{name:"hair_30",img: "resources/customisation/character_hairs/character_hairs29.png"},
{name:"hair_31",img: "resources/customisation/character_hairs/character_hairs30.png"},
{name:"hair_32",img: "resources/customisation/character_hairs/character_hairs31.png"},
{name:"hair_33",img: "resources/customisation/character_hairs/character_hairs32.png"},
{name:"hair_34",img: "resources/customisation/character_hairs/character_hairs33.png"},
{name:"hair_35",img: "resources/customisation/character_hairs/character_hairs34.png"},
{name:"hair_36",img: "resources/customisation/character_hairs/character_hairs35.png"},
{name:"hair_37",img: "resources/customisation/character_hairs/character_hairs36.png"},
{name:"hair_38",img: "resources/customisation/character_hairs/character_hairs37.png"},
{name:"hair_39",img: "resources/customisation/character_hairs/character_hairs38.png"},
{name:"hair_40",img: "resources/customisation/character_hairs/character_hairs39.png"},
{name:"hair_41",img: "resources/customisation/character_hairs/character_hairs40.png"},
{name:"hair_42",img: "resources/customisation/character_hairs/character_hairs41.png"},
{name:"hair_43",img: "resources/customisation/character_hairs/character_hairs42.png"},
{name:"hair_44",img: "resources/customisation/character_hairs/character_hairs43.png"},
{name:"hair_45",img: "resources/customisation/character_hairs/character_hairs44.png"},
{name:"hair_46",img: "resources/customisation/character_hairs/character_hairs45.png"},
{name:"hair_47",img: "resources/customisation/character_hairs/character_hairs46.png"},
{name:"hair_48",img: "resources/customisation/character_hairs/character_hairs47.png"},
{name:"hair_49",img: "resources/customisation/character_hairs/character_hairs48.png"},
{name:"hair_50",img: "resources/customisation/character_hairs/character_hairs49.png"},
{name:"hair_51",img: "resources/customisation/character_hairs/character_hairs50.png"},
{name:"hair_52",img: "resources/customisation/character_hairs/character_hairs51.png"},
{name:"hair_53",img: "resources/customisation/character_hairs/character_hairs52.png"},
{name:"hair_54",img: "resources/customisation/character_hairs/character_hairs53.png"},
{name:"hair_55",img: "resources/customisation/character_hairs/character_hairs54.png"},
{name:"hair_56",img: "resources/customisation/character_hairs/character_hairs55.png"},
{name:"hair_57",img: "resources/customisation/character_hairs/character_hairs56.png"},
{name:"hair_58",img: "resources/customisation/character_hairs/character_hairs57.png"},
{name:"hair_59",img: "resources/customisation/character_hairs/character_hairs58.png"},
{name:"hair_60",img: "resources/customisation/character_hairs/character_hairs59.png"},
{name:"hair_61",img: "resources/customisation/character_hairs/character_hairs60.png"},
{name:"hair_62",img: "resources/customisation/character_hairs/character_hairs61.png"},
{name:"hair_63",img: "resources/customisation/character_hairs/character_hairs62.png"},
{name:"hair_64",img: "resources/customisation/character_hairs/character_hairs63.png"},
{name:"hair_65",img: "resources/customisation/character_hairs/character_hairs64.png"},
{name:"hair_66",img: "resources/customisation/character_hairs/character_hairs65.png"},
{name:"hair_67",img: "resources/customisation/character_hairs/character_hairs66.png"},
{name:"hair_68",img: "resources/customisation/character_hairs/character_hairs67.png"},
{name:"hair_69",img: "resources/customisation/character_hairs/character_hairs68.png"},
{name:"hair_70",img: "resources/customisation/character_hairs/character_hairs69.png"},
{name:"hair_71",img: "resources/customisation/character_hairs/character_hairs70.png"},
{name:"hair_72",img: "resources/customisation/character_hairs/character_hairs71.png"},
{name:"hair_73",img: "resources/customisation/character_hairs/character_hairs72.png"},
{name:"hair_74",img: "resources/customisation/character_hairs/character_hairs73.png"}
];
export const CLOTHES_RESOURCES: Array<BodyResourceDescriptionInterface> = [
{name:"clothes_1", img: "resources/customisation/character_clothes/character_clothes0.png"},
{name:"clothes_2", img: "resources/customisation/character_clothes/character_clothes1.png"},
{name:"clothes_3", img: "resources/customisation/character_clothes/character_clothes2.png"},
{name:"clothes_4", img: "resources/customisation/character_clothes/character_clothes3.png"},
{name:"clothes_5", img: "resources/customisation/character_clothes/character_clothes4.png"},
{name:"clothes_6", img: "resources/customisation/character_clothes/character_clothes5.png"},
{name:"clothes_7", img: "resources/customisation/character_clothes/character_clothes6.png"},
{name:"clothes_8", img: "resources/customisation/character_clothes/character_clothes7.png"},
{name:"clothes_9", img: "resources/customisation/character_clothes/character_clothes8.png"},
{name:"clothes_10",img: "resources/customisation/character_clothes/character_clothes9.png"},
{name:"clothes_11",img: "resources/customisation/character_clothes/character_clothes10.png"},
{name:"clothes_12",img: "resources/customisation/character_clothes/character_clothes11.png"},
{name:"clothes_13",img: "resources/customisation/character_clothes/character_clothes12.png"},
{name:"clothes_14",img: "resources/customisation/character_clothes/character_clothes13.png"},
{name:"clothes_15",img: "resources/customisation/character_clothes/character_clothes14.png"},
{name:"clothes_16",img: "resources/customisation/character_clothes/character_clothes15.png"},
{name:"clothes_17",img: "resources/customisation/character_clothes/character_clothes16.png"},
{name:"clothes_18",img: "resources/customisation/character_clothes/character_clothes17.png"},
{name:"clothes_19",img: "resources/customisation/character_clothes/character_clothes18.png"},
{name:"clothes_20",img: "resources/customisation/character_clothes/character_clothes19.png"},
{name:"clothes_21",img: "resources/customisation/character_clothes/character_clothes20.png"},
{name:"clothes_22",img: "resources/customisation/character_clothes/character_clothes21.png"},
{name:"clothes_23",img: "resources/customisation/character_clothes/character_clothes22.png"},
{name:"clothes_24",img: "resources/customisation/character_clothes/character_clothes23.png"},
{name:"clothes_25",img: "resources/customisation/character_clothes/character_clothes24.png"},
{name:"clothes_26",img: "resources/customisation/character_clothes/character_clothes25.png"},
{name:"clothes_27",img: "resources/customisation/character_clothes/character_clothes26.png"},
{name:"clothes_28",img: "resources/customisation/character_clothes/character_clothes27.png"},
{name:"clothes_29",img: "resources/customisation/character_clothes/character_clothes28.png"},
{name:"clothes_30",img: "resources/customisation/character_clothes/character_clothes29.png"},
{name:"clothes_31",img: "resources/customisation/character_clothes/character_clothes30.png"},
{name:"clothes_32",img: "resources/customisation/character_clothes/character_clothes31.png"},
{name:"clothes_33",img: "resources/customisation/character_clothes/character_clothes32.png"},
{name:"clothes_34",img: "resources/customisation/character_clothes/character_clothes33.png"},
{name:"clothes_35",img: "resources/customisation/character_clothes/character_clothes34.png"},
{name:"clothes_36",img: "resources/customisation/character_clothes/character_clothes35.png"},
{name:"clothes_37",img: "resources/customisation/character_clothes/character_clothes36.png"},
{name:"clothes_38",img: "resources/customisation/character_clothes/character_clothes37.png"},
{name:"clothes_39",img: "resources/customisation/character_clothes/character_clothes38.png"},
{name:"clothes_40",img: "resources/customisation/character_clothes/character_clothes39.png"},
{name:"clothes_41",img: "resources/customisation/character_clothes/character_clothes40.png"},
{name:"clothes_42",img: "resources/customisation/character_clothes/character_clothes41.png"},
{name:"clothes_43",img: "resources/customisation/character_clothes/character_clothes42.png"},
{name:"clothes_44",img: "resources/customisation/character_clothes/character_clothes43.png"},
{name:"clothes_45",img: "resources/customisation/character_clothes/character_clothes44.png"},
{name:"clothes_46",img: "resources/customisation/character_clothes/character_clothes45.png"},
{name:"clothes_47",img: "resources/customisation/character_clothes/character_clothes46.png"},
{name:"clothes_48",img: "resources/customisation/character_clothes/character_clothes47.png"},
{name:"clothes_49",img: "resources/customisation/character_clothes/character_clothes48.png"},
{name:"clothes_50",img: "resources/customisation/character_clothes/character_clothes49.png"},
{name:"clothes_51",img: "resources/customisation/character_clothes/character_clothes50.png"},
{name:"clothes_52",img: "resources/customisation/character_clothes/character_clothes51.png"},
{name:"clothes_53",img: "resources/customisation/character_clothes/character_clothes52.png"},
{name:"clothes_54",img: "resources/customisation/character_clothes/character_clothes53.png"},
{name:"clothes_55",img: "resources/customisation/character_clothes/character_clothes54.png"},
{name:"clothes_56",img: "resources/customisation/character_clothes/character_clothes55.png"},
{name:"clothes_57",img: "resources/customisation/character_clothes/character_clothes56.png"},
{name:"clothes_58",img: "resources/customisation/character_clothes/character_clothes57.png"},
{name:"clothes_59",img: "resources/customisation/character_clothes/character_clothes58.png"},
{name:"clothes_60",img: "resources/customisation/character_clothes/character_clothes59.png"},
{name:"clothes_61",img: "resources/customisation/character_clothes/character_clothes60.png"},
{name:"clothes_62",img: "resources/customisation/character_clothes/character_clothes61.png"},
{name:"clothes_63",img: "resources/customisation/character_clothes/character_clothes62.png"},
{name:"clothes_64",img: "resources/customisation/character_clothes/character_clothes63.png"},
{name:"clothes_65",img: "resources/customisation/character_clothes/character_clothes64.png"},
{name:"clothes_66",img: "resources/customisation/character_clothes/character_clothes65.png"},
{name:"clothes_67",img: "resources/customisation/character_clothes/character_clothes66.png"},
{name:"clothes_68",img: "resources/customisation/character_clothes/character_clothes67.png"},
{name:"clothes_69",img: "resources/customisation/character_clothes/character_clothes68.png"},
{name:"clothes_70",img: "resources/customisation/character_clothes/character_clothes69.png"},
{name:"clothes_pride_shirt",img: "resources/customisation/character_clothes/pride_shirt.png"},
{name:"clothes_black_hoodie",img: "resources/customisation/character_clothes/black_hoodie.png"},
{name:"clothes_white_hoodie",img: "resources/customisation/character_clothes/white_hoodie.png"},
{name:"clothes_engelbert",img: "resources/customisation/character_clothes/engelbert.png"}
];
export const HATS_RESOURCES: Array<BodyResourceDescriptionInterface> = [
{name: "hats_1", img: "resources/customisation/character_hats/character_hats1.png"},
{name: "hats_2", img: "resources/customisation/character_hats/character_hats2.png"},
{name: "hats_3", img: "resources/customisation/character_hats/character_hats3.png"},
{name: "hats_4", img: "resources/customisation/character_hats/character_hats4.png"},
{name: "hats_5", img: "resources/customisation/character_hats/character_hats5.png"},
{name: "hats_6", img: "resources/customisation/character_hats/character_hats6.png"},
{name: "hats_7", img: "resources/customisation/character_hats/character_hats7.png"},
{name: "hats_8", img: "resources/customisation/character_hats/character_hats8.png"},
{name: "hats_9", img: "resources/customisation/character_hats/character_hats9.png"},
{name: "hats_10", img: "resources/customisation/character_hats/character_hats10.png"},
{name: "hats_11", img: "resources/customisation/character_hats/character_hats11.png"},
{name: "hats_12", img: "resources/customisation/character_hats/character_hats12.png"},
{name: "hats_13", img: "resources/customisation/character_hats/character_hats13.png"},
{name: "hats_14", img: "resources/customisation/character_hats/character_hats14.png"},
{name: "hats_15", img: "resources/customisation/character_hats/character_hats15.png"},
{name: "hats_16", img: "resources/customisation/character_hats/character_hats16.png"},
{name: "hats_17", img: "resources/customisation/character_hats/character_hats17.png"},
{name: "hats_18", img: "resources/customisation/character_hats/character_hats18.png"},
{name: "hats_19", img: "resources/customisation/character_hats/character_hats19.png"},
{name: "hats_20", img: "resources/customisation/character_hats/character_hats20.png"},
{name: "hats_21", img: "resources/customisation/character_hats/character_hats21.png"},
{name: "hats_22", img: "resources/customisation/character_hats/character_hats22.png"},
{name: "hats_23", img: "resources/customisation/character_hats/character_hats23.png"},
{name: "hats_24", img: "resources/customisation/character_hats/character_hats24.png"},
{name: "hats_25", img: "resources/customisation/character_hats/character_hats25.png"},
{name: "hats_26", img: "resources/customisation/character_hats/character_hats26.png"},
{name: "tinfoil_hat1", img: "resources/customisation/character_hats/tinfoil_hat1.png"}
];
export const ACCESSORIES_RESOURCES: Array<BodyResourceDescriptionInterface> = [
{name: "accessory_1", img: "resources/customisation/character_accessories/character_accessories1.png"},
{name: "accessory_2", img: "resources/customisation/character_accessories/character_accessories2.png"},
{name: "accessory_3", img: "resources/customisation/character_accessories/character_accessories3.png"},
{name: "accessory_4", img: "resources/customisation/character_accessories/character_accessories4.png"},
{name: "accessory_5", img: "resources/customisation/character_accessories/character_accessories5.png"},
{name: "accessory_6", img: "resources/customisation/character_accessories/character_accessories6.png"},
{name: "accessory_7", img: "resources/customisation/character_accessories/character_accessories7.png"},
{name: "accessory_8", img: "resources/customisation/character_accessories/character_accessories8.png"},
{name: "accessory_9", img: "resources/customisation/character_accessories/character_accessories9.png"},
{name: "accessory_10", img: "resources/customisation/character_accessories/character_accessories10.png"},
{name: "accessory_11", img: "resources/customisation/character_accessories/character_accessories11.png"},
{name: "accessory_12", img: "resources/customisation/character_accessories/character_accessories12.png"},
{name: "accessory_13", img: "resources/customisation/character_accessories/character_accessories13.png"},
{name: "accessory_14", img: "resources/customisation/character_accessories/character_accessories14.png"},
{name: "accessory_15", img: "resources/customisation/character_accessories/character_accessories15.png"},
{name: "accessory_16", img: "resources/customisation/character_accessories/character_accessories16.png"},
{name: "accessory_17", img: "resources/customisation/character_accessories/character_accessories17.png"},
{name: "accessory_18", img: "resources/customisation/character_accessories/character_accessories18.png"},
{name: "accessory_19", img: "resources/customisation/character_accessories/character_accessories19.png"},
{name: "accessory_20", img: "resources/customisation/character_accessories/character_accessories20.png"},
{name: "accessory_21", img: "resources/customisation/character_accessories/character_accessories21.png"},
{name: "accessory_22", img: "resources/customisation/character_accessories/character_accessories22.png"},
{name: "accessory_23", img: "resources/customisation/character_accessories/character_accessories23.png"},
{name: "accessory_24", img: "resources/customisation/character_accessories/character_accessories24.png"},
{name: "accessory_25", img: "resources/customisation/character_accessories/character_accessories25.png"},
{name: "accessory_26", img: "resources/customisation/character_accessories/character_accessories26.png"},
{name: "accessory_27", img: "resources/customisation/character_accessories/character_accessories27.png"},
{name: "accessory_28", img: "resources/customisation/character_accessories/character_accessories28.png"},
{name: "accessory_29", img: "resources/customisation/character_accessories/character_accessories29.png"},
{name: "accessory_30", img: "resources/customisation/character_accessories/character_accessories30.png"},
{name: "accessory_31", img: "resources/customisation/character_accessories/character_accessories31.png"},
{name: "accessory_32", img: "resources/customisation/character_accessories/character_accessories32.png"},
{name: "accessory_mate_bottle", img: "resources/customisation/character_accessories/mate_bottle1.png"},
{name: "accessory_mask", img: "resources/customisation/character_accessories/mask.png"}
];
export const LAYERS: Array<Array<BodyResourceDescriptionInterface>> = [
COLOR_RESOURCES,
EYES_RESOURCES,
HAIR_RESOURCES,
CLOTHES_RESOURCES,
HATS_RESOURCES,
ACCESSORIES_RESOURCES
];
export const loadAllLayers = (load: LoaderPlugin) => {
for (let j = 0; j < LAYERS.length; j++) {
for (let i = 0; i < LAYERS[j].length; i++) {
load.spritesheet(
LAYERS[j][i].name,
LAYERS[j][i].img,
{frameWidth: 32, frameHeight: 32}
)
}
}
}
export const loadCustomTexture = (load: LoaderPlugin, texture: CharacterTexture) => {
const name = 'customCharacterTexture'+texture.id;
load.spritesheet(
name,
texture.url,
{frameWidth: 32, frameHeight: 32}
);
}
export const OBJECTS: Array<PlayerResourceDescriptionInterface> = [
{name:'layout_modes', img:'resources/objects/layout_modes.png'},
{name:'teleportation', img:'resources/objects/teleportation.png'},
];
export const loadObject = (load: LoaderPlugin) => {
for (let j = 0; j < OBJECTS.length; j++) {
load.spritesheet(
OBJECTS[j].name,
OBJECTS[j].img,
{frameWidth: 32, frameHeight: 32}
)
}
}
export const loadPlayerCharacters = (load: LoaderPlugin) => {
PLAYER_RESOURCES.forEach((playerResource: PlayerResourceDescriptionInterface) => {
load.spritesheet(
playerResource.name,
playerResource.img,
{frameWidth: 32, frameHeight: 32}
);
});
}

View File

@ -1,5 +1,5 @@
import {PointInterface} from "../../Connexion/ConnexionModels";
import {BodyResourceDescriptionInterface} from "../Entity/body_character";
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
export interface AddPlayerInterface {
userId: number;

View File

@ -55,7 +55,10 @@ export class GameManager {
return this.playerName;
}
getCharacterLayers(): string[]|null {
getCharacterLayers(): string[] {
if (!this.characterLayers) {
throw 'characterLayers are not set';
}
return this.characterLayers;
}

View File

@ -30,7 +30,7 @@ import {RemotePlayer} from "../Entity/RemotePlayer";
import {Queue} from 'queue-typescript';
import {SimplePeer, UserSimplePeerInterface} from "../../WebRtc/SimplePeer";
import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene";
import {loadAllLayers, loadObject, loadPlayerCharacters} from "../Entity/body_character";
import {lazyLoadPlayerCharacterTextures, loadCustomTexture} from "../Entity/PlayerTexturesLoadingManager";
import {
CenterListener,
layoutManager,
@ -45,7 +45,6 @@ import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR;
import {GameMap} from "./GameMap";
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
import {mediaManager} from "../../WebRtc/MediaManager";
import {FourOFourSceneName} from "../Reconnecting/FourOFourScene";
import {ItemFactoryInterface} from "../Items/ItemFactoryInterface";
import {ActionableItem} from "../Items/ActionableItem";
import {UserInputManager} from "../UserInput/UserInputManager";
@ -66,7 +65,10 @@ import {ChatModeIcon} from "../Components/ChatModeIcon";
import {OpenChatIcon, openChatIconName} from "../Components/OpenChatIcon";
import {SelectCharacterScene, SelectCharacterSceneName} from "../Login/SelectCharacterScene";
import {TextureError} from "../../Exception/TextureError";
import {TextField} from "../Components/TextField";
import {addLoader} from "../Components/Loader";
import {ErrorSceneName} from "../Reconnecting/ErrorScene";
import {localUserStore} from "../../Connexion/LocalUserStore";
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
export interface GameSceneInitInterface {
initPosition: PointInterface|null,
@ -111,7 +113,7 @@ export class GameScene extends ResizableScene implements CenterListener {
MapPlayers!: Phaser.Physics.Arcade.Group;
MapPlayersByKey : Map<number, RemotePlayer> = new Map<number, RemotePlayer>();
Map!: Phaser.Tilemaps.Tilemap;
Layers!: Array<Phaser.Tilemaps.TilemapLayer>;
Layers!: Array<Phaser.Tilemaps.StaticTilemapLayer>;
Objects!: Array<Phaser.Physics.Arcade.Sprite>;
mapFile!: ITiledMap;
groups: Map<number, Sprite>;
@ -181,12 +183,22 @@ export class GameScene extends ResizableScene implements CenterListener {
//hook preload scene
preload(): void {
this.initProgressBar();
addLoader(this);
const localUser = localUserStore.getLocalUser();
const textures = localUser?.textures;
if (textures) {
for (const texture of textures) {
loadCustomTexture(this.load, texture);
}
}
this.load.image(openChatIconName, 'resources/objects/talk.png');
this.load.on(FILE_LOAD_ERROR, (file: {src: string}) => {
this.scene.start(FourOFourSceneName, {
file: file.src
this.scene.start(ErrorSceneName, {
title: 'Network error',
subTitle: 'An error occurred while loading resource:',
message: file.src
});
});
this.load.on('filecomplete-tilemapJSON-'+this.MapUrlFile, (key: string, type: string, data: unknown) => {
@ -201,28 +213,10 @@ export class GameScene extends ResizableScene implements CenterListener {
this.onMapLoad(data);
}
//add player png
loadPlayerCharacters(this.load);
loadAllLayers(this.load);
loadObject(this.load);
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');
}
private initProgressBar(): void {
const loadingText = this.add.text(this.game.renderer.width / 2, 200, 'Loading');
const progress = this.add.graphics();
this.load.on('progress', (value: number) => {
progress.clear();
progress.fillStyle(0xffffff, 1);
progress.fillRect(0, 270, 800 * value, 60);
});
this.load.on('complete', () => {
loadingText.destroy();
progress.destroy();
});
}
// FIXME: we need to put a "unknown" instead of a "any" and validate the structure of the JSON we are receiving.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private async onMapLoad(data: any): Promise<void> {
@ -340,11 +334,7 @@ export class GameScene extends ResizableScene implements CenterListener {
throw 'playerName is not set';
}
this.playerName = playerName;
const characterLayers = gameManager.getCharacterLayers();
if (!characterLayers) {
throw 'characterLayers are not set';
}
this.characterLayers = characterLayers;
this.characterLayers = gameManager.getCharacterLayers();
//initalise map
@ -359,11 +349,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.TilemapLayer>();
this.Layers = new Array<Phaser.Tilemaps.StaticTilemapLayer>();
let depth = -2;
for (const layer of this.mapFile.layers) {
if (layer.type === 'tilelayer') {
this.addLayer(this.Map.createLayer(layer.name, this.Terrains, 0, 0).setDepth(depth));
this.addLayer(this.Map.createStaticLayer(layer.name, this.Terrains, 0, 0).setDepth(depth));
const exitSceneUrl = this.getExitSceneUrl(layer);
if (exitSceneUrl !== undefined) {
@ -544,6 +534,7 @@ export class GameScene extends ResizableScene implements CenterListener {
this.simplePeer = new SimplePeer(this.connection, !this.room.isPublic, this.playerName);
this.GlobalMessageManager = new GlobalMessageManager(this.connection);
this.UserMessageManager = new UserMessageManager(this.connection);
this.UserMessageManager.setReceiveBanListener(this.bannedUser.bind(this));
const self = this;
this.simplePeer.registerPeerConnectionListener({
@ -620,8 +611,18 @@ export class GameScene extends ResizableScene implements CenterListener {
if (url === undefined) {
audioManager.unloadAudio();
} else {
const mapDirUrl = this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf('/'));
const realAudioPath = mapDirUrl + '/' + url;
const audioPath = url as string;
let realAudioPath = '';
if (audioPath.indexOf('://') > 0) {
// remote file or stream
realAudioPath = audioPath;
} else {
// local file, include it relative to map directory
const mapDirUrl = this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf('/'));
realAudioPath = mapDirUrl + '/' + url;
}
audioManager.loadAudio(realAudioPath);
if (loop) {
@ -718,6 +719,10 @@ export class GameScene extends ResizableScene implements CenterListener {
}
public cleanupClosingScene(): void {
// stop playing audio, close any open website, stop any open Jitsi
coWebsiteManager.closeCoWebsite();
this.stopJitsi();
this.playAudio(undefined);
// We are completely destroying the current scene to avoid using a half-backed instance when coming back to the same map.
if(this.connection) {
this.connection.closeConnection();
@ -854,13 +859,13 @@ export class GameScene extends ResizableScene implements CenterListener {
this.cameras.main.setZoom(ZOOM_LEVEL);
}
addLayer(Layer : Phaser.Tilemaps.TilemapLayer){
addLayer(Layer : Phaser.Tilemaps.StaticTilemapLayer){
this.Layers.push(Layer);
}
createCollisionWithPlayer() {
//add collision layer
this.Layers.forEach((Layer: Phaser.Tilemaps.TilemapLayer) => {
this.Layers.forEach((Layer: Phaser.Tilemaps.StaticTilemapLayer) => {
this.physics.add.collider(this.CurrentPlayer, Layer, (object1: GameObject, object2: GameObject) => {
//this.CurrentPlayer.say("Collision with layer : "+ (object2 as Tile).layer.name)
});
@ -877,15 +882,15 @@ export class GameScene extends ResizableScene implements CenterListener {
}
createCurrentPlayer(){
//initialise player
//TODO create animation moving between exit and start
const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, this.characterLayers);
try {
this.CurrentPlayer = new Player(
this,
this.startX,
this.startY,
this.playerName,
this.characterLayers,
texturesPromise,
PlayerAnimationNames.WalkDown,
false,
this.userInputManager
@ -1062,10 +1067,7 @@ export class GameScene extends ResizableScene implements CenterListener {
});
}
/**
* Create new player
*/
private async doAddPlayer(addPlayerData : AddPlayerInterface) : Promise<void> {
private doAddPlayer(addPlayerData : AddPlayerInterface): void {
//check if exist player, if exist, move position
if(this.MapPlayersByKey.has(addPlayerData.userId)){
this.updatePlayerPosition({
@ -1074,39 +1076,21 @@ export class GameScene extends ResizableScene implements CenterListener {
});
return;
}
// Load textures (in case it is a custom texture)
const characterLayerList: string[] = [];
const loadPromises: Promise<void>[] = [];
for (const characterLayer of addPlayerData.characterLayers) {
characterLayerList.push(characterLayer.name);
if (characterLayer.img) {
console.log('LOADING ', characterLayer.name, characterLayer.img)
loadPromises.push(this.loadSpritesheet(characterLayer.name, characterLayer.img));
}
}
if (loadPromises.length > 0) {
this.load.start();
}
//initialise player
const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, addPlayerData.characterLayers);
const player = new RemotePlayer(
addPlayerData.userId,
this,
addPlayerData.position.x,
addPlayerData.position.y,
addPlayerData.name,
[], // Let's go with no textures and let's load textures when promises have returned.
texturesPromise,
addPlayerData.position.direction,
addPlayerData.position.moving
);
this.MapPlayers.add(player);
this.MapPlayersByKey.set(player.userId, player);
player.updatePosition(addPlayerData.position);
await Promise.all(loadPromises);
player.addTextures(characterLayerList, 1);
}
/**
@ -1262,23 +1246,14 @@ export class GameScene extends ResizableScene implements CenterListener {
mediaManager.removeTriggerCloseJitsiFrameButton('close-jisi');
}
private loadSpritesheet(name: string, url: string): Promise<void> {
return new Promise<void>(((resolve, reject) => {
if (this.textures.exists(name)) {
resolve();
return;
}
this.load.spritesheet(
name,
url,
{frameWidth: 32, frameHeight: 32}
);
this.load.on('filecomplete-spritesheet-'+name, () => {
console.log('RESOURCE LOADED!');
resolve();
});
}))
private bannedUser(){
this.cleanupClosingScene();
this.userInputManager.clearAllInputKeyboard();
this.scene.start(ErrorSceneName, {
title: 'Banned',
subTitle: 'You was banned of WorkAdventure',
message: 'If you want more information, you can contact us: workadventure@thecodingmachine.com'
});
}
}

View File

@ -42,8 +42,11 @@ export class ActionableItem {
return;
}
this.isSelectable = true;
this.sprite.setPipeline(OutlinePipeline.KEY);
this.sprite.pipeline.set2f('uTextureSize', this.sprite.texture.getSourceImage().width, this.sprite.texture.getSourceImage().height);
if (this.sprite.pipeline) {
// Commented out to try to fix MacOS issue
/*this.sprite.setPipeline(OutlinePipeline.KEY);
this.sprite.pipeline.set2f('uTextureSize', this.sprite.texture.getSourceImage().width, this.sprite.texture.getSourceImage().height);*/
}
}
/**
@ -54,7 +57,8 @@ export class ActionableItem {
return;
}
this.isSelectable = false;
this.sprite.resetPipeline();
// Commented out to try to fix MacOS issue
//this.sprite.resetPipeline();
}
/**

View File

@ -0,0 +1,41 @@
import {ResizableScene} from "./ResizableScene";
import {localUserStore} from "../../Connexion/LocalUserStore";
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
import {loadCustomTexture} from "../Entity/PlayerTexturesLoadingManager";
import {CharacterTexture} from "../../Connexion/LocalUser";
export abstract class AbstractCharacterScene extends ResizableScene {
loadCustomSceneSelectCharacters() : Promise<BodyResourceDescriptionInterface[]> {
const textures = this.getTextures();
const promises : Promise<BodyResourceDescriptionInterface>[] = [];
if (textures) {
for (const texture of textures) {
if (texture.level === -1) {
continue;
}
promises.push(loadCustomTexture(this.load, texture));
}
}
return Promise.all(promises)
}
loadSelectSceneCharacters() : Promise<BodyResourceDescriptionInterface[]> {
const textures = this.getTextures();
const promises: Promise<BodyResourceDescriptionInterface>[] = [];
if (textures) {
for (const texture of textures) {
if (texture.level !== -1) {
continue;
}
promises.push(loadCustomTexture(this.load, texture));
}
}
return Promise.all(promises)
}
private getTextures() : CharacterTexture[]|undefined{
const localUser = localUserStore.getLocalUser();
return localUser?.textures;
}
}

View File

@ -2,15 +2,15 @@ import {EnableCameraSceneName} from "./EnableCameraScene";
import {TextField} from "../Components/TextField";
import Image = Phaser.GameObjects.Image;
import Rectangle = Phaser.GameObjects.Rectangle;
import {BodyResourceDescriptionInterface, LAYERS, loadAllLayers, loadCustomTexture} from "../Entity/body_character";
import {loadAllLayers, loadCustomTexture} 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 {PlayerResourceDescriptionInterface} from "../Entity/Character";
import {SelectCharacterSceneName} from "./SelectCharacterScene";
import {LoginSceneName} from "./LoginScene";
import {addLoader} from "../Components/Loader";
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
import {AbstractCharacterScene} from "./AbstractCharacterScene";
export const CustomizeSceneName = "CustomizeScene";
@ -21,7 +21,7 @@ enum CustomizeTextures{
arrowUp = "arrow_up",
}
export class CustomizeScene extends ResizableScene {
export class CustomizeScene extends AbstractCharacterScene {
private textField!: TextField;
private enterField!: TextField;
@ -48,30 +48,22 @@ export class CustomizeScene extends ResizableScene {
}
preload() {
addLoader(this);
this.layers = loadAllLayers(this.load);
this.loadCustomSceneSelectCharacters().then((bodyResourceDescriptions) => {
bodyResourceDescriptions.forEach((bodyResourceDescription) => {
if(!bodyResourceDescription.level){
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');
//load all the png files
loadAllLayers(this.load);
// load custom layers
this.layers = LAYERS;
const localUser = localUserStore.getLocalUser();
const textures = localUser?.textures;
if (textures) {
for (const texture of textures) {
loadCustomTexture(this.load, texture);
const name = 'customCharacterTexture'+texture.id;
this.layers[texture.level].unshift({
name,
img: texture.url
});
}
}
}
create() {

View File

@ -253,6 +253,8 @@ export class EnableCameraScene extends Phaser.Scene {
this.pressReturnField.setVisible(!!(Math.floor(time / 500) % 2));
this.soundMeterSprite.setVolume(this.soundMeter.getVolume());
mediaManager.setLastUpdateScene();
}
private login(): void {

View File

@ -1,7 +1,7 @@
import {gameManager} from "../Game/GameManager";
import {Scene} from "phaser";
import {LoginSceneName} from "./LoginScene";
import {FourOFourSceneName} from "../Reconnecting/FourOFourScene";
import {ErrorScene} from "../Reconnecting/ErrorScene";
import {WAError} from "../Reconnecting/WAError";
export const EntrySceneName = "EntryScene";
@ -20,10 +20,11 @@ export class EntryScene extends Scene {
gameManager.init(this.scene).then((nextSceneName) => {
this.scene.start(nextSceneName);
}).catch((err) => {
console.error(err)
this.scene.start(FourOFourSceneName, {
url: window.location.pathname.toString()
});
if (err.response && err.response.status == 404) {
ErrorScene.showError(new WAError('Page Not Found', 'Could not find map', window.location.pathname), this.scene);
} else {
ErrorScene.showError(err, this.scene);
}
});
}
}

View File

@ -2,11 +2,8 @@ import {gameManager} from "../Game/GameManager";
import {TextField} from "../Components/TextField";
import {TextInput} from "../Components/TextInput";
import Image = Phaser.GameObjects.Image;
import {PLAYER_RESOURCES, PlayerResourceDescriptionInterface} from "../Entity/Character";
import {cypressAsserter} from "../../Cypress/CypressAsserter";
import {SelectCharacterSceneName} from "./SelectCharacterScene";
import {ResizableScene} from "./ResizableScene";
import {EnableCameraSceneName} from "./EnableCameraScene";
//todo: put this constants in a dedicated file
export const LoginSceneName = "LoginScene";
@ -31,24 +28,13 @@ export class LoginScene extends ResizableScene {
}
preload() {
cypressAsserter.preloadStarted();
//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');
cypressAsserter.preloadFinished();
//add player png
PLAYER_RESOURCES.forEach((playerResource: PlayerResourceDescriptionInterface) => {
this.load.spritesheet(
playerResource.name,
playerResource.img,
{frameWidth: 32, frameHeight: 32}
);
});
}
create() {
cypressAsserter.initStarted();
this.textField = new TextField(this, this.game.renderer.width / 2, 50, 'Enter your name:');
this.nameInput = new TextInput(this, this.game.renderer.width / 2, 70, 8, this.name,(text: string) => {
@ -69,8 +55,6 @@ export class LoginScene extends ResizableScene {
}
this.login(this.name);
});
cypressAsserter.initFinished();
}
update(time: number, delta: number): void {

View File

@ -2,11 +2,14 @@ import {gameManager} from "../Game/GameManager";
import {TextField} from "../Components/TextField";
import Image = Phaser.GameObjects.Image;
import Rectangle = Phaser.GameObjects.Rectangle;
import {PLAYER_RESOURCES, PlayerResourceDescriptionInterface} from "../Entity/Character";
import {EnableCameraSceneName} from "./EnableCameraScene";
import {CustomizeSceneName} from "./CustomizeScene";
import {ResizableScene} from "./ResizableScene";
import {localUserStore} from "../../Connexion/LocalUserStore";
import {loadAllDefaultModels, loadCustomTexture} from "../Entity/PlayerTexturesLoadingManager";
import {addLoader} from "../Components/Loader";
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
import {AbstractCharacterScene} from "./AbstractCharacterScene";
//todo: put this constants in a dedicated file
@ -19,8 +22,8 @@ enum LoginTextures {
customizeButtonSelected = "customize_button_selected"
}
export class SelectCharacterScene extends ResizableScene {
private readonly nbCharactersPerRow = 4;
export class SelectCharacterScene extends AbstractCharacterScene {
private readonly nbCharactersPerRow = 6;
private textField!: TextField;
private pressReturnField!: TextField;
private logo!: Image;
@ -32,6 +35,7 @@ export class SelectCharacterScene extends ResizableScene {
private selectedRectangleYPos = 0; // Number of the character selected in the columns
private selectedPlayer!: Phaser.Physics.Arcade.Sprite|null; // null if we are selecting the "customize" option
private players: Array<Phaser.Physics.Arcade.Sprite> = new Array<Phaser.Physics.Arcade.Sprite>();
private playerModels!: BodyResourceDescriptionInterface[];
constructor() {
super({
@ -40,26 +44,30 @@ export class SelectCharacterScene extends ResizableScene {
}
preload() {
addLoader(this);
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');
//add player png
PLAYER_RESOURCES.forEach((playerResource: PlayerResourceDescriptionInterface) => {
this.load.spritesheet(
playerResource.name,
playerResource.img,
{frameWidth: 32, frameHeight: 32}
);
});
this.playerModels = loadAllDefaultModels(this.load);
this.load.image(LoginTextures.customizeButton, 'resources/objects/customize.png');
this.load.image(LoginTextures.customizeButtonSelected, 'resources/objects/customize_selected.png');
}
create() {
this.textField = new TextField(this, this.game.renderer.width / 2, 50, 'Select your character');
this.pressReturnField = new TextField(this, this.game.renderer.width / 2, 256, 'Press enter to start');
this.pressReturnField = new TextField(
this,
this.game.renderer.width / 2,
90 + 32 * Math.ceil( this.playerModels.length / this.nbCharactersPerRow) + 40,
'Press enter to start');
const rectangleXStart = this.game.renderer.width / 2 - (this.nbCharactersPerRow / 2) * 32 + 16;
@ -73,25 +81,41 @@ export class SelectCharacterScene extends ResizableScene {
});
this.input.keyboard.on('keydown-RIGHT', () => {
if (this.selectedRectangleXPos < this.nbCharactersPerRow - 1) {
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) {
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(PLAYER_RESOURCES.length / this.nbCharactersPerRow)) {
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) {
if (
this.selectedRectangleYPos > 0
&& (((this.selectedRectangleYPos - 1) * this.nbCharactersPerRow) + this.selectedRectangleXPos + 1) <= this.playerModels.length
) {
this.selectedRectangleYPos--;
}
this.updateSelectedPlayer();
@ -106,7 +130,7 @@ export class SelectCharacterScene extends ResizableScene {
this.selectedRectangleYPos = Math.floor(playerNumber / this.nbCharactersPerRow);
this.updateSelectedPlayer();
} else if (playerNumber === -1) {
this.selectedRectangleYPos = Math.ceil(PLAYER_RESOURCES.length / this.nbCharactersPerRow);
this.selectedRectangleYPos = Math.ceil(this.playerModels.length / this.nbCharactersPerRow);
this.updateSelectedPlayer();
}
}
@ -127,8 +151,8 @@ export class SelectCharacterScene extends ResizableScene {
}
createCurrentPlayer(): void {
for (let i = 0; i <PLAYER_RESOURCES.length; i++) {
const playerResource = PLAYER_RESOURCES[i];
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);
@ -151,21 +175,22 @@ export class SelectCharacterScene extends ResizableScene {
this.players.push(player);
}
this.customizeButton = new Image(this, this.game.renderer.width / 2, 90 + 32 * 4 + 6, LoginTextures.customizeButton);
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 * 4 + 6, LoginTextures.customizeButtonSelected);
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.customizeButton.setInteractive().on("pointerdown", () => {
this.selectedRectangleYPos = Math.ceil(PLAYER_RESOURCES.length / this.nbCharactersPerRow);
this.selectedRectangleYPos = Math.ceil(this.playerModels.length / this.nbCharactersPerRow);
this.updateSelectedPlayer();
});
this.selectedPlayer = this.players[0];
this.selectedPlayer.play(PLAYER_RESOURCES[0].name);
this.selectedPlayer.play(this.playerModels[0].name);
}
/**
@ -181,7 +206,7 @@ export class SelectCharacterScene extends ResizableScene {
private updateSelectedPlayer(): void {
this.selectedPlayer?.anims.pause();
// If we selected the customize button
if (this.selectedRectangleYPos === Math.ceil(PLAYER_RESOURCES.length / this.nbCharactersPerRow)) {
if (this.selectedRectangleYPos === Math.ceil(this.playerModels.length / this.nbCharactersPerRow)) {
this.selectedPlayer = null;
this.selectedRectangle.setVisible(false);
this.customizeButtonSelected.setVisible(true);
@ -198,7 +223,7 @@ export class SelectCharacterScene extends ResizableScene {
this.selectedRectangle.setSize(32, 32);
const playerNumber = this.selectedRectangleXPos + this.selectedRectangleYPos * this.nbCharactersPerRow;
const player = this.players[playerNumber];
player.play(PLAYER_RESOURCES[playerNumber].name);
player.play(this.playerModels[playerNumber].name);
this.selectedPlayer = player;
localUserStore.setPlayerCharacterIndex(playerNumber);
}
@ -211,7 +236,7 @@ export class SelectCharacterScene extends ResizableScene {
this.customizeButton.x = this.game.renderer.width / 2;
this.customizeButtonSelected.x = this.game.renderer.width / 2;
for (let i = 0; i <PLAYER_RESOURCES.length; i++) {
for (let i = 0; i <this.playerModels.length; i++) {
const player = this.players[i];
const col = i % this.nbCharactersPerRow;

View File

@ -110,6 +110,7 @@ export class MenuScene extends Phaser.Scene {
if (!this.sideMenuOpened) return;
this.sideMenuOpened = false;
this.closeAll();
this.menuButton.getChildByID('openMenuButton').innerHTML = `<img src="/static/images/menu.svg">`;
gameManager.getCurrentGameScene(this).ConsoleGlobalMessageManager.disabledMessageConsole();
this.tweens.add({
targets: this.menuElement,
@ -121,6 +122,7 @@ export class MenuScene extends Phaser.Scene {
private openGameSettingsMenu(): void {
if (this.settingsMenuOpened) {
this.closeGameQualityMenu();
return;
}
//close all
@ -278,6 +280,5 @@ export class MenuScene extends Phaser.Scene {
private closeAll(){
this.closeGameQualityMenu();
this.closeGameShare();
this.menuButton.getChildByID('openMenuButton').innerHTML = `<img src="/static/images/menu.svg">`;
}
}

View File

@ -19,12 +19,12 @@ export class Player extends Character implements CurrentGamerInterface {
x: number,
y: number,
name: string,
PlayerTextures: string[],
texturesPromise: Promise<string[]>,
direction: string,
moving: boolean,
private userInputManager: UserInputManager
) {
super(Scene, x, y, PlayerTextures, name, direction, moving, 1);
super(Scene, x, y, texturesPromise, name, direction, moving, 1);
//the current player model should be push away by other players to prevent conflict
this.getBody().setImmovable(false);

View File

@ -0,0 +1,120 @@
import {TextField} from "../Components/TextField";
import Image = Phaser.GameObjects.Image;
import Sprite = Phaser.GameObjects.Sprite;
import Text = Phaser.GameObjects.Text;
import ScenePlugin = Phaser.Scenes.ScenePlugin;
import {WAError} from "./WAError";
export const ErrorSceneName = "ErrorScene";
enum Textures {
icon = "icon",
mainFont = "main_font"
}
export class ErrorScene extends Phaser.Scene {
private titleField!: TextField;
private subTitleField!: TextField;
private messageField!: Text;
private logo!: Image;
private cat!: Sprite;
private title!: string;
private subTitle!: string;
private message!: string;
constructor() {
super({
key: ErrorSceneName
});
}
init({title, subTitle, message}: { title?: string, subTitle?: string, message?: string }) {
this.title = title ? title : '';
this.subTitle = subTitle ? subTitle : '';
this.message = message ? message : '';
}
preload() {
this.load.image(Textures.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(Textures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
this.load.spritesheet(
'cat',
'resources/characters/pipoya/Cat 01-1.png',
{frameWidth: 32, frameHeight: 32}
);
}
create() {
this.logo = new Image(this, this.game.renderer.width - 30, this.game.renderer.height - 20, Textures.icon);
this.add.existing(this.logo);
this.titleField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height / 2, this.title);
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, {
fontFamily: 'Georgia, "Goudy Bookletter 1911", Times, serif',
fontSize: '10px'
});
this.messageField.setOrigin(0.5, 0.5);
this.cat = this.physics.add.sprite(this.game.renderer.width / 2, this.game.renderer.height / 2 - 32, 'cat', 6);
this.cat.flipY = true;
}
/**
* Displays the error page, with an error message matching the "error" parameters passed in.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public static showError(error: any, scene: ScenePlugin): void {
console.error(error);
if (typeof error === 'string' || error instanceof String) {
scene.start(ErrorSceneName, {
title: 'An error occurred',
subTitle: error
});
} else if (error instanceof WAError) {
scene.start(ErrorSceneName, {
title: error.title,
subTitle: error.subTitle,
message: error.details
});
} else if (error.response) {
// Axios HTTP error
// client received an error response (5xx, 4xx)
scene.start(ErrorSceneName, {
title: 'HTTP ' + error.response.status + ' - ' + error.response.statusText,
subTitle: 'An error occurred while accessing URL:',
message: error.response.config.url
});
} else if (error.request) {
// Axios HTTP error
// client never received a response, or request never left
scene.start(ErrorSceneName, {
title: 'Network error',
subTitle: error.message
});
} else if (error instanceof Error) {
// Error
scene.start(ErrorSceneName, {
title: 'An error occurred',
subTitle: error.name,
message: error.message
});
} else {
throw error;
}
}
/**
* Displays the error page, with an error message matching the "error" parameters passed in.
*/
public static startErrorPage(title: string, subTitle: string, message: string, scene: ScenePlugin): void {
scene.start(ErrorSceneName, {
title,
subTitle,
message
});
}
}

View File

@ -1,68 +0,0 @@
import {TextField} from "../Components/TextField";
import Image = Phaser.GameObjects.Image;
import Sprite = Phaser.GameObjects.Sprite;
import Text = Phaser.GameObjects.Text;
export const FourOFourSceneName = "FourOFourScene";
enum Textures {
icon = "icon",
mainFont = "main_font"
}
export class FourOFourScene extends Phaser.Scene {
private mapNotFoundField!: TextField;
private couldNotFindField!: TextField;
private fileNameField!: Text;
private logo!: Image;
private cat!: Sprite;
private file: string|undefined;
private url: string|undefined;
constructor() {
super({
key: FourOFourSceneName
});
}
init({ file, url }: { file?: string, url?: string }) {
this.file = file;
this.url = url;
}
preload() {
this.load.image(Textures.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(Textures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
this.load.spritesheet(
'cat',
'resources/characters/pipoya/Cat 01-1.png',
{frameWidth: 32, frameHeight: 32}
);
}
create() {
this.logo = new Image(this, this.game.renderer.width - 30, this.game.renderer.height - 20, Textures.icon);
this.add.existing(this.logo);
this.mapNotFoundField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height / 2, "404 - File not found");
let text: string = '';
if (this.file !== undefined) {
text = "Could not load map"
}
if (this.url !== undefined) {
text = "Invalid URL"
}
this.couldNotFindField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height / 2 + 24, text);
const url = this.file ? this.file : this.url;
if (url !== undefined) {
this.fileNameField = this.add.text(this.game.renderer.width / 2, this.game.renderer.height / 2 + 38, url, { fontFamily: 'Georgia, "Goudy Bookletter 1911", Times, serif', fontSize: '10px' });
this.fileNameField.setOrigin(0.5, 0.5);
}
this.cat = this.physics.add.sprite(this.game.renderer.width / 2, this.game.renderer.height / 2 - 32, 'cat', 6);
this.cat.flipY=true;
}
}

View File

@ -0,0 +1,26 @@
export class WAError extends Error {
private _title: string;
private _subTitle: string;
private _details: string;
constructor (title: string, subTitle: string, details: string) {
super(title+' - '+subTitle+' - '+details);
this._title = title;
this._subTitle = subTitle;
this._details = details;
// Set the prototype explicitly.
Object.setPrototypeOf (this, WAError.prototype);
}
get title(): string {
return this._title;
}
get subTitle(): string {
return this._subTitle;
}
get details(): string {
return this._details;
}
}

View File

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

View File

@ -60,7 +60,7 @@ export class UserInputManager {
}
clearAllInputKeyboard(){
this.Scene.input.keyboard.removeAllKeys();
this.Scene.input.keyboard.removeAllListeners();
}
getEventListForGameTick(): ActiveEventList {

View File

@ -32,20 +32,6 @@ class UrlManager {
return match ? match [1] : null;
}
//todo: simply use the roomId
//todo: test this with cypress
public editUrlForRoom(roomSlug: string, organizationSlug: string|null, worldSlug: string |null): string {
let newUrl:string;
if (organizationSlug) {
newUrl = '/@/'+organizationSlug+'/'+worldSlug+'/'+roomSlug;
} else {
newUrl = '/_/global/'+roomSlug;
}
history.pushState({}, 'WorkAdventure', newUrl);
return newUrl;
}
public pushRoomIdToUrl(room:Room): void {
if (window.location.pathname === room.id) return;
const hash = window.location.hash;

View File

@ -59,6 +59,16 @@ export class DiscussionManager {
const sendDivMessage: HTMLDivElement = document.createElement('div');
sendDivMessage.classList.add('send-message');
const inputMessage: HTMLInputElement = document.createElement('input');
inputMessage.onfocus = () => {
if(this.userInputManager) {
this.userInputManager.clearAllInputKeyboard();
}
}
inputMessage.onblur = () => {
if(this.userInputManager) {
this.userInputManager.initKeyBoardEvent();
}
}
inputMessage.type = "text";
inputMessage.addEventListener('keyup', (event: KeyboardEvent) => {
if (event.key === 'Enter') {
@ -151,7 +161,7 @@ export class DiscussionManager {
const pMessage: HTMLParagraphElement = document.createElement('p');
const date = new Date();
if(isMe){
name = 'Moi';
name = 'Me';
}
pMessage.innerHTML = `<span style="font-weight: bold">${name}</span>
<span style="color:#bac2cc;display:inline-block;font-size:12px;">
@ -160,11 +170,18 @@ export class DiscussionManager {
divMessage.appendChild(pMessage);
const userMessage: HTMLParagraphElement = document.createElement('p');
userMessage.innerText = message;
userMessage.innerHTML = HtmlUtils.urlify(message);
userMessage.classList.add('body');
divMessage.appendChild(userMessage);
this.divMessages?.appendChild(divMessage);
//automatic scroll when there are new message
setTimeout(() => {
this.divMessages?.scroll({
top: this.divMessages?.scrollTop + divMessage.getBoundingClientRect().y,
behavior: 'smooth'
});
}, 200);
}
public removeParticipant(userId: number|string){
@ -188,17 +205,11 @@ export class DiscussionManager {
private showDiscussion(){
this.activeDiscussion = true;
if(this.userInputManager) {
this.userInputManager.clearAllInputKeyboard();
}
this.divDiscuss?.classList.add('active');
}
private hideDiscussion(){
this.activeDiscussion = false;
if(this.userInputManager) {
this.userInputManager.initKeyBoardEvent();
}
this.divDiscuss?.classList.remove('active');
}

View File

@ -1,20 +1,29 @@
export class HtmlUtils {
public static getElementByIdOrFail<T extends HTMLElement>(id: string): T {
const elem = document.getElementById(id);
if (elem === null) {
throw new Error("Cannot find HTML element with id '"+id+"'");
if (HtmlUtils.isHtmlElement<T>(elem)) {
return elem;
}
// FIXME: does not check the type of the returned type
return elem as T;
throw new Error("Cannot find HTML element with id '"+id+"'");
}
public static removeElementByIdOrFail<T extends HTMLElement>(id: string): T {
const elem = document.getElementById(id);
if (elem === null) {
throw new Error("Cannot find HTML element with id '"+id+"'");
if (HtmlUtils.isHtmlElement<T>(elem)) {
elem.remove();
return elem;
}
// FIXME: does not check the type of the returned type
elem.remove();
return elem as T;
throw new Error("Cannot find HTML element with id '"+id+"'");
}
public static urlify(text: string): string {
const urlRegex = /(https?:\/\/[^\s]+)/g;
return text.replace(urlRegex, (url: string) => {
return '<a href="' + url + '" target="_blank" style=":visited {color: white}">' + url + '</a>';
})
}
private static isHtmlElement<T extends HTMLElement>(elem: HTMLElement | null): elem is T {
return elem !== null;
}
}

View File

@ -13,7 +13,7 @@ if(localValueVideo){
let videoConstraint: boolean|MediaTrackConstraints = {
width: { min: 640, ideal: 1280, max: 1920 },
height: { min: 400, ideal: 720 },
frameRate: {exact: valueVideo, ideal: valueVideo},
frameRate: { ideal: valueVideo },
facingMode: "user",
resizeMode: 'crop-and-scale',
aspectRatio: 1.777777778
@ -209,10 +209,19 @@ export class MediaManager {
}
public enableCamera() {
this.enableCameraStyle();
this.constraintsMedia.video = videoConstraint;
this.getCamera().then((stream: MediaStream) => {
//TODO show error message tooltip upper of camera button
//TODO message : please check camera permission of your navigator
if(stream.getVideoTracks().length === 0) {
throw Error('Video track is empty, please check camera permission of your navigator')
}
this.enableCameraStyle();
this.triggerUpdatedLocalStreamCallbacks(stream);
}).catch((err) => {
console.error(err);
this.disableCameraStyle();
});
}
@ -228,11 +237,19 @@ export class MediaManager {
}
public enableMicrophone() {
this.enableMicrophoneStyle();
this.constraintsMedia.audio = true;
this.getCamera().then((stream) => {
//TODO show error message tooltip upper of camera button
//TODO message : please check microphone permission of your navigator
if(stream.getAudioTracks().length === 0) {
throw Error('Audio track is empty, please check microphone permission of your navigator')
}
this.enableMicrophoneStyle();
this.triggerUpdatedLocalStreamCallbacks(stream);
}).catch((err) => {
console.error(err);
this.disableMicrophoneStyle();
});
}
@ -318,6 +335,9 @@ export class MediaManager {
const localScreenCapture = this.localScreenCapture;
this.getCamera().then((stream) => {
this.triggerStoppedScreenSharingCallbacks(localScreenCapture);
}).catch((err) => { //catch error get camera
console.error(err);
this.triggerStoppedScreenSharingCallbacks(localScreenCapture);
});
this.localScreenCapture = null;
}

View File

@ -229,6 +229,14 @@ export class SimplePeer {
} catch (err) {
console.error("closeConnection", err)
}
//if user left discussion, clear array peer connection of sharing
if(this.Users.length === 0) {
for (const userId of this.PeerScreenSharingConnectionArray.keys()) {
this.closeScreenSharingConnection(userId);
this.PeerScreenSharingConnectionArray.delete(userId);
}
}
}
/**
@ -245,9 +253,11 @@ export class SimplePeer {
// FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray"
// I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel.
peer.destroy();
if(!this.PeerScreenSharingConnectionArray.delete(userId)){
//Comment this peer connexion because if we delete and try to reshare screen, the RTCPeerConnection send renegociate event. This array will be remove when user left circle discussion
/*if(!this.PeerScreenSharingConnectionArray.delete(userId)){
throw 'Couln\'t delete peer screen sharing connexion';
}
}*/
//console.log('Nb users in peerConnectionArray '+this.PeerConnectionArray.size);
} catch (err) {
console.error("closeConnection", err)
@ -301,11 +311,13 @@ export class SimplePeer {
peer.signal(data.signal);
} else {
console.error('Could not find peer whose ID is "'+data.userId+'" in receiveWebrtcScreenSharingSignal');
console.info('tentative to create new peer connexion');
this.sendLocalScreenSharingStreamToUser(data.userId);
}
} catch (e) {
console.error(`receiveWebrtcSignal => ${data.userId}`, e);
//force delete and recreate peer connexion
this.PeerScreenSharingConnectionArray.delete(data.userId);
//Comment this peer connexion because if we delete and try to reshare screen, the RTCPeerConnection send renegociate event. This array will be remove when user left circle discussion
//this.PeerScreenSharingConnectionArray.delete(data.userId);
this.receiveWebrtcScreenSharingSignal(data);
}
}
@ -416,8 +428,8 @@ export class SimplePeer {
if (!PeerConnectionScreenSharing.isReceivingScreenSharingStream()) {
PeerConnectionScreenSharing.destroy();
this.PeerScreenSharingConnectionArray.delete(userId);
//Comment this peer connexion because if we delete and try to reshare screen, the RTCPeerConnection send renegociate event. This array will be remove when user left circle discussion
//this.PeerScreenSharingConnectionArray.delete(userId);
}
}
}

View File

@ -1,12 +1,10 @@
import 'phaser';
import GameConfig = Phaser.Types.Core.GameConfig;
import {DEBUG_MODE, JITSI_URL, RESOLUTION} from "./Enum/EnvironmentVariable";
import {cypressAsserter} from "./Cypress/CypressAsserter";
import {LoginScene} from "./Phaser/Login/LoginScene";
import {ReconnectingScene} from "./Phaser/Reconnecting/ReconnectingScene";
import {SelectCharacterScene} from "./Phaser/Login/SelectCharacterScene";
import {EnableCameraScene} from "./Phaser/Login/EnableCameraScene";
import {FourOFourScene} from "./Phaser/Reconnecting/FourOFourScene";
import WebGLRenderer = Phaser.Renderer.WebGL.WebGLRenderer;
import {OutlinePipeline} from "./Phaser/Shaders/OutlinePipeline";
import {CustomizeScene} from "./Phaser/Login/CustomizeScene";
@ -15,6 +13,7 @@ import {EntryScene} from "./Phaser/Login/EntryScene";
import {coWebsiteManager} from "./WebRtc/CoWebsiteManager";
import {MenuScene} from "./Phaser/Menu/MenuScene";
import {localUserStore} from "./Connexion/LocalUserStore";
import {ErrorScene} from "./Phaser/Reconnecting/ErrorScene";
// Load Jitsi if the environment variable is set.
if (JITSI_URL) {
@ -53,13 +52,33 @@ const fps : Phaser.Types.Core.FPSConfig = {
smoothStep: false
}
// the ?phaserMode=canvas parameter can be used to force Canvas usage
const params = new URLSearchParams(document.location.search.substring(1));
const phaserMode = params.get("phaserMode");
let mode: number;
switch (phaserMode) {
case 'auto':
case null:
mode = Phaser.AUTO;
break;
case 'canvas':
mode = Phaser.CANVAS;
break;
case 'webgl':
mode = Phaser.WEBGL;
break;
default:
throw new Error('phaserMode parameter must be one of "auto", "canvas" or "webgl"');
}
const config: GameConfig = {
type: Phaser.AUTO,
type: mode,
title: "WorkAdventure",
width: width / RESOLUTION,
height: height / RESOLUTION,
parent: "game",
scene: [EntryScene, LoginScene, SelectCharacterScene, EnableCameraScene, ReconnectingScene, FourOFourScene, CustomizeScene, MenuScene],
scene: [EntryScene, LoginScene, SelectCharacterScene, EnableCameraScene, ReconnectingScene, ErrorScene, CustomizeScene, MenuScene],
zoom: RESOLUTION,
fps: fps,
dom: {
@ -73,15 +92,15 @@ const config: GameConfig = {
},
callbacks: {
postBoot: game => {
// FIXME: we should fore WebGL in the config.
const renderer = game.renderer as WebGLRenderer;
renderer.pipelines.add(OutlinePipeline.KEY, new OutlinePipeline(game));
// Commented out to try to fix MacOS bug
/*const renderer = game.renderer;
if (renderer instanceof WebGLRenderer) {
renderer.pipelines.add(OutlinePipeline.KEY, new OutlinePipeline(game));
}*/
}
}
};
cypressAsserter.gameStarted();
const game = new Phaser.Game(config);
window.addEventListener('resize', function (event) {

26
front/templater.sh Executable file
View File

@ -0,0 +1,26 @@
#!/usr/bin/env bash
set -x
set -o nounset errexit
template_file_index=/usr/share/nginx/html/index.html.tmpl
generated_file_index=/usr/share/nginx/html/index.html
tmp_trackcodefile=/tmp/trackcode
# To inject tracking code, you have two choices:
# 1) Template using the provided google analytics
# 2) Insert with your own provided code, by overriding the ANALYTICS_CODE_PATH
# The ANALYTICS_CODE_PATH is the location of a file inside the container
ANALYTICS_CODE_PATH="${ANALYTICS_CODE_PATH:-/usr/share/nginx/html/ga.html.tmpl}"
if [[ "${INSERT_ANALYTICS:-NO}" == "NO" ]]; then
echo "" > "${tmp_trackcodefile}"
fi
# Automatically insert analytics if GA_TRACKING_ID is set
if [[ "${GA_TRACKING_ID:-}" != "" || "${INSERT_ANALYTICS:-NO}" != "NO" ]]; then
echo "Templating code from ${ANALYTICS_CODE_PATH}"
sed "s#<!-- TRACKING NUMBER -->#${GA_TRACKING_ID:-}#g" "${ANALYTICS_CODE_PATH}" > "$tmp_trackcodefile"
fi
echo "Templating ${generated_file_index} from ${template_file_index}"
sed "/<!-- TRACK CODE -->/r ${tmp_trackcodefile}" "${template_file_index}" > "${generated_file_index}"
rm "${tmp_trackcodefile}"

View File

@ -0,0 +1,14 @@
import "jasmine";
import {HtmlUtils} from "../../../src/WebRtc/HtmlUtils";
describe("urlify()", () => {
it("should transform an url into a link", () => {
const text = HtmlUtils.urlify('https://google.com');
expect(text).toEqual('<a href="https://google.com" target="_blank" style=":visited {color: white}">https://google.com</a>');
});
it("should not transform a normal text into a link", () => {
const text = HtmlUtils.urlify('hello');
expect(text).toEqual('hello');
});
});

View File

@ -0,0 +1,28 @@
import "jasmine";
import {getRessourceDescriptor} from "../../../src/Phaser/Entity/PlayerTexturesLoadingManager";
describe("getRessourceDescriptor()", () => {
it(", if given a valid descriptor as parameter, should return it", () => {
const desc = getRessourceDescriptor({name: 'name', img: 'url'});
expect(desc.name).toEqual('name');
expect(desc.img).toEqual('url');
});
it(", if given a string as parameter, should search trough hardcoded values", () => {
const desc = getRessourceDescriptor('male1');
expect(desc.name).toEqual('male1');
expect(desc.img).toEqual("resources/characters/pipoya/Male 01-1.png");
});
it(", if given a string as parameter, should search trough hardcoded values (bis)", () => {
const desc = getRessourceDescriptor('color_2');
expect(desc.name).toEqual('color_2');
expect(desc.img).toEqual("resources/customisation/character_color/character_color1.png");
});
it(", if given a descriptor without url as parameter, should search trough hardcoded values", () => {
const desc = getRessourceDescriptor({name: 'male1', img: ''});
expect(desc.name).toEqual('male1');
expect(desc.img).toEqual("resources/characters/pipoya/Male 01-1.png");
});
});

View File

@ -45,7 +45,7 @@ module.exports = {
new webpack.ProvidePlugin({
Phaser: 'phaser'
}),
new webpack.EnvironmentPlugin(['API_URL', 'UPLOADER_URL', 'ADMIN_URL', 'DEBUG_MODE', 'TURN_SERVER', 'TURN_USER', 'TURN_PASSWORD', 'JITSI_URL', 'JITSI_PRIVATE_MODE'])
new webpack.EnvironmentPlugin(['API_URL', 'UPLOADER_URL', 'ADMIN_URL', 'DEBUG_MODE', 'TURN_SERVER', 'TURN_USER', 'TURN_PASSWORD', 'JITSI_URL', 'JITSI_PRIVATE_MODE', 'START_ROOM_URL'])
],
};

View File

@ -1771,7 +1771,7 @@ eventemitter3@^2.0.3:
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba"
integrity sha1-teEHm1n7XhuidxwKmTvgYKWMmbo=
eventemitter3@^4.0.0, eventemitter3@^4.0.7:
eventemitter3@^4.0.0, eventemitter3@^4.0.4:
version "4.0.7"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
@ -1829,7 +1829,7 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2:
dependencies:
homedir-polyfill "^1.0.1"
exports-loader@^1.1.1:
exports-loader@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/exports-loader/-/exports-loader-1.1.1.tgz#88c9a6877ee6a5519d7c41a016bdd99148421e69"
integrity sha512-CmyhIR2sJ3KOfVsHjsR0Yvo+0lhRhRMAevCbB8dhTVLHsZPs0lCQTvRmR9YNvBXDBxUuhmCE2f54KqEjZUaFrg==
@ -2528,7 +2528,7 @@ import-local@^2.0.0:
pkg-dir "^3.0.0"
resolve-cwd "^2.0.0"
imports-loader@^1.2.0:
imports-loader@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/imports-loader/-/imports-loader-1.2.0.tgz#b06823d0bb42e6f5ff89bc893829000eda46693f"
integrity sha512-zPvangKEgrrPeqeUqH0Uhc59YqK07JqZBi9a9cQ3v/EKUIqrbJHY4CvUrDus2lgQa5AmPyXuGrWP8JJTqzE5RQ==
@ -3663,14 +3663,14 @@ pbkdf2@^3.0.3:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
phaser@^3.22.0:
version "3.51.0"
resolved "https://registry.yarnpkg.com/phaser/-/phaser-3.51.0.tgz#b0c7ee2b21e795830d74f476dd30816a42b023bd"
integrity sha512-Z7XNToZWO60Zx/YetaoeGSeELy5ND45TPPfYB9HtQU2692ACXc/nioQaWp20NzTMgeBsgl6vYf3CI82y/DzSyg==
phaser@3.24.1:
version "3.24.1"
resolved "https://registry.yarnpkg.com/phaser/-/phaser-3.24.1.tgz#376e0c965d2a35af37c06ee78627dafbde5be017"
integrity sha512-WbrRMkbpEzarkfrq83akeauc6b8xNxsOTpDygyW7wrU2G2ne6kOYu3hji4UAaGnZaOLrVuj8ycYPjX9P1LxcDw==
dependencies:
eventemitter3 "^4.0.7"
exports-loader "^1.1.1"
imports-loader "^1.2.0"
eventemitter3 "^4.0.4"
exports-loader "^1.1.0"
imports-loader "^1.1.0"
path "^0.12.7"
picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1:

View File

@ -278,7 +278,7 @@
{
"name":"exitUrl",
"type":"string",
"value":"..\/Floor1\/floor1.json"
"value":"\/@\/tcm\/workadventure\/floor1"
}],
"type":"tilelayer",
"visible":true,

View File

@ -85,7 +85,7 @@
{
"name":"exitSceneUrl",
"type":"string",
"value":"..\/Floor0\/floor0.json#down-the-stairs"
"value":"\/@\/tcm\/workadventure\/floor0#down-the-stairs"
}],
"type":"tilelayer",
"visible":true,
@ -103,7 +103,7 @@
{
"name":"exitSceneUrl",
"type":"string",
"value":"..\/Floor2\/floor2.json#down-the-stairs"
"value":"\/@\/tcm\/workadventure\/floor2#down-the-stairs"
}],
"type":"tilelayer",
"visible":true,
@ -121,7 +121,7 @@
{
"name":"exitSceneUrl",
"type":"string",
"value":"..\/Floor2\/floor2.json#down-the-stairs-secours"
"value":"\/@\/tcm\/workadventure\/floor2#down-the-stairs-secours"
}],
"type":"tilelayer",
"visible":true,

View File

@ -103,7 +103,7 @@
{
"name":"exitSceneUrl",
"type":"string",
"value":"..\/Floor1\/floor1.json#down-the-stairs"
"value":"\/@\/tcm\/workadventure\/floor1#down-the-stairs"
}],
"type":"tilelayer",
"visible":true,
@ -139,7 +139,7 @@
{
"name":"exitSceneUrl",
"type":"string",
"value":"..\/Floor1\/floor1.json#down-the-stairs-secours"
"value":"\/@\/tcm\/workadventure\/floor1#down-the-stairs-secours"
}],
"type":"tilelayer",
"visible":true,

View File

@ -37,7 +37,7 @@
{
"name":"exitSceneUrl",
"type":"string",
"value":"..\/Floor0\/floor0.json"
"value":"\/@\/tcm\/workadventure\/floor0"
}],
"type":"tilelayer",
"visible":true,

View File

@ -193,6 +193,11 @@ message SendUserMessage{
string message = 2;
}
message BanUserMessage{
string type = 1;
string message = 2;
}
message ServerToClientMessage {
oneof message {
BatchMessage batchMessage = 1;
@ -207,6 +212,7 @@ message ServerToClientMessage {
TeleportMessageMessage teleportMessageMessage = 10;
SendJitsiJwtMessage sendJitsiJwtMessage = 11;
SendUserMessage sendUserMessage = 12;
BanUserMessage banUserMessage = 13;
}
}
@ -220,6 +226,7 @@ message JoinRoomMessage {
string userUuid = 4;
string roomId = 5;
repeated string tag = 6;
string IPAddress = 7;
}
message UserJoinedZoneMessage {
@ -272,6 +279,8 @@ message PusherToBackMessage {
StopGlobalMessage stopGlobalMessage = 9;
ReportPlayerMessage reportPlayerMessage = 10;
QueryJitsiJwtMessage queryJitsiJwtMessage = 11;
SendUserMessage sendUserMessage = 12;
BanUserMessage banUserMessage = 13;
}
}
@ -288,6 +297,7 @@ message SubToPusherMessage {
UserLeftZoneMessage userLeftZoneMessage = 5;
ItemEventMessage itemEventMessage = 6;
SendUserMessage sendUserMessage = 7;
BanUserMessage banUserMessage = 8;
}
}
@ -306,10 +316,20 @@ message ServerToAdminClientMessage {
repeated SubToAdminPusherMessage payload = 2;
}*/
message UserJoinedRoomMessage {
string uuid = 1;
string ipAddress = 2;
string name = 3;
}
message UserLeftRoomMessage {
string uuid = 1;
}
message ServerToAdminClientMessage {
oneof message {
string userUuidJoinedRoom = 1;
string userUuidLeftRoom = 2;
UserJoinedRoomMessage userJoinedRoom = 1;
UserLeftRoomMessage userLeftRoom = 2;
}
}

View File

@ -10,8 +10,8 @@
"runprod": "node --max-old-space-size=4096 ./dist/server.js",
"profile": "tsc && node --prof ./dist/server.js",
"test": "ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json",
"lint": "node_modules/.bin/eslint src/ . --ext .ts",
"fix": "node_modules/.bin/eslint --fix src/ . --ext .ts"
"lint": "DEBUG= node_modules/.bin/eslint src/ . --ext .ts",
"fix": "DEBUG= node_modules/.bin/eslint --fix src/ . --ext .ts"
},
"repository": {
"type": "git",
@ -52,7 +52,7 @@
"multer": "^1.4.2",
"prom-client": "^12.0.0",
"query-string": "^6.13.3",
"systeminformation": "^4.30.5",
"systeminformation": "^4.31.1",
"ts-node-dev": "^1.0.0-pre.44",
"typescript": "^3.8.3",
"uWebSockets.js": "uNetworking/uWebSockets.js#v18.5.0",

View File

@ -60,10 +60,7 @@ export class AuthenticateController extends BaseController {
}));
} catch (e) {
console.error("An error happened", e)
res.writeStatus(e.status || "500 Internal Server Error");
this.addCorsHeaders(res);
res.end('An error happened');
this.errorToResponse(e, res);
}

View File

@ -8,4 +8,21 @@ export class BaseController {
res.writeHeader('access-control-allow-methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
res.writeHeader('access-control-allow-origin', '*');
}
/**
* Turns any exception into a HTTP response (and logs the error)
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
protected errorToResponse(e: any, res: HttpResponse): void {
console.error("An error happened", e);
if (e.response) {
res.writeStatus(e.response.status+" "+e.response.statusText);
this.addCorsHeaders(res);
res.end("An error occurred: "+e.response.status+" "+e.response.statusText);
} else {
res.writeStatus("500 Internal Server Error")
this.addCorsHeaders(res);
res.end("An error occurred");
}
}
}

View File

@ -91,18 +91,11 @@ export class IoSocketController {
if(message.event === 'user-message') {
const messageToEmit = (message.message as { message: string, type: string, userUuid: string });
switch (message.message.type) {
case 'ban': {
socketManager.emitSendUserMessage(messageToEmit.userUuid, messageToEmit.message, roomId);
break;
}
case 'banned': {
socketManager.emitBan(messageToEmit.userUuid, messageToEmit.message, roomId);
break;
}
default: {
break;
}
if(messageToEmit.type === 'banned'){
socketManager.emitBan(messageToEmit.userUuid, messageToEmit.message, messageToEmit.type);
}
if(messageToEmit.type === 'ban') {
socketManager.emitSendUserMessage(messageToEmit.userUuid, messageToEmit.message, messageToEmit.type);
}
}
}catch (err) {
@ -148,6 +141,7 @@ export class IoSocketController {
const websocketKey = req.getHeader('sec-websocket-key');
const websocketProtocol = req.getHeader('sec-websocket-protocol');
const websocketExtensions = req.getHeader('sec-websocket-extensions');
const IPAddress = req.getHeader('x-forwarded-for');
const roomId = query.roomId;
if (typeof roomId !== 'string') {
@ -176,7 +170,7 @@ export class IoSocketController {
characterLayers = [ characterLayers ];
}
const userUuid = await jwtTokenManager.getUserUuidFromToken(token);
const userUuid = await jwtTokenManager.getUserUuidFromToken(token, IPAddress, roomId);
let memberTags: string[] = [];
let memberTextures: CharacterTexture[] = [];
@ -217,6 +211,7 @@ export class IoSocketController {
url,
token,
userUuid,
IPAddress,
roomId,
name,
characterLayers: characterLayerObjs,
@ -336,6 +331,7 @@ export class IoSocketController {
client.userId = this.nextUserId;
this.nextUserId++;
client.userUuid = ws.userUuid;
client.IPAddress = ws.IPAddress;
client.token = ws.token;
client.batchedMessages = new BatchMessage();
client.batchTimeout = null;

View File

@ -59,10 +59,7 @@ export class MapController extends BaseController{
this.addCorsHeaders(res);
res.end(JSON.stringify(mapDetails));
} catch (e) {
console.error(e.message || e);
res.writeStatus("500 Internal Server Error")
this.addCorsHeaders(res);
res.end("An error occurred");
this.errorToResponse(e, res);
}
})();

View File

@ -1,5 +1,4 @@
const SECRET_KEY = process.env.SECRET_KEY || "THECODINGMACHINE_SECRET_KEY";
const URL_ROOM_STARTED = "/Floor0/floor0.json";
const MINIMUM_DISTANCE = process.env.MINIMUM_DISTANCE ? Number(process.env.MINIMUM_DISTANCE) : 64;
const GROUP_RADIUS = process.env.GROUP_RADIUS ? Number(process.env.GROUP_RADIUS) : 48;
const ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLERY == 'true' : false;
@ -16,7 +15,6 @@ export const SOCKET_IDLE_TIMER = parseInt(process.env.SOCKET_IDLE_TIMER as strin
export {
SECRET_KEY,
URL_ROOM_STARTED,
MINIMUM_DISTANCE,
API_URL,
ADMIN_API_URL,

View File

@ -24,6 +24,7 @@ export interface ExSocketInterface extends WebSocket, Identificable {
roomId: string;
//userId: number; // A temporary (autoincremented) identifier for this user
userUuid: string; // A unique identifier for this user
IPAddress: string; // IP address
name: string;
characterLayers: CharacterLayer[];
position: PointInterface;

View File

@ -14,6 +14,11 @@ export interface AdminApiData {
textures: CharacterTexture[]
}
export interface AdminBannedData {
is_banned: boolean,
message: string
}
export interface CharacterTexture {
id: number,
level: number,
@ -110,6 +115,18 @@ class AdminApi {
headers: {"Authorization": `${ADMIN_API_TOKEN}`}
});
}
async verifyBanUser(organizationMemberToken: string, ipAddress: string, organization: string, world: string): Promise<AdminBannedData> {
if (!ADMIN_API_URL) {
return Promise.reject('No admin backoffice set!');
}
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
return Axios.get(ADMIN_API_URL + '/api/check-moderate-user/'+organization+'/'+world+'?ipAddress='+ipAddress+'&token='+organizationMemberToken,
{headers: {"Authorization": `${ADMIN_API_TOKEN}`}}
).then((data) => {
return data.data;
});
}
}
export const adminApi = new AdminApi();

View File

@ -2,7 +2,7 @@ import {ADMIN_API_URL, ALLOW_ARTILLERY, SECRET_KEY} from "../Enum/EnvironmentVar
import {uuid} from "uuidv4";
import Jwt from "jsonwebtoken";
import {TokenInterface} from "../Controller/AuthenticateController";
import {adminApi, AdminApiData} from "../Services/AdminApi";
import {adminApi, AdminBannedData} from "../Services/AdminApi";
class JWTTokenManager {
@ -10,7 +10,7 @@ class JWTTokenManager {
return Jwt.sign({userUuid: userUuid}, SECRET_KEY, {expiresIn: '200d'}); //todo: add a mechanic to refresh or recreate token
}
public async getUserUuidFromToken(token: unknown): Promise<string> {
public async getUserUuidFromToken(token: unknown, ipAddress?: string, room?: string): Promise<string> {
if (!token) {
throw new Error('An authentication error happened, a user tried to connect without a token.');
@ -50,14 +50,22 @@ class JWTTokenManager {
if (ADMIN_API_URL) {
//verify user in admin
adminApi.fetchCheckUserByToken(tokenInterface.userUuid).then(() => {
resolve(tokenInterface.userUuid);
}).catch((err) => {
//anonymous user
if(err.response && err.response.status && err.response.status === 404){
let promise = new Promise((resolve) => resolve());
if(ipAddress && room) {
promise = this.verifyBanUser(tokenInterface.userUuid, ipAddress, room);
}
promise.then(() => {
adminApi.fetchCheckUserByToken(tokenInterface.userUuid).then(() => {
resolve(tokenInterface.userUuid);
return;
}
}).catch((err) => {
//anonymous user
if (err.response && err.response.status && err.response.status === 404) {
resolve(tokenInterface.userUuid);
return;
}
reject(err);
});
}).catch((err) => {
reject(err);
});
} else {
@ -67,6 +75,27 @@ class JWTTokenManager {
});
}
private verifyBanUser(userUuid: string, ipAddress: string, room: string): Promise<AdminBannedData> {
const parts = room.split('/');
if (parts.length < 3 || parts[0] !== '@') {
return Promise.resolve({
is_banned: false,
message: ''
});
}
const organization = parts[1];
const world = parts[2];
return adminApi.verifyBanUser(userUuid, ipAddress, organization, world).then((data: AdminBannedData) => {
if (data && data.is_banned) {
throw new Error('User was banned');
}
return data;
}).catch((err) => {
throw err;
});
}
private isValidToken(token: object): token is TokenInterface {
return !(typeof((token as TokenInterface).userUuid) !== 'string');
}

View File

@ -2,11 +2,8 @@ import {PusherRoom} from "../Model/PusherRoom";
import {CharacterLayer, ExSocketInterface} from "../Model/Websocket/ExSocketInterface";
import {
GroupDeleteMessage,
GroupUpdateMessage,
ItemEventMessage,
ItemStateMessage,
PlayGlobalMessage,
PointMessage,
PositionMessage,
RoomJoinedMessage,
ServerToClientMessage,
@ -14,23 +11,19 @@ import {
SilentMessage,
SubMessage,
ReportPlayerMessage,
UserJoinedMessage,
UserLeftMessage,
UserMovedMessage,
UserMovesMessage,
ViewportMessage,
WebRtcDisconnectMessage,
WebRtcSignalToClientMessage,
WebRtcSignalToServerMessage,
WebRtcStartMessage,
QueryJitsiJwtMessage,
SendJitsiJwtMessage,
SendUserMessage,
JoinRoomMessage,
CharacterLayerMessage,
PusherToBackMessage,
AdminPusherToBackMessage,
ServerToAdminClientMessage, AdminMessage, BanMessage
ServerToAdminClientMessage,
SendUserMessage,
BanUserMessage, UserJoinedRoomMessage, UserLeftRoomMessage
} from "../Messages/generated/messages_pb";
import {PointInterface} from "../Model/Websocket/PointInterface";
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
@ -79,23 +72,33 @@ export class SocketManager implements ZoneEventListener {
}
async handleAdminRoom(client: ExAdminSocketInterface, roomId: string): Promise<void> {
console.log('Calling adminRoom')
const apiClient = await apiClientRepository.getClient(roomId);
const adminRoomStream = apiClient.adminRoom();
client.adminConnection = adminRoomStream;
adminRoomStream.on('data', (message: ServerToAdminClientMessage) => {
if (message.hasUseruuidjoinedroom()) {
const userUuid = message.getUseruuidjoinedroom();
if (message.hasUserjoinedroom()) {
const userJoinedRoomMessage = message.getUserjoinedroom() as UserJoinedRoomMessage;
if (!client.disconnecting) {
client.send('MemberJoin:'+userUuid+';'+roomId);
client.send(JSON.stringify({
type: 'MemberJoin',
data: {
uuid: userJoinedRoomMessage.getUuid(),
name: userJoinedRoomMessage.getName(),
ipAddress: userJoinedRoomMessage.getIpaddress(),
roomId: roomId,
}
}));
}
} else if (message.hasUseruuidleftroom()) {
const userUuid = message.getUseruuidleftroom();
} else if (message.hasUserleftroom()) {
const userLeftRoomMessage = message.getUserleftroom() as UserLeftRoomMessage;
if (!client.disconnecting) {
client.send('MemberLeave:'+userUuid+';'+roomId);
client.send(JSON.stringify({
type: 'MemberLeave',
data: {
uuid: userLeftRoomMessage.getUuid()
}
}));
}
} else {
throw new Error('Unexpected admin message');
@ -145,15 +148,16 @@ export class SocketManager implements ZoneEventListener {
}
async handleJoinRoom(client: ExSocketInterface): Promise<void> {
const position = client.position;
const viewport = client.viewport;
try {
const joinRoomMessage = new JoinRoomMessage();
joinRoomMessage.setUseruuid(client.userUuid);
joinRoomMessage.setIpaddress(client.IPAddress);
joinRoomMessage.setRoomid(client.roomId);
joinRoomMessage.setName(client.name);
joinRoomMessage.setPositionmessage(ProtobufUtils.toPositionMessage(client.position));
joinRoomMessage.setTagList(client.tags);
for (const characterLayer of client.characterLayers) {
const characterLayerMessage = new CharacterLayerMessage();
characterLayerMessage.setName(characterLayer.name);
@ -540,51 +544,54 @@ export class SocketManager implements ZoneEventListener {
client.send(serverToClientMessage.serializeBinary().buffer, true);
}
public async emitSendUserMessage(userUuid: string, message: string, roomId: string): Promise<void> {
public emitSendUserMessage(userUuid: string, message: string, type: string): void {
const client = this.searchClientByUuid(userUuid);
if(!client){
throw Error('client not found');
}
const backConnection = await apiClientRepository.getClient(roomId);
const adminMessage = new AdminMessage();
adminMessage.setRecipientuuid(userUuid);
const adminMessage = new SendUserMessage();
adminMessage.setMessage(message);
adminMessage.setRoomid(roomId);
adminMessage.setType(type);
const pusherToBackMessage = new PusherToBackMessage();
pusherToBackMessage.setSendusermessage(adminMessage);
client.backConnection.write(pusherToBackMessage);
/*const backConnection = await apiClientRepository.getClient(client.roomId);
const adminMessage = new AdminMessage();
adminMessage.setMessage(message);
adminMessage.setRoomid(client.roomId);
adminMessage.setRecipientuuid(client.userUuid);
backConnection.sendAdminMessage(adminMessage, (error) => {
if (error !== null) {
console.error('Error while sending admin message', error);
}
});
/*
const socket = this.searchClientByUuid(messageToSend.userUuid);
if(!socket){
throw 'socket was not found';
}
const sendUserMessage = new SendUserMessage();
sendUserMessage.setMessage(messageToSend.message);
sendUserMessage.setType(messageToSend.type);
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setSendusermessage(sendUserMessage);
if (!socket.disconnecting) {
socket.send(serverToClientMessage.serializeBinary().buffer, true);
}
return socket;*/
});*/
}
public async emitBan(userUuid: string, message: string, roomId: string): Promise<void> {
const backConnection = await apiClientRepository.getClient(roomId);
public emitBan(userUuid: string, message: string, type: string): void {
const client = this.searchClientByUuid(userUuid);
if(!client){
throw Error('client not found');
}
const banMessage = new BanMessage();
banMessage.setRecipientuuid(userUuid);
banMessage.setRoomid(roomId);
const banUserMessage = new BanUserMessage();
banUserMessage.setMessage(message);
banUserMessage.setType(type);
const pusherToBackMessage = new PusherToBackMessage();
pusherToBackMessage.setBanusermessage(banUserMessage);
client.backConnection.write(pusherToBackMessage);
backConnection.ban(banMessage, (error) => {
/*const backConnection = await apiClientRepository.getClient(client.roomId);
const adminMessage = new AdminMessage();
adminMessage.setMessage(message);
adminMessage.setRoomid(client.roomId);
adminMessage.setRecipientuuid(client.userUuid);
backConnection.sendAdminMessage(adminMessage, (error) => {
if (error !== null) {
console.error('Error while sending ban message', error);
console.error('Error while sending admin message', error);
}
});
});*/
}
/**

View File

@ -2723,10 +2723,10 @@ supports-color@^7.1.0:
dependencies:
has-flag "^4.0.0"
systeminformation@^4.30.5:
version "4.30.5"
resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-4.30.5.tgz#2219c305c8be56a2cfa527a5519c45bc81d4916c"
integrity sha512-aYWs8yttl8ePpr6VOQ/Ak8cznuc9L/NQODVhbOKhInX73ZMLvV2BS86Mzr7LLfmUteVFR36CTDNQgiJgRqq+SQ==
systeminformation@^4.31.1:
version "4.31.1"
resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-4.31.1.tgz#2e02c26987494d4b6a4d2d83138724593bc98d50"
integrity sha512-dVCDWNMN8ncMZo5vbMCA5dpAdMgzafK2ucuJy5LFmGtp1cG6farnPg8QNvoOSky9SkFoEX1Aw0XhcOFV6TnLYA==
table@^5.2.3:
version "5.4.6"

5
website/.gitignore vendored
View File

@ -1,5 +0,0 @@
/node_modules/
/dist/bundle.js
/dist/main.css
/dist/fonts
/dist/images

View File

@ -1,9 +0,0 @@
# we are rebuilding on each deploy to cope with the GAME_URL environment URL
FROM thecodingmachine/nodejs:12-apache
COPY --chown=docker:docker . .
RUN yarn install
ENV NODE_ENV=production
ENV STARTUP_COMMAND_1="yarn run build"
ENV APACHE_DOCUMENT_ROOT=dist/

View File

@ -1,45 +0,0 @@
Basic Webpack config for simple website.
Install all packages:
```
$ npm install
```
Run webpack
```
$ npm run build
```
Done! Open index.html in browser for a cat image.
----
### Notice about production mode and postcss.config.js
In *postcss.config.js* there is a check for **process.env.NODE_ENV** variable. The thing is even if you set Webpack mode to production it *won't* automatically change Node environment variable.
The simplest way to configure this is to install *cross-env* package:
```
$ npm install --save-dev cross-env
```
Then just add another npm script in *package.json* for production mode:
```javascript
"scripts": {
"build": "webpack --config webpack.config.js",
"build-production": "cross-env NODE_ENV=production webpack --config webpack.config.js"
}
```
Now when you run `npm run build-production` the *process.env.NODE_ENV* variable will be production and postcss.config.js check is going to work:
```javascript
if(process.env.NODE_ENV === 'production') {
module.exports = {
plugins: [
require('autoprefixer'),
require('cssnano')
]
}
}
```
[From Webpack documentation](https://webpack.js.org/guides/production/):
Technically, *NODE_ENV* is a system environment variable that Node.js exposes into running scripts. It is used by convention to determine dev-vs-prod behavior by server tools, build scripts, and client-side libraries. Contrary to expectations, *process.env.NODE_ENV* **is not set to "production"** within the build script webpack.config.js. Thus, conditionals like `process.env.NODE_ENV === 'production' ? '[name].[hash].bundle.js' : '[name].bundle.js'` within webpack configurations do not work as expected.

View File

@ -1,201 +0,0 @@
<!doctype html>
<html class="no-js" lang="">
<head>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-10196481-11"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-10196481-11');
</script>
<link rel="apple-touch-icon" sizes="57x57" href="static/images/favicons/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="static/images/favicons/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="static/images/favicons/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="static/images/favicons/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="static/images/favicons/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="static/images/favicons/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="static/images/favicons/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="static/images/favicons/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="static/images/favicons/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="static/images/favicons/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="static/images/favicons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="static/images/favicons/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="static/images/favicons/favicon-16x16.png">
<link rel="manifest" href="static/images/favicons/manifest.json">
<meta name="msapplication-TileColor" content="#000000">
<meta name="msapplication-TileImage" content="static/images/favicons/ms-icon-144x144.png">
<meta name="theme-color" content="#000000">
<meta charset="utf-8">
<title>Choose map - WorkAdventu.re</title>
<link rel="stylesheet" href="main.css">
<script src="bundle.js"></script>
</head>
<body class="choose-map">
<div class="container">
<div class="row">
<div class="col">
<a href="/" class="d-block mt-3 pixel-text">
&lt;&lt; BACK TO HOMEPAGE
</a>
</div>
</div>
</div>
<div class="container-fluid container-lg section pt-5">
<h1 class="text-center pixel-title">CHOOSE YOUR MAP&nbsp;!</h1>
<div class="row">
<div class="col text-center">
<p>Pick a map that suits your mood! If you don't find what you need, you can always <a href="create-map.html">CREATE YOUR OWN MAP</a>.</p>
</div>
</div>
<div class="row no-gutters justify-content-center">
<div class="col-12 col-sm-6 col-md-4">
<div class="map-item" data-url="npeguin.github.io/skapa-map/map.json" id="map_1">
<img src="static/images/maps/creative.png">
<p>Need some inspiration? Enter our CREATIVE SPACE&nbsp;!</p>
</div>
</div>
<div class="col-12 col-sm-6 col-md-4">
<div class="map-item" data-url="npeguin.github.io/pub-map/map.json" id="map_2">
<img src="static/images/maps/pub.png">
<p>Too late for working ? Just GO TO THE PUB&nbsp;!</p>
</div>
</div>
<div class="col-12 col-sm-6 col-md-4">
<div class="map-item" data-url="npeguin.github.io/office-map/map.json" id="map_3">
<img src="static/images/maps/office.png">
<p>Want to try a SIMPLE OFFICE map&nbsp;?</p>
</div>
</div>
<div class="col-12 col-sm-6 col-md-4">
<div class="map-item" data-url="npeguin.github.io/classroom-map/map.json" id="map_4">
<img src="static/images/maps/school.png">
<p>Send your kids BACK TO SCHOOL... and rest a bit&nbsp;;)</p>
</div>
</div>
<div class="col-12 col-sm-6 col-md-4">
<div class="map-item" data-url="npeguin.github.io/tower-map/map.json" id="map_5">
<img src="static/images/maps/dungeon.png">
<p>Dungeons & Dragons Nostalgia&nbsp;?</p>
</div>
</div>
<div class="col-12 col-sm-6 col-md-4">
<div class="map-item" data-url="npeguin.github.io/fantasy-map/map.json" id="map_6">
<img src="static/images/maps/fantasy.png">
<p>Explore a fantasy world&nbsp;!</p>
</div>
</div>
<div class="col-12 col-sm-6 col-md-4">
<div class="map-item" data-url="maps.workadventu.re/Floor0/floor0.json" id="map_7">
<img src="static/images/maps/tcm.png">
<p>Need a bigger Office? Visit us&nbsp;!</p>
</div>
</div>
</div>
<div class="row">
<div class="col text-center">
<div class="map-item" data-url="npeguin.github.io/corridor-map/map.json" id="map_8">
<img src="static/images/maps/street.png">
<p>NO IDEA, or just want to ROAM THE STREETS&nbsp;? Enter the street map and choose your own path&nbsp;!</p>
</div>
</div>
</div>
<div class="row">
<div class="col text-center">
<p>Could not find what you need? No worries, you can always <a href="create-map.html">CREATE YOUR OWN MAP</a>.</p>
</div>
</div>
</div>
<div class="container-fluid container-lg section text-center" id="map-link-container" style="display: none;">
<h1 class="mb-3">YOUR MAP URL IS</h1>
<p id="wa-link" class="mb-5"></p>
<div class="row align-items-center justify-content-center">
<div class="col-sm-8 text-right mb-4 pb-sm-0">
<button class="copy-btn" onclick="copyToClipboard()">COPY MAP URL<small> TO CLIPBOARD</small></button>
</div>
<div class="col-sm-4 text-center text-sm-left">
<span id="new-url">to share it !</span>
<span id="url-copied"><img src="static/images/check.png">Link copied !</span>
</div>
</div>
<div class="row start-area justify-content-center mt-5">
<div class="col-12 col-sm-1 d-none d-sm-block">
<img src="static/images/female-character.gif">
</div>
<div class="col-12 col-sm-4 mb-3 mb-sm-0">
<button id="start-btn" onclick="play()">START <span>&gt;&gt;</span></button>
</div>
<div class="col-12 col-sm-1">
<img src="static/images/male-character.gif">
</div>
</div>
</div>
<script>
var rand = '';
var characters = 'abcdefghijklmnopqrstuvwxyz';
var charactersLength = characters.length;
for ( var i = 0; i < 9; i++ ) {
rand += characters.charAt(Math.floor(Math.random() * charactersLength));
}
var id = rand.slice(0,3) + '-' + rand.slice(3,6) + '-' + rand.slice(6);
var mapLink = document.getElementById('wa-link');
var mapLinkContainer = document.getElementById('map-link-container');
function setSelectedMap(element){
var items = document.querySelectorAll(".map-item");
[].forEach.call(items, function(el) {
el.classList.remove("active");
});
element.classList.add("active");
mapLink.innerText = window.location.protocol + "//play."+window.location.host + "/_/" + id + "/" + element.dataset.url;
mapLinkContainer.style.display = 'block';
document.getElementById('new-url').style.display = 'inline';
document.getElementById('url-copied').style.display = 'none';
mapLinkContainer.scrollIntoView({
block: "start",
inline: "nearest",
behavior: "smooth"
})
}
function copyToClipboard() {
var aux = document.createElement("input");
aux.setAttribute("value", mapLink.innerHTML);
document.body.appendChild(aux);
aux.select();
document.execCommand("copy");
document.body.removeChild(aux);
document.getElementById('new-url').style.display = 'none';
document.getElementById('url-copied').style.display = 'inline';
setTimeout(function(){
document.getElementById('new-url').style.display = 'inline';
document.getElementById('url-copied').style.display = 'none';
}, 2000);
}
function play(){
window.location.assign(mapLink.innerText);
}
(function() {
document.addEventListener('click', function (event) {
// If the clicked element doesn't have the right selector, bail
var mapItem = event.target.closest('.map-item');
if (mapItem === null) {
return;
}
// Don't follow the link
event.preventDefault();
setSelectedMap(mapItem);
}, false);
})();
</script>
</body>
</html>

View File

@ -1,240 +0,0 @@
<!doctype html>
<html class="no-js" lang="">
<head>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-10196481-11"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-10196481-11');
</script>
<link rel="apple-touch-icon" sizes="57x57" href="static/images/favicons/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="static/images/favicons/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="static/images/favicons/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="static/images/favicons/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="static/images/favicons/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="static/images/favicons/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="static/images/favicons/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="static/images/favicons/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="static/images/favicons/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="static/images/favicons/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="static/images/favicons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="static/images/favicons/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="static/images/favicons/favicon-16x16.png">
<link rel="manifest" href="static/images/favicons/manifest.json">
<meta name="msapplication-TileColor" content="#000000">
<meta name="msapplication-TileImage" content="static/images/favicons/ms-icon-144x144.png">
<meta name="theme-color" content="#000000">
<meta charset="utf-8">
<title>Choose map - WorkAdventu.re</title>
<link rel="stylesheet" href="main.css">
<script src="bundle.js"></script>
</head>
<body class="create-map">
<div class="container">
<div class="row">
<div class="col">
<a href="/" class="d-block mt-3 pixel-text">
&lt;&lt; BACK TO HOMEPAGE
</a>
</div>
</div>
</div>
<div class="container-fluid container-lg section pt-5">
<h1 class="text-center pixel-title">CREATE YOUR MAP&nbsp;!</h1>
<div class="row">
<div class="col text-center">
<p>Learn how to create your own map! If you want to go the easy route, you can instead <a href="choose-map.html">PICK A PREBUILT MAP</a>.</p>
</div>
</div>
<div class="row">
<div class="col">
<h2 id="tools-you-will-need" class="pixel-title">Tools you will need</h2>
<p>In order to build your own map for WorkAdventure, you need:</p>
<ul>
<li>the <a href="https://www.mapeditor.org/">Tiled editor</a> software</li>
<li>&quot;tiles&quot; (i.e. images) to create your map (this starter kit provides a good default tileset for offices)</li>
<li>a web-server to serve your map (this starter kit proposes to use Github static pages as a web-server which is both free and performant)</li>
</ul>
<p>WorkAdventure comes with a "map starter kit" that we recommend using to start designing your map quickly.</p>
<h2 id="getting-started" class="pixel-title">Getting started</h2>
<p>Start by <a href="https://github.com/join">creating a GitHub account</a> if you don't already have one.</p>
<p>Then, go to the <a href="https://github.com/thecodingmachine/workadventure-map-starter-kit">Github map starter kit repository page</a>
and click the <strong>&quot;Use this template&quot;</strong> button.</p>
<p class="text-center"><img src="docs/use_this_template.png" alt="" style="width: 70%"></p>
<p>You will be prompted to enter a repository name for your map.</p>
<p class="text-center"><img src="docs/create_repo.png" alt="" style="width: 70%"></p>
<p>Be sure to keep the repository &quot;Public&quot;.</p>
<p>In your newly created repository, click on the <strong>Settings tab</strong> and scroll down to the <strong>GitHub Pages</strong> section.
Then select the <strong>gh-pages</strong> branch. It might already be selected, but please be sure to click on it nonetheless (otherwise
GitHub will not enable GitHub pages).</p>
<p class="text-center"><img src="docs/github_pages.png" alt="" style="width: 70%"></p>
<p>Wait a few minutes... Github will deploy a new website with the content of the repository.
The address of the website is visible in the &quot;GitHub Pages&quot; section.</p>
<p class="text-center"><img src="docs/website_address.png" alt="" style="width: 70%"></p>
<p>Click on the link. You should be redirected directly to WorkAdventure, on your map!</p>
<h2 id="customizing-your-map" class="pixel-title">Customizing your map</h2>
<p>Your map is now up and online, but this is still the demo map from the starter kit. You need to customize it.</p>
<h3 id="cloning-the-map" class="pixel-title">Cloning the map</h3>
<p>Start by cloning the map. If you are used to Git and GitHub, simply clone the map
to your computer using your preferred tool and <a href="#loading-the-map-in-tiled">jump to the next chapter</a>.</p>
<p>If you are new to Git, cloning the map means downloading the map to your computer.
To do this, you will need Git, or a Git compatible tool. Our advice is to use
<a href="https://desktop.github.com/">GitHub Desktop</a>. We recommend you take some time mastering
the notion of pull / commit / push as this will make uploading your maps really easier.</p>
<p>As an (easier) alternative, you can simply use the "Export" button to download the code of the map in a big Zip file.
When you want to upload your work again, you will simply drag'n'drop your files in the GitHub website.</p>
<h3 id="loading-the-map-in-tiled" class="pixel-title">Loading the map in Tiled</h3>
<p>The sample map is in the file <code>map.json</code>.
You can load this file in <a href="https://www.mapeditor.org/">Tiled</a>.</p>
<p>Now, it&#39;s up to you to edit the map and write your own map.</p>
<p>Some resources regarding Tiled:</p>
<ul>
<li><a href="https://doc.mapeditor.org/en/stable/manual/introduction/" target="_blank">Tiled documentation</a></li>
<li><a href="https://www.gamefromscratch.com/post/2015/10/14/Tiled-Map-Editor-Tutorial-Series.aspx" target="_blank">Tiled video tutorials</a></li>
</ul>
<h2 id="about-workadventu-re-maps" class="pixel-title">About WorkAdventure maps</h2>
<p>In order to design a map that will be readable by WorkAdventure, you will have to respect some constraints.</p>
<p>In particular, you will need to:</p>
<ul>
<li>set a start position for the players</li>
<li>configure the &quot;floor layer&quot; (so that WorkAdventure can correctly display characters above the floor, but under the ceiling)</li>
<li>eventually, you can place exits that link to other maps</li>
<li>the "Tile Layer Format" must be set to CSV. If you started from the map starter kit as explained above,
you have nothing to do. However, if you start from scratch, please be sure to select "CSV" for the tile layer format
when creating the map. You can change this setting later in the map properties.</li>
</ul>
<h3 id="workadventure-maps-rules" class="pixel-title">WorkAdventure Map Rules</h3>
<p>A few things to notice:</p>
<ul>
<li>your map can have as many layers as you want</li>
<li>your map MUST contain a layer named &quot;floorLayer&quot; of type &quot;objectgroup&quot; that represents the layer on which characters will be drawn.
Every layer above the &quot;floorLayer&quot; will be displayed on top of the characters.</li>
<li>the tilesets in your map MUST be embedded. You cannot refer to an external typeset in a TSX file. Click the &quot;embed tileset&quot; button in the tileset tab to embed tileset data.</li>
<li>your map MUST be exported in JSON format. You need to use a recent version of Tiled to get JSON format export (1.3+)</li>
<li>WorkAdventure doesn't support object layers and will ignore them</li>
<li>If you are starting from a blank map, your map MUST be orthogonal and tiles size should be 32x32. </li>
</ul>
<p class="text-center"><img src="docs/tiled_screenshot_1.png" alt="" style="width: 70%"></p>
<h3 id="building-walls" class="pixel-title">Building walls and "collidable" areas</h3>
<p>By default, the characters can traverse any tiles. If you want to prevent your characeter from
going through a tile (like a wall or a desktop), you must make this tile "collidable". You can do
this by settings the <code>collides</code> property on a given tile.</p>
<p>To make a tile "collidable", you should:</p>
<ol>
<li>select the relevant tileset and switch to "edit" mode:<br/>
<img src="docs/collides-1.png" alt="">
</li>
<li>right click on a tile of the tileset to select it:<br/>
<img src="docs/collides-2.png" alt="">
</li>
<li>on the left pane in the custom properties section, right click and select "Add properties":<br/>
<img src="docs/collides-3.png" alt=""><br/>
Please add a <code>collides</code> property. The type of the property must be <strong>bool</strong>.
</li>
<li>finally, check the checkbox for the <code>collides</code> property:<br/>
<img src="docs/collides-4.png" alt="">
</li>
</ol>
<p>Repeat for every tile that should be "collidable".</p>
<h3 id="defining-a-default-entry-point" class="pixel-title">Defining a default entry point</h3>
<p>In order to define a default start position, you MUST create a layer named &quot;start&quot; on your map.
This layer MUST contain at least one tile. The players will start on the tile of this layer.
If the layer contains many tiles selected, the players will start randomly on one of those tiles.</p>
<p class="text-center"><img src="docs/start_layer.png" alt="Start layer screenshot" style="width: 70%"></p>
<p>In the screenshot above, the start layer is made of the 2 white tiles. These tiles are
not visible to the end user because they are hidden below the "bottom" layer that displays the floor
of the map.</p>
<h3 id="defining-exits" class="pixel-title">Defining exits</h3>
<p>In order to place an exit on your scene that leads to another scene:</p>
<ul>
<li>You must create a specific layer. When a character reaches ANY tile of that layer, it will exit the scene.</li>
<li>In layer properties, you MUST add "exitUrl&quot; property. It represents the URL of the next scene. You can put relative or absolute URLs.</li>
<li>If you want to have multiple exits, you can create many layers with name &quot;exit&quot;. Each layer has a different key <code>exitUrl</code> and have tiles that represent exits to another scene.</li>
</ul>
<p>
<strong>Understanding map URLs in WorkAdventure:</strong><br/>
There are 2 kinds of URLs in WorkAdventure:
<ul>
<li>Public URLs are in the form https://play.workadventu.re/_/[instance]/[server]/[path to map]</li>
<li>Private URLs (used in paid accounts) are in the form https://play.workadventu.re/@/[organization]/[world]/[map]</li>
</ul>
Assuming your JSON map is hosted at "https://example.com/my/map.json", then you can browse your map at "https://play.workadventu.re/_/global/example.com/my/map.json".
Here, "global" is a name of an "instance" of your map. You can put anything instead of "global" here. People on the same instance of the map can see each others.
If 2 users use 2 different instances, they are on the same map, but in 2 parallel universes. They cannot see each other.
</p>
<p class="text-center"><img src="docs/exit_layer_map.png" alt="" style="width: 90%"></p>
<p>Note: in older releases of WorkAdventure, you could link to a map file directly using properties "exitSceneUrl&quot; and &quot;exitInstance&quot;. Those properties are now deprecated. Use "exitUrl" instead.</p>
<h3 id="defining-several-entry-points" class="pixel-title">Defining several entry points</h3>
<p>Often your map will have several exits, and therefore, several entry points. For instance, if there
is an exit by a door that leads to the garden map, when you come back from the garden you expect to
come back by the same door. Therefore, a map can have several entry points.
Those entry points are &quot;named&quot; (they have a name).</p>
<p>In order to create a named entry point:</p>
<ul>
<li>You must create a specific layer. When a character enters the map by this entry point, it will enter the map randomly on ANY tile of that layer.</li>
<li>In layer properties, you MUST add a boolean &quot;startLayer&quot; property. It should be set to true.</li>
<li>The name of the entry point is the name of the layer</li>
<li>To enter via this entry point, simply add a hash with the entry point name to the URL (&quot;#[<em>startLayerName</em>]&quot;). For instance: &quot;https://workadventu.re/_/global/mymap.com/path/map.json#my-entry-point&quot;.</li>
<li>You can of course use the &quot;#&quot; notation in an exit scene URL (so an exit scene URL will point to a given entry scene URL)</li>
</ul>
<h3 id="opening-a-website-when-walking-on-the-map" class="pixel-title">Opening a website when walking on the map</h3>
<p>On your map, you can define special zones. When a player will pass over these zones, a website will open
(as an iframe on the right side of the screen)</p>
<p>In order to create a zone that opens websites:</p>
<ul>
<li>You must create a specific layer.</li>
<li>In layer properties, you MUST add a &quot;openWebsite&quot; property (of type "string"). The value of the property is the URL of the website to open (the URL must start with "https://")</li>
</ul>
<h3 id="opening-a-jitsi-meet-when-walking-on-the-map" class="pixel-title">Opening a Jitsi meet when walking on the map</h3>
<p>On your map, you can define special zones (meeting rooms) that will trigger the opening of a Jitsi meet. When a player will pass over these zones, a Jitsi meet will open
(as an iframe on the right side of the screen)</p>
<p>In order to create Jitsi meet zones:</p>
<ul>
<li>You must create a specific layer.</li>
<li>In layer properties, you MUST add a &quot;jitsiRoom&quot; property (of type "string"). The value of the property is the name of the room in Jitsi. Note: the name of the room will be prepended with the name of the instance of the map (so that different instances of the map have different rooms)</li>
</ul>
<h3 id="making-a-silent-zone" class="pixel-title">Making a "silent" zone</h3>
<p>On your map, you can define special silent zones where nobody is allowed to talk.
In these zones, users will not speak to each others, even if they are next to each others.</p>
<p>In order to create a silent zone:</p>
<ul>
<li>You must create a specific layer.</li>
<li>In layer properties, you MUST add a boolean &quot;silent&quot; property. If the silent property is checked, the users are entering the silent zone when they walk on any tile of the layer.</li>
</ul>
<h3 id="pushing-the-map" class="pixel-title">Pushing the map</h3>
<p>When your changes are ready, you need to &quot;commit&quot; and &quot;push&quot; (i.e. "upload") the changes back to GitHub.
Just wait a few minutes, and your map will be propagated automatically to the GitHub pages web-server.</p>
<h3 id="need-some-help" class="pixel-title">Need some help?</h3>
<p>WorkAdventure is a young project and much needs to be said / written regarding map editing.</p>
<p>If you are facing any troubles, do not hesitate to open an "issue" in the
<a href="https://github.com/thecodingmachine/workadventure/issues" target="_blank">GitHub WorkAdventure account</a>.</p>
</div>
</div>
</div>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 401 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

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