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= JITSI_ISS=
SECRET_JITSI_KEY= SECRET_JITSI_KEY=
ADMIN_API_TOKEN=123 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 }} tags: ${{ env.GITHUB_REF_SLUG }}
add_git_labels: true 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: build-maps:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -156,7 +133,6 @@ jobs:
- build-pusher - build-pusher
- build-maps - build-maps
- build-uploader - build-uploader
- build-website
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -185,32 +161,3 @@ jobs:
with: with:
msg: Environment deployed at https://${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com msg: Environment deployed at https://${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com
check_for_duplicate_msg: true 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 run: yarn run proto && yarn run copy-to-front
working-directory: "messages" working-directory: "messages"
- name: "Create index.html"
run: ./templater.sh
working-directory: "front"
- name: "Build" - name: "Build"
run: yarn run build run: yarn run build
env: env:
@ -53,6 +57,49 @@ jobs:
run: yarn test run: yarn test
working-directory: "front" 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: continuous-integration-back:
name: "Continuous Integration Back" name: "Continuous Integration Back"

View File

@ -1,5 +1,4 @@
const SECRET_KEY = process.env.SECRET_KEY || "THECODINGMACHINE_SECRET_KEY"; 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 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 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; 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 { export {
SECRET_KEY, SECRET_KEY,
URL_ROOM_STARTED,
MINIMUM_DISTANCE, MINIMUM_DISTANCE,
ADMIN_API_URL, ADMIN_API_URL,
ADMIN_API_TOKEN, ADMIN_API_TOKEN,

View File

@ -9,7 +9,7 @@ import {
PusherToBackMessage, PusherToBackMessage,
ServerToAdminClientMessage, ServerToAdminClientMessage,
ServerToClientMessage, ServerToClientMessage,
SubMessage SubMessage, UserJoinedRoomMessage, UserLeftRoomMessage
} from "../Messages/generated/messages_pb"; } from "../Messages/generated/messages_pb";
import {CharacterLayer} from "_Model/Websocket/CharacterLayer"; import {CharacterLayer} from "_Model/Websocket/CharacterLayer";
import {AdminSocket} from "../RoomManager"; 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(); 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); this.socket.write(serverToAdminClientMessage);
} }
public sendUserLeft(uuid: string): void { public sendUserLeft(uuid: string/*, name: string, ip: string*/): void {
const serverToAdminClientMessage = new ServerToAdminClientMessage(); const serverToAdminClientMessage = new ServerToAdminClientMessage();
serverToAdminClientMessage.setUseruuidleftroom(uuid);
const userLeftRoomMessage = new UserLeftRoomMessage();
userLeftRoomMessage.setUuid(uuid);
serverToAdminClientMessage.setUserleftroom(userLeftRoomMessage);
this.socket.write(serverToAdminClientMessage); this.socket.write(serverToAdminClientMessage);
} }

View File

@ -102,7 +102,17 @@ export class GameRoom {
} }
const position = ProtobufUtils.toPointInterface(positionMessage); 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.nextUserId++;
this.users.set(user.id, user); this.users.set(user.id, user);
this.usersByUuid.set(user.uuid, user); this.usersByUuid.set(user.uuid, user);
@ -112,7 +122,7 @@ export class GameRoom {
// Notify admins // Notify admins
for (const admin of this.admins) { for (const admin of this.admins) {
admin.sendUserJoin(user.uuid); admin.sendUserJoin(user.uuid, user.name, user.IPAddress);
} }
return user; return user;
@ -135,7 +145,7 @@ export class GameRoom {
// Notify admins // Notify admins
for (const admin of this.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 // Let's send all connected users
for (const user of this.users.values()) { 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 constructor(
public id: number, public id: number,
public readonly uuid: string, public readonly uuid: string,
public readonly IPAddress: string,
private position: PointInterface, private position: PointInterface,
public silent: boolean, public silent: boolean,
private positionNotifier: PositionNotifier, private positionNotifier: PositionNotifier,

View File

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

View File

@ -1,24 +1,16 @@
import {GameRoom} from "../Model/GameRoom"; import {GameRoom} from "../Model/GameRoom";
import {CharacterLayer} from "_Model/Websocket/CharacterLayer"; import {CharacterLayer} from "_Model/Websocket/CharacterLayer";
import { import {
GroupDeleteMessage,
GroupUpdateMessage,
ItemEventMessage, ItemEventMessage,
ItemStateMessage, ItemStateMessage,
PlayGlobalMessage, PlayGlobalMessage,
PointMessage, PointMessage,
PositionMessage,
RoomJoinedMessage, RoomJoinedMessage,
ServerToClientMessage, ServerToClientMessage,
SetPlayerDetailsMessage,
SilentMessage, SilentMessage,
SubMessage, SubMessage,
ReportPlayerMessage,
UserJoinedMessage,
UserLeftMessage,
UserMovedMessage, UserMovedMessage,
UserMovesMessage, UserMovesMessage,
ViewportMessage,
WebRtcDisconnectMessage, WebRtcDisconnectMessage,
WebRtcSignalToClientMessage, WebRtcSignalToClientMessage,
WebRtcSignalToServerMessage, WebRtcSignalToServerMessage,
@ -28,24 +20,23 @@ import {
SendUserMessage, SendUserMessage,
JoinRoomMessage, JoinRoomMessage,
Zone as ProtoZone, Zone as ProtoZone,
BatchMessage,
BatchToPusherMessage, BatchToPusherMessage,
SubToPusherMessage, SubToPusherMessage,
UserJoinedZoneMessage, GroupUpdateZoneMessage, GroupLeftZoneMessage, UserLeftZoneMessage, AdminMessage, BanMessage UserJoinedZoneMessage, GroupUpdateZoneMessage, GroupLeftZoneMessage, UserLeftZoneMessage, BanUserMessage
} from "../Messages/generated/messages_pb"; } from "../Messages/generated/messages_pb";
import {User, UserSocket} from "../Model/User"; import {User, UserSocket} from "../Model/User";
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils"; import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
import {Group} from "../Model/Group"; import {Group} from "../Model/Group";
import {cpuTracker} from "./CpuTracker"; 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 {Movable} from "../Model/Movable";
import {PositionInterface} from "../Model/PositionInterface"; import {PositionInterface} from "../Model/PositionInterface";
import {adminApi, CharacterTexture, FetchMemberDataByUuidResponse} from "./AdminApi"; import {adminApi, CharacterTexture} from "./AdminApi";
import Jwt from "jsonwebtoken"; import Jwt from "jsonwebtoken";
import {JITSI_URL} from "../Enum/EnvironmentVariable"; import {JITSI_URL} from "../Enum/EnvironmentVariable";
import {clientEventsEmitter} from "./ClientEventsEmitter"; import {clientEventsEmitter} from "./ClientEventsEmitter";
import {gaugeManager} from "./GaugeManager"; import {gaugeManager} from "./GaugeManager";
import {AdminSocket, ZoneSocket} from "../RoomManager"; import {ZoneSocket} from "../RoomManager";
import {Zone} from "_Model/Zone"; import {Zone} from "_Model/Zone";
import Debug from "debug"; import Debug from "debug";
import {Admin} from "_Model/Admin"; import {Admin} from "_Model/Admin";
@ -119,7 +110,7 @@ export class SocketManager {
//const things = room.setViewport(client, viewport); //const things = room.setViewport(client, viewport);
const roomJoinedMessage = new RoomJoinedMessage(); const roomJoinedMessage = new RoomJoinedMessage();
roomJoinedMessage.setTagList(joinRoomMessage.getTagList());
/*for (const thing of things) { /*for (const thing of things) {
if (thing instanceof User) { if (thing instanceof User) {
const player: ExSocketInterface|undefined = this.sockets.get(thing.id); const player: ExSocketInterface|undefined = this.sockets.get(thing.id);
@ -626,6 +617,33 @@ export class SocketManager {
user.socket.write(serverToClientMessage); 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. * 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); 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); const room = this.rooms.get(roomId);
if (!room) { 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?"); 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); room.leave(recipient);
const sendUserMessage = new SendUserMessage(); const sendUserMessage = new SendUserMessage();
sendUserMessage.setMessage(message);
sendUserMessage.setType('banned'); sendUserMessage.setType('banned');
const subToPusherMessage = new SubToPusherMessage(); const subToPusherMessage = new SubToPusherMessage();

View File

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

View File

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

View File

@ -3,7 +3,8 @@
local namespace = env.GITHUB_REF_SLUG, local namespace = env.GITHUB_REF_SLUG,
local tag = namespace, local tag = namespace,
local url = if namespace == "master" then "workadventu.re" else namespace+".workadventure.test.thecodingmachine.com", local url = if namespace == "master" then "workadventu.re" else namespace+".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", "$schema": "https://raw.githubusercontent.com/thecodingmachine/deeployer/master/deeployer.schema.json",
"version": "1.0", "version": "1.0",
"containers": { "containers": {
@ -72,13 +73,15 @@
"env": { "env": {
"API_URL": "pusher."+url, "API_URL": "pusher."+url,
"UPLOADER_URL": "uploader."+url, "UPLOADER_URL": "uploader."+url,
"ADMIN_URL": "admin."+url, "ADMIN_URL": url,
"JITSI_URL": env.JITSI_URL, "JITSI_URL": env.JITSI_URL,
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY, "SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
"TURN_SERVER": "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443", "TURN_SERVER": "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443",
"TURN_USER": "workadventure", "TURN_USER": "workadventure",
"TURN_PASSWORD": "WorkAdventure123", "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": { "uploader": {
@ -100,17 +103,6 @@
}, },
"ports": [80] "ports": [80]
}, },
"website": {
"image": "thecodingmachine/workadventure-website:"+tag,
"host": {
"url": url,
"https": "enable"
},
"ports": [80],
"env": {
"GAME_URL": "https://play."+url
}
}
}, },
"config": { "config": {
"https": { "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 NODE_ENV: development
API_URL: pusher.workadventure.localhost API_URL: pusher.workadventure.localhost
UPLOADER_URL: uploader.workadventure.localhost UPLOADER_URL: uploader.workadventure.localhost
ADMIN_URL: admin.workadventure.localhost ADMIN_URL: workadventure.localhost
STARTUP_COMMAND_1: yarn install STARTUP_COMMAND_1: ./templater.sh
STARTUP_COMMAND_2: yarn install
TURN_SERVER: "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443" TURN_SERVER: "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443"
TURN_USER: workadventure TURN_USER: workadventure
TURN_PASSWORD: WorkAdventure123 TURN_PASSWORD: WorkAdventure123
START_ROOM_URL: "$START_ROOM_URL"
command: yarn run start command: yarn run start
volumes: volumes:
- ./front:/usr/src/app - ./front:/usr/src/app
@ -135,23 +137,6 @@ services:
- "traefik.http.routers.uploader-ssl.tls=true" - "traefik.http.routers.uploader-ssl.tls=true"
- "traefik.http.routers.uploader-ssl.service=uploader" - "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: messages:
#image: thecodingmachine/nodejs:14 #image: thecodingmachine/nodejs:14
image: thecodingmachine/workadventure-back-base:latest 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"
}
}

3
front/.gitignore vendored
View File

@ -5,4 +5,5 @@
/dist/webpack.config.js /dist/webpack.config.js
/dist/webpack.config.js.map /dist/webpack.config.js.map
/dist/src /dist/src
*.sh *.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 # final production image
FROM nginx:1.19.6-alpine@sha256:01747306a7247dbe928db991eab42e4002118bf636dd85b4ffea05dd907e5b66 FROM nginx:1.19.6-alpine@sha256:01747306a7247dbe928db991eab42e4002118bf636dd85b4ffea05dd907e5b66
COPY front/nginx-vhost.conf /etc/nginx/conf.d/default.conf 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 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"> 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"> <meta http-equiv="X-UA-Compatible" content="ie=edge">
<!-- Global site tag (gtag.js) - Google Analytics --> <!-- TRACK CODE -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-10196481-11"></script> <!-- END TRACK CODE -->
<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="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="60x60" href="static/images/favicons/apple-icon-60x60.png">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -25,7 +25,7 @@ export class Room {
this.id = this.id.substr(0, indexOfHash); this.id = this.id.substr(0, indexOfHash);
} }
} }
public static getIdFromIdentifier(identifier: string, baseUrl: string, currentInstance: string): {roomId: string, hash: string} { public static getIdFromIdentifier(identifier: string, baseUrl: string, currentInstance: string): {roomId: string, hash: string} {
let roomId = ''; let roomId = '';
let hash = ''; let hash = '';
@ -72,8 +72,9 @@ export class Room {
console.log('Map ', this.id, ' resolves to URL ', data.mapUrl); console.log('Map ', this.id, ' resolves to URL ', data.mapUrl);
resolve(data.mapUrl); resolve(data.mapUrl);
return; return;
}).catch((reason) => {
reject(reason);
}); });
} }
}); });
} }

View File

@ -41,7 +41,7 @@ import {
ViewportInterface, WebRtcDisconnectMessageInterface, ViewportInterface, WebRtcDisconnectMessageInterface,
WebRtcSignalReceivedMessageInterface, WebRtcSignalReceivedMessageInterface,
} from "./ConnexionModels"; } from "./ConnexionModels";
import {BodyResourceDescriptionInterface} from "../Phaser/Entity/body_character"; import {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures";
const manualPingDelay = 20000; 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 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 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 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_SERVER: string = process.env.TURN_SERVER || "turn:numb.viagenie.ca";
const TURN_USER: string = process.env.TURN_USER || 'g.parant@thecodingmachine.com'; const TURN_USER: string = process.env.TURN_USER || 'g.parant@thecodingmachine.com';
const TURN_PASSWORD: string = process.env.TURN_PASSWORD || 'itcugcOHxle9Acqi$'; 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 { export {
DEBUG_MODE, DEBUG_MODE,
START_ROOM_URL,
API_URL, API_URL,
UPLOADER_URL, UPLOADER_URL,
ADMIN_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 Sprite = Phaser.GameObjects.Sprite;
import {TextureError} from "../../Exception/TextureError"; 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 { interface AnimationData {
key: string; key: string;
frameRate: number; frameRate: number;
@ -48,11 +21,12 @@ export abstract class Character extends Container {
public sprites: Map<string, Sprite>; public sprites: Map<string, Sprite>;
private lastDirection: string = PlayerAnimationNames.WalkDown; private lastDirection: string = PlayerAnimationNames.WalkDown;
//private teleportation: Sprite; //private teleportation: Sprite;
private invisible: boolean;
constructor(scene: Phaser.Scene, constructor(scene: Phaser.Scene,
x: number, x: number,
y: number, y: number,
textures: string[], texturesPromise: Promise<string[]>,
name: string, name: string,
direction: string, direction: string,
moving: boolean, moving: boolean,
@ -60,10 +34,15 @@ export abstract class Character extends Container {
) { ) {
super(scene, x, y/*, texture, frame*/); super(scene, x, y/*, texture, frame*/);
this.PlayerValue = name; this.PlayerValue = name;
this.invisible = true
this.sprites = new Map<string, Sprite>(); 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 = new Sprite(scene, -20, -10, 'teleportation', 3);
this.teleportation.setInteractive(); this.teleportation.setInteractive();
@ -148,6 +127,7 @@ export abstract class Character extends Container {
} }
protected playAnimation(direction : string, moving: boolean): void { protected playAnimation(direction : string, moving: boolean): void {
if (this.invisible) return;
for (const [texture, sprite] of this.sprites.entries()) { for (const [texture, sprite] of this.sprites.entries()) {
if (!sprite.anims) { if (!sprite.anims) {
console.error('ANIMS IS NOT DEFINED!!!'); console.error('ANIMS IS NOT DEFINED!!!');
@ -190,7 +170,6 @@ export abstract class Character extends Container {
this.playAnimation(PlayerAnimationNames.WalkLeft, true); this.playAnimation(PlayerAnimationNames.WalkLeft, true);
} }
//update depth user
this.setDepth(this.y); 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, x: number,
y: number, y: number,
name: string, name: string,
PlayerTextures: string[], texturesPromise: Promise<string[]>,
direction: string, direction: string,
moving: boolean moving: boolean
) { ) {
super(Scene, x, y, PlayerTextures, name, direction, moving, 1); super(Scene, x, y, texturesPromise, name, direction, moving, 1);
//set data //set data
this.userId = userId; 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 {PointInterface} from "../../Connexion/ConnexionModels";
import {BodyResourceDescriptionInterface} from "../Entity/body_character"; import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
export interface AddPlayerInterface { export interface AddPlayerInterface {
userId: number; userId: number;

View File

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

View File

@ -30,7 +30,7 @@ import {RemotePlayer} from "../Entity/RemotePlayer";
import {Queue} from 'queue-typescript'; import {Queue} from 'queue-typescript';
import {SimplePeer, UserSimplePeerInterface} from "../../WebRtc/SimplePeer"; import {SimplePeer, UserSimplePeerInterface} from "../../WebRtc/SimplePeer";
import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene"; import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene";
import {loadAllLayers, loadObject, loadPlayerCharacters} from "../Entity/body_character"; import {lazyLoadPlayerCharacterTextures, loadCustomTexture} from "../Entity/PlayerTexturesLoadingManager";
import { import {
CenterListener, CenterListener,
layoutManager, layoutManager,
@ -45,7 +45,6 @@ import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR;
import {GameMap} from "./GameMap"; import {GameMap} from "./GameMap";
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager"; import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
import {mediaManager} from "../../WebRtc/MediaManager"; import {mediaManager} from "../../WebRtc/MediaManager";
import {FourOFourSceneName} from "../Reconnecting/FourOFourScene";
import {ItemFactoryInterface} from "../Items/ItemFactoryInterface"; import {ItemFactoryInterface} from "../Items/ItemFactoryInterface";
import {ActionableItem} from "../Items/ActionableItem"; import {ActionableItem} from "../Items/ActionableItem";
import {UserInputManager} from "../UserInput/UserInputManager"; import {UserInputManager} from "../UserInput/UserInputManager";
@ -66,7 +65,10 @@ import {ChatModeIcon} from "../Components/ChatModeIcon";
import {OpenChatIcon, openChatIconName} from "../Components/OpenChatIcon"; import {OpenChatIcon, openChatIconName} from "../Components/OpenChatIcon";
import {SelectCharacterScene, SelectCharacterSceneName} from "../Login/SelectCharacterScene"; import {SelectCharacterScene, SelectCharacterSceneName} from "../Login/SelectCharacterScene";
import {TextureError} from "../../Exception/TextureError"; 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 { export interface GameSceneInitInterface {
initPosition: PointInterface|null, initPosition: PointInterface|null,
@ -111,7 +113,7 @@ export class GameScene extends ResizableScene implements CenterListener {
MapPlayers!: Phaser.Physics.Arcade.Group; MapPlayers!: Phaser.Physics.Arcade.Group;
MapPlayersByKey : Map<number, RemotePlayer> = new Map<number, RemotePlayer>(); MapPlayersByKey : Map<number, RemotePlayer> = new Map<number, RemotePlayer>();
Map!: Phaser.Tilemaps.Tilemap; Map!: Phaser.Tilemaps.Tilemap;
Layers!: Array<Phaser.Tilemaps.TilemapLayer>; Layers!: Array<Phaser.Tilemaps.StaticTilemapLayer>;
Objects!: Array<Phaser.Physics.Arcade.Sprite>; Objects!: Array<Phaser.Physics.Arcade.Sprite>;
mapFile!: ITiledMap; mapFile!: ITiledMap;
groups: Map<number, Sprite>; groups: Map<number, Sprite>;
@ -181,12 +183,22 @@ export class GameScene extends ResizableScene implements CenterListener {
//hook preload scene //hook preload scene
preload(): void { 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.image(openChatIconName, 'resources/objects/talk.png');
this.load.on(FILE_LOAD_ERROR, (file: {src: string}) => { this.load.on(FILE_LOAD_ERROR, (file: {src: string}) => {
this.scene.start(FourOFourSceneName, { this.scene.start(ErrorSceneName, {
file: file.src 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) => { 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); this.onMapLoad(data);
} }
//add player png this.load.spritesheet('layout_modes', 'resources/objects/layout_modes.png', {frameWidth: 32, frameHeight: 32});
loadPlayerCharacters(this.load);
loadAllLayers(this.load);
loadObject(this.load);
this.load.bitmapFont('main_font', 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml'); 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. // 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 // eslint-disable-next-line @typescript-eslint/no-explicit-any
private async onMapLoad(data: any): Promise<void> { private async onMapLoad(data: any): Promise<void> {
@ -340,11 +334,7 @@ export class GameScene extends ResizableScene implements CenterListener {
throw 'playerName is not set'; throw 'playerName is not set';
} }
this.playerName = playerName; this.playerName = playerName;
const characterLayers = gameManager.getCharacterLayers(); this.characterLayers = gameManager.getCharacterLayers();
if (!characterLayers) {
throw 'characterLayers are not set';
}
this.characterLayers = characterLayers;
//initalise map //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); this.physics.world.setBounds(0, 0, this.Map.widthInPixels, this.Map.heightInPixels);
//add layer on map //add layer on map
this.Layers = new Array<Phaser.Tilemaps.TilemapLayer>(); this.Layers = new Array<Phaser.Tilemaps.StaticTilemapLayer>();
let depth = -2; let depth = -2;
for (const layer of this.mapFile.layers) { for (const layer of this.mapFile.layers) {
if (layer.type === 'tilelayer') { 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); const exitSceneUrl = this.getExitSceneUrl(layer);
if (exitSceneUrl !== undefined) { if (exitSceneUrl !== undefined) {
@ -397,7 +387,7 @@ export class GameScene extends ResizableScene implements CenterListener {
//notify game manager can to create currentUser in map //notify game manager can to create currentUser in map
this.createCurrentPlayer(); this.createCurrentPlayer();
this.removeAllRemotePlayers(); //cleanup the list of remote players in case the scene was rebooted this.removeAllRemotePlayers(); //cleanup the list of remote players in case the scene was rebooted
this.initCamera(); this.initCamera();
this.initCirclesCanvas(); this.initCirclesCanvas();
@ -544,6 +534,7 @@ export class GameScene extends ResizableScene implements CenterListener {
this.simplePeer = new SimplePeer(this.connection, !this.room.isPublic, this.playerName); this.simplePeer = new SimplePeer(this.connection, !this.room.isPublic, this.playerName);
this.GlobalMessageManager = new GlobalMessageManager(this.connection); this.GlobalMessageManager = new GlobalMessageManager(this.connection);
this.UserMessageManager = new UserMessageManager(this.connection); this.UserMessageManager = new UserMessageManager(this.connection);
this.UserMessageManager.setReceiveBanListener(this.bannedUser.bind(this));
const self = this; const self = this;
this.simplePeer.registerPeerConnectionListener({ this.simplePeer.registerPeerConnectionListener({
@ -620,8 +611,18 @@ export class GameScene extends ResizableScene implements CenterListener {
if (url === undefined) { if (url === undefined) {
audioManager.unloadAudio(); audioManager.unloadAudio();
} else { } else {
const mapDirUrl = this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf('/')); const audioPath = url as string;
const realAudioPath = mapDirUrl + '/' + url; 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); audioManager.loadAudio(realAudioPath);
if (loop) { if (loop) {
@ -718,6 +719,10 @@ export class GameScene extends ResizableScene implements CenterListener {
} }
public cleanupClosingScene(): void { 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. // We are completely destroying the current scene to avoid using a half-backed instance when coming back to the same map.
if(this.connection) { if(this.connection) {
this.connection.closeConnection(); this.connection.closeConnection();
@ -854,13 +859,13 @@ export class GameScene extends ResizableScene implements CenterListener {
this.cameras.main.setZoom(ZOOM_LEVEL); this.cameras.main.setZoom(ZOOM_LEVEL);
} }
addLayer(Layer : Phaser.Tilemaps.TilemapLayer){ addLayer(Layer : Phaser.Tilemaps.StaticTilemapLayer){
this.Layers.push(Layer); this.Layers.push(Layer);
} }
createCollisionWithPlayer() { createCollisionWithPlayer() {
//add collision layer //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.physics.add.collider(this.CurrentPlayer, Layer, (object1: GameObject, object2: GameObject) => {
//this.CurrentPlayer.say("Collision with layer : "+ (object2 as Tile).layer.name) //this.CurrentPlayer.say("Collision with layer : "+ (object2 as Tile).layer.name)
}); });
@ -877,15 +882,15 @@ export class GameScene extends ResizableScene implements CenterListener {
} }
createCurrentPlayer(){ createCurrentPlayer(){
//initialise player
//TODO create animation moving between exit and start //TODO create animation moving between exit and start
const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, this.characterLayers);
try { try {
this.CurrentPlayer = new Player( this.CurrentPlayer = new Player(
this, this,
this.startX, this.startX,
this.startY, this.startY,
this.playerName, this.playerName,
this.characterLayers, texturesPromise,
PlayerAnimationNames.WalkDown, PlayerAnimationNames.WalkDown,
false, false,
this.userInputManager this.userInputManager
@ -1062,10 +1067,7 @@ export class GameScene extends ResizableScene implements CenterListener {
}); });
} }
/** private doAddPlayer(addPlayerData : AddPlayerInterface): void {
* Create new player
*/
private async doAddPlayer(addPlayerData : AddPlayerInterface) : Promise<void> {
//check if exist player, if exist, move position //check if exist player, if exist, move position
if(this.MapPlayersByKey.has(addPlayerData.userId)){ if(this.MapPlayersByKey.has(addPlayerData.userId)){
this.updatePlayerPosition({ this.updatePlayerPosition({
@ -1074,39 +1076,21 @@ export class GameScene extends ResizableScene implements CenterListener {
}); });
return; 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( const player = new RemotePlayer(
addPlayerData.userId, addPlayerData.userId,
this, this,
addPlayerData.position.x, addPlayerData.position.x,
addPlayerData.position.y, addPlayerData.position.y,
addPlayerData.name, addPlayerData.name,
[], // Let's go with no textures and let's load textures when promises have returned. texturesPromise,
addPlayerData.position.direction, addPlayerData.position.direction,
addPlayerData.position.moving addPlayerData.position.moving
); );
this.MapPlayers.add(player); this.MapPlayers.add(player);
this.MapPlayersByKey.set(player.userId, player); this.MapPlayersByKey.set(player.userId, player);
player.updatePosition(addPlayerData.position); player.updatePosition(addPlayerData.position);
await Promise.all(loadPromises);
player.addTextures(characterLayerList, 1);
} }
/** /**
@ -1235,7 +1219,7 @@ export class GameScene extends ResizableScene implements CenterListener {
// Let's put this in Game coordinates by applying the zoom level: // Let's put this in Game coordinates by applying the zoom level:
xCenter /= ZOOM_LEVEL * RESOLUTION; xCenter /= ZOOM_LEVEL * RESOLUTION;
yCenter /= ZOOM_LEVEL * RESOLUTION; yCenter /= ZOOM_LEVEL * RESOLUTION;
this.cameras.main.startFollow(this.CurrentPlayer, true, 1, 1, xCenter - this.game.renderer.width / 2, yCenter - this.game.renderer.height / 2); this.cameras.main.startFollow(this.CurrentPlayer, true, 1, 1, xCenter - this.game.renderer.width / 2, yCenter - this.game.renderer.height / 2);
} }
@ -1262,23 +1246,14 @@ export class GameScene extends ResizableScene implements CenterListener {
mediaManager.removeTriggerCloseJitsiFrameButton('close-jisi'); mediaManager.removeTriggerCloseJitsiFrameButton('close-jisi');
} }
private loadSpritesheet(name: string, url: string): Promise<void> { private bannedUser(){
return new Promise<void>(((resolve, reject) => { this.cleanupClosingScene();
if (this.textures.exists(name)) { this.userInputManager.clearAllInputKeyboard();
resolve(); this.scene.start(ErrorSceneName, {
return; title: 'Banned',
} subTitle: 'You was banned of WorkAdventure',
this.load.spritesheet( message: 'If you want more information, you can contact us: workadventure@thecodingmachine.com'
name, });
url,
{frameWidth: 32, frameHeight: 32}
);
this.load.on('filecomplete-spritesheet-'+name, () => {
console.log('RESOURCE LOADED!');
resolve();
});
}))
} }
} }

View File

@ -42,8 +42,11 @@ export class ActionableItem {
return; return;
} }
this.isSelectable = true; this.isSelectable = true;
this.sprite.setPipeline(OutlinePipeline.KEY); if (this.sprite.pipeline) {
this.sprite.pipeline.set2f('uTextureSize', this.sprite.texture.getSourceImage().width, this.sprite.texture.getSourceImage().height); // 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; return;
} }
this.isSelectable = false; 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 {TextField} from "../Components/TextField";
import Image = Phaser.GameObjects.Image; import Image = Phaser.GameObjects.Image;
import Rectangle = Phaser.GameObjects.Rectangle; 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 Sprite = Phaser.GameObjects.Sprite;
import Container = Phaser.GameObjects.Container; import Container = Phaser.GameObjects.Container;
import {gameManager} from "../Game/GameManager"; import {gameManager} from "../Game/GameManager";
import {ResizableScene} from "./ResizableScene"; import {ResizableScene} from "./ResizableScene";
import {localUserStore} from "../../Connexion/LocalUserStore"; import {localUserStore} from "../../Connexion/LocalUserStore";
import {PlayerResourceDescriptionInterface} from "../Entity/Character"; import {addLoader} from "../Components/Loader";
import {SelectCharacterSceneName} from "./SelectCharacterScene"; import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
import {LoginSceneName} from "./LoginScene"; import {AbstractCharacterScene} from "./AbstractCharacterScene";
export const CustomizeSceneName = "CustomizeScene"; export const CustomizeSceneName = "CustomizeScene";
@ -21,7 +21,7 @@ enum CustomizeTextures{
arrowUp = "arrow_up", arrowUp = "arrow_up",
} }
export class CustomizeScene extends ResizableScene { export class CustomizeScene extends AbstractCharacterScene {
private textField!: TextField; private textField!: TextField;
private enterField!: TextField; private enterField!: TextField;
@ -48,30 +48,22 @@ export class CustomizeScene extends ResizableScene {
} }
preload() { 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.arrowRight, "resources/objects/arrow_right.png");
this.load.image(CustomizeTextures.icon, "resources/logos/tcm_full.png"); this.load.image(CustomizeTextures.icon, "resources/logos/tcm_full.png");
this.load.image(CustomizeTextures.arrowUp, "resources/objects/arrow_up.png"); this.load.image(CustomizeTextures.arrowUp, "resources/objects/arrow_up.png");
this.load.bitmapFont(CustomizeTextures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml'); this.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() { create() {

View File

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

View File

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

View File

@ -2,11 +2,8 @@ import {gameManager} from "../Game/GameManager";
import {TextField} from "../Components/TextField"; import {TextField} from "../Components/TextField";
import {TextInput} from "../Components/TextInput"; import {TextInput} from "../Components/TextInput";
import Image = Phaser.GameObjects.Image; import Image = Phaser.GameObjects.Image;
import {PLAYER_RESOURCES, PlayerResourceDescriptionInterface} from "../Entity/Character";
import {cypressAsserter} from "../../Cypress/CypressAsserter";
import {SelectCharacterSceneName} from "./SelectCharacterScene"; import {SelectCharacterSceneName} from "./SelectCharacterScene";
import {ResizableScene} from "./ResizableScene"; import {ResizableScene} from "./ResizableScene";
import {EnableCameraSceneName} from "./EnableCameraScene";
//todo: put this constants in a dedicated file //todo: put this constants in a dedicated file
export const LoginSceneName = "LoginScene"; export const LoginSceneName = "LoginScene";
@ -31,24 +28,13 @@ export class LoginScene extends ResizableScene {
} }
preload() { preload() {
cypressAsserter.preloadStarted();
//this.load.image(LoginTextures.playButton, "resources/objects/play_button.png"); //this.load.image(LoginTextures.playButton, "resources/objects/play_button.png");
this.load.image(LoginTextures.icon, "resources/logos/tcm_full.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 // Note: arcade.png from the Phaser 3 examples at: https://github.com/photonstorm/phaser3-examples/tree/master/public/assets/fonts/bitmap
this.load.bitmapFont(LoginTextures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml'); this.load.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() { create() {
cypressAsserter.initStarted();
this.textField = new TextField(this, this.game.renderer.width / 2, 50, 'Enter your name:'); this.textField = new TextField(this, this.game.renderer.width / 2, 50, 'Enter your name:');
this.nameInput = new TextInput(this, this.game.renderer.width / 2, 70, 8, this.name,(text: string) => { this.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); this.login(this.name);
}); });
cypressAsserter.initFinished();
} }
update(time: number, delta: number): void { update(time: number, delta: number): void {

View File

@ -2,11 +2,14 @@ import {gameManager} from "../Game/GameManager";
import {TextField} from "../Components/TextField"; import {TextField} from "../Components/TextField";
import Image = Phaser.GameObjects.Image; import Image = Phaser.GameObjects.Image;
import Rectangle = Phaser.GameObjects.Rectangle; import Rectangle = Phaser.GameObjects.Rectangle;
import {PLAYER_RESOURCES, PlayerResourceDescriptionInterface} from "../Entity/Character";
import {EnableCameraSceneName} from "./EnableCameraScene"; import {EnableCameraSceneName} from "./EnableCameraScene";
import {CustomizeSceneName} from "./CustomizeScene"; import {CustomizeSceneName} from "./CustomizeScene";
import {ResizableScene} from "./ResizableScene"; import {ResizableScene} from "./ResizableScene";
import {localUserStore} from "../../Connexion/LocalUserStore"; 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 //todo: put this constants in a dedicated file
@ -19,8 +22,8 @@ enum LoginTextures {
customizeButtonSelected = "customize_button_selected" customizeButtonSelected = "customize_button_selected"
} }
export class SelectCharacterScene extends ResizableScene { export class SelectCharacterScene extends AbstractCharacterScene {
private readonly nbCharactersPerRow = 4; private readonly nbCharactersPerRow = 6;
private textField!: TextField; private textField!: TextField;
private pressReturnField!: TextField; private pressReturnField!: TextField;
private logo!: Image; private logo!: Image;
@ -32,6 +35,7 @@ export class SelectCharacterScene extends ResizableScene {
private selectedRectangleYPos = 0; // Number of the character selected in the columns 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 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 players: Array<Phaser.Physics.Arcade.Sprite> = new Array<Phaser.Physics.Arcade.Sprite>();
private playerModels!: BodyResourceDescriptionInterface[];
constructor() { constructor() {
super({ super({
@ -40,26 +44,30 @@ export class SelectCharacterScene extends ResizableScene {
} }
preload() { 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.playButton, "resources/objects/play_button.png");
this.load.image(LoginTextures.icon, "resources/logos/tcm_full.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 // Note: arcade.png from the Phaser 3 examples at: https://github.com/photonstorm/phaser3-examples/tree/master/public/assets/fonts/bitmap
this.load.bitmapFont(LoginTextures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml'); this.load.bitmapFont(LoginTextures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
//add player png this.playerModels = loadAllDefaultModels(this.load);
PLAYER_RESOURCES.forEach((playerResource: PlayerResourceDescriptionInterface) => {
this.load.spritesheet(
playerResource.name,
playerResource.img,
{frameWidth: 32, frameHeight: 32}
);
});
this.load.image(LoginTextures.customizeButton, 'resources/objects/customize.png'); this.load.image(LoginTextures.customizeButton, 'resources/objects/customize.png');
this.load.image(LoginTextures.customizeButtonSelected, 'resources/objects/customize_selected.png'); this.load.image(LoginTextures.customizeButtonSelected, 'resources/objects/customize_selected.png');
} }
create() { create() {
this.textField = new TextField(this, this.game.renderer.width / 2, 50, 'Select your character'); this.textField = new TextField(this, this.game.renderer.width / 2, 50, 'Select your character');
this.pressReturnField = new TextField(
this.pressReturnField = new TextField(this, this.game.renderer.width / 2, 256, 'Press enter to start'); 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; 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', () => { 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.selectedRectangleXPos++;
} }
this.updateSelectedPlayer(); this.updateSelectedPlayer();
}); });
this.input.keyboard.on('keydown-LEFT', () => { 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.selectedRectangleXPos--;
} }
this.updateSelectedPlayer(); this.updateSelectedPlayer();
}); });
this.input.keyboard.on('keydown-DOWN', () => { 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.selectedRectangleYPos++;
} }
this.updateSelectedPlayer(); this.updateSelectedPlayer();
}); });
this.input.keyboard.on('keydown-UP', () => { 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.selectedRectangleYPos--;
} }
this.updateSelectedPlayer(); this.updateSelectedPlayer();
@ -106,7 +130,7 @@ export class SelectCharacterScene extends ResizableScene {
this.selectedRectangleYPos = Math.floor(playerNumber / this.nbCharactersPerRow); this.selectedRectangleYPos = Math.floor(playerNumber / this.nbCharactersPerRow);
this.updateSelectedPlayer(); this.updateSelectedPlayer();
} else if (playerNumber === -1) { } else if (playerNumber === -1) {
this.selectedRectangleYPos = Math.ceil(PLAYER_RESOURCES.length / this.nbCharactersPerRow); this.selectedRectangleYPos = Math.ceil(this.playerModels.length / this.nbCharactersPerRow);
this.updateSelectedPlayer(); this.updateSelectedPlayer();
} }
} }
@ -127,8 +151,8 @@ export class SelectCharacterScene extends ResizableScene {
} }
createCurrentPlayer(): void { createCurrentPlayer(): void {
for (let i = 0; i <PLAYER_RESOURCES.length; i++) { for (let i = 0; i <this.playerModels.length; i++) {
const playerResource = PLAYER_RESOURCES[i]; const playerResource = this.playerModels[i];
const col = i % this.nbCharactersPerRow; const col = i % this.nbCharactersPerRow;
const row = Math.floor(i / this.nbCharactersPerRow); const row = Math.floor(i / this.nbCharactersPerRow);
@ -151,21 +175,22 @@ export class SelectCharacterScene extends ResizableScene {
this.players.push(player); 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.customizeButton.setOrigin(0.5, 0.5);
this.add.existing(this.customizeButton); 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.setOrigin(0.5, 0.5);
this.customizeButtonSelected.setVisible(false); this.customizeButtonSelected.setVisible(false);
this.add.existing(this.customizeButtonSelected); this.add.existing(this.customizeButtonSelected);
this.customizeButton.setInteractive().on("pointerdown", () => { 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.updateSelectedPlayer();
}); });
this.selectedPlayer = this.players[0]; 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 { private updateSelectedPlayer(): void {
this.selectedPlayer?.anims.pause(); this.selectedPlayer?.anims.pause();
// If we selected the customize button // 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.selectedPlayer = null;
this.selectedRectangle.setVisible(false); this.selectedRectangle.setVisible(false);
this.customizeButtonSelected.setVisible(true); this.customizeButtonSelected.setVisible(true);
@ -198,7 +223,7 @@ export class SelectCharacterScene extends ResizableScene {
this.selectedRectangle.setSize(32, 32); this.selectedRectangle.setSize(32, 32);
const playerNumber = this.selectedRectangleXPos + this.selectedRectangleYPos * this.nbCharactersPerRow; const playerNumber = this.selectedRectangleXPos + this.selectedRectangleYPos * this.nbCharactersPerRow;
const player = this.players[playerNumber]; const player = this.players[playerNumber];
player.play(PLAYER_RESOURCES[playerNumber].name); player.play(this.playerModels[playerNumber].name);
this.selectedPlayer = player; this.selectedPlayer = player;
localUserStore.setPlayerCharacterIndex(playerNumber); localUserStore.setPlayerCharacterIndex(playerNumber);
} }
@ -211,7 +236,7 @@ export class SelectCharacterScene extends ResizableScene {
this.customizeButton.x = this.game.renderer.width / 2; this.customizeButton.x = this.game.renderer.width / 2;
this.customizeButtonSelected.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 player = this.players[i];
const col = i % this.nbCharactersPerRow; const col = i % this.nbCharactersPerRow;

View File

@ -110,6 +110,7 @@ export class MenuScene extends Phaser.Scene {
if (!this.sideMenuOpened) return; if (!this.sideMenuOpened) return;
this.sideMenuOpened = false; this.sideMenuOpened = false;
this.closeAll(); this.closeAll();
this.menuButton.getChildByID('openMenuButton').innerHTML = `<img src="/static/images/menu.svg">`;
gameManager.getCurrentGameScene(this).ConsoleGlobalMessageManager.disabledMessageConsole(); gameManager.getCurrentGameScene(this).ConsoleGlobalMessageManager.disabledMessageConsole();
this.tweens.add({ this.tweens.add({
targets: this.menuElement, targets: this.menuElement,
@ -121,6 +122,7 @@ export class MenuScene extends Phaser.Scene {
private openGameSettingsMenu(): void { private openGameSettingsMenu(): void {
if (this.settingsMenuOpened) { if (this.settingsMenuOpened) {
this.closeGameQualityMenu();
return; return;
} }
//close all //close all
@ -278,6 +280,5 @@ export class MenuScene extends Phaser.Scene {
private closeAll(){ private closeAll(){
this.closeGameQualityMenu(); this.closeGameQualityMenu();
this.closeGameShare(); 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, x: number,
y: number, y: number,
name: string, name: string,
PlayerTextures: string[], texturesPromise: Promise<string[]>,
direction: string, direction: string,
moving: boolean, moving: boolean,
private userInputManager: UserInputManager 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 //the current player model should be push away by other players to prevent conflict
this.getBody().setImmovable(false); 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 // the unique id of this pipeline
public static readonly KEY = 'Outline'; public static readonly KEY = 'Outline';

View File

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

View File

@ -32,26 +32,12 @@ class UrlManager {
return match ? match [1] : null; 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 { public pushRoomIdToUrl(room:Room): void {
if (window.location.pathname === room.id) return; if (window.location.pathname === room.id) return;
const hash = window.location.hash; const hash = window.location.hash;
history.pushState({}, 'WorkAdventure', room.id+hash); history.pushState({}, 'WorkAdventure', room.id+hash);
} }
public getStartLayerNameFromUrl(): string|null { public getStartLayerNameFromUrl(): string|null {
const hash = window.location.hash; const hash = window.location.hash;
return hash.length > 1 ? hash.substring(1) : null; return hash.length > 1 ? hash.substring(1) : null;

View File

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

View File

@ -1,20 +1,29 @@
export class HtmlUtils { export class HtmlUtils {
public static getElementByIdOrFail<T extends HTMLElement>(id: string): T { public static getElementByIdOrFail<T extends HTMLElement>(id: string): T {
const elem = document.getElementById(id); const elem = document.getElementById(id);
if (elem === null) { if (HtmlUtils.isHtmlElement<T>(elem)) {
throw new Error("Cannot find HTML element with id '"+id+"'"); return elem;
} }
// FIXME: does not check the type of the returned type throw new Error("Cannot find HTML element with id '"+id+"'");
return elem as T;
} }
public static removeElementByIdOrFail<T extends HTMLElement>(id: string): T { public static removeElementByIdOrFail<T extends HTMLElement>(id: string): T {
const elem = document.getElementById(id); const elem = document.getElementById(id);
if (elem === null) { if (HtmlUtils.isHtmlElement<T>(elem)) {
throw new Error("Cannot find HTML element with id '"+id+"'"); elem.remove();
return elem;
} }
// FIXME: does not check the type of the returned type throw new Error("Cannot find HTML element with id '"+id+"'");
elem.remove(); }
return elem as T;
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 = { let videoConstraint: boolean|MediaTrackConstraints = {
width: { min: 640, ideal: 1280, max: 1920 }, width: { min: 640, ideal: 1280, max: 1920 },
height: { min: 400, ideal: 720 }, height: { min: 400, ideal: 720 },
frameRate: {exact: valueVideo, ideal: valueVideo}, frameRate: { ideal: valueVideo },
facingMode: "user", facingMode: "user",
resizeMode: 'crop-and-scale', resizeMode: 'crop-and-scale',
aspectRatio: 1.777777778 aspectRatio: 1.777777778
@ -209,10 +209,19 @@ export class MediaManager {
} }
public enableCamera() { public enableCamera() {
this.enableCameraStyle();
this.constraintsMedia.video = videoConstraint; this.constraintsMedia.video = videoConstraint;
this.getCamera().then((stream: MediaStream) => { 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); this.triggerUpdatedLocalStreamCallbacks(stream);
}).catch((err) => {
console.error(err);
this.disableCameraStyle();
}); });
} }
@ -228,11 +237,19 @@ export class MediaManager {
} }
public enableMicrophone() { public enableMicrophone() {
this.enableMicrophoneStyle();
this.constraintsMedia.audio = true; this.constraintsMedia.audio = true;
this.getCamera().then((stream) => { 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); this.triggerUpdatedLocalStreamCallbacks(stream);
}).catch((err) => {
console.error(err);
this.disableMicrophoneStyle();
}); });
} }
@ -318,6 +335,9 @@ export class MediaManager {
const localScreenCapture = this.localScreenCapture; const localScreenCapture = this.localScreenCapture;
this.getCamera().then((stream) => { this.getCamera().then((stream) => {
this.triggerStoppedScreenSharingCallbacks(localScreenCapture); this.triggerStoppedScreenSharingCallbacks(localScreenCapture);
}).catch((err) => { //catch error get camera
console.error(err);
this.triggerStoppedScreenSharingCallbacks(localScreenCapture);
}); });
this.localScreenCapture = null; this.localScreenCapture = null;
} }

View File

@ -229,6 +229,14 @@ export class SimplePeer {
} catch (err) { } catch (err) {
console.error("closeConnection", 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" // 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. // I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel.
peer.destroy(); 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'; throw 'Couln\'t delete peer screen sharing connexion';
} }*/
//console.log('Nb users in peerConnectionArray '+this.PeerConnectionArray.size); //console.log('Nb users in peerConnectionArray '+this.PeerConnectionArray.size);
} catch (err) { } catch (err) {
console.error("closeConnection", err) console.error("closeConnection", err)
@ -301,11 +311,13 @@ export class SimplePeer {
peer.signal(data.signal); peer.signal(data.signal);
} else { } else {
console.error('Could not find peer whose ID is "'+data.userId+'" in receiveWebrtcScreenSharingSignal'); 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) { } catch (e) {
console.error(`receiveWebrtcSignal => ${data.userId}`, e); console.error(`receiveWebrtcSignal => ${data.userId}`, e);
//force delete and recreate peer connexion //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.PeerScreenSharingConnectionArray.delete(data.userId);
this.receiveWebrtcScreenSharingSignal(data); this.receiveWebrtcScreenSharingSignal(data);
} }
} }
@ -416,8 +428,8 @@ export class SimplePeer {
if (!PeerConnectionScreenSharing.isReceivingScreenSharingStream()) { if (!PeerConnectionScreenSharing.isReceivingScreenSharingStream()) {
PeerConnectionScreenSharing.destroy(); PeerConnectionScreenSharing.destroy();
//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); //this.PeerScreenSharingConnectionArray.delete(userId);
} }
} }
} }

View File

@ -1,12 +1,10 @@
import 'phaser'; import 'phaser';
import GameConfig = Phaser.Types.Core.GameConfig; import GameConfig = Phaser.Types.Core.GameConfig;
import {DEBUG_MODE, JITSI_URL, RESOLUTION} from "./Enum/EnvironmentVariable"; import {DEBUG_MODE, JITSI_URL, RESOLUTION} from "./Enum/EnvironmentVariable";
import {cypressAsserter} from "./Cypress/CypressAsserter";
import {LoginScene} from "./Phaser/Login/LoginScene"; import {LoginScene} from "./Phaser/Login/LoginScene";
import {ReconnectingScene} from "./Phaser/Reconnecting/ReconnectingScene"; import {ReconnectingScene} from "./Phaser/Reconnecting/ReconnectingScene";
import {SelectCharacterScene} from "./Phaser/Login/SelectCharacterScene"; import {SelectCharacterScene} from "./Phaser/Login/SelectCharacterScene";
import {EnableCameraScene} from "./Phaser/Login/EnableCameraScene"; import {EnableCameraScene} from "./Phaser/Login/EnableCameraScene";
import {FourOFourScene} from "./Phaser/Reconnecting/FourOFourScene";
import WebGLRenderer = Phaser.Renderer.WebGL.WebGLRenderer; import WebGLRenderer = Phaser.Renderer.WebGL.WebGLRenderer;
import {OutlinePipeline} from "./Phaser/Shaders/OutlinePipeline"; import {OutlinePipeline} from "./Phaser/Shaders/OutlinePipeline";
import {CustomizeScene} from "./Phaser/Login/CustomizeScene"; import {CustomizeScene} from "./Phaser/Login/CustomizeScene";
@ -15,6 +13,7 @@ import {EntryScene} from "./Phaser/Login/EntryScene";
import {coWebsiteManager} from "./WebRtc/CoWebsiteManager"; import {coWebsiteManager} from "./WebRtc/CoWebsiteManager";
import {MenuScene} from "./Phaser/Menu/MenuScene"; import {MenuScene} from "./Phaser/Menu/MenuScene";
import {localUserStore} from "./Connexion/LocalUserStore"; import {localUserStore} from "./Connexion/LocalUserStore";
import {ErrorScene} from "./Phaser/Reconnecting/ErrorScene";
// Load Jitsi if the environment variable is set. // Load Jitsi if the environment variable is set.
if (JITSI_URL) { if (JITSI_URL) {
@ -53,13 +52,33 @@ const fps : Phaser.Types.Core.FPSConfig = {
smoothStep: false 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 = { const config: GameConfig = {
type: Phaser.AUTO, type: mode,
title: "WorkAdventure", title: "WorkAdventure",
width: width / RESOLUTION, width: width / RESOLUTION,
height: height / RESOLUTION, height: height / RESOLUTION,
parent: "game", parent: "game",
scene: [EntryScene, LoginScene, SelectCharacterScene, EnableCameraScene, ReconnectingScene, FourOFourScene, CustomizeScene, MenuScene], scene: [EntryScene, LoginScene, SelectCharacterScene, EnableCameraScene, ReconnectingScene, ErrorScene, CustomizeScene, MenuScene],
zoom: RESOLUTION, zoom: RESOLUTION,
fps: fps, fps: fps,
dom: { dom: {
@ -73,15 +92,15 @@ const config: GameConfig = {
}, },
callbacks: { callbacks: {
postBoot: game => { postBoot: game => {
// FIXME: we should fore WebGL in the config. // Commented out to try to fix MacOS bug
const renderer = game.renderer as WebGLRenderer; /*const renderer = game.renderer;
renderer.pipelines.add(OutlinePipeline.KEY, new OutlinePipeline(game)); if (renderer instanceof WebGLRenderer) {
renderer.pipelines.add(OutlinePipeline.KEY, new OutlinePipeline(game));
}*/
} }
} }
}; };
cypressAsserter.gameStarted();
const game = new Phaser.Game(config); const game = new Phaser.Game(config);
window.addEventListener('resize', function (event) { 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({ new webpack.ProvidePlugin({
Phaser: 'phaser' 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" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba"
integrity sha1-teEHm1n7XhuidxwKmTvgYKWMmbo= integrity sha1-teEHm1n7XhuidxwKmTvgYKWMmbo=
eventemitter3@^4.0.0, eventemitter3@^4.0.7: eventemitter3@^4.0.0, eventemitter3@^4.0.4:
version "4.0.7" version "4.0.7"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
@ -1829,7 +1829,7 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2:
dependencies: dependencies:
homedir-polyfill "^1.0.1" homedir-polyfill "^1.0.1"
exports-loader@^1.1.1: exports-loader@^1.1.0:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/exports-loader/-/exports-loader-1.1.1.tgz#88c9a6877ee6a5519d7c41a016bdd99148421e69" resolved "https://registry.yarnpkg.com/exports-loader/-/exports-loader-1.1.1.tgz#88c9a6877ee6a5519d7c41a016bdd99148421e69"
integrity sha512-CmyhIR2sJ3KOfVsHjsR0Yvo+0lhRhRMAevCbB8dhTVLHsZPs0lCQTvRmR9YNvBXDBxUuhmCE2f54KqEjZUaFrg== integrity sha512-CmyhIR2sJ3KOfVsHjsR0Yvo+0lhRhRMAevCbB8dhTVLHsZPs0lCQTvRmR9YNvBXDBxUuhmCE2f54KqEjZUaFrg==
@ -2528,7 +2528,7 @@ import-local@^2.0.0:
pkg-dir "^3.0.0" pkg-dir "^3.0.0"
resolve-cwd "^2.0.0" resolve-cwd "^2.0.0"
imports-loader@^1.2.0: imports-loader@^1.1.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/imports-loader/-/imports-loader-1.2.0.tgz#b06823d0bb42e6f5ff89bc893829000eda46693f" resolved "https://registry.yarnpkg.com/imports-loader/-/imports-loader-1.2.0.tgz#b06823d0bb42e6f5ff89bc893829000eda46693f"
integrity sha512-zPvangKEgrrPeqeUqH0Uhc59YqK07JqZBi9a9cQ3v/EKUIqrbJHY4CvUrDus2lgQa5AmPyXuGrWP8JJTqzE5RQ== integrity sha512-zPvangKEgrrPeqeUqH0Uhc59YqK07JqZBi9a9cQ3v/EKUIqrbJHY4CvUrDus2lgQa5AmPyXuGrWP8JJTqzE5RQ==
@ -3663,14 +3663,14 @@ pbkdf2@^3.0.3:
safe-buffer "^5.0.1" safe-buffer "^5.0.1"
sha.js "^2.4.8" sha.js "^2.4.8"
phaser@^3.22.0: phaser@3.24.1:
version "3.51.0" version "3.24.1"
resolved "https://registry.yarnpkg.com/phaser/-/phaser-3.51.0.tgz#b0c7ee2b21e795830d74f476dd30816a42b023bd" resolved "https://registry.yarnpkg.com/phaser/-/phaser-3.24.1.tgz#376e0c965d2a35af37c06ee78627dafbde5be017"
integrity sha512-Z7XNToZWO60Zx/YetaoeGSeELy5ND45TPPfYB9HtQU2692ACXc/nioQaWp20NzTMgeBsgl6vYf3CI82y/DzSyg== integrity sha512-WbrRMkbpEzarkfrq83akeauc6b8xNxsOTpDygyW7wrU2G2ne6kOYu3hji4UAaGnZaOLrVuj8ycYPjX9P1LxcDw==
dependencies: dependencies:
eventemitter3 "^4.0.7" eventemitter3 "^4.0.4"
exports-loader "^1.1.1" exports-loader "^1.1.0"
imports-loader "^1.2.0" imports-loader "^1.1.0"
path "^0.12.7" path "^0.12.7"
picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1: picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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-methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
res.writeHeader('access-control-allow-origin', '*'); 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') { if(message.event === 'user-message') {
const messageToEmit = (message.message as { message: string, type: string, userUuid: string }); const messageToEmit = (message.message as { message: string, type: string, userUuid: string });
switch (message.message.type) { if(messageToEmit.type === 'banned'){
case 'ban': { socketManager.emitBan(messageToEmit.userUuid, messageToEmit.message, messageToEmit.type);
socketManager.emitSendUserMessage(messageToEmit.userUuid, messageToEmit.message, roomId); }
break; if(messageToEmit.type === 'ban') {
} socketManager.emitSendUserMessage(messageToEmit.userUuid, messageToEmit.message, messageToEmit.type);
case 'banned': {
socketManager.emitBan(messageToEmit.userUuid, messageToEmit.message, roomId);
break;
}
default: {
break;
}
} }
} }
}catch (err) { }catch (err) {
@ -148,6 +141,7 @@ export class IoSocketController {
const websocketKey = req.getHeader('sec-websocket-key'); const websocketKey = req.getHeader('sec-websocket-key');
const websocketProtocol = req.getHeader('sec-websocket-protocol'); const websocketProtocol = req.getHeader('sec-websocket-protocol');
const websocketExtensions = req.getHeader('sec-websocket-extensions'); const websocketExtensions = req.getHeader('sec-websocket-extensions');
const IPAddress = req.getHeader('x-forwarded-for');
const roomId = query.roomId; const roomId = query.roomId;
if (typeof roomId !== 'string') { if (typeof roomId !== 'string') {
@ -176,7 +170,7 @@ export class IoSocketController {
characterLayers = [ characterLayers ]; characterLayers = [ characterLayers ];
} }
const userUuid = await jwtTokenManager.getUserUuidFromToken(token); const userUuid = await jwtTokenManager.getUserUuidFromToken(token, IPAddress, roomId);
let memberTags: string[] = []; let memberTags: string[] = [];
let memberTextures: CharacterTexture[] = []; let memberTextures: CharacterTexture[] = [];
@ -217,6 +211,7 @@ export class IoSocketController {
url, url,
token, token,
userUuid, userUuid,
IPAddress,
roomId, roomId,
name, name,
characterLayers: characterLayerObjs, characterLayers: characterLayerObjs,
@ -336,6 +331,7 @@ export class IoSocketController {
client.userId = this.nextUserId; client.userId = this.nextUserId;
this.nextUserId++; this.nextUserId++;
client.userUuid = ws.userUuid; client.userUuid = ws.userUuid;
client.IPAddress = ws.IPAddress;
client.token = ws.token; client.token = ws.token;
client.batchedMessages = new BatchMessage(); client.batchedMessages = new BatchMessage();
client.batchTimeout = null; client.batchTimeout = null;

View File

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

View File

@ -1,5 +1,4 @@
const SECRET_KEY = process.env.SECRET_KEY || "THECODINGMACHINE_SECRET_KEY"; 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 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 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; 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 { export {
SECRET_KEY, SECRET_KEY,
URL_ROOM_STARTED,
MINIMUM_DISTANCE, MINIMUM_DISTANCE,
API_URL, API_URL,
ADMIN_API_URL, ADMIN_API_URL,

View File

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

View File

@ -14,6 +14,11 @@ export interface AdminApiData {
textures: CharacterTexture[] textures: CharacterTexture[]
} }
export interface AdminBannedData {
is_banned: boolean,
message: string
}
export interface CharacterTexture { export interface CharacterTexture {
id: number, id: number,
level: number, level: number,
@ -110,6 +115,18 @@ class AdminApi {
headers: {"Authorization": `${ADMIN_API_TOKEN}`} 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(); 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 {uuid} from "uuidv4";
import Jwt from "jsonwebtoken"; import Jwt from "jsonwebtoken";
import {TokenInterface} from "../Controller/AuthenticateController"; import {TokenInterface} from "../Controller/AuthenticateController";
import {adminApi, AdminApiData} from "../Services/AdminApi"; import {adminApi, AdminBannedData} from "../Services/AdminApi";
class JWTTokenManager { 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 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) { if (!token) {
throw new Error('An authentication error happened, a user tried to connect without a 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) { if (ADMIN_API_URL) {
//verify user in admin //verify user in admin
adminApi.fetchCheckUserByToken(tokenInterface.userUuid).then(() => { let promise = new Promise((resolve) => resolve());
resolve(tokenInterface.userUuid); if(ipAddress && room) {
}).catch((err) => { promise = this.verifyBanUser(tokenInterface.userUuid, ipAddress, room);
//anonymous user }
if(err.response && err.response.status && err.response.status === 404){ promise.then(() => {
adminApi.fetchCheckUserByToken(tokenInterface.userUuid).then(() => {
resolve(tokenInterface.userUuid); 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); reject(err);
}); });
} else { } 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 { private isValidToken(token: object): token is TokenInterface {
return !(typeof((token as TokenInterface).userUuid) !== 'string'); 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 {CharacterLayer, ExSocketInterface} from "../Model/Websocket/ExSocketInterface";
import { import {
GroupDeleteMessage, GroupDeleteMessage,
GroupUpdateMessage,
ItemEventMessage, ItemEventMessage,
ItemStateMessage,
PlayGlobalMessage, PlayGlobalMessage,
PointMessage,
PositionMessage, PositionMessage,
RoomJoinedMessage, RoomJoinedMessage,
ServerToClientMessage, ServerToClientMessage,
@ -14,23 +11,19 @@ import {
SilentMessage, SilentMessage,
SubMessage, SubMessage,
ReportPlayerMessage, ReportPlayerMessage,
UserJoinedMessage,
UserLeftMessage, UserLeftMessage,
UserMovedMessage,
UserMovesMessage, UserMovesMessage,
ViewportMessage, ViewportMessage,
WebRtcDisconnectMessage,
WebRtcSignalToClientMessage,
WebRtcSignalToServerMessage, WebRtcSignalToServerMessage,
WebRtcStartMessage,
QueryJitsiJwtMessage, QueryJitsiJwtMessage,
SendJitsiJwtMessage, SendJitsiJwtMessage,
SendUserMessage,
JoinRoomMessage, JoinRoomMessage,
CharacterLayerMessage, CharacterLayerMessage,
PusherToBackMessage, PusherToBackMessage,
AdminPusherToBackMessage, AdminPusherToBackMessage,
ServerToAdminClientMessage, AdminMessage, BanMessage ServerToAdminClientMessage,
SendUserMessage,
BanUserMessage, UserJoinedRoomMessage, UserLeftRoomMessage
} from "../Messages/generated/messages_pb"; } from "../Messages/generated/messages_pb";
import {PointInterface} from "../Model/Websocket/PointInterface"; import {PointInterface} from "../Model/Websocket/PointInterface";
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils"; import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
@ -79,23 +72,33 @@ export class SocketManager implements ZoneEventListener {
} }
async handleAdminRoom(client: ExAdminSocketInterface, roomId: string): Promise<void> { async handleAdminRoom(client: ExAdminSocketInterface, roomId: string): Promise<void> {
console.log('Calling adminRoom')
const apiClient = await apiClientRepository.getClient(roomId); const apiClient = await apiClientRepository.getClient(roomId);
const adminRoomStream = apiClient.adminRoom(); const adminRoomStream = apiClient.adminRoom();
client.adminConnection = adminRoomStream; client.adminConnection = adminRoomStream;
adminRoomStream.on('data', (message: ServerToAdminClientMessage) => { adminRoomStream.on('data', (message: ServerToAdminClientMessage) => {
if (message.hasUseruuidjoinedroom()) { if (message.hasUserjoinedroom()) {
const userUuid = message.getUseruuidjoinedroom(); const userJoinedRoomMessage = message.getUserjoinedroom() as UserJoinedRoomMessage;
if (!client.disconnecting) { 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()) { } else if (message.hasUserleftroom()) {
const userUuid = message.getUseruuidleftroom(); const userLeftRoomMessage = message.getUserleftroom() as UserLeftRoomMessage;
if (!client.disconnecting) { if (!client.disconnecting) {
client.send('MemberLeave:'+userUuid+';'+roomId); client.send(JSON.stringify({
type: 'MemberLeave',
data: {
uuid: userLeftRoomMessage.getUuid()
}
}));
} }
} else { } else {
throw new Error('Unexpected admin message'); throw new Error('Unexpected admin message');
@ -145,15 +148,16 @@ export class SocketManager implements ZoneEventListener {
} }
async handleJoinRoom(client: ExSocketInterface): Promise<void> { async handleJoinRoom(client: ExSocketInterface): Promise<void> {
const position = client.position;
const viewport = client.viewport; const viewport = client.viewport;
try { try {
const joinRoomMessage = new JoinRoomMessage(); const joinRoomMessage = new JoinRoomMessage();
joinRoomMessage.setUseruuid(client.userUuid); joinRoomMessage.setUseruuid(client.userUuid);
joinRoomMessage.setIpaddress(client.IPAddress);
joinRoomMessage.setRoomid(client.roomId); joinRoomMessage.setRoomid(client.roomId);
joinRoomMessage.setName(client.name); joinRoomMessage.setName(client.name);
joinRoomMessage.setPositionmessage(ProtobufUtils.toPositionMessage(client.position)); joinRoomMessage.setPositionmessage(ProtobufUtils.toPositionMessage(client.position));
joinRoomMessage.setTagList(client.tags);
for (const characterLayer of client.characterLayers) { for (const characterLayer of client.characterLayers) {
const characterLayerMessage = new CharacterLayerMessage(); const characterLayerMessage = new CharacterLayerMessage();
characterLayerMessage.setName(characterLayer.name); characterLayerMessage.setName(characterLayer.name);
@ -540,51 +544,54 @@ export class SocketManager implements ZoneEventListener {
client.send(serverToClientMessage.serializeBinary().buffer, true); 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 SendUserMessage();
const adminMessage = new AdminMessage();
adminMessage.setRecipientuuid(userUuid);
adminMessage.setMessage(message); 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) => { backConnection.sendAdminMessage(adminMessage, (error) => {
if (error !== null) { if (error !== null) {
console.error('Error while sending admin message', error); 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> { public emitBan(userUuid: string, message: string, type: string): void {
const backConnection = await apiClientRepository.getClient(roomId); const client = this.searchClientByUuid(userUuid);
if(!client){
throw Error('client not found');
}
const banMessage = new BanMessage(); const banUserMessage = new BanUserMessage();
banMessage.setRecipientuuid(userUuid); banUserMessage.setMessage(message);
banMessage.setRoomid(roomId); 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) { 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: dependencies:
has-flag "^4.0.0" has-flag "^4.0.0"
systeminformation@^4.30.5: systeminformation@^4.31.1:
version "4.30.5" version "4.31.1"
resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-4.30.5.tgz#2219c305c8be56a2cfa527a5519c45bc81d4916c" resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-4.31.1.tgz#2e02c26987494d4b6a4d2d83138724593bc98d50"
integrity sha512-aYWs8yttl8ePpr6VOQ/Ak8cznuc9L/NQODVhbOKhInX73ZMLvV2BS86Mzr7LLfmUteVFR36CTDNQgiJgRqq+SQ== integrity sha512-dVCDWNMN8ncMZo5vbMCA5dpAdMgzafK2ucuJy5LFmGtp1cG6farnPg8QNvoOSky9SkFoEX1Aw0XhcOFV6TnLYA==
table@^5.2.3: table@^5.2.3:
version "5.4.6" 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